Instructions to use FastVideo/FastH3-Omni-MLX with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use FastVideo/FastH3-Omni-MLX with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download FastVideo/FastH3-Omni-MLX --local-dir FastH3-Omni-MLX
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Download bench/run_matrix.py from FastVideo/FastH3-Omni-MLX: direct link, hf CLI and curl.
- Browser
- Download file 55.8 kB
-
https://huggingface.co/FastVideo/FastH3-Omni-MLX/resolve/main/bench/run_matrix.py
- Command line
-
hf download hf://FastVideo/FastH3-Omni-MLX/bench/run_matrix.py
-
curl -L -o run_matrix.py https://huggingface.co/FastVideo/FastH3-Omni-MLX/resolve/main/bench/run_matrix.py
55.8 kB
| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """FastH3 Omni (Ref2VA) MLX benchmark matrix for Apple Silicon. | |
| Runs the same workload matrix as the DGX Spark benchmark: compositions x | |
| decoders (36L / 8L) x canvases (480p / 768p), each cell being one excluded | |
| seed-42 warmup followed by measured seeds 0, 1, 2 at concurrency 1. Each cell | |
| runs in a fresh subprocess so memory never accumulates between cells. The run | |
| is resumable (re-run the same command), survives a crashing or OOM cell, and | |
| ends by writing latency.csv, benchmark.json, README.md and SEND_BACK.tar.gz. | |
| Quick start (see README.md): | |
| python3 run_matrix.py --smoke # ~4 minute install check | |
| python3 run_matrix.py # full matrix, resumable | |
| Pure stdlib plus ffmpeg/ffprobe on PATH. The worker subprocess imports mlx and | |
| fastvideo (run this script with the Python environment that has them). | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import datetime as dt | |
| import hashlib | |
| import json | |
| import os | |
| import re | |
| import shutil | |
| import signal | |
| import socket | |
| import subprocess | |
| import sys | |
| import tarfile | |
| import threading | |
| import time | |
| import traceback | |
| from pathlib import Path | |
| SCRIPT_DIR = Path(__file__).resolve().parent | |
| sys.path.insert(0, str(SCRIPT_DIR)) | |
| import aggregate as agg # noqa: E402 | |
| GIB = 2**30 | |
| WARMUP_SEED = 42 | |
| EXPECTED_FRAMES = {5: 124, 10: 243, 15: 362} | |
| DEFAULT_CACHE_LIMIT_GIB = 8.0 | |
| WIRED_FRACTION = 0.66 | |
| WIRED_CAP_GIB = 64.0 | |
| SAMPLE_INTERVAL_S = 2.0 | |
| HEAD_HASH_BYTES = 64 * 1024 * 1024 | |
| LOCK_DIR = Path("/tmp/fh3mlx/gpu.lock") | |
| LOCK_POLL_S = 30 | |
| BUNDLE_MP4_CAP_BYTES = 2 * GIB | |
| DEFAULT_CELL_TIMEOUT_S = 4 * 3600 | |
| SMOKE_WIDTH, SMOKE_HEIGHT = 448, 256 | |
| DECODER_FILES = { | |
| "36l": "vae/fasth3-quality-36L-decoder-nvfp4.safetensors", | |
| "8l": "vae/fasth3-fast-8L-decoder-nvfp4.safetensors", | |
| } | |
| ENCODER_FILE = "vae/lightvae-lite-h3-encoder.safetensors" | |
| TEXT_ENCODER_DIR = "text_encoder-nvfp4" | |
| def utc_now() -> str: | |
| return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) | |
| def sha256_file(path: Path, limit: int | None = None) -> str: | |
| digest = hashlib.sha256() | |
| remaining = limit | |
| with open(path, "rb") as handle: | |
| while remaining is None or remaining > 0: | |
| block = handle.read(1 << 24 if remaining is None else min(1 << 24, remaining)) | |
| if not block: | |
| break | |
| digest.update(block) | |
| if remaining is not None: | |
| remaining -= len(block) | |
| return digest.hexdigest() | |
| def write_json(path: Path, payload: dict) -> None: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| temporary = path.with_suffix(path.suffix + ".tmp") | |
| temporary.write_text(json.dumps(payload, indent=2, sort_keys=True, default=str) + "\n", encoding="utf-8") | |
| temporary.replace(path) | |
| def append_jsonl(path: Path, record: dict) -> None: | |
| """Durably append one record so a crash cannot erase completed requests.""" | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| with open(path, "a", encoding="utf-8") as handle: | |
| handle.write(json.dumps(record, sort_keys=True, default=str) + "\n") | |
| handle.flush() | |
| os.fsync(handle.fileno()) | |
| def run_text(command: list[str]) -> str: | |
| try: | |
| done = subprocess.run(command, capture_output=True, text=True, check=False, timeout=30) | |
| except (OSError, subprocess.TimeoutExpired): | |
| return "" | |
| return done.stdout.strip() | |
| # --------------------------------------------------------------------------- data bundle | |
| def verify_data_bundle(data_root: Path) -> dict: | |
| """Check every SHA256SUMS entry; returns identity facts and a path->sha map. Raises on any mismatch.""" | |
| sums = data_root / "SHA256SUMS" | |
| if not sums.is_file(): | |
| raise FileNotFoundError(f"{sums} not found. Point --data-root (or OMNIREF_DATA) at the omniref-handoff bundle.") | |
| entries: dict[str, str] = {} | |
| bad: list[str] = [] | |
| for line in sums.read_text(encoding="utf-8").splitlines(): | |
| if not line.strip(): | |
| continue | |
| digest, _, name = line.partition(" ") | |
| name = name.strip().lstrip("*") | |
| path = data_root / name | |
| entries[name] = digest | |
| if not path.is_file(): | |
| bad.append(f"missing: {name}") | |
| elif sha256_file(path) != digest: | |
| bad.append(f"mismatch: {name}") | |
| if bad: | |
| raise ValueError(f"data bundle verification failed ({len(bad)} problems): " + "; ".join(bad[:10])) | |
| return { | |
| "root": str(data_root), | |
| "sha256sums_sha256": sha256_file(sums), | |
| "sha256sums_lines": len(entries), | |
| "verification": "SHA256SUMS verified by run_matrix.py", | |
| "file_sha256": entries, | |
| } | |
| def load_index(data_root: Path) -> dict: | |
| return json.loads((data_root / "prompts" / "index.json").read_text(encoding="utf-8")) | |
| def index_record(index: dict, composition: str, seconds: int) -> dict: | |
| matches = [r for r in index["records"] if r["composition"] == composition and int(r["target_seconds"]) == seconds] | |
| if len(matches) != 1: | |
| raise ValueError(f"expected one index record for {composition} {seconds}s, got {len(matches)}") | |
| record = matches[0] | |
| if int(record["aligned_frames"]) != EXPECTED_FRAMES[seconds]: | |
| raise ValueError(f"{composition} {seconds}s has unexpected frame count {record['aligned_frames']}") | |
| return record | |
| def read_manifest(data_root: Path, relative: str) -> list[dict]: | |
| path = data_root / relative | |
| rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] | |
| if not rows: | |
| raise ValueError(f"empty manifest {path}") | |
| return rows | |
| def request_rows(data_root: Path, index: dict, composition: str, seconds: int) -> tuple[dict, dict[int, dict]]: | |
| """(warmup row for seed 42, {seed: measured row}) from the bundle's manifests.""" | |
| record = index_record(index, composition, seconds) | |
| warmup = read_manifest(data_root, record["warmup_manifest"]) | |
| measured = read_manifest(data_root, record["measured_manifest"]) | |
| if [int(r["seed"]) for r in warmup] != [WARMUP_SEED]: | |
| raise ValueError(f"{composition} {seconds}s warmup manifest is not seed {WARMUP_SEED}") | |
| if [int(r["seed"]) for r in measured] != list(agg.DEFAULT_SEEDS): | |
| raise ValueError(f"{composition} {seconds}s measured seeds are not 0/1/2") | |
| for row in (*warmup, *measured): | |
| if int(row["metadata"]["aligned_frames"]) != EXPECTED_FRAMES[seconds]: | |
| raise ValueError(f"manifest row {row['id']} has inconsistent frame count") | |
| if row["conditions"] != record["conditions"]: | |
| raise ValueError(f"manifest row {row['id']} conditions differ from the prompt index") | |
| return warmup[0], {int(r["seed"]): r for r in measured} | |
| def media_path(data_root: Path, condition: dict) -> Path: | |
| root = (data_root / "inputs").resolve() | |
| path = (root / condition["media"]).resolve() | |
| if root not in path.parents: | |
| raise ValueError(f"condition media escapes the input root: {path}") | |
| if not path.is_file(): | |
| raise FileNotFoundError(f"condition media is missing: {path}") | |
| return path | |
| def condition_entries(row: dict, file_sha256: dict[str, str]) -> list[dict]: | |
| return [{ | |
| "type": c["type"], | |
| "media": c["media"], | |
| "sha256": file_sha256.get(f"inputs/{c['media']}"), | |
| } for c in row["conditions"]] | |
| # --------------------------------------------------------------------------- model layout | |
| def default_model_dir() -> Path: | |
| """HF layout: this script lives in <repo>/bench/, the model root in <repo>/. Falls back to the local layout.""" | |
| candidates = [SCRIPT_DIR.parent, Path.home() / "models" / "fasth3-omni-mlx"] | |
| return next((c for c in candidates if (c / "root").is_dir()), candidates[0]) | |
| def resolve_model_paths(model_dir: Path, dit: str, decoder: str, conditioner_override: Path | None) -> dict: | |
| dit_path = Path(dit).expanduser() if os.sep in dit or Path(dit).expanduser().is_dir() else model_dir / "dit" / dit | |
| root = model_dir / "root" | |
| conditioner = conditioner_override | |
| if conditioner is None and not (root / "text_encoder" / "model.safetensors").exists(): | |
| conditioner = model_dir / TEXT_ENCODER_DIR # symlink missing or broken after a download/copy | |
| return { | |
| "model_root": root, | |
| "dit": dit_path, | |
| "vae_decoder": model_dir / DECODER_FILES[decoder], | |
| "vae_encoder": model_dir / ENCODER_FILE, | |
| "conditioner_dir": conditioner, | |
| } | |
| def preflight_paths(paths: dict, decoders: list[str], model_dir: Path, dit: str) -> list[str]: | |
| problems = [] | |
| root = paths["model_root"] | |
| for rel in ("fastvideo_inference.json", "tokenizer", "processor", "audio_vae", "scheduler", "vae/config.json"): | |
| if not (root / rel).exists(): | |
| problems.append(f"{root / rel}") | |
| if not (paths["dit"] / "mlx_h3_dit.json").is_file(): | |
| problems.append(str(paths["dit"] / "mlx_h3_dit.json")) | |
| conditioner = paths["conditioner_dir"] or root / "text_encoder" | |
| if not (Path(conditioner) / "model.safetensors").exists(): | |
| problems.append(str(Path(conditioner) / "model.safetensors")) | |
| for decoder in decoders: | |
| if not (model_dir / DECODER_FILES[decoder]).is_file(): | |
| problems.append(str(model_dir / DECODER_FILES[decoder])) | |
| if not (model_dir / ENCODER_FILE).is_file(): | |
| problems.append(str(model_dir / ENCODER_FILE)) | |
| return problems | |
| def file_identity(path: Path) -> dict: | |
| path = path.resolve() | |
| return {"path": str(path), "bytes": path.stat().st_size, "head64mb_sha256": sha256_file(path, HEAD_HASH_BYTES)} | |
| def weights_identity(model_dir: Path, dit_dir: Path, decoders: list[str], conditioner: Path | None) -> dict: | |
| root = model_dir / "root" | |
| text_dir = conditioner or root / "text_encoder" | |
| identity: dict = { | |
| "dit_dir": str(dit_dir), | |
| "dit_manifest_sha256": sha256_file(dit_dir / "mlx_h3_dit.json"), | |
| "dit_weights": [file_identity(p) for p in sorted(dit_dir.glob("*.safetensors"))], | |
| "contract_sha256": sha256_file(root / "fastvideo_inference.json"), | |
| "text_encoder": [file_identity(p) for p in sorted(Path(text_dir).resolve().glob("*.safetensors"))], | |
| "text_encoder_manifest_sha256": None, | |
| "vae_encoder": file_identity(model_dir / ENCODER_FILE), | |
| "vae_decoders": {d: file_identity(model_dir / DECODER_FILES[d]) for d in decoders}, | |
| } | |
| manifest = Path(text_dir) / "mlx_h3_nvfp4_encoder.json" | |
| if manifest.is_file(): | |
| identity["text_encoder_manifest_sha256"] = sha256_file(manifest) | |
| return identity | |
| def num_steps_from_contract(model_root: Path) -> int: | |
| contract = json.loads((model_root / "fastvideo_inference.json").read_text(encoding="utf-8")) | |
| return int(contract.get("transformer_forwards", contract.get("num_inference_steps", 8))) | |
| # --------------------------------------------------------------------------- ffprobe / output validation | |
| def ffprobe_media(ffprobe: str, path: Path) -> dict: | |
| command = [ffprobe, "-v", "error", "-show_entries", | |
| "stream=index,codec_name,codec_type,width,height,r_frame_rate,sample_rate,channels,duration,nb_frames" | |
| ":format=duration,size,format_name", "-of", "json", str(path)] | |
| done = subprocess.run(command, capture_output=True, text=True, check=False) | |
| if done.returncode != 0: | |
| return {"ok": False, "error": done.stderr.strip()[:2000]} | |
| try: | |
| return {"ok": True, "probe": json.loads(done.stdout)} | |
| except json.JSONDecodeError as error: | |
| return {"ok": False, "error": f"ffprobe JSON decode failed: {error}"} | |
| def output_probe(ffprobe: str, path: Path, out_dir: Path, expected_frames: int, width: int, height: int) -> dict: | |
| if not path.is_file(): | |
| return {"ok": False, "error": "output MP4 does not exist", "warnings": []} | |
| probe = ffprobe_media(ffprobe, path) | |
| payload: dict = {"ok": False, "bytes": path.stat().st_size, "sha256": sha256_file(path), | |
| "relative_path": str(path.relative_to(out_dir)), "warnings": []} | |
| if not probe["ok"]: | |
| payload["error"] = probe.get("error", "ffprobe failed") | |
| return payload | |
| streams = probe["probe"].get("streams", []) | |
| video = [s for s in streams if s.get("codec_type") == "video"] | |
| audio = [s for s in streams if s.get("codec_type") == "audio"] | |
| payload.update(video_streams=video, audio_streams=audio, format=probe["probe"].get("format", {})) | |
| if not video: | |
| payload["error"] = "no video stream" | |
| elif not audio: | |
| payload["error"] = "no audio stream" | |
| else: | |
| payload["ok"] = True | |
| frames = video[0].get("nb_frames") | |
| if frames is not None and int(frames) != expected_frames: | |
| payload["warnings"].append(f"video has {frames} frames, expected {expected_frames}") | |
| if (video[0].get("width"), video[0].get("height")) != (width, height): | |
| payload["warnings"].append( | |
| f"video is {video[0].get('width')}x{video[0].get('height')}, requested {width}x{height}") | |
| return payload | |
| # --------------------------------------------------------------------------- GPU lock | |
| class GpuLock: | |
| """Cross-job GPU lock: atomic mkdir of /tmp/fh3mlx/gpu.lock with our PID in owner. Released in finally.""" | |
| def __init__(self, path: Path = LOCK_DIR, steal_stale: bool = False) -> None: | |
| self.path = path | |
| self.steal_stale = steal_stale | |
| self.held = False | |
| def _owner(self) -> str: | |
| try: | |
| return (self.path / "owner").read_text(encoding="utf-8").strip() | |
| except OSError: | |
| return "unknown" | |
| def _owner_dead(self) -> bool: | |
| owner = self._owner() | |
| if not owner.isdigit(): | |
| return False | |
| try: | |
| os.kill(int(owner), 0) | |
| except ProcessLookupError: | |
| return True | |
| except PermissionError: | |
| return False | |
| return False | |
| def acquire(self) -> None: | |
| self.path.parent.mkdir(parents=True, exist_ok=True) | |
| waited = 0 | |
| while True: | |
| try: | |
| os.mkdir(self.path) | |
| break | |
| except FileExistsError: | |
| if self.steal_stale and self._owner_dead(): | |
| print(f"[lock] owner pid {self._owner()} is dead; removing stale lock", flush=True) | |
| shutil.rmtree(self.path, ignore_errors=True) | |
| continue | |
| if waited % (10 * LOCK_POLL_S) == 0: | |
| print(f"[lock] GPU lock {self.path} held by pid {self._owner()}; waiting " | |
| f"(retry every {LOCK_POLL_S} s, waited {waited} s)", flush=True) | |
| time.sleep(LOCK_POLL_S) | |
| waited += LOCK_POLL_S | |
| self.held = True | |
| (self.path / "owner").write_text(f"{os.getpid()}\n", encoding="utf-8") | |
| print(f"[lock] acquired {self.path} (pid {os.getpid()})", flush=True) | |
| def release(self) -> None: | |
| if self.held: | |
| shutil.rmtree(self.path, ignore_errors=True) | |
| self.held = False | |
| print(f"[lock] released {self.path}", flush=True) | |
| # --------------------------------------------------------------------------- hardware / receipt | |
| def hardware_facts() -> dict: | |
| memsize = run_text(["sysctl", "-n", "hw.memsize"]) | |
| return { | |
| "chip": run_text(["sysctl", "-n", "machdep.cpu.brand_string"]), | |
| "memory_bytes": int(memsize) if memsize.isdigit() else None, | |
| "memory_gib": round(int(memsize) / GIB, 2) if memsize.isdigit() else None, | |
| "macos": run_text(["sw_vers", "-productVersion"]), | |
| "macos_build": run_text(["sw_vers", "-buildVersion"]), | |
| "machine": run_text(["uname", "-m"]), | |
| "cpu_cores": run_text(["sysctl", "-n", "hw.ncpu"]), | |
| "power": (run_text(["pmset", "-g", "batt"]).splitlines() or [""])[0], | |
| } | |
| def auto_wired_limit_gib(memory_gib: float | None) -> float | None: | |
| if not memory_gib: | |
| return None | |
| return round(min(WIRED_FRACTION * memory_gib, WIRED_CAP_GIB), 2) | |
| def software_probe_main() -> int: | |
| """Worker-side: report what the pipeline environment actually is. Exits non-zero if imports fail.""" | |
| import importlib.metadata as metadata | |
| import mlx.core as mx | |
| import fastvideo | |
| from fastvideo.mlx_runtime.minimax_h3_pipeline import MiniMaxH3MLXPipeline # noqa: F401 | |
| from fastvideo.mlx_runtime.minimax_h3_reference import MiniMaxH3Reference # noqa: F401 | |
| package_dir = Path(fastvideo.__file__).resolve().parent.parent | |
| sha = run_text(["git", "-C", str(package_dir), "rev-parse", "HEAD"]) | |
| info_fn = getattr(mx, "device_info", None) or mx.metal.device_info | |
| print(json.dumps({ | |
| "python": sys.version.split()[0], | |
| "python_executable": sys.executable, | |
| "mlx": metadata.version("mlx"), | |
| "mx_device_info": info_fn(), | |
| "fastvideo": { | |
| "file": str(Path(fastvideo.__file__).resolve()), | |
| "git_sha": sha or None, | |
| "git_branch": run_text(["git", "-C", str(package_dir), "rev-parse", "--abbrev-ref", "HEAD"]) or None, | |
| "dirty": bool(run_text(["git", "-C", str(package_dir), "status", "--porcelain", "--untracked-files=no"])), | |
| }, | |
| }, default=str)) | |
| return 0 | |
| def worker_env(args: argparse.Namespace) -> dict: | |
| env = dict(os.environ, PYTHONUNBUFFERED="1") | |
| if args.fastvideo_root: | |
| env["PYTHONPATH"] = os.pathsep.join(filter(None, [str(args.fastvideo_root), env.get("PYTHONPATH", "")])) | |
| return env | |
| def probe_software(args: argparse.Namespace) -> dict: | |
| done = subprocess.run([args.python, str(Path(__file__).resolve()), "_software"], capture_output=True, text=True, | |
| env=worker_env(args), check=False, cwd=str(SCRIPT_DIR)) | |
| if done.returncode != 0: | |
| raise RuntimeError("Cannot import mlx / fastvideo with " | |
| f"{args.python}. Install them or pass --python / --fastvideo-root.\n{done.stderr[-2000:]}") | |
| return json.loads(done.stdout.strip().splitlines()[-1]) | |
| # --------------------------------------------------------------------------- CLI | |
| def parse_csv(value: str) -> list[str]: | |
| return [item.strip() for item in value.split(",") if item.strip()] | |
| def build_parser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| parser.add_argument("--out", type=Path, help="results directory (default ./results-<hostname>-<date>[-smoke])") | |
| parser.add_argument("--data-root", type=Path, | |
| default=Path(os.environ.get("OMNIREF_DATA", Path.home() / "omniref-handoff")), | |
| help="omniref-handoff data bundle (env OMNIREF_DATA)") | |
| parser.add_argument("--model-dir", type=Path, default=Path(os.environ.get("OMNIREF_MODEL", default_model_dir())), | |
| help="dir holding root/, dit/<fmt>/, text_encoder-nvfp4/, vae/ (env OMNIREF_MODEL)") | |
| parser.add_argument("--dit", default="int6", help="DiT conversion under <model-dir>/dit/ (int6, int8, bf16) or a path") | |
| parser.add_argument("--quant-gemm", choices=("auto", "dequant", "qmm"), default="auto", | |
| help="INT8/INT6 linear path (auto|dequant|qmm); ignored by a bf16 DiT") | |
| parser.add_argument("--m5-sweep", action="store_true", | |
| help="fastest-config sweep: image composition, 480p, 36L decoder, seed 0 only, over " | |
| "dit {int6,int8,bf16 if present} x quant-gemm {dequant,qmm} (bf16 ignores quant-gemm). " | |
| "Writes m5-sweep.md/csv") | |
| parser.add_argument("--conditioner-dir", type=Path, help="text encoder dir; default root/text_encoder, falling " | |
| "back to <model-dir>/text_encoder-nvfp4 when that symlink is missing") | |
| parser.add_argument("--compositions", type=parse_csv, | |
| help="comma list; default image,image-voice,images3,clip,video. Also: mixed,clips3,images9") | |
| parser.add_argument("--seconds", type=int, default=5, choices=sorted(EXPECTED_FRAMES), help="clip length (default 5)") | |
| parser.add_argument("--decoders", type=parse_csv, help="36l,8l (default both)") | |
| parser.add_argument("--canvases", type=parse_csv, help="480p,768p (default both); or WxH") | |
| parser.add_argument("--width", type=int, help="custom canvas width (needs --height)") | |
| parser.add_argument("--height", type=int, help="custom canvas height (needs --width)") | |
| parser.add_argument("--seeds", type=lambda v: [int(x) for x in parse_csv(v)], default=list(agg.DEFAULT_SEEDS), | |
| help="measured seeds, subset of 0,1,2 (default 0,1,2)") | |
| parser.add_argument("--no-warmup", action="store_true", help="skip the excluded seed-42 warmup") | |
| parser.add_argument("--smoke", action="store_true", | |
| help=f"install check: one seed-42 request, 36L, {SMOKE_WIDTH}x{SMOKE_HEIGHT}, composition image") | |
| parser.add_argument("--vae-dtype", default="bf16") | |
| parser.add_argument("--metal-wired-limit-gib", default="auto", | |
| help=f"GiB to wire, or 'auto' (~{WIRED_FRACTION} x memory, cap {WIRED_CAP_GIB:g}); 'off' to skip") | |
| parser.add_argument("--mlx-cache-limit-gib", type=float, default=DEFAULT_CACHE_LIMIT_GIB, | |
| help="mx.set_cache_limit in GiB (negative = leave unset)") | |
| parser.add_argument("--reference-max-pixels", default=None, | |
| help="cap each reference canvas: a pixel count or 'match'. Default unset = trained canvas") | |
| parser.add_argument("--steps", type=int, default=0, help="transformer forwards; 0 = contract's count") | |
| parser.add_argument("--retry-failed", action="store_true", help="re-run cells previously recorded as failed") | |
| parser.add_argument("--skip-verify", action="store_true", help="do not hash the data bundle against SHA256SUMS") | |
| parser.add_argument("--cell-timeout-s", type=float, default=DEFAULT_CELL_TIMEOUT_S, help="kill a cell after this long") | |
| parser.add_argument("--python", default=sys.executable, help="python used for cell subprocesses") | |
| parser.add_argument("--fastvideo-root", type=Path, default=os.environ.get("FASTVIDEO_ROOT"), | |
| help="FastVideo checkout to put on PYTHONPATH (env FASTVIDEO_ROOT)") | |
| parser.add_argument("--ffprobe", default=os.environ.get("OMNIREF_FFPROBE") or shutil.which("ffprobe") or "ffprobe") | |
| parser.add_argument("--lock-dir", type=Path, default=LOCK_DIR) | |
| parser.add_argument("--steal-stale-lock", action="store_true", help="remove the GPU lock if its owner pid is dead") | |
| parser.add_argument("--no-bundle", action="store_true", help="do not create SEND_BACK.tar.gz") | |
| parser.add_argument("--dry-run", action="store_true", help="print the plan and exit; no GPU, no lock") | |
| parser.add_argument("--self-test", action="store_true", help="validate manifests and CSV schema; no GPU") | |
| parser.add_argument("--aggregate-only", action="store_true", help="rebuild csv/json/README from --out and exit") | |
| return parser | |
| def resolve_selection(args: argparse.Namespace) -> dict: | |
| """Turn flags (including --smoke defaults) into the ordered list of cells.""" | |
| if args.m5_sweep: | |
| return resolve_m5_sweep(args) | |
| custom = args.width is not None or args.height is not None | |
| if custom and (args.width is None or args.height is None): | |
| raise SystemExit("--width and --height must be given together") | |
| if args.smoke and not custom and not args.canvases: | |
| args.width, args.height, custom = SMOKE_WIDTH, SMOKE_HEIGHT, True | |
| compositions = args.compositions or (["image"] if args.smoke else list(agg.DEFAULT_COMPOSITIONS)) | |
| decoders = [d.lower() for d in (args.decoders or (["36l"] if args.smoke else list(agg.DECODERS)))] | |
| canvases = {} | |
| labels = [f"{args.width}x{args.height}"] if custom else (args.canvases or list(agg.STANDARD_CANVASES)) | |
| for label in labels: | |
| if label in agg.STANDARD_CANVASES: | |
| canvases[label] = agg.STANDARD_CANVASES[label] | |
| elif re.fullmatch(r"\d+x\d+", label): | |
| width, height = (int(v) for v in label.split("x")) | |
| canvases[label] = (width, height) | |
| else: | |
| raise SystemExit(f"unknown canvas {label!r}; use 480p, 768p or WxH") | |
| bad = [d for d in decoders if d not in agg.DECODERS] | |
| if bad: | |
| raise SystemExit(f"unknown decoder(s) {bad}; use 36l and/or 8l") | |
| seeds = [agg.WARMUP_SEED] if args.smoke else args.seeds | |
| if not args.smoke and not set(seeds) <= set(agg.DEFAULT_SEEDS): | |
| raise SystemExit("--seeds must be a subset of 0,1,2 (the bundle's measured seeds)") | |
| cells = [] | |
| for label, (width, height) in canvases.items(): # all 480p first, 36L before 8L, compositions in order | |
| for decoder in decoders: | |
| for composition in compositions: | |
| cells.append({"cell_id": agg.cell_id_for(composition, args.seconds, decoder, label), | |
| "composition": composition, "target_seconds": args.seconds, "decoder": decoder, | |
| "canvas": label, "width": width, "height": height, "smoke": args.smoke}) | |
| return {"cells": cells, "seeds": seeds, "decoders": decoders, "compositions": compositions} | |
| SWEEP_DITS = ("int6", "int8", "bf16") | |
| SWEEP_GEMMS = ("dequant", "qmm") | |
| def sweep_cell_id(seconds: int, dit: str, gemm: str | None) -> str: | |
| base = agg.cell_id_for("image", seconds, "36l", "480p") | |
| return f"{base}-dit-{dit}" + (f"-gemm-{gemm}" if gemm else "") | |
| def resolve_m5_sweep(args: argparse.Namespace) -> dict: | |
| """Cells for the M5 fastest-config sweep: one cell per (dit, quant-gemm) at image/480p/36L, seed 0.""" | |
| width, height = agg.STANDARD_CANVASES["480p"] | |
| cells = [] | |
| for dit in SWEEP_DITS: | |
| if dit == "bf16" and not (args.model_dir / "dit" / "bf16" / "mlx_h3_dit.json").is_file(): | |
| print("[m5-sweep] no dit/bf16 checkpoint found; sweeping int6/int8 only", flush=True) | |
| continue | |
| for gemm in ((None,) if dit == "bf16" else SWEEP_GEMMS): | |
| cells.append({"cell_id": sweep_cell_id(args.seconds, dit, gemm), "composition": "image", | |
| "target_seconds": args.seconds, | |
| "decoder": "36l", "canvas": "480p", "width": width, "height": height, "smoke": False, | |
| "dit": dit, "quant_gemm": gemm or "auto"}) | |
| return {"cells": cells, "seeds": [0], "decoders": ["36l"], "compositions": ["image"]} | |
| def cell_dit(cell: dict, args: argparse.Namespace) -> str: | |
| return cell.get("dit") or args.dit | |
| def cell_quant_gemm(cell: dict, args: argparse.Namespace) -> str: | |
| return cell.get("quant_gemm") or args.quant_gemm | |
| def write_sweep_summary(out: Path, cells: list[dict]) -> None: | |
| """m5-sweep.csv/md: one row per (dit, quant-gemm) cell, ranked by mean seed-0 wall time.""" | |
| records = agg.read_jsonl(out / "per-request-records.jsonl") | |
| rows = [] | |
| for cell in cells: | |
| summary = agg.summarize_cell(cell, records, agg.read_json(out / "status" / f"{cell['cell_id']}.json")) | |
| seed0 = summary["by_seed"].get(0) | |
| timings = (seed0 or {}).get("timings", {}) | |
| denoise = next((v for k, v in timings.items() if "denoise" in k or "dit" in k), None) | |
| rows.append({"dit": cell["dit"], "quant_gemm": cell["quant_gemm"] if cell["dit"] != "bf16" else "n/a", | |
| "status": summary["classification"], "wall_s": seed0["wall_s"] if seed0 else None, | |
| "denoise_s": denoise, "peak_gib": max(((seed0 or {}).get("peak_memory_gib") or {0: 0}).values())}) | |
| rows.sort(key=lambda r: (r["wall_s"] is None, r["wall_s"] or 0.0)) | |
| header = ["dit", "quant_gemm", "status", "wall_s", "denoise_s", "peak_gib"] | |
| (out / "m5-sweep.csv").write_text("\n".join([",".join(header)] + [",".join(str(r[h]) for h in header) for r in rows]) + "\n") | |
| lines = ["# M5 fastest-config sweep (image, 480p, 36L, seed 0)", "", "| " + " | ".join(header) + " |", | |
| "| " + " | ".join("---" for _ in header) + " |"] | |
| lines += ["| " + " | ".join(str(r[h]) for h in header) + " |" for r in rows] | |
| (out / "m5-sweep.md").write_text("\n".join(lines) + "\n") | |
| def wired_limit_value(args: argparse.Namespace, memory_gib: float | None) -> float | None: | |
| value = str(args.metal_wired_limit_gib).lower() | |
| if value == "auto": | |
| return auto_wired_limit_gib(memory_gib) | |
| if value in ("off", "none", "0"): | |
| return None | |
| return float(value) | |
| # --------------------------------------------------------------------------- orchestrator | |
| def remaining_work(cell: dict, seeds: list[int], out: Path, args: argparse.Namespace) -> tuple[list[dict], str]: | |
| """Plan items still to run for a cell, plus a skip reason (empty if the cell must run).""" | |
| records = agg.read_jsonl(out / "per-request-records.jsonl") | |
| status = agg.read_json(out / "status" / f"{cell['cell_id']}.json") | |
| summary = agg.summarize_cell({**cell, "smoke": cell["smoke"]}, records, | |
| {**status, "requested_seeds": seeds, "smoke": cell["smoke"]}) | |
| if summary["classification"] == "measured": | |
| return [], "already measured" | |
| if summary["classification"] == "failed" and status.get("state") == "failed" and not args.retry_failed: | |
| return [], "previously failed (use --retry-failed)" | |
| todo = [s for s in seeds if s not in summary["by_seed"]] | |
| plan = [] | |
| if not cell["smoke"] and not args.no_warmup: | |
| plan.append({"partition": "warmup", "seed": agg.WARMUP_SEED}) | |
| plan += [{"partition": "smoke" if cell["smoke"] else "measured", "seed": s} for s in todo] | |
| return plan, "" | |
| def tail_lines(path: Path, count: int = 40) -> str: | |
| try: | |
| lines = path.read_text(encoding="utf-8", errors="replace").splitlines() | |
| except OSError: | |
| return "" | |
| return "\n".join(lines[-count:])[-4000:] | |
| def terminate_group(proc: subprocess.Popen) -> None: | |
| if proc.poll() is not None: | |
| return | |
| for sig, grace in ((signal.SIGTERM, 20), (signal.SIGKILL, 10)): | |
| try: | |
| os.killpg(proc.pid, sig) | |
| except ProcessLookupError: | |
| return | |
| try: | |
| proc.wait(timeout=grace) | |
| return | |
| except subprocess.TimeoutExpired: | |
| continue | |
| def run_cell_subprocess(spec_path: Path, log_path: Path, args: argparse.Namespace, cell_id: str) -> tuple[int, bool]: | |
| """Run the worker; returns (returncode, timed_out). Always reaps the child, even on Ctrl-C.""" | |
| log_path.parent.mkdir(parents=True, exist_ok=True) | |
| started = time.time() | |
| with open(log_path, "ab") as log: | |
| proc = subprocess.Popen([args.python, str(Path(__file__).resolve()), "_worker", str(spec_path)], stdout=log, | |
| stderr=subprocess.STDOUT, env=worker_env(args), start_new_session=True, | |
| cwd=str(SCRIPT_DIR)) | |
| try: | |
| while True: | |
| try: | |
| return proc.wait(timeout=60), False | |
| except subprocess.TimeoutExpired: | |
| elapsed = time.time() - started | |
| last = (tail_lines(log_path, 1) or "").strip()[:140] | |
| print(f"[{cell_id}] running {elapsed / 60:.0f} min | {last}", flush=True) | |
| if elapsed > args.cell_timeout_s: | |
| terminate_group(proc) | |
| return proc.returncode if proc.returncode is not None else -9, True | |
| finally: | |
| terminate_group(proc) | |
| def record_cell_crash(out: Path, cell: dict, returncode: int, timed_out: bool, log_path: Path, seeds: list[int]) -> None: | |
| sig = -returncode if returncode < 0 else None | |
| note = "timeout" if timed_out else (f"killed by signal {sig}" if sig else f"exit code {returncode}") | |
| hint = " (likely out of memory)" if sig in (6, 9) else "" | |
| append_jsonl(out / "per-request-records.jsonl", { | |
| "schema": agg.RECORD_SCHEMA, "cell_id": cell["cell_id"], "composition": cell["composition"], | |
| "target_seconds": cell["target_seconds"], "decoder": cell["decoder"], "canvas": cell["canvas"], | |
| "status": "failed", "error_type": "CellProcessDied", "error_message": f"cell process {note}{hint}", | |
| "exit_code": returncode, "signal": sig, "log_tail": tail_lines(log_path), "finished_utc": utc_now()}) | |
| status = agg.read_json(out / "status" / f"{cell['cell_id']}.json") | |
| write_json(out / "status" / f"{cell['cell_id']}.json", | |
| {**status, "state": "failed", "finished_utc": utc_now(), "failure": f"cell process {note}{hint}"}) | |
| def make_bundle(out: Path) -> Path: | |
| bundle = out / "SEND_BACK.tar.gz" | |
| files = [p for p in out.rglob("*") if p.is_file() and p.name != bundle.name and not p.name.endswith(".tmp")] | |
| total = sum(p.stat().st_size for p in files) | |
| drop_mp4 = total > BUNDLE_MP4_CAP_BYTES | |
| keep = re.compile(r"-(seed0|smoke)\.mp4$") | |
| temporary = out / "SEND_BACK.tar.gz.tmp" | |
| with tarfile.open(temporary, "w:gz") as tar: | |
| for path in sorted(files): | |
| if drop_mp4 and path.suffix == ".mp4" and not keep.search(path.name): | |
| continue | |
| tar.add(path, arcname=f"{out.name}/{path.relative_to(out)}") | |
| temporary.replace(bundle) | |
| note = " (MP4s trimmed to one seed-0/smoke video per cell)" if drop_mp4 else "" | |
| print(f"[bundle] {bundle} {bundle.stat().st_size / 1e6:.1f} MB{note}", flush=True) | |
| return bundle | |
| def write_receipt(out: Path, args: argparse.Namespace, software: dict, data: dict, weights: dict, settings: dict) -> None: | |
| path = out / "environment_receipt.json" | |
| previous = agg.read_json(path) | |
| hardware = hardware_facts() | |
| write_json(path, { | |
| "created_utc": previous.get("created_utc", utc_now()), | |
| "updated_utc": utc_now(), | |
| "hostname": socket.gethostname(), | |
| "hardware": hardware, | |
| "software": {"python": software["python"], "mlx": software["mlx"], "fastvideo": software["fastvideo"], | |
| "mx_device_info": software["mx_device_info"], "python_executable": software["python_executable"], | |
| "ffmpeg": (run_text(["ffmpeg", "-version"]).splitlines() or [""])[0], | |
| "ffprobe": (run_text([args.ffprobe, "-version"]).splitlines() or [""])[0]}, | |
| "data": {k: v for k, v in data.items() if k != "file_sha256"}, | |
| "weights": weights, | |
| "run_settings": settings, | |
| "invocations": [*previous.get("invocations", []), {"utc": utc_now(), "argv": sys.argv}], | |
| "notes": "Column names nvfp4_* keep the Spark spelling; on Apple Silicon the NVFP4 decoders run as W4A16.", | |
| }) | |
| def orchestrate(args: argparse.Namespace) -> int: | |
| selection = resolve_selection(args) | |
| if args.smoke and args.reference_max_pixels is None: | |
| args.reference_max_pixels = "match" # references at the trained 1344x768 canvas dominate a tiny smoke run | |
| print("[smoke] --reference-max-pixels defaults to 'match' (the smoke canvas area)", flush=True) | |
| cells, seeds = selection["cells"], selection["seeds"] | |
| hardware = hardware_facts() | |
| out = (args.out or Path(f"results-{socket.gethostname().split('.')[0]}-{dt.date.today():%Y%m%d}" | |
| f"{'-smoke' if args.smoke else ''}")).resolve() | |
| if args.aggregate_only: | |
| agg.aggregate(out, cells) | |
| print(f"aggregated {out}") | |
| return 0 | |
| wired = wired_limit_value(args, hardware["memory_gib"]) | |
| print(f"[plan] {len(cells)} cells -> {out}", flush=True) | |
| for cell in cells: | |
| print(f" {cell['cell_id']} {cell['width']}x{cell['height']}", flush=True) | |
| problems = [] | |
| for dit in dict.fromkeys(cell_dit(cell, args) for cell in cells): | |
| problems += preflight_paths(resolve_model_paths(args.model_dir, dit, selection["decoders"][0], args.conditioner_dir), | |
| selection["decoders"], args.model_dir, dit) | |
| problems = list(dict.fromkeys(problems)) | |
| if problems: | |
| raise SystemExit("Missing model files (check --model-dir / --dit):\n " + "\n ".join(problems)) | |
| for tool in ("ffmpeg", args.ffprobe): | |
| if shutil.which(tool) is None: | |
| raise SystemExit(f"{tool} not found on PATH (install ffmpeg, e.g. `brew install ffmpeg`).") | |
| if args.dry_run: | |
| return 0 | |
| data = {"root": str(args.data_root), "file_sha256": {}, "verification": "skipped (--skip-verify)"} | |
| if args.skip_verify: | |
| print("[data] verification skipped", flush=True) | |
| else: | |
| print("[data] verifying SHA256SUMS ...", flush=True) | |
| data = verify_data_bundle(args.data_root) | |
| print(f"[data] ok: {data['sha256sums_lines']} files", flush=True) | |
| index = load_index(args.data_root) | |
| for cell in cells: # fail on a bad manifest now, not hours in | |
| request_rows(args.data_root, index, cell["composition"], cell["target_seconds"]) | |
| software = probe_software(args) | |
| contract_steps = args.steps or num_steps_from_contract(args.model_dir / "root") | |
| reference_paths = resolve_model_paths(args.model_dir, cell_dit(cells[0], args), selection["decoders"][0], | |
| args.conditioner_dir) | |
| weights = weights_identity(args.model_dir, reference_paths["dit"], selection["decoders"], | |
| reference_paths["conditioner_dir"]) | |
| settings = {"dit": reference_paths["dit"].name, "dit_path": str(reference_paths["dit"]), "vae_dtype": args.vae_dtype, | |
| "metal_wired_limit_gib": wired, "mlx_cache_limit_gib": args.mlx_cache_limit_gib, | |
| "reference_max_pixels": args.reference_max_pixels, "num_steps": contract_steps, | |
| "seeds": seeds, "warmup": not args.no_warmup and not args.smoke, "smoke": args.smoke, | |
| "conditioner_dir": str(reference_paths["conditioner_dir"]) if reference_paths["conditioner_dir"] else None, | |
| "quant_gemm": args.quant_gemm, "m5_sweep": args.m5_sweep} | |
| out.mkdir(parents=True, exist_ok=True) | |
| write_receipt(out, args, software, data, weights, settings) | |
| # Ship the exact prompts/conditions with the results so the bundle is self-describing. | |
| prompts_out = out / "prompts" | |
| prompts_out.mkdir(exist_ok=True) | |
| for name in ("index.json", "index.csv"): | |
| source = Path(args.data_root) / "prompts" / name | |
| if source.is_file(): | |
| shutil.copy2(source, prompts_out / name) | |
| exit_code = 0 | |
| lock = GpuLock(args.lock_dir, args.steal_stale_lock) | |
| # SIGINT is explicit: it is ignored by default when launched as a background job (nohup, agents). | |
| previous_handlers = {s: signal.signal(s, _raise_interrupt) for s in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP)} | |
| current: dict | None = None | |
| try: | |
| lock.acquire() | |
| for position, cell in enumerate(cells, 1): | |
| plan, skip = remaining_work(cell, seeds, out, args) | |
| if skip: | |
| print(f"[{cell['cell_id']}] skip: {skip} ({position}/{len(cells)})", flush=True) | |
| continue | |
| current = cell | |
| exit_code |= run_one_cell(cell, plan, seeds, out, args, settings, data, weights, position, len(cells)) | |
| current = None | |
| agg.aggregate(out, cells) | |
| if args.m5_sweep: | |
| write_sweep_summary(out, cells) | |
| except KeyboardInterrupt: | |
| print("\n[interrupted] stopping; releasing GPU lock. Re-run the same command to resume.", flush=True) | |
| if current is not None: | |
| status = agg.read_json(out / "status" / f"{current['cell_id']}.json") | |
| write_json(out / "status" / f"{current['cell_id']}.json", {**status, "state": "interrupted"}) | |
| exit_code = 130 | |
| finally: | |
| lock.release() | |
| for sig, handler in previous_handlers.items(): | |
| signal.signal(sig, handler) | |
| agg.aggregate(out, cells) | |
| if args.m5_sweep: | |
| write_sweep_summary(out, cells) | |
| if not args.no_bundle: | |
| make_bundle(out) | |
| print(f"[done] results in {out}", flush=True) | |
| return exit_code | |
| def _raise_interrupt(signum, frame) -> None: # noqa: ARG001 | |
| raise KeyboardInterrupt | |
| def run_one_cell(cell: dict, plan: list[dict], seeds: list[int], out: Path, args: argparse.Namespace, settings: dict, | |
| data: dict, weights: dict, position: int, total: int) -> int: | |
| cell_id = cell["cell_id"] | |
| paths = resolve_model_paths(args.model_dir, cell_dit(cell, args), cell["decoder"], args.conditioner_dir) | |
| settings = {**settings, "dit": paths["dit"].name, "dit_path": str(paths["dit"]), | |
| "quant_gemm": cell_quant_gemm(cell, args)} | |
| spec = { | |
| "cell": cell, "plan": plan, "requested_seeds": seeds, "out": str(out), "data_root": str(args.data_root), | |
| "ffprobe": args.ffprobe, "settings": settings, "file_sha256": data["file_sha256"], | |
| "paths": {k: (str(v) if v is not None else None) for k, v in paths.items()}, | |
| "weights": {"dit_manifest_sha256": sha256_file(paths["dit"] / "mlx_h3_dit.json"), | |
| "decoder": weights["vae_decoders"][cell["decoder"]], "encoder": weights["vae_encoder"]}, | |
| } | |
| spec_path = out / "status" / f"{cell_id}.spec.json" | |
| write_json(spec_path, spec) | |
| write_json(out / "status" / f"{cell_id}.json", { | |
| "cell_id": cell_id, "composition": cell["composition"], "target_seconds": cell["target_seconds"], | |
| "decoder": cell["decoder"], "canvas": cell["canvas"], "width": cell["width"], "height": cell["height"], | |
| "smoke": cell["smoke"], "requested_seeds": seeds, "state": "starting", "started_utc": utc_now(), | |
| "dit": settings["dit"], "quant_gemm": settings["quant_gemm"]}) | |
| print(f"[{cell_id}] start ({position}/{total}); plan: " | |
| + ", ".join(f"{p['partition']}:{p['seed']}" for p in plan), flush=True) | |
| log_path = out / "logs" / f"{cell_id}.log" | |
| code, timed_out = run_cell_subprocess(spec_path, log_path, args, cell_id) | |
| status = agg.read_json(out / "status" / f"{cell_id}.json") | |
| if status.get("state") not in ("complete", "failed"): | |
| record_cell_crash(out, cell, code, timed_out, log_path, seeds) | |
| print(f"[{cell_id}] FAILED: process exited {code}{' (timeout)' if timed_out else ''}", flush=True) | |
| return 1 | |
| print(f"[{cell_id}] {status['state'].upper()}", flush=True) | |
| return 0 if status["state"] == "complete" else 1 | |
| # --------------------------------------------------------------------------- worker (one cell, fresh process) | |
| class MemorySampler: | |
| """Every SAMPLE_INTERVAL_S: MLX active/cache/peak GiB and swap used, appended to a JSONL trace.""" | |
| def __init__(self, mx, trace_path: Path) -> None: | |
| self.mx, self.trace_path = mx, trace_path | |
| self.maxima = {"max_active_gib": 0.0, "max_cache_gib": 0.0, "max_peak_gib": 0.0, "max_swap_used_mb": None} | |
| self.first_swap_mb: float | None = None | |
| self._stop = threading.Event() | |
| self._thread = threading.Thread(target=self._run, daemon=True) | |
| def swap_used_mb() -> float | None: | |
| match = re.search(r"used = ([\d.]+)([MGK])", run_text(["sysctl", "-n", "vm.swapusage"])) | |
| if not match: | |
| return None | |
| return float(match.group(1)) * {"K": 1 / 1024, "M": 1, "G": 1024}[match.group(2)] | |
| def _sample(self, handle) -> None: | |
| mx = self.mx | |
| row = {"t": round(time.time(), 2), "active_gib": mx.get_active_memory() / GIB, | |
| "cache_gib": mx.get_cache_memory() / GIB, "peak_gib": mx.get_peak_memory() / GIB, | |
| "swap_used_mb": self.swap_used_mb()} | |
| for key, value in (("max_active_gib", row["active_gib"]), ("max_cache_gib", row["cache_gib"]), | |
| ("max_peak_gib", row["peak_gib"])): | |
| self.maxima[key] = max(self.maxima[key], value) | |
| if row["swap_used_mb"] is not None: | |
| self.first_swap_mb = row["swap_used_mb"] if self.first_swap_mb is None else self.first_swap_mb | |
| self.maxima["max_swap_used_mb"] = max(self.maxima["max_swap_used_mb"] or 0.0, row["swap_used_mb"]) | |
| handle.write(json.dumps({k: (round(v, 3) if isinstance(v, float) else v) for k, v in row.items()}) + "\n") | |
| handle.flush() | |
| def _run(self) -> None: | |
| with open(self.trace_path, "w", encoding="utf-8") as handle: | |
| while not self._stop.is_set(): | |
| try: | |
| self._sample(handle) | |
| except Exception: # noqa: BLE001 - a sampling glitch must never kill the benchmark | |
| pass | |
| self._stop.wait(SAMPLE_INTERVAL_S) | |
| def __enter__(self) -> "MemorySampler": | |
| self._thread.start() | |
| return self | |
| def __exit__(self, *exc_info) -> None: | |
| self._stop.set() | |
| self._thread.join(timeout=10) | |
| def summary(self) -> dict: | |
| swap_delta = None | |
| if self.first_swap_mb is not None and self.maxima["max_swap_used_mb"] is not None: | |
| swap_delta = self.maxima["max_swap_used_mb"] - self.first_swap_mb | |
| return {**{k: (round(v, 3) if isinstance(v, float) else v) for k, v in self.maxima.items()}, | |
| "swap_growth_mb": swap_delta, "sample_interval_s": SAMPLE_INTERVAL_S} | |
| def reference_max_pixels(value: str | None, width: int, height: int) -> int | None: | |
| if value is None: | |
| return None | |
| if value == "match": | |
| return width * height | |
| pixels = int(value) | |
| if pixels <= 0: | |
| raise ValueError("--reference-max-pixels must be positive or 'match'") | |
| return pixels | |
| def _record_base(spec: dict, row: dict, item: dict, tag: str, rewarm: bool, index_rec: dict) -> dict: | |
| cell, settings = spec["cell"], spec["settings"] | |
| return { | |
| "schema": agg.RECORD_SCHEMA, "cell_id": cell["cell_id"], "composition": cell["composition"], | |
| "target_seconds": cell["target_seconds"], "decoder": cell["decoder"], "canvas": cell["canvas"], | |
| "partition": item["partition"], "seed": item["seed"], "request_id": row["id"], "tag": tag, "rewarm": rewarm, | |
| "width": cell["width"], "height": cell["height"], "num_frames": EXPECTED_FRAMES[cell["target_seconds"]], | |
| "prompt_sha256": index_rec["prompt_sha256"], | |
| "prompt_sha256_computed": hashlib.sha256(row["prompt"].encode("utf-8")).hexdigest(), | |
| "condition_media": condition_entries(row, spec["file_sha256"]), | |
| "settings": settings, "weights": spec["weights"], "model_paths": spec["paths"], | |
| } | |
| def worker_main(spec_path: str) -> int: | |
| spec = json.loads(Path(spec_path).read_text(encoding="utf-8")) | |
| cell, out, settings = spec["cell"], Path(spec["out"]), spec["settings"] | |
| data_root, cell_id = Path(spec["data_root"]), cell["cell_id"] | |
| status_path = out / "status" / f"{cell_id}.json" | |
| records_path = out / "per-request-records.jsonl" | |
| media_dir = out / "media" / cell_id | |
| media_dir.mkdir(parents=True, exist_ok=True) | |
| base_status = agg.read_json(status_path) | |
| def set_status(**fields) -> None: | |
| write_json(status_path, {**agg.read_json(status_path), **fields}) | |
| def fail(stage: str, error: BaseException, extra: dict | None = None) -> None: | |
| append_jsonl(records_path, { | |
| "schema": agg.RECORD_SCHEMA, "cell_id": cell_id, "composition": cell["composition"], | |
| "target_seconds": cell["target_seconds"], "decoder": cell["decoder"], "canvas": cell["canvas"], | |
| "status": "failed", "stage": stage, "error_type": type(error).__name__, "error_message": str(error)[:4000], | |
| "traceback": traceback.format_exc()[-8000:], "settings": settings, "finished_utc": utc_now(), **(extra or {})}) | |
| print(f"[{cell_id}] FAILED during {stage}: {type(error).__name__}: {error}", flush=True) | |
| set_status(state="loading_model", pid=os.getpid()) | |
| pipeline = None | |
| ok = True | |
| try: | |
| import mlx.core as mx | |
| import fastvideo | |
| from fastvideo.mlx_runtime.minimax_h3_pipeline import MiniMaxH3MLXPipeline | |
| from fastvideo.mlx_runtime.minimax_h3_reference import MiniMaxH3Reference | |
| index = load_index(data_root) | |
| index_rec = index_record(index, cell["composition"], cell["target_seconds"]) | |
| warmup_row, measured_rows = request_rows(data_root, index, cell["composition"], cell["target_seconds"]) | |
| paths = spec["paths"] | |
| started = time.perf_counter() | |
| pipeline = MiniMaxH3MLXPipeline( | |
| model_root=paths["model_root"], mlx_dit_checkpoint=paths["dit"], vae_dtype=settings["vae_dtype"], | |
| vae_decoder_path=paths["vae_decoder"], vae_encoder_path=paths["vae_encoder"], | |
| conditioner_dir=paths["conditioner_dir"], metal_wired_limit_gib=settings["metal_wired_limit_gib"], | |
| mlx_cache_limit_gib=(settings["mlx_cache_limit_gib"] if settings["mlx_cache_limit_gib"] >= 0 else None), | |
| quant_gemm=settings.get("quant_gemm", "auto")) | |
| init_s = time.perf_counter() - started | |
| print(f"[{cell_id}] pipeline ready in {init_s:.1f} s ({fastvideo.__file__})", flush=True) | |
| except Exception as error: # noqa: BLE001 - the native failure is the evidence | |
| fail("setup", error) | |
| set_status(state="failed", finished_utc=utc_now()) | |
| return 3 | |
| previous = [r for r in agg.read_jsonl(records_path) if r.get("cell_id") == cell_id and r.get("partition") == "warmup"] | |
| try: | |
| for item in spec["plan"]: | |
| ok = run_request(spec, item, pipeline, mx, MiniMaxH3Reference, index_rec, warmup_row, measured_rows, init_s, | |
| rewarm=item["partition"] == "warmup" and bool(previous), set_status=set_status) and ok | |
| if not ok: | |
| break | |
| except Exception as error: # noqa: BLE001 | |
| ok = False | |
| fail("generate", error) | |
| finally: | |
| try: | |
| pipeline.close() | |
| except Exception: # noqa: BLE001 - close must not mask the result | |
| traceback.print_exc() | |
| set_status(state="complete" if ok else "failed", finished_utc=utc_now(), model_init_s=round(init_s, 3), | |
| **{k: v for k, v in base_status.items() if k in ("requested_seeds",)}) | |
| print(f"[{cell_id}] {'COMPLETE' if ok else 'CELL_FAILED'}", flush=True) | |
| return 0 if ok else 3 | |
| def run_request(spec, item, pipeline, mx, reference_cls, index_rec, warmup_row, measured_rows, init_s, rewarm, | |
| set_status) -> bool: | |
| cell, settings, out = spec["cell"], spec["settings"], Path(spec["out"]) | |
| data_root = Path(spec["data_root"]) | |
| seed = item["seed"] | |
| row = warmup_row if seed == agg.WARMUP_SEED else measured_rows[seed] | |
| tag = {"warmup": "warmup", "smoke": "smoke"}.get(item["partition"], f"seed{seed}") | |
| output_path = out / "media" / cell["cell_id"] / f"{cell['cell_id']}-{tag}.mp4" | |
| record = _record_base(spec, row, item, tag, rewarm, index_rec) | |
| references = [reference_cls(source=str(media_path(data_root, c)), media_type=c["type"]) for c in row["conditions"]] | |
| set_status(state="request_running", request=row["id"], partition=item["partition"], seed=seed) | |
| trace_path = out / "media" / cell["cell_id"] / f"{cell['cell_id']}-{tag}.mem.jsonl" | |
| num_frames = EXPECTED_FRAMES[cell["target_seconds"]] | |
| try: | |
| with MemorySampler(mx, trace_path) as sampler: | |
| started = time.perf_counter() | |
| result = pipeline.generate( | |
| row["prompt"], output_path=output_path, references=references, height=cell["height"], | |
| width=cell["width"], num_frames=num_frames, seed=seed, num_steps=settings["num_steps"], | |
| reference_max_pixels=reference_max_pixels(settings["reference_max_pixels"], cell["width"], cell["height"])) | |
| wall_s = time.perf_counter() - started | |
| except Exception as error: # noqa: BLE001 | |
| append_jsonl(out / "per-request-records.jsonl", { | |
| **record, "status": "failed", "stage": "generate", "error_type": type(error).__name__, | |
| "error_message": str(error)[:4000], "traceback": traceback.format_exc()[-8000:], | |
| "finished_utc": utc_now()}) | |
| print(f"[{cell['cell_id']}] {row['id']} FAILED: {type(error).__name__}: {error}", flush=True) | |
| return False | |
| probe = output_probe(spec["ffprobe"], output_path, out, num_frames, cell["width"], cell["height"]) | |
| record.update( | |
| wall_s=round(wall_s, 6), timings={k: round(float(v), 4) for k, v in result.timings.items()}, | |
| peak_memory_gib={k: round(float(v), 3) for k, v in result.peak_memory_gib.items()}, | |
| vsa=result.vsa, video_decode_backend=getattr(result, "video_decode_backend", None), | |
| audio_samples=int(result.waveform.shape[-1]) if getattr(result, "waveform", None) is not None else None, | |
| pipeline_init_s=round(init_s, 3), memory=sampler.summary(), mem_trace=str(trace_path.relative_to(out)), | |
| output=probe, validation_warnings=probe.get("warnings", []), status="ok" if probe["ok"] else "output_invalid", | |
| finished_utc=utc_now()) | |
| append_jsonl(out / "per-request-records.jsonl", record) | |
| print(f"[{cell['cell_id']}] {row['id']} wall={wall_s:.2f}s peak={max(record['peak_memory_gib'].values(), default=0):.1f}GiB " | |
| f"swap_max={record['memory']['max_swap_used_mb']}MB ok={probe['ok']} warn={probe.get('warnings')}", flush=True) | |
| return probe["ok"] | |
| # --------------------------------------------------------------------------- self test | |
| def self_test(args: argparse.Namespace) -> int: | |
| import tempfile | |
| problems = [] | |
| sums = args.data_root / "SHA256SUMS" | |
| if sums.is_file(): | |
| index = load_index(args.data_root) | |
| count = 0 | |
| for rec in index["records"]: | |
| request_rows(args.data_root, index, rec["composition"], int(rec["target_seconds"])) | |
| count += 1 | |
| print(f"manifests ok: {count} index records parsed") | |
| else: | |
| print(f"(no data bundle at {args.data_root}; skipping manifest parse)") | |
| with tempfile.TemporaryDirectory() as tmp: | |
| out = Path(tmp) | |
| cell = {"cell_id": "image-5s-36l-480p", "composition": "image", "target_seconds": 5, "decoder": "36l", | |
| "canvas": "480p", "width": 832, "height": 480, "smoke": False} | |
| write_json(out / "status" / "image-5s-36l-480p.json", {**cell, "state": "complete", "requested_seeds": [0, 1, 2]}) | |
| for seed, wall in ((0, 10.0), (1, 12.0), (2, 14.0)): | |
| append_jsonl(out / "per-request-records.jsonl", { | |
| "cell_id": cell["cell_id"], "partition": "measured", "seed": seed, "status": "ok", "wall_s": wall, | |
| "output": {"ok": True}, "peak_memory_gib": {"denoise": 20.0}, "memory": {"max_swap_used_mb": 0.0}}) | |
| agg.aggregate(out, [cell]) | |
| header = (out / "latency.csv").read_text(encoding="utf-8").splitlines()[0].split(",") | |
| expected = ["composition", "target_seconds", "gpu_count"] + [ | |
| f"nvfp4_{d}_{c}_{s}" for d, c in agg.SPARK_COLUMN_ORDER for s in ("mean_s", "sample_std_s", "status")] | |
| if header != expected: | |
| problems.append(f"csv header mismatch: {header}") | |
| row = (out / "latency.csv").read_text(encoding="utf-8").splitlines()[1].split(",") | |
| if not (row[3] == "12.0" and abs(float(row[4]) - 2.0) < 1e-9 and row[5] == "measured"): | |
| problems.append(f"csv row mismatch: {row}") | |
| print("csv schema ok" if not problems else "\n".join(problems)) | |
| return 1 if problems else 0 | |
| def main() -> int: | |
| if len(sys.argv) >= 2 and sys.argv[1] == "_worker": | |
| return worker_main(sys.argv[2]) | |
| if len(sys.argv) >= 2 and sys.argv[1] == "_software": | |
| return software_probe_main() | |
| args = build_parser().parse_args() | |
| if args.self_test: | |
| return self_test(args) | |
| return orchestrate(args) | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |