Download scripts/merge_three_block_sweep_shards.py from Cccccz/Self-Forcing: direct link, hf CLI and curl.
- Browser
- Download file 5.54 kB
-
https://huggingface.co/Cccccz/Self-Forcing/resolve/main/scripts/merge_three_block_sweep_shards.py
- Command line
-
hf download hf://Cccccz/Self-Forcing/scripts/merge_three_block_sweep_shards.py
-
curl -L -o merge_three_block_sweep_shards.py https://huggingface.co/Cccccz/Self-Forcing/resolve/main/scripts/merge_three_block_sweep_shards.py
5.54 kB
| #!/usr/bin/env python3 | |
| """Merge four independent consecutive three-block training sweeps.""" | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import json | |
| import os | |
| import shutil | |
| from pathlib import Path | |
| from typing import Any | |
| EXPECTED_TRIPLES = [(index, index + 1, index + 2) for index in range(28)] | |
| EXPECTED_PROMPTS = {"train": list(range(80)), "val": list(range(80, 100))} | |
| GPU_SHARDS = { | |
| "0": EXPECTED_TRIPLES[0:7], | |
| "2": EXPECTED_TRIPLES[7:14], | |
| "6": EXPECTED_TRIPLES[14:21], | |
| "7": EXPECTED_TRIPLES[21:28], | |
| } | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--shard_root", type=Path, required=True) | |
| parser.add_argument("--output_dir", type=Path, required=True) | |
| return parser.parse_args() | |
| def experiment_name(triple: tuple[int, int, int]) -> str: | |
| return "triple_" + "_".join(f"{layer:02d}" for layer in triple) | |
| 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 validate_metrics( | |
| metrics: dict[str, Any], triple: tuple[int, int, int] | |
| ) -> None: | |
| name = experiment_name(triple) | |
| expected = { | |
| "status": "complete", | |
| "name": name, | |
| "architecture": "three_block_predictor", | |
| "initialization_method": "teacher_full", | |
| "source_layers": list(triple), | |
| "seed": 0, | |
| "max_steps": 1000, | |
| "train_prompt_ids": EXPECTED_PROMPTS["train"], | |
| "val_prompt_ids": EXPECTED_PROMPTS["val"], | |
| } | |
| for key, value in expected.items(): | |
| if metrics.get(key) != value: | |
| raise RuntimeError( | |
| f"{name}: expected {key}={value!r}, got {metrics.get(key)!r}" | |
| ) | |
| def result_row(metrics: dict[str, Any]) -> dict[str, Any]: | |
| return { | |
| "name": metrics["name"], | |
| "source_layer_1": metrics["source_layers"][0], | |
| "source_layer_2": metrics["source_layers"][1], | |
| "source_layer_3": metrics["source_layers"][2], | |
| "final_val_flow_mse": metrics["final_val_flow_mse"], | |
| "final_val_hidden_mse": metrics["final_val_hidden_mse"], | |
| "final_val_total_loss": metrics["final_val_total_loss"], | |
| "val_flow_mse_auc": metrics["val_flow_mse_auc"], | |
| "val_hidden_mse_auc": metrics["val_hidden_mse_auc"], | |
| "val_total_loss_auc": metrics["val_total_loss_auc"], | |
| "training_time_s": metrics["training_time_s"], | |
| } | |
| def write_summary(output_dir: Path, rows: list[dict[str, Any]]) -> None: | |
| rows.sort(key=lambda row: float(row["final_val_flow_mse"])) | |
| destination = output_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(output_dir / "summary.json", rows) | |
| def main() -> None: | |
| args = parse_args() | |
| shard_root = args.shard_root.resolve() | |
| output_dir = args.output_dir.resolve() | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| rows = [] | |
| fingerprints = set() | |
| for gpu, triples in GPU_SHARDS.items(): | |
| shard_dir = shard_root / f"gpu{gpu}" | |
| shard_manifest = json.loads( | |
| (shard_dir / "sweep_manifest.json").read_text(encoding="utf-8") | |
| ) | |
| if shard_manifest.get("status") != "complete": | |
| raise RuntimeError(f"GPU {gpu} shard is not complete") | |
| if shard_manifest.get("triples") != [list(value) for value in triples]: | |
| raise RuntimeError(f"GPU {gpu} shard contains unexpected triples") | |
| fingerprints.add(shard_manifest["batch_schedule_sha256"]) | |
| for triple in triples: | |
| name = experiment_name(triple) | |
| source = shard_dir / name | |
| metrics = json.loads( | |
| (source / "metrics.json").read_text(encoding="utf-8") | |
| ) | |
| validate_metrics(metrics, triple) | |
| destination = output_dir / name | |
| if destination.exists(): | |
| shutil.rmtree(destination) | |
| shutil.copytree(source, destination) | |
| rows.append(result_row(metrics)) | |
| if len(rows) != 28: | |
| raise RuntimeError(f"Expected 28 results, found {len(rows)}") | |
| if len(fingerprints) != 1: | |
| raise RuntimeError(f"Batch schedules differ across shards: {fingerprints}") | |
| write_summary(output_dir, rows) | |
| atomic_json( | |
| output_dir / "sweep_manifest.json", | |
| { | |
| "status": "complete", | |
| "architecture": "three_block_predictor", | |
| "initialization_method": "teacher_full", | |
| "triples": [list(value) for value in EXPECTED_TRIPLES], | |
| "num_triples": 28, | |
| "max_steps": 1000, | |
| "seed": 0, | |
| "train_prompt_ids": EXPECTED_PROMPTS["train"], | |
| "val_prompt_ids": EXPECTED_PROMPTS["val"], | |
| "batch_schedule_sha256": fingerprints.pop(), | |
| "gpu_shards": { | |
| gpu: [list(value) for value in triples] | |
| for gpu, triples in GPU_SHARDS.items() | |
| }, | |
| "source_shard_root": str(shard_root), | |
| }, | |
| ) | |
| print( | |
| f"[merge] {len(rows)} triples -> {output_dir / 'summary.csv'}", | |
| flush=True, | |
| ) | |
| if __name__ == "__main__": | |
| main() | |