Buckets:
| """Guards for the K-v2 programme protocol side (docs/decisions.md 2026-09-14 "K-v2 programme — S0 pre-registration", protocol C23): | |
| * `kv2_gate` S1 rule on synthetic per-clip arrays: survive / kill / HM guard / tie-with-fewer-components / ≤ 2 survivors; | |
| * S1b continuation rule; S3 argmin + 0.10-px tie -> fewer parameters; | |
| * MotionBERT-kp name-encoded variants build with the expected parameter counts, TRUNK_PREFIXES unchanged, foreign suffixes rejected; | |
| * run-name / flag helpers shared by the launcher, the gate and the report; | |
| * `dry_trunk_load` mechanics (REUSE on a matching checkpoint, NEED on a foreign one); | |
| * `report_kv2_program` smoke on a tmp out-root with partial results (pending runs, conservative NOT ADOPTED line, append-only). | |
| python -m pytest aide/tests/test_kv2.py -q | |
| """ | |
| from __future__ import annotations | |
| import os | |
| os.environ.setdefault("OMP_NUM_THREADS", "4") | |
| import glob # noqa: E402 | |
| import json # noqa: E402 | |
| import shutil # noqa: E402 | |
| import sys # noqa: E402 | |
| from pathlib import Path # noqa: E402 | |
| import numpy as np # noqa: E402 | |
| import pytest # noqa: E402 | |
| import torch # noqa: E402 | |
| torch.set_num_threads(4) | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[2])) | |
| from aide.data import protocol as P # noqa: E402 | |
| from aide.eval import kv2_gate as G # noqa: E402 | |
| from aide.models import motionbert_kp # noqa: E402 | |
| N_BOOT = 400 | |
| def _synthetic(seed=0, n=405, n_hm=40): | |
| rng = np.random.default_rng(seed) | |
| base = np.abs(rng.normal(55, 25, n)) | |
| hm = np.zeros(n, bool) | |
| hm[rng.choice(n, n_hm, replace=False)] = True | |
| return rng, base, hm | |
| def _cand(rng, base, shift, noise=0.05): | |
| return base + shift + rng.normal(0, noise, len(base)) | |
| def test_s1_rule_survive_kill_guard_tie_and_cap(): | |
| rng, base, hm = _synthetic() | |
| cands = { # cfg: (shift on all clips, extra shift on HM clips, components) | |
| "full": (-0.50, 0.0, 5), # survives, mean −0.50 | |
| "nohaz": (-0.45, 0.0, 3), # within 0.10 of full with fewer components -> ranked FIRST | |
| "plain": (-0.31, 0.0, 4), # survives but only 2 slots | |
| "khead": (-0.10, 0.0, 4), # KILL (> −0.30) | |
| "q060": (-0.30, 0.0, 5), # exactly at the threshold -> survives (≤) | |
| "hazaux": (-0.60, +6.0, 4), # best All but HM guard: Δ HM > +3 with CI excl. 0 -> GUARD_KILL | |
| } | |
| rows = [] | |
| for cfg, (sh, sh_hm, nc) in cands.items(): | |
| c = _cand(rng, base, sh) | |
| c_hm = c[hm] + sh_hm | |
| rows.append(G.s1_row(cfg, G.kv2_run(cfg, 42), c, base, c_hm, base[hm], nc, N_BOOT, 0)) | |
| st = {r["cfg"]: r["status"] for r in rows} | |
| assert st == {"full": "SURVIVE", "nohaz": "SURVIVE", "plain": "SURVIVE", "khead": "KILL", "q060": "SURVIVE", "hazaux": "GUARD_KILL"} | |
| r_haz = next(r for r in rows if r["cfg"] == "hazaux") | |
| assert r_haz["survive_all"] and r_haz["guard_kill"] and r_haz["d_hm"]["ci_lo"] > 0 and r_haz["d_hm"]["mean"] > 3.0 | |
| assert abs(next(r for r in rows if r["cfg"] == "full")["d_all"]["mean"] + 0.50) < 0.02 | |
| surv = G.s1_select(rows) | |
| assert surv == ["nohaz", "full"] # tie rule (fewer components first), then the cap of 2 (plain / q060 excluded, hazaux guard-killed) | |
| # without the tie: a clear leader wins regardless of its component count | |
| rows2 = [r for r in rows if r["cfg"] != "nohaz"] | |
| assert G.s1_select(rows2) == ["full", "plain"] | |
| # a guard flagged but the All rule failed -> plain KILL, never GUARD_KILL | |
| c = _cand(rng, base, -0.05) | |
| r = G.s1_row("lam03", "x", c, base, c[hm] + 6.0, base[hm], 5, N_BOOT, 0) | |
| assert r["status"] == "KILL" and r["guard_kill"] | |
| assert G.s1_select([]) == [] | |
| def test_s1b_rule(): | |
| ok, why = G.s1b_decide(-0.40, -0.15) | |
| assert ok and why.startswith("CONTINUE") # Δ123 < 0 and mean −0.275 ≤ −0.25 | |
| assert not G.s1b_decide(-0.60, +0.05)[0] # Δ123 ≥ 0 -> killed even though the mean passes | |
| assert not G.s1b_decide(-0.30, -0.10)[0] # mean −0.20 > −0.25 -> killed | |
| ok, why = G.s1b_decide(-0.25, -0.25) | |
| assert ok and "val-killed" not in why | |
| assert "val-killed at seed 123" in G.s1b_decide(-0.30, -0.10)[1] | |
| def test_s3_argmin_and_tie_to_fewer_params(): | |
| rows = [{"cfg": "default", "val_all": 55.55, "n_params": 813_088}, | |
| {"cfg": "d192", "val_all": 55.50, "n_params": 1_809_440}, # within 0.10 of the best -> fewer params wins | |
| {"cfg": "L4", "val_all": 55.70, "n_params": 1_077_664}] | |
| assert G.s3_select(rows) == "default" | |
| rows.append({"cfg": "do0.1", "val_all": 55.30, "n_params": 813_088}) # clear leader (> 0.10 better than default) | |
| assert G.s3_select(rows) == "do0.1" | |
| rows.append({"cfg": "wd0.1", "val_all": 55.25, "n_params": 813_088}) # ties do0.1 (same params) -> lower val | |
| assert G.s3_select(rows) == "wd0.1" | |
| assert G.s3_select([{"cfg": "x", "val_all": None, "n_params": 1}]) is None | |
| assert G.s3_select([]) is None | |
| def test_mb_name_variants_build_with_expected_params_and_same_trunk_prefixes(): | |
| expected = {"motionbert_kp": 813_088, "motionbert_kp_2b": 548_512, "motionbert_kp_d192": 1_809_440, | |
| "motionbert_kp_L4": 1_077_664, "motionbert_kp_d192_L4": 2_402_912} | |
| ref = motionbert_kp.MotionBERTKP.TRUNK_PREFIXES | |
| assert ref == ("inp.", "joint_emb", "time_emb", "blocks.", "ln_f.") | |
| for name, n in expected.items(): | |
| m = motionbert_kp.build(name, dropout=0.0) | |
| assert sum(p.numel() for p in m.parameters()) == n, name | |
| assert m.TRUNK_PREFIXES == ref | |
| x = torch.rand(2, P.T_OBS, P.J, 3) * torch.tensor([P.W, P.H, 1.0]) | |
| with torch.no_grad(): | |
| y = m.eval()(x) | |
| assert y.shape == (2, P.T_PRED, P.J, 2) and torch.equal(y, x[:, -1:, :, :2].expand_as(y)) # zero-velocity at init | |
| assert motionbert_kp.parse_variant("motionbert_kp_d192_L4") == {"d": 192, "blocks": 4} | |
| for bad in ("motionbert_kp_do0.1", "motionbert_kp_lr1e-4", "motionbert_kp_d192_d128", "motionbert_kpx", "stgcn_kp"): | |
| with pytest.raises(ValueError): | |
| motionbert_kp.build(bad) | |
| with pytest.raises(ValueError): | |
| motionbert_kp.build("motionbert_kp_d192", d=128) # width given twice | |
| # every MB grid model name builds through the same dispatcher train_kp uses (name.startswith("motionbert_kp")) | |
| for cfg, (model, _) in G.MB_GRID.items(): | |
| motionbert_kp.build(model, dropout=0.0) | |
| def test_run_names_flags_and_grid_sizes(): | |
| assert set(G.KV2_CONFIGS) == {"full", "nospatial", "khead", "nohaz", "hazaux", "plain", "q060", "q090", "lam03"} | |
| assert G.kv2_run("full", 42) == "cwm3d_v2_s42_v2" | |
| assert G.kv2_run("nospatial", 123) == "cwm3d_v2_nospatial_s123_v2" | |
| assert G.kv2_run("nospatial", 123, pre=True) == "cwm3d_v2_nospatial_batonpre_trunkp123_s123_v2" | |
| assert G.kv2_run("full", 789, pre=True) == "cwm3d_v2_batonpre_trunkp123_s789_v2" | |
| assert G.mb_run("default", 42) == "motionbert_kp_s42_v2" | |
| assert G.mb_run("default", 789, pre=True) == "motionbert_kp_batonpre_trunkp123_s789_v2" | |
| assert G.mb_run("d192_L4_do0.1", 42) == "motionbert_kp_d192_L4_do0.1_s42_v2" | |
| assert G.k_run(1011) == "cwm3d_kp_s1011_v2" and G.k_run(42, pre=True) == "cwm3d_kp_batonpre_trunkp123_s42_v2" | |
| assert len(G.MB_GRID) == 10 and len(G.MB_NEW) == 9 and "default" not in G.MB_NEW | |
| assert G.flags("kv2", "plain") == ["--model", "cwm3d_v2", "--no-physics", "--w-nll", "0"] | |
| assert G.flags("kv2", "hazaux") == ["--model", "cwm3d_v2", "--haz-mod", "0"] | |
| assert G.flags("mb", "d192_L4_do0.1") == ["--model", "motionbert_kp_d192_L4", "--dropout", "0.1"] | |
| assert G.flags("mb", "lr1e-4") == ["--model", "motionbert_kp", "--lr", "1e-4"] | |
| assert G.mb_changes_dl("d192") and G.mb_changes_dl("d192_L4") and not G.mb_changes_dl("wd0.1") and not G.mb_changes_dl("default") | |
| assert G.mb_pretrain_run("d192") == "motionbert_kp_d192_batonpre_pretrain_s123" | |
| assert G.kv2_pretrain_run("nospatial") == "cwm3d_v2_nospatial_batonpre_pretrain_s123" # own trunk architecture (no spatial / pool) | |
| assert G.kv2_pretrain_run("lam03") == G.kv2_pretrain_run("full") == "cwm3d_v2_batonpre_pretrain_s123" | |
| # component counts as pre-registered | |
| assert {c: n for c, (_, n) in G.KV2_CONFIGS.items()} == {"full": 5, "q060": 5, "q090": 5, "lam03": 5, "nospatial": 4, "khead": 4, "hazaux": 4, "plain": 4, "nohaz": 3} | |
| # the trainer flags used by the plain / K-side configs exist in train_kp today | |
| src = (Path(G.ROOT) / "aide" / "train" / "train_kp.py").read_text() | |
| assert '"--no-physics"' in src and '"--w-nll"' in src and '"--init-parts"' in src | |
| def test_dry_trunk_load_reuse_vs_need(tmp_path): | |
| torch.manual_seed(0) | |
| src = motionbert_kp.build("motionbert_kp", dropout=0.0) | |
| with torch.no_grad(): | |
| for p in src.parameters(): | |
| p.add_(0.05 * torch.randn_like(p)) | |
| ck = tmp_path / "mb.pt" | |
| torch.save({"model": src.state_dict(), "epoch": 3}, ck) | |
| torch.manual_seed(1) | |
| dst = motionbert_kp.build("motionbert_kp", dropout=0.0) | |
| rep = G.dry_trunk_load(dst, ck) | |
| assert rep["decision"] == "REUSE_K_CKPT" and rep["trunk_complete"] is True and rep["n_skipped_shape"] == 0 and rep["n_missing_trunk"] == 0 | |
| assert rep["n_loaded"] == rep["n_trunk_tensors"] == sum(k.startswith(tuple(dst.TRUNK_PREFIXES)) for k in dst.state_dict()) | |
| # a wider model against the same checkpoint: every trunk tensor is shape-mismatched or missing -> NEED | |
| torch.manual_seed(2) | |
| wide = motionbert_kp.build("motionbert_kp_d192", dropout=0.0) | |
| rep2 = G.dry_trunk_load(wide, ck) | |
| assert rep2["decision"] == "NEED_V2_PRETRAIN" and rep2["trunk_complete"] is False and (rep2["n_skipped_shape"] > 0 or rep2["n_missing_trunk"] > 0) | |
| # ----------------------------------------------------------------------------- report smoke (needs the AIDE cache + one finished K run) | |
| def _copy_run(runs_dst: Path, src_name: str, dst_name: str, shift_px: float = 0.0, seed: int = 0, haz: bool = False): | |
| """Copy an existing run's outputs into a tmp runs dir under a new name; optionally move every prediction by `shift_px` along x | |
| (a synthetic 'different model') and add a fake haz_P array; rewrite the run json name / t_start_utc.""" | |
| src = P.RESULTS / "runs" / src_name | |
| dst = runs_dst / dst_name | |
| dst.mkdir(parents=True) | |
| rng = np.random.default_rng(seed) | |
| for split in ("val", "test"): | |
| z = dict(np.load(src / f"pred_{split}.npz", allow_pickle=False)) | |
| if shift_px: | |
| z["pred"] = (z["pred"] + rng.normal(shift_px, 1.0, z["pred"].shape).astype(np.float32)) | |
| if haz: | |
| z["haz_P"] = rng.uniform(0, 1, (len(z["pred"]), 4, 5)).astype(np.float32) | |
| np.savez(dst / f"pred_{split}.npz", **z) | |
| shutil.copy(src / f"metrics_{split}.json", dst / f"metrics_{split}.json") | |
| js = sorted(glob.glob(str(src / f"run_{src_name}_2*.json")))[-1] | |
| rec = json.load(open(js)) | |
| rec["name"] = dst_name | |
| rec["t_start_utc"] = "2026-09-15T01:00:00Z" | |
| (dst / f"run_{dst_name}_20260915T010000Z.json").write_text(json.dumps(rec, default=str)) | |
| def test_report_smoke_with_partial_results(tmp_path): | |
| from aide.eval import report_kv2_program as R | |
| runs = tmp_path / "runs" | |
| for s in (42, 123): | |
| _copy_run(runs, f"cwm3d_kp_s{s}_v2", G.k_run(s)) # K baselines (copied, not retrained) | |
| _copy_run(runs, f"motionbert_kp_s{s}_v2", G.mb_run("default", s)) | |
| _copy_run(runs, "cwm3d_kp_s42_v2", G.kv2_run("full", 42), shift_px=0.0, seed=1, haz=True) # identical predictions -> Δ = 0 -> KILL | |
| _copy_run(runs, "cwm3d_kp_s42_v2", G.kv2_run("nospatial", 42), shift_px=+3.0, seed=2) # worse | |
| _copy_run(runs, "motionbert_kp_s42_v2", G.mb_run("do0.1", 42), shift_px=+2.0, seed=3) | |
| kv2 = tmp_path / "kv2" | |
| kv2.mkdir() | |
| (kv2 / "S0_UTC.txt").write_text("2026-09-14T22:51:07Z\n") | |
| # gate on partial results: incomplete, pending rows, no survivors | |
| dec = G.stage_s1(runs, kv2, N_BOOT) | |
| assert dec["complete"] is False and dec["survivors"] == [] | |
| st = {r["cfg"]: r["status"] for r in dec["rows"]} | |
| assert st["full"] == "KILL" and st["nospatial"] == "KILL" and st["khead"] == "PENDING" and sum(v == "PENDING" for v in st.values()) == 7 | |
| assert abs(next(r for r in dec["rows"] if r["cfg"] == "full")["d_all"]["mean"]) < 1e-9 | |
| assert (kv2 / "s1_decision.json").exists() and len(list(kv2.glob("s1_decision_*.json"))) == 1 | |
| j = json.load(open(kv2 / "s1_decision.json")) | |
| assert set(("utc", "rule", "rows", "survivors")) <= set(j) | |
| dec3 = G.stage_s3(runs, kv2, N_BOOT) | |
| assert dec3["complete"] is False and dec3["mb_best"] == "default" # do0.1 is +2 px worse | |
| assert (kv2 / "s3_decision.json").exists() | |
| assert G.stage_s1b("full", runs, kv2, N_BOOT)["decision"] == "PENDING" # seed 123 K-v2 missing | |
| # cli read helper | |
| assert G.main(["read", "s1_decision", "complete", "--out", str(kv2)]) == 0 | |
| # report on the tmp root: runs gracefully, pending everywhere, conservative adoption line, append-only on the second call | |
| tables = tmp_path / "tables" | |
| assert R.main(["--out-root", str(tmp_path), "--n-boot", str(N_BOOT)]) == 0 | |
| md = (tables / "kv2_program.md").read_text(encoding="utf-8") | |
| for sec in ("## 0. Pre-registration", "## 1. S1 val screen", "### 1.1 S1b", "## 2. S2", "## 3. S3", "## 4. Primary endpoints", "### 4.1 Paper-wording", "## 5. GPU minutes", "## 6. Provenance"): | |
| assert sec in md, sec | |
| assert "**ADOPTION A1: NOT ADOPTED**" in md and "**ADOPTION A2: NOT ADOPTED**" in md and "**ADOPTION A1: ADOPTED**" not in md # exact bold marker only | |
| assert "pending" in md and "PENDING" in md | |
| assert "S0 UTC: **2026-09-14T22:51:07Z**" in md and "VIOLATION" not in md # copied runs start after S0 | |
| assert "K-v2 programme — S0 pre-registration" in md # the row is quoted from docs/decisions.md | |
| assert (tables / "kv2_program.json").exists() and len(json.load(open(tables / "kv2_program.json"))) == 1 | |
| assert list(tables.glob("run_report_kv2_program_2*.json")) | |
| n1 = md.count("## 0. Pre-registration") | |
| assert R.main(["--out-root", str(tmp_path), "--n-boot", str(N_BOOT)]) == 0 | |
| md2 = (tables / "kv2_program.md").read_text(encoding="utf-8") | |
| assert md2.startswith(md) and md2.count("## 0. Pre-registration") == n1 + 1 and "appended regeneration" in md2 | |
| assert len(json.load(open(tables / "kv2_program.json"))) == 2 | |
| # AUC helper: perfect separation -> 1, inverted -> 0, ties -> 0.5 | |
| assert R.rank_auc([0.1, 0.2, 0.9, 0.8], [False, False, True, True]) == 1.0 | |
| assert R.rank_auc([0.9, 0.8, 0.1, 0.2], [False, False, True, True]) == 0.0 | |
| assert R.rank_auc([0.5, 0.5, 0.5, 0.5], [False, False, True, True]) == 0.5 | |
Xet Storage Details
- Size:
- 15.1 kB
- Xet hash:
- df00b0abaf6fc8222a97c19265646f74b90778176893c75907be910b4c232856
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.