"""Compare two `pretrain.py` runs — one machine against another. python benchmarks/compare.py "MPS bench" "CUDA bench" --out COMPARISON.md Both runs start from bit-identical weights on bit-identical data, so every number below is a difference the *arithmetic* produced. What that means depends on the row: - **the portable models** (``*_portable_*``, RNNs, conv, attention) run the same pure-torch code on both machines, so a difference is float non-associativity — different reduction orders on different hardware. Expect small and growing-with-steps, not zero. - **the vendored models** run the authors' *fused* kernels on CUDA and their reference path on MPS. Those rows are fused-vs-reference, and they are the ones worth reading: this is the check PLAN.md fixes as the rule for any fast path. A loss trajectory is chaotic, so late-step agreement is not the right test — two runs of the same model can separate simply because gradient descent amplifies. The honest summary is the *early* divergence and whether both runs landed in the same place, which is why both are reported. """ from __future__ import annotations import argparse import json import math from pathlib import Path import torch def load(path: Path) -> dict: manifest = json.loads((path / "manifest.json").read_text()) manifest["_dir"] = path return manifest def weights_of(run: Path, name: str) -> dict | None: blob = run / name / "weights.pt" if not blob.exists(): return None return torch.load(blob, map_location="cpu", weights_only=True) def weight_delta(a: dict, b: dict) -> tuple[float, float]: """Relative and absolute distance between two trained parameter sets.""" num = 0.0 den = 0.0 worst = 0.0 for key, va in a["model"].items(): vb = b["model"][key] d = (va.double() - vb.double()).abs() num += float((d**2).sum()) den += float((va.double() ** 2).sum()) worst = max(worst, float(d.max())) return (math.sqrt(num / max(den, 1e-30)), worst) def first_divergence(la: list[float], lb: list[float], tol: float = 1e-6) -> int | None: """The first step whose losses differ by more than `tol` relatively. Says when the two machines stopped agreeing, which is far more informative than how far apart they ended up: the end of a chaotic trajectory is not a measurement of anything. """ for i, (x, y) in enumerate(zip(la, lb, strict=False)): scale = max(abs(x), abs(y), 1e-12) if abs(x - y) / scale > tol: return i return None def _weights_provenance(left: dict, right: dict) -> list[str]: """Say — and check — where each run's starting weights came from. The comparison used to assert that both machines began from bit-identical weights. For S4 and S4D that was false: `torch.linalg.eigh` fixes eigenvectors only up to a phase, so their `B` and `P` differ between macOS and Linux under the same seed, and the comparison reported a 2.6e-01 difference that was two different models rather than two devices. A claim the instrument cannot check is worth less than one it can, so this reads what the runs recorded instead of assuming. """ def sources(run: dict) -> set[str]: return {m.get("weights_from", "seed") for m in run["models"] if "error" not in m} both = sources(left) | sources(right) if both <= {"written", "loaded"}: return [ "Both runs load one shared set of starting weights, so the initial", "conditions are bit-identical and every difference below is arithmetic.", "", ] return [ "> **The two runs did not share starting weights** " f"({', '.join(sorted(both))}). The seed alone does not give identical", "> S4/S4D weights across platforms — `eigh`'s eigenvectors are fixed only", "> up to a phase — so differences below may be initialisation rather than", "> arithmetic. Re-run both sides with `--init