quantum-cortex 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. cortex_c2/__init__.py +161 -0
  2. cortex_c2/consolidation.py +146 -0
  3. cortex_c2/router_ablation.py +254 -0
  4. cortex_c2/router_v2.py +109 -0
  5. cortex_c2b/__init__.py +724 -0
  6. cortex_c2b/companion_demo.py +203 -0
  7. cortex_c2b/crypto.py +102 -0
  8. cortex_c2b/external_arm.py +591 -0
  9. cortex_c2b/hm_lm.py +983 -0
  10. cortex_c2b/hm_protocol.py +308 -0
  11. cortex_c2b/lifecycle.py +454 -0
  12. cortex_c2b/lm_bridge.py +338 -0
  13. cortex_c2b/organ_use.py +254 -0
  14. cortex_c2b/read_path.py +218 -0
  15. cortex_c2b/reconstruction_probe.py +471 -0
  16. cortex_c2b/write_path.py +176 -0
  17. cortex_data/__init__.py +439 -0
  18. cortex_data/relay.py +174 -0
  19. cortex_eval/__init__.py +20 -0
  20. cortex_eval/ablation_mqar.py +187 -0
  21. cortex_eval/confirmation_run.py +133 -0
  22. cortex_eval/domain_map.py +127 -0
  23. cortex_eval/external_baseline.py +148 -0
  24. cortex_eval/ingest_confirmation.py +194 -0
  25. cortex_eval/mqar.py +122 -0
  26. cortex_eval/multiseed.py +148 -0
  27. cortex_eval/recurrent_base.py +197 -0
  28. cortex_eval/recurrent_ladder.py +159 -0
  29. cortex_eval/resumable_ladder.py +231 -0
  30. cortex_eval/resumable_rb.py +207 -0
  31. cortex_eval/run_mqar.py +127 -0
  32. cortex_eval/serial_position.py +116 -0
  33. quantum_cortex-1.0.0.dist-info/METADATA +209 -0
  34. quantum_cortex-1.0.0.dist-info/RECORD +41 -0
  35. quantum_cortex-1.0.0.dist-info/WHEEL +5 -0
  36. quantum_cortex-1.0.0.dist-info/licenses/AUTHORS.md +30 -0
  37. quantum_cortex-1.0.0.dist-info/licenses/LICENSE +73 -0
  38. quantum_cortex-1.0.0.dist-info/licenses/LICENSE-DOCS.md +34 -0
  39. quantum_cortex-1.0.0.dist-info/licenses/NOTICE +50 -0
  40. quantum_cortex-1.0.0.dist-info/top_level.txt +5 -0
  41. train.py +885 -0
@@ -0,0 +1,159 @@
1
+ """cortex_eval.recurrent_ladder — the recurrent-family ladder (D19 Level 1c, refined).
2
+
3
+ The founder's question: "custom GLA vs official Mamba — isn't there an
4
+ intermediate?" — and the answer: the space between them is CONTINUOUS. Mamba is
5
+ the canonical gated linear attention PLUS a stack of documented refinements.
6
+ So instead of one custom GLA or one official Mamba, we build a LADDER: the
7
+ canonical GLA, then each refinement added one at a time behind a flag, each
8
+ re-implemented from its paper (zero borrowed code), each measured. Our own
9
+ ablation discipline (one variable at a time, equal at everything) applied to
10
+ the construction of the reference itself.
11
+
12
+ The rungs (each credited to the paper that introduced the idea):
13
+ L0 canonical GLA S_t = g ⊙ S_{t-1} + k vᵀ ; o = q S (linear attention lineage)
14
+ L1 + input-dependent gate g_t = σ(W_g x_t) — Mamba's "selectivity" (Gu & Dao 2023)
15
+ L2 + channel-wise decay per-channel forget instead of per-head (Kimi Linear's KDA finding)
16
+ L3 + short local conv a depthwise conv on the input before mixing (Mamba / RWKV lineage)
17
+ L4 + delta-rule write S += β k (v − Sᵀk)ᵀ with L2-normalised keys (DeltaNet / KDA)
18
+
19
+ Reading rule (the founder's curve principle): read the CURVE of MQAR accuracy
20
+ vs rung, not one point. Note L4 is exactly our own DeltaMemory — so the top of
21
+ the ladder places our component INSIDE the recurrent family, and "L4 pure" vs
22
+ "our hybrid (L4 + attention)" isolates the contribution of hybridisation.
23
+
24
+ Claim discipline: we compare to "GLA + <named rungs>", never to "Mamba" — an
25
+ official pre-trained Mamba/RWKV remains a separate, non-equalised external
26
+ reference (a later job, license-checked at ingestion).
27
+ """
28
+ from __future__ import annotations
29
+
30
+ from dataclasses import dataclass
31
+ from datetime import datetime, timezone
32
+
33
+ import numpy as np
34
+
35
+ from cortex_eval.mqar import MQARTier, make_batch
36
+
37
+ VOCAB = 256 + 8
38
+
39
+
40
+ @dataclass
41
+ class Rung:
42
+ name: str
43
+ selective_gate: bool = False # L1
44
+ channel_decay: bool = False # L2
45
+ local_conv: bool = False # L3
46
+ delta_rule: bool = False # L4
47
+
48
+
49
+ LADDER = [
50
+ Rung("L0-canonical-gla"),
51
+ Rung("L1-+selective-gate", selective_gate=True),
52
+ Rung("L2-+channel-decay", selective_gate=True, channel_decay=True),
53
+ Rung("L3-+local-conv", selective_gate=True, channel_decay=True, local_conv=True),
54
+ Rung("L4-+delta-rule", selective_gate=True, channel_decay=True, local_conv=True, delta_rule=True),
55
+ ]
56
+
57
+
58
+ def _build(rung: Rung, d_model: int, n_head: int):
59
+ import torch, torch.nn as nn
60
+ dk = d_model // n_head
61
+
62
+ class RecurrentRung(nn.Module):
63
+ def __init__(self):
64
+ super().__init__()
65
+ self.emb = nn.Embedding(VOCAB, d_model)
66
+ self.ln = nn.LayerNorm(d_model)
67
+ self.conv = (nn.Conv1d(d_model, d_model, kernel_size=3, padding=2, groups=d_model)
68
+ if rung.local_conv else None) # L3: depthwise, causal (crop)
69
+ self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
70
+ # gate: constant-per-head (L0) or input-dependent (L1); per-head or per-channel (L2)
71
+ gate_out = n_head * dk if rung.channel_decay else n_head
72
+ self.gate = (nn.Linear(d_model, gate_out, bias=True) if rung.selective_gate
73
+ else nn.Parameter(torch.zeros(gate_out))) # L0: learned constant
74
+ self.beta = nn.Linear(d_model, n_head, bias=True) if rung.delta_rule else None # L4
75
+ self.out = nn.Linear(d_model, d_model, bias=False)
76
+ self.ln2 = nn.LayerNorm(d_model)
77
+ self.mlp = nn.Sequential(nn.Linear(d_model, 4 * d_model), nn.GELU(),
78
+ nn.Linear(4 * d_model, d_model))
79
+ self.head = nn.Linear(d_model, VOCAB, bias=False)
80
+
81
+ def forward(self, idx):
82
+ x = self.emb(idx)
83
+ b, T, c = x.shape
84
+ z = self.ln(x)
85
+ if self.conv is not None: # L3
86
+ z = self.conv(z.transpose(1, 2))[:, :, :T].transpose(1, 2) # causal crop
87
+ q, k, v = self.qkv(z).split(c, dim=2)
88
+ q = torch.nn.functional.normalize(q.view(b, T, n_head, dk), dim=-1)
89
+ k = torch.nn.functional.normalize(k.view(b, T, n_head, dk), dim=-1)
90
+ v = v.view(b, T, n_head, dk)
91
+ if rung.selective_gate: # L1
92
+ g_raw = torch.sigmoid(self.gate(z))
93
+ else: # L0
94
+ g_raw = torch.sigmoid(self.gate).expand(b, T, -1)
95
+ if rung.channel_decay: # L2
96
+ g = g_raw.view(b, T, n_head, dk)
97
+ else:
98
+ g = g_raw.view(b, T, n_head, 1).expand(b, T, n_head, dk)
99
+ beta = torch.sigmoid(self.beta(z)).view(b, T, n_head, 1) if rung.delta_rule else None
100
+ S = torch.zeros(b, n_head, dk, dk, device=x.device)
101
+ outs = []
102
+ for i in range(T):
103
+ S = S * g[:, i].unsqueeze(2) # forget (columns)
104
+ if rung.delta_rule: # L4
105
+ u = torch.einsum('bhij,bhi->bhj', S, k[:, i])
106
+ S = S + beta[:, i].unsqueeze(-1) * torch.einsum('bhi,bhj->bhij', k[:, i], v[:, i] - u)
107
+ else: # L0–L3: plain write
108
+ S = S + torch.einsum('bhi,bhj->bhij', k[:, i], v[:, i])
109
+ outs.append(torch.einsum('bhi,bhij->bhj', q[:, i], S).reshape(b, c))
110
+ o = torch.stack(outs, 1)
111
+ x = x + self.out(o)
112
+ x = x + self.mlp(self.ln2(x))
113
+ return self.head(x)
114
+
115
+ return RecurrentRung()
116
+
117
+
118
+ def run_rung(rung: Rung, tier: MQARTier, steps: int, seed: int, d_model: int = 64,
119
+ n_head: int = 2, batch: int = 32, lr: float = 3e-3, eval_batch: int = 64) -> float:
120
+ import torch, torch.nn as nn
121
+ torch.manual_seed(seed)
122
+ model = _build(rung, d_model, n_head)
123
+ opt = torch.optim.AdamW(model.parameters(), lr=lr)
124
+ for step in range(steps):
125
+ X, Y = make_batch(tier, batch, seed=seed + step)
126
+ logits = model(torch.from_numpy(X))
127
+ loss = nn.functional.cross_entropy(logits.view(-1, VOCAB),
128
+ torch.from_numpy(Y).view(-1), ignore_index=-100)
129
+ if not torch.isfinite(loss):
130
+ return float("nan") # circuit-breaker spirit
131
+ opt.zero_grad(); loss.backward()
132
+ nn.utils.clip_grad_norm_(model.parameters(), 1.0); opt.step()
133
+ Xe, Ye = make_batch(tier, eval_batch, seed=seed + 10_000)
134
+ with torch.no_grad():
135
+ preds = model(torch.from_numpy(Xe)).argmax(-1).numpy()
136
+ mask = Ye != -100
137
+ return float((preds[mask] == Ye[mask]).mean())
138
+
139
+
140
+ def run_ladder(tiers: list[MQARTier] | None = None, steps: int = 1500, seed: int = 1337,
141
+ rungs: list[Rung] | None = None, **kw) -> dict:
142
+ tiers = tiers or [MQARTier(kv_pairs=4, seq_len=128), MQARTier(kv_pairs=8, seq_len=128),
143
+ MQARTier(kv_pairs=16, seq_len=128)]
144
+ rungs = rungs or LADDER
145
+ curve = []
146
+ for rung in rungs:
147
+ accs = [run_rung(rung, t, steps, seed, **kw) for t in tiers]
148
+ auc = float(np.nanmean(accs)) if any(a == a for a in accs) else float("nan")
149
+ curve.append({"rung": rung.name, "per_tier": accs, "auc": round(auc, 4)})
150
+ print(f"[ladder] {rung.name:22s} -> auc={auc:.4f}")
151
+ return {
152
+ "benchmark": "recurrent_ladder",
153
+ "timestamp_utc": datetime.now(timezone.utc).isoformat(),
154
+ "regime": {"tiers": [(t.kv_pairs, t.seq_len) for t in tiers], "steps": steps, "seed": seed},
155
+ "curve": curve,
156
+ "reading": "MQAR AUC vs recurrent refinement rung. L4 (delta-rule, normalised keys) is our own "
157
+ "DeltaMemory — the top of the ladder places our component inside the recurrent family; "
158
+ "'L4 pure' vs our hybrid isolates hybridisation. Compare to 'GLA + named rungs', never to 'Mamba'.",
159
+ }
@@ -0,0 +1,231 @@
1
+ """cortex_eval.resumable_ladder — the 8-seed ladder that survives a quota cut.
2
+
3
+ The lesson of run B (2026-09-09): a run that writes only at the end loses
4
+ everything when Kaggle kills the session. This runner makes the ladder
5
+ **resumable at the unit level**:
6
+
7
+ unit = (rung, seed, tier) → one small JSON file, written the moment the unit
8
+ finishes, in a checkpoint directory.
9
+
10
+ On start, the runner scans the checkpoint directory (and any *previous*
11
+ checkpoint directories mounted as Kaggle Inputs), loads every finished unit,
12
+ and skips them. A session cut at unit 37 of 80 restarts at unit 38. Nothing is
13
+ ever recomputed; nothing is ever lost.
14
+
15
+ Kaggle specifics: a killed session loses /kaggle/working, but the Output of the
16
+ previous *version* is retained and can be mounted as an Input of the next
17
+ version. So we READ from every directory in `resume_from` (the mounted Inputs)
18
+ and WRITE to `ckpt_dir` (the current Output). Same pattern as the N1 trainer's
19
+ --resume from an Input-mounted checkpoint.
20
+
21
+ Time budget: `--time-budget-min` stops cleanly before the quota wall (units
22
+ are small, ~6-10 min each, so the loss at the wall is at most one unit).
23
+
24
+ When every unit exists, the aggregate (per-rung stats, CIs, paired tests) is
25
+ computed from the unit files — deterministic, reproducible from the units alone.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import json
31
+ import math
32
+ import time
33
+ from datetime import datetime, timezone
34
+ from pathlib import Path
35
+
36
+ import numpy as np
37
+
38
+ from cortex_eval.mqar import MQARTier
39
+ from cortex_eval.recurrent_ladder import LADDER, Rung, run_rung
40
+ from cortex_eval.multiseed import _mean_ci, _paired_test
41
+ from cortex_eval.run_mqar import run_curriculum
42
+
43
+ # The three ARCHITECTURE paths measured alongside the pure ladder, same seeds, same
44
+ # tiers, same steps — so every comparison below is a PAIRED test on identical data.
45
+ ARCH = ("none", "hopfield", "delta") # control / Hopfield hybrid / delta hybrid
46
+ ARCH_EXTRA = ("none+local-conv",) # the control plus a local convolution (Rev60): a declared reference, --paths only,
47
+ # never part of the full ladder's 128 units nor of its aggregate
48
+
49
+ DEFAULT_SEEDS = (1337, 2024, 7, 42, 99, 3, 11, 2026) # 8 seeds → df 7, t_crit 2.365
50
+ DEFAULT_TIERS = [MQARTier(kv_pairs=16, seq_len=128), MQARTier(kv_pairs=8, seq_len=128)]
51
+
52
+
53
+ def unit_id(rung, seed: int, tier: MQARTier) -> str:
54
+ """rung is a Rung (pure ladder) or a str in ARCH (control / hybrids)."""
55
+ name = rung.name if isinstance(rung, Rung) else f"ARCH-{rung}"
56
+ return f"{name}__s{seed}__kv{tier.kv_pairs}_seq{tier.seq_len}"
57
+
58
+
59
+ def run_unit(rung, tier: MQARTier, steps: int, seed: int, **kw) -> float:
60
+ if isinstance(rung, Rung):
61
+ return run_rung(rung, tier, steps, seed, **kw)
62
+ if rung == "none+local-conv": # Rev60: the control, one variable added
63
+ res = run_curriculum(c2_variant="none", attn_local_conv=True, steps=steps, tiers=[tier], seed=seed, **kw)
64
+ return float(res.per_tier[0]["accuracy"])
65
+ res = run_curriculum(c2_variant=rung, steps=steps, tiers=[tier], seed=seed, **kw)
66
+ return float(res.per_tier[0]["accuracy"])
67
+
68
+
69
+ def load_done(dirs: list[Path]) -> dict[str, dict]:
70
+ """Every finished unit across all given directories (later dirs win on duplicates)."""
71
+ done = {}
72
+ for d in dirs:
73
+ if not d or not d.exists():
74
+ continue
75
+ for p in sorted(d.glob("unit-*.json")):
76
+ try:
77
+ u = json.loads(p.read_text())
78
+ if u.get("status") == "done" and u.get("accuracy") == u.get("accuracy"):
79
+ done[u["unit_id"]] = u
80
+ except Exception:
81
+ continue
82
+ return done
83
+
84
+
85
+ def run_resumable_ladder(seeds=DEFAULT_SEEDS, tiers=None, steps: int = 1500,
86
+ ckpt_dir: str | Path = "metrics/mqar/ladder8-ckpt",
87
+ resume_from: list[str | Path] | None = None,
88
+ time_budget_min: float | None = None, paths: list[str] | None = None,
89
+ relay: str | None = None, **kw) -> dict:
90
+ """`paths` (optional): restrict the run to these path names (e.g. the sigma run of
91
+ the convergence, L3 alone at 3000 steps on the seeds that stayed at the floor).
92
+ A subset writes its units and a SUBSET aggregate (per path stats, no paired
93
+ tests against absent paths); the full readout needs all eight paths."""
94
+ tiers = tiers or DEFAULT_TIERS
95
+ ckpt = Path(ckpt_dir); ckpt.mkdir(parents=True, exist_ok=True)
96
+ if relay: # the checkpoint relay: the last push, before anything else
97
+ import tempfile
98
+ from cortex_data import relay as _relay
99
+ pulled = Path(tempfile.mkdtemp(prefix="relay-")) / ckpt.name
100
+ if _relay.pull(relay, pulled):
101
+ resume_from = list(resume_from or []) + [pulled]
102
+ dirs = [Path(p) for p in (resume_from or [])] + [ckpt]
103
+ done = load_done(dirs)
104
+ all_paths = list(LADDER) + list(ARCH)
105
+ if paths:
106
+ names = {(r.name if isinstance(r, Rung) else f"ARCH-{r}"): r for r in all_paths + list(ARCH_EXTRA)}
107
+ unknown = [n for n in paths if n not in names]
108
+ if unknown:
109
+ raise SystemExit(f"unknown paths {unknown}; known: {sorted(names)}")
110
+ all_paths = [names[n] for n in paths]
111
+ units = [(r, s, t) for r in all_paths for s in seeds for t in tiers]
112
+ todo = [u for u in units if unit_id(*u) not in done]
113
+ print(f"[resumable] {len(units)} units total, {len(done)} already done, {len(todo)} to run")
114
+ t0 = time.time()
115
+ stopped_early = False
116
+ for rung, seed, tier in todo:
117
+ if time_budget_min is not None and (time.time() - t0) / 60.0 > time_budget_min:
118
+ print(f"[resumable] time budget reached — stopping cleanly; rerun to resume")
119
+ stopped_early = True
120
+ break
121
+ uid = unit_id(rung, seed, tier)
122
+ t1 = time.time()
123
+ acc = run_unit(rung, tier, steps, seed, **kw)
124
+ rec = {"unit_id": uid, "rung": rung.name if isinstance(rung, Rung) else f"ARCH-{rung}", "seed": seed, "kv_pairs": tier.kv_pairs,
125
+ "seq_len": tier.seq_len, "steps": steps, "accuracy": acc,
126
+ "status": "done" if acc == acc else "diverged",
127
+ "wall_s": round(time.time() - t1, 1), "written_utc": datetime.now(timezone.utc).isoformat()}
128
+ (ckpt / f"unit-{uid}.json").write_text(json.dumps(rec) + "\n") # written THE MOMENT it finishes
129
+ done[uid] = rec
130
+ if relay:
131
+ from cortex_data import relay as _relay
132
+ _relay.push(ckpt, relay, uid) # small files: a blocking push, seconds
133
+ print(f"[unit] {uid} -> acc={acc:.4f} ({rec['wall_s']}s) [{len(done)}/{len(units)}]")
134
+ complete = all(unit_id(*u) in done for u in units)
135
+ result = {"benchmark": "recurrent_ladder_8seed_resumable",
136
+ "timestamp_utc": datetime.now(timezone.utc).isoformat(),
137
+ "regime": {"seeds": list(seeds), "n_seeds": len(seeds), "steps": steps,
138
+ "tiers": [(t.kv_pairs, t.seq_len) for t in tiers]},
139
+ "units_done": len(done), "units_total": len(units), "complete": complete,
140
+ "stopped_early": stopped_early}
141
+ if complete and paths:
142
+ result["subset"] = list(paths)
143
+ result["stats"] = {}
144
+ for r in all_paths:
145
+ name = r.name if isinstance(r, Rung) else f"ARCH-{r}"
146
+ vals = [float(np.mean([done[unit_id(r, s, t)]["accuracy"] for t in tiers])) for s in seeds]
147
+ per_tier = {f"kv{t.kv_pairs}": [done[unit_id(r, s, t)]["accuracy"] for s in seeds] for t in tiers}
148
+ result["stats"][name] = {**_mean_ci(vals), "per_seed": vals, "per_tier": per_tier}
149
+ tag = "-".join(paths).replace("+", "").replace("/", "")[:40]
150
+ (ckpt / f"SUBSET-{tag}-{steps}steps.json").write_text(json.dumps(result, indent=2) + "\n")
151
+ print(f"[resumable] COMPLETE (subset {paths}) — per path stats written; no paired tests on a subset")
152
+ elif complete:
153
+ result.update(aggregate(done, seeds, tiers))
154
+ (ckpt / "LATEST-ladder8.json").write_text(json.dumps(result, indent=2) + "\n")
155
+ print("[resumable] COMPLETE — aggregate written")
156
+ else:
157
+ (ckpt / "PROGRESS-ladder8.json").write_text(json.dumps(result, indent=2) + "\n")
158
+ print(f"[resumable] incomplete ({len(done)}/{len(units)}) — progress written; rerun with this dir as Input to resume")
159
+ return result
160
+
161
+
162
+ def aggregate(done: dict, seeds, tiers) -> dict:
163
+ """Per-rung AUC per seed (mean over tiers), then stats + paired tests. Deterministic
164
+ from the unit files alone — re-runnable offline on the committed units."""
165
+ per_rung: dict[str, list[float]] = {}
166
+ for r in LADDER:
167
+ vals = []
168
+ for s in seeds:
169
+ accs = [done[unit_id(r, s, t)]["accuracy"] for t in tiers]
170
+ vals.append(float(np.mean(accs)))
171
+ per_rung[r.name] = vals
172
+ per_arch: dict[str, list[float]] = {}
173
+ for a in ARCH:
174
+ vals = []
175
+ for s in seeds:
176
+ accs = [done[unit_id(a, s, t)]["accuracy"] for t in tiers]
177
+ vals.append(float(np.mean(accs)))
178
+ per_arch[a] = vals
179
+ stats = {r: _mean_ci(v) for r, v in {**per_rung, **{f"ARCH-{a}": v for a, v in per_arch.items()}}.items()}
180
+ best_pure = max(per_rung, key=lambda r: np.mean(per_rung[r]))
181
+ n = len(seeds)
182
+ tests = {
183
+ "hopfield_hybrid_vs_best_pure": _paired_test(per_arch["hopfield"], per_rung[best_pure]),
184
+ "delta_hybrid_vs_best_pure": _paired_test(per_arch["delta"], per_rung[best_pure]),
185
+ "control_vs_best_pure": _paired_test(per_arch["none"], per_rung[best_pure]),
186
+ "hopfield_vs_control": _paired_test(per_arch["hopfield"], per_arch["none"]),
187
+ "delta_vs_control": _paired_test(per_arch["delta"], per_arch["none"]),
188
+ "L4_vs_L3": _paired_test(per_rung["L4-+delta-rule"], per_rung["L3-+local-conv"]),
189
+ }
190
+ gate = {k: bool(v.get("significant_95")) and n >= 3 for k, v in tests.items()}
191
+ return {"per_rung_per_seed": per_rung, "per_arch_per_seed": per_arch, "stats": stats,
192
+ "best_pure_rung": best_pure, "paired_tests": tests, "gate_readout": gate,
193
+ "note": "every test is paired on seed at identical steps/tiers; gated = significant at 95% with >= 3 seeds."}
194
+
195
+
196
+ def _cli():
197
+ ap = argparse.ArgumentParser(description="Resumable 8-seed recurrent ladder")
198
+ ap.add_argument("--steps", type=int, default=1500)
199
+ ap.add_argument("--seeds", type=int, nargs="+", default=list(DEFAULT_SEEDS))
200
+ ap.add_argument("--ckpt-dir", default="metrics/mqar/ladder8-ckpt")
201
+ ap.add_argument("--resume-from", nargs="*", default=[],
202
+ help="previous checkpoint dirs (e.g. Kaggle Inputs) whose finished units are reused")
203
+ ap.add_argument("--time-budget-min", type=float, default=None)
204
+ ap.add_argument("--paths", nargs="*", default=None,
205
+ help="restrict to these path names (e.g. L3-+local-conv for the sigma run)")
206
+ ap.add_argument("--tiers", nargs="*", default=None, help="restrict to these tiers as KVxSEQ (e.g. 8x128 for the tier where L3 takes off); a subset aggregate, never a paired claim across tiers")
207
+ ap.add_argument("--relay", default=None, help="checkpoint relay: a private Kaggle dataset slug pulled before the resume lookup and pushed after every unit")
208
+ ap.add_argument("--quick", action="store_true")
209
+ args = ap.parse_args()
210
+ tiers = parse_tiers(args.tiers)
211
+ if args.quick:
212
+ run_resumable_ladder(seeds=(1, 2), tiers=tiers or [MQARTier(kv_pairs=4, seq_len=32)], steps=40,
213
+ ckpt_dir=args.ckpt_dir, resume_from=args.resume_from, d_model=32, paths=args.paths, relay=args.relay)
214
+ else:
215
+ run_resumable_ladder(seeds=tuple(args.seeds), tiers=tiers, steps=args.steps, ckpt_dir=args.ckpt_dir,
216
+ resume_from=args.resume_from, time_budget_min=args.time_budget_min, paths=args.paths, relay=args.relay)
217
+
218
+
219
+ def parse_tiers(spec):
220
+ """'8x128 16x128' -> [MQARTier(8, 128), MQARTier(16, 128)]; None -> None (the default tiers)."""
221
+ if not spec:
222
+ return None
223
+ out = []
224
+ for s in spec:
225
+ kv, seq = s.lower().split("x")
226
+ out.append(MQARTier(kv_pairs=int(kv), seq_len=int(seq)))
227
+ return out
228
+
229
+
230
+ if __name__ == "__main__":
231
+ _cli()
@@ -0,0 +1,207 @@
1
+ """cortex_eval.resumable_rb -- the recurrent base hybrid run, resumable (Rev38; declared before the run).
2
+
3
+ 64 units = 4 arms (bare, critical, cls, cls-phi) x 8 seeds x 2 tiers, at the
4
+ ladder's 1500 steps, each written the moment it finishes. The references are
5
+ NOT retrained: the 8 seed ladder's units (`metrics/mqar/ladder8-ckpt/`) give L3
6
+ pure and the transformer control at the same seeds, tiers and steps, so every
7
+ comparison is paired on seed on identical data.
8
+
9
+ The readout, declared 2026-09-12 before any unit ran:
10
+ per arm: mean full accuracy (mean over tiers per seed), trunk only accuracy,
11
+ take off rate (trunk only at 8 pairs >= 0.3), the 16 pair mean, the
12
+ consolidation gap, the gate at the end;
13
+ paired tests (95 %, n >= 3): each arm's full model vs L3 pure; each arm's full
14
+ model vs the control; each arm's trunk only vs L3 pure (did the base
15
+ keep or gain its take off?); cls-phi vs cls (phi against 1/2).
16
+ gated = significant at 95 %; a bimodal comparator gets its reserve written
17
+ next to the number, as for the ladder.
18
+ No number here is a result until the units exist and the log is read cold.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ import json
24
+ import time
25
+ from datetime import datetime, timezone
26
+ from pathlib import Path
27
+
28
+ import numpy as np
29
+
30
+ from cortex_eval.mqar import MQARTier
31
+ from cortex_eval.multiseed import _mean_ci, _paired_test
32
+ from cortex_eval.recurrent_base import ARMS, ARM_BY_NAME, RBArm, run_rb_unit
33
+ from cortex_eval.resumable_ladder import DEFAULT_SEEDS, DEFAULT_TIERS, load_done
34
+
35
+ REF_L3, REF_CONTROL = "L3-+local-conv", "ARCH-none"
36
+ REF_CONTROL_CONV = "ARCH-none+local-conv" # the control plus a local convolution (Rev60), a declared reference
37
+
38
+
39
+ def rb_unit_id(arm: RBArm, seed: int, tier: MQARTier) -> str:
40
+ return f"{arm.name}__s{seed}__kv{tier.kv_pairs}_seq{tier.seq_len}"
41
+
42
+
43
+ def ref_unit_id(name: str, seed: int, tier: MQARTier) -> str:
44
+ return f"{name}__s{seed}__kv{tier.kv_pairs}_seq{tier.seq_len}"
45
+
46
+
47
+ def run_resumable_rb(seeds=DEFAULT_SEEDS, tiers=None, steps: int = 1500, relay: str | None = None, latest_name: str = "LATEST-rb.json",
48
+ ckpt_dir: str | Path = "metrics/mqar/rb-ckpt",
49
+ resume_from: list[str | Path] | None = None,
50
+ reference_from: list[str | Path] | None = None,
51
+ time_budget_min: float | None = None, arms: list[str] | None = None, **kw) -> dict:
52
+ tiers = tiers or DEFAULT_TIERS
53
+ ckpt = Path(ckpt_dir); ckpt.mkdir(parents=True, exist_ok=True)
54
+ if relay: # the checkpoint relay: the last push, before anything else
55
+ import tempfile
56
+ from cortex_data import relay as _relay
57
+ pulled = Path(tempfile.mkdtemp(prefix="relay-")) / ckpt.name
58
+ if _relay.pull(relay, pulled):
59
+ resume_from = list(resume_from or []) + [pulled]
60
+ done = load_done([Path(p) for p in (resume_from or [])] + [ckpt])
61
+ refs = load_done([Path(p) for p in (reference_from or [])])
62
+ chosen = [ARM_BY_NAME[a] for a in arms] if arms else list(ARMS)
63
+ units = [(a, s, t) for a in chosen for s in seeds for t in tiers]
64
+ todo = [u for u in units if rb_unit_id(*u) not in done]
65
+ print(f"[resumable-rb] {len(units)} units total, {len(done)} already done, {len(todo)} to run; "
66
+ f"{len(refs)} reference units loaded")
67
+ t0 = time.time(); stopped_early = False
68
+ for arm, seed, tier in todo:
69
+ if time_budget_min is not None and (time.time() - t0) / 60.0 > time_budget_min:
70
+ print("[resumable-rb] time budget reached \u2014 stopping cleanly; rerun to resume")
71
+ stopped_early = True
72
+ break
73
+ uid = rb_unit_id(arm, seed, tier)
74
+ t1 = time.time()
75
+ r = run_rb_unit(arm, tier, steps, seed, **kw)
76
+ rec = {"unit_id": uid, "rung": arm.name, "arm": arm.name, "seed": seed, "kv_pairs": tier.kv_pairs,
77
+ "seq_len": tier.seq_len, "steps": steps, **r,
78
+ "wall_s": round(time.time() - t1, 1), "written_utc": datetime.now(timezone.utc).isoformat()}
79
+ (ckpt / f"unit-{uid}.json").write_text(json.dumps(rec) + "\n") # written the moment it finishes
80
+ done[uid] = rec
81
+ if relay:
82
+ from cortex_data import relay as _relay
83
+ _relay.push(ckpt, relay, uid)
84
+ print(f"[unit] {uid} -> acc={r['accuracy']:.4f} trunk={r.get('accuracy_trunk_only', float('nan')):.4f} "
85
+ f"gate={r.get('gate_final')} ({rec['wall_s']}s) [{len(done)}/{len(units)}]")
86
+ complete = all(rb_unit_id(*u) in done for u in units)
87
+ result = {"benchmark": "recurrent_base_hybrid_resumable", "timestamp_utc": datetime.now(timezone.utc).isoformat(),
88
+ "regime": {"seeds": list(seeds), "n_seeds": len(seeds), "steps": steps,
89
+ "tiers": [(t.kv_pairs, t.seq_len) for t in tiers], "arms": [a.name for a in chosen],
90
+ "arm_hashes": {a.name: a.config_hash() for a in chosen}},
91
+ "units_done": len(done), "units_total": len(units), "complete": complete, "stopped_early": stopped_early}
92
+ if complete:
93
+ result.update(aggregate(done, refs, chosen, seeds, tiers))
94
+ (ckpt / latest_name).write_text(json.dumps(result, indent=2) + "\n")
95
+ print(f"[resumable-rb] COMPLETE \u2014 aggregate written ({latest_name})")
96
+ else:
97
+ (ckpt / "PROGRESS-rb.json").write_text(json.dumps(result, indent=2) + "\n")
98
+ print(f"[resumable-rb] incomplete ({len(done)}/{len(units)}) \u2014 progress written; rerun with this dir as Input")
99
+ return result
100
+
101
+
102
+ def aggregate(done: dict, refs: dict, arms, seeds, tiers) -> dict:
103
+ """Deterministic from the unit files alone; references from the ladder8 units."""
104
+ kv8 = [t for t in tiers if t.kv_pairs == 8]
105
+ kv16 = [t for t in tiers if t.kv_pairs == 16]
106
+ per_full, per_trunk, stats = {}, {}, {}
107
+ for a in arms:
108
+ full = [float(np.mean([done[rb_unit_id(a, s, t)]["accuracy"] for t in tiers])) for s in seeds]
109
+ trunk = [float(np.mean([done[rb_unit_id(a, s, t)]["accuracy_trunk_only"] for t in tiers])) for s in seeds]
110
+ per_full[a.name], per_trunk[a.name] = full, trunk
111
+ st = {"full": _mean_ci(full), "trunk_only": _mean_ci(trunk),
112
+ "take_off_rate": (float(np.mean([done[rb_unit_id(a, s, t)]["took_off"] for s in seeds for t in kv8])) if kv8 else None),
113
+ "kv16_mean": (float(np.mean([done[rb_unit_id(a, s, t)]["accuracy"] for s in seeds for t in kv16])) if kv16 else None),
114
+ "gap_mean": float(np.mean([done[rb_unit_id(a, s, t)]["consolidation_gap"] for s in seeds for t in tiers])),
115
+ "gate_final_mean": float(np.mean([done[rb_unit_id(a, s, t)]["gate_final"] for s in seeds for t in tiers]))}
116
+ stats[a.name] = st
117
+ tests, missing = {}, []
118
+ def ref_values(name):
119
+ try:
120
+ return [float(np.mean([refs[ref_unit_id(name, s, t)]["accuracy"] for t in tiers])) for s in seeds]
121
+ except KeyError:
122
+ missing.append(name); return None
123
+ l3, ctrl = ref_values(REF_L3), ref_values(REF_CONTROL)
124
+ for a in arms:
125
+ if l3 is not None:
126
+ tests[f"{a.name}_full_vs_L3"] = _paired_test(per_full[a.name], l3)
127
+ tests[f"{a.name}_trunk_vs_L3"] = _paired_test(per_trunk[a.name], l3)
128
+ if ctrl is not None:
129
+ tests[f"{a.name}_full_vs_control"] = _paired_test(per_full[a.name], ctrl)
130
+ if "RB-cls-phi" in per_full and "RB-cls" in per_full:
131
+ tests["RB-cls-phi_vs_RB-cls"] = _paired_test(per_full["RB-cls-phi"], per_full["RB-cls"])
132
+ gate = {k: ("gated" if (v.get("significant_95") and len(seeds) >= 3) else "held") for k, v in tests.items()}
133
+ return {"stats": stats, "per_seed": {"full": per_full, "trunk_only": per_trunk},
134
+ "references": {"L3": l3, "control": ctrl, "missing": sorted(set(missing))},
135
+ "paired_tests": tests, "gate_readout": gate,
136
+ "note": "paired on seed at identical steps/tiers against the ladder8 units; gated = significant at 95% with >= 3 seeds; "
137
+ "a bimodal comparator (L3) gets its reserve written next to the number."}
138
+
139
+
140
+ def control_conv_readout(refs: dict, done: dict, arms, seeds, tiers) -> dict:
141
+ """Rev60, declared before the run: the control plus a local convolution against
142
+ the control (does the convolution lift the two layer transformer), against L3
143
+ pure (does it reach the recurrent trunk's level) and against each measured arm's
144
+ full accuracy (does the arm add anything beyond a convolved transformer). Paired
145
+ on seed at identical steps and tier; gated = significant at 95 % with >= 3 seeds.
146
+ Deterministic from the unit files alone."""
147
+ def ref_values(name):
148
+ return [float(np.mean([refs[ref_unit_id(name, s, t)]["accuracy"] for t in tiers])) for s in seeds]
149
+ cc, ctrl, l3 = ref_values(REF_CONTROL_CONV), ref_values(REF_CONTROL), ref_values(REF_L3)
150
+ tests = {"control_conv_vs_control": _paired_test(cc, ctrl), "control_conv_vs_L3": _paired_test(cc, l3)}
151
+ per_full = {}
152
+ for a in arms:
153
+ try:
154
+ per_full[a.name] = [float(np.mean([done[rb_unit_id(a, s, t)]["accuracy"] for t in tiers])) for s in seeds]
155
+ except KeyError:
156
+ continue
157
+ tests[f"{a.name}_full_vs_control_conv"] = _paired_test(per_full[a.name], cc)
158
+ gate = {k: ("gated" if (v.get("significant_95") and len(seeds) >= 3) else "held") for k, v in tests.items()}
159
+ return {"benchmark": "control_plus_local_conv_readout", "timestamp_utc": datetime.now(timezone.utc).isoformat(),
160
+ "regime": {"seeds": list(seeds), "n_seeds": len(seeds), "tiers": [(t.kv_pairs, t.seq_len) for t in tiers]},
161
+ "stats": {REF_CONTROL_CONV: _mean_ci(cc), REF_CONTROL: _mean_ci(ctrl), REF_L3: _mean_ci(l3)},
162
+ "per_seed": {REF_CONTROL_CONV: cc, REF_CONTROL: ctrl, REF_L3: l3, "arms_full": per_full},
163
+ "paired_tests": tests, "gate_readout": gate,
164
+ "note": "Rev60: the readings were declared before the run; a subset readout, paired on seed at identical steps and tier"}
165
+
166
+
167
+ def _cli():
168
+ ap = argparse.ArgumentParser(description="Resumable recurrent base hybrid run (4 arms)")
169
+ ap.add_argument("--steps", type=int, default=1500)
170
+ ap.add_argument("--seeds", type=int, nargs="+", default=list(DEFAULT_SEEDS))
171
+ ap.add_argument("--arms", nargs="*", default=None, help=f"subset of {[a.name for a in ARMS]}")
172
+ ap.add_argument("--ckpt-dir", default="metrics/mqar/rb-ckpt")
173
+ ap.add_argument("--resume-from", nargs="*", default=[])
174
+ ap.add_argument("--reference-from", nargs="*", default=["metrics/mqar/ladder8-ckpt"],
175
+ help="dirs holding the ladder8 units (L3 pure and the control at the same seeds/tiers/steps)")
176
+ ap.add_argument("--time-budget-min", type=float, default=None)
177
+ ap.add_argument("--tiers", nargs="*", default=None, help="restrict to these tiers as KVxSEQ (e.g. 8x128 for the tier where L3 takes off); a subset aggregate, never a paired claim across tiers")
178
+ ap.add_argument("--relay", default=None, help="checkpoint relay: a private Kaggle dataset slug pulled before the resume lookup and pushed after every unit")
179
+ ap.add_argument("--quick", action="store_true")
180
+ ap.add_argument("--latest-name", default="LATEST-rb.json",
181
+ help="the aggregate's file name in --ckpt-dir (Rev62: a 16 seed aggregate next to the 8 seed one, never over it)")
182
+ ap.add_argument("--control-conv-readout", default=None, metavar="OUT",
183
+ help="Rev60: no training; read the control plus convolution units from --reference-from and the arms from "
184
+ "--ckpt-dir, compute the declared paired tests, write them to OUT")
185
+ args = ap.parse_args()
186
+ from cortex_eval.resumable_ladder import parse_tiers
187
+ tiers = parse_tiers(args.tiers)
188
+ if args.control_conv_readout:
189
+ refs = load_done([Path(p) for p in args.reference_from])
190
+ done = load_done([Path(p) for p in args.resume_from] + [Path(args.ckpt_dir)])
191
+ arms = [ARM_BY_NAME[n] for n in (args.arms or [a.name for a in ARMS])]
192
+ r = control_conv_readout(refs, done, arms, tuple(args.seeds), tiers or list(DEFAULT_TIERS))
193
+ out = Path(args.control_conv_readout); out.parent.mkdir(parents=True, exist_ok=True)
194
+ out.write_text(json.dumps(r, indent=2) + "\n")
195
+ print(out); print(json.dumps({k: r[k] for k in ("stats", "paired_tests", "gate_readout")}, indent=1))
196
+ return
197
+ if args.quick:
198
+ run_resumable_rb(seeds=(1, 2), tiers=tiers or [MQARTier(kv_pairs=4, seq_len=32)], steps=40, ckpt_dir=args.ckpt_dir,
199
+ resume_from=args.resume_from, reference_from=args.reference_from, arms=args.arms, d_model=32, relay=args.relay)
200
+ else:
201
+ run_resumable_rb(seeds=tuple(args.seeds), tiers=tiers, steps=args.steps, ckpt_dir=args.ckpt_dir, resume_from=args.resume_from,
202
+ reference_from=args.reference_from, time_budget_min=args.time_budget_min, arms=args.arms, relay=args.relay,
203
+ latest_name=args.latest_name)
204
+
205
+
206
+ if __name__ == "__main__":
207
+ _cli()