| """Prompt trajectories and fixed FFFF targets for Predictor-v4 Stage 2.""" |
|
|
| from __future__ import annotations |
|
|
| import json |
| from pathlib import Path |
| from typing import Any |
|
|
| import torch |
| from safetensors import safe_open |
| from torch.utils.data import Dataset |
|
|
|
|
| class PredictorV4TrajectoryDataset(Dataset): |
| """One deterministic seven-chunk trajectory per training prompt.""" |
|
|
| def __init__( |
| self, |
| cases_path: str | Path, |
| *, |
| data_root: str | Path, |
| max_cases: int | None = None, |
| ) -> None: |
| self.cases_path = Path(cases_path).resolve() |
| self.data_root = Path(data_root).resolve() |
| with self.cases_path.open("r", encoding="utf-8") as handle: |
| cases = [json.loads(line) for line in handle if line.strip()] |
| if max_cases is not None: |
| cases = cases[: int(max_cases)] |
| if not cases: |
| raise ValueError(f"No trajectory cases in {self.cases_path}") |
| for position, case in enumerate(cases): |
| if int(case["case_id"]) != position: |
| raise ValueError("Stage-2 case_id values must be dense and ordered") |
| if int(case.get("seed", -1)) != 0: |
| raise ValueError(f"case {position} does not use latent seed 0") |
| if not str(case.get("prompt", "")).strip(): |
| raise ValueError(f"case {position} has an empty prompt") |
| self.cases = cases |
|
|
| def __len__(self) -> int: |
| return len(self.cases) |
|
|
| def __getitem__(self, index: int) -> dict[str, Any]: |
| case = self.cases[index] |
| return { |
| "case_id": int(case["case_id"]), |
| "prompt": str(case["prompt"]), |
| "seed": int(case["seed"]), |
| } |
|
|
| def offline_step_path(self, case_id: int, chunk_id: int) -> Path: |
| path = ( |
| self.data_root |
| / "steps" |
| / f"case_{int(case_id):06d}" |
| / f"chunk_{int(chunk_id):02d}.safetensors" |
| ) |
| if not path.is_file(): |
| raise FileNotFoundError(path) |
| return path |
|
|
|
|
| def trajectory_collate(items: list[dict[str, Any]]) -> dict[str, Any]: |
| if len(items) != 1: |
| raise ValueError( |
| "Stage-2 rollout currently requires one trajectory per rank" |
| ) |
| return items[0] |
|
|
|
|
| def load_offline_ffff_target( |
| path: str | Path, |
| *, |
| step_id: int, |
| device: torch.device, |
| ) -> dict[str, torch.Tensor]: |
| """Load one immutable Full-trajectory hidden/flow target.""" |
|
|
| path = Path(path) |
| names = { |
| "hidden": f"step_{int(step_id)}_final_hidden", |
| "flow": f"step_{int(step_id)}_flow", |
| "timestep": f"step_{int(step_id)}_timestep", |
| } |
| with safe_open(str(path), framework="pt", device="cpu") as handle: |
| missing = set(names.values()).difference(handle.keys()) |
| if missing: |
| raise KeyError(f"{path} lacks fixed FFFF tensors {sorted(missing)}") |
| result = {key: handle.get_tensor(name) for key, name in names.items()} |
| if result["hidden"].dtype != torch.bfloat16: |
| raise TypeError(f"{path}: target hidden must be BF16") |
| if result["flow"].dtype != torch.bfloat16: |
| raise TypeError(f"{path}: target flow must be BF16") |
| if result["timestep"].dtype != torch.int64: |
| raise TypeError(f"{path}: legacy offline timestep must be INT64") |
| return { |
| key: value.to(device=device, non_blocking=True) |
| for key, value in result.items() |
| } |
|
|
|
|
| __all__ = [ |
| "PredictorV4TrajectoryDataset", |
| "load_offline_ffff_target", |
| "trajectory_collate", |
| ] |
|
|