Self-Forcing / scripts /merge_two_block_fppp_shards.py
Cccccz's picture
Upload Python scripts
bc29ee3 verified
Raw History Blame Contribute Delete
4.52 kB
#!/usr/bin/env python3
"""Merge independently evaluated two-block FPPP shards into one result set."""
from __future__ import annotations
import argparse
import csv
import json
import os
import shutil
from pathlib import Path
from typing import Any
PROMPT_IDS = list(range(80, 100))
SCHEDULE = "chunk0=FFFF; chunks1-6=FPPP"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--main_dir", type=Path, required=True)
parser.add_argument("--shard_dir", type=Path, required=True)
parser.add_argument("--sweep_dir", type=Path, required=True)
return parser.parse_args()
def atomic_json(path: Path, value: Any) -> None:
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_text(
json.dumps(value, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
os.replace(temporary, path)
def read_experiment_names(sweep_dir: Path) -> list[str]:
with (sweep_dir / "summary.csv").open(
"r", encoding="utf-8", newline=""
) as handle:
return [row["name"] for row in csv.DictReader(handle)]
def merge_pair_directories(main_dir: Path, shard_dir: Path) -> None:
for source in shard_dir.glob("pair_*"):
if source.is_dir():
shutil.copytree(source, main_dir / source.name, dirs_exist_ok=True)
def collect_rows(main_dir: Path, names: list[str]) -> list[dict[str, Any]]:
rows = []
for name in names:
metrics_path = main_dir / name / "metrics.json"
if not metrics_path.exists():
raise FileNotFoundError(f"Missing completed metrics: {metrics_path}")
metrics = json.loads(metrics_path.read_text(encoding="utf-8"))
if metrics.get("status") != "complete":
raise RuntimeError(f"Incomplete metrics for {name}")
if metrics.get("schedule") != SCHEDULE:
raise RuntimeError(f"Unexpected schedule for {name}: {metrics.get('schedule')}")
if metrics.get("prompt_ids") != PROMPT_IDS:
raise RuntimeError(f"Unexpected prompt split for {name}")
rows.append(
{
"name": metrics["name"],
"source_layer_1": metrics["source_layers"][0],
"source_layer_2": metrics["source_layers"][1],
"pair_kind": metrics["pair_kind"],
"schedule": metrics["schedule"],
"num_prompts": metrics["num_prompts"],
"psnr": metrics["psnr"],
"ssim": metrics["ssim"],
"lpips": metrics["lpips"],
"rollout_psnr": metrics["rollout_psnr"],
"rollout_ssim": metrics["rollout_ssim"],
"rollout_lpips": metrics["rollout_lpips"],
"offline_final_val_flow_mse": metrics[
"offline_final_val_flow_mse"
],
"mean_generation_time_s": metrics["mean_generation_time_s"],
}
)
rows.sort(key=lambda row: float(row["lpips"]))
return rows
def write_summary(main_dir: Path, rows: list[dict[str, Any]]) -> None:
destination = main_dir / "summary.csv"
temporary = destination.with_suffix(".csv.tmp")
with temporary.open("w", encoding="utf-8", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
os.replace(temporary, destination)
atomic_json(main_dir / "summary.json", rows)
def main() -> None:
args = parse_args()
main_dir = args.main_dir.resolve()
shard_dir = args.shard_dir.resolve()
sweep_dir = args.sweep_dir.resolve()
names = read_experiment_names(sweep_dir)
if len(names) != 43:
raise RuntimeError(f"Expected 43 experiments, found {len(names)}")
merge_pair_directories(main_dir, shard_dir)
rows = collect_rows(main_dir, names)
write_summary(main_dir, rows)
atomic_json(
main_dir / "manifest.json",
{
"status": "complete",
"architecture": "two_block_predictor",
"prompt_ids": PROMPT_IDS,
"experiments": names,
"rollout_schedule": "FPPP",
"rollout_definition": SCHEDULE,
"generation_seed_reset_per_prompt": 0,
"gpu_shards": {"2": 22, "4": 21},
"merged_shard_dir": str(shard_dir),
},
)
print(f"[merge] {len(rows)} pairs -> {main_dir / 'summary.csv'}", flush=True)
if __name__ == "__main__":
main()