betterwithage commited on
Commit
372c4e1
·
verified ·
1 Parent(s): 99e43cf

code(atelier): CPU silhouette trainer — not a 1.5B retrain

Browse files
Files changed (1) hide show
  1. train_cohort.py +404 -0
train_cohort.py ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """CPU-train WILLAY + Chaski silhouettes. Measure kernels. No 1.5B retrain.
3
+
4
+ Evidence: MEASURED on synthetic overlapping features (not linearly separable
5
+ by design). ROADMAP stays empty. STUB stays STUB. Joblib stays quarantined.
6
+ Energy UNAVAILABLE. Lambda = Conjecture 1 OPEN.
7
+
8
+ python train_cohort.py # fit silhouettes + measure kernels
9
+ python train_cohort.py --kernels-only # re-measure organs, keep MLP packs
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import sys
15
+ from datetime import datetime, timezone
16
+ from pathlib import Path
17
+
18
+ import numpy as np
19
+
20
+ SEED = 20260721
21
+ rng = np.random.default_rng(SEED)
22
+ KERNELS_ONLY = "--kernels-only" in sys.argv
23
+
24
+ HERE = Path(__file__).resolve().parent
25
+ if (HERE / "kernels").is_dir():
26
+ # Space kit copy: public/space/kit/train_cohort.py
27
+ KERNELS = HERE / "kernels"
28
+ SPACE = HERE.parent
29
+ WEIGHTS = SPACE / "weights"
30
+ NANO = Path("/workspace/src/lib/nano-weights.json")
31
+ if not NANO.exists():
32
+ NANO = SPACE / "nano-weights.json"
33
+ SPACE_NANO = SPACE / "nano-weights.json"
34
+ KIT_NANO = HERE / "nano-weights.json" if (HERE / "nano-weights.json").exists() else SPACE / "nano-weights.json"
35
+ ROOT = NANO.parents[2] if NANO.name == "nano-weights.json" and NANO.parent.name == "lib" else SPACE
36
+ else:
37
+ ROOT = HERE.parent
38
+ KERNELS = ROOT / "public/space/kit/kernels"
39
+ NANO = ROOT / "src/lib/nano-weights.json"
40
+ SPACE_NANO = ROOT / "public/space/nano-weights.json"
41
+ KIT_NANO = ROOT / "public/kit/nano-weights.json"
42
+ WEIGHTS = ROOT / "public/space/weights"
43
+
44
+
45
+ def sigmoid(z: np.ndarray) -> np.ndarray:
46
+ z = np.clip(z, -40, 40)
47
+ return 1.0 / (1.0 + np.exp(-z))
48
+
49
+
50
+ def softmax(z: np.ndarray) -> np.ndarray:
51
+ z = z - z.max(axis=-1, keepdims=True)
52
+ e = np.exp(z)
53
+ return e / e.sum(axis=-1, keepdims=True)
54
+
55
+
56
+ def mlp_train(x: np.ndarray, y: np.ndarray, h: int, k: int, epochs: int, lr: float):
57
+ n, d = x.shape
58
+ w1 = rng.normal(0, 0.4, (d, h))
59
+ b1 = np.zeros(h)
60
+ w2 = rng.normal(0, 0.4, (h, k))
61
+ b2 = np.zeros(k)
62
+ yoh = np.eye(k)[y.astype(int)]
63
+ hist = []
64
+ for ep in range(epochs):
65
+ h1 = np.tanh(x @ w1 + b1)
66
+ logits = h1 @ w2 + b2
67
+ p = softmax(logits)
68
+ loss = float(-np.mean(np.sum(yoh * np.log(p + 1e-9), axis=1)))
69
+ dz2 = (p - yoh) / n
70
+ dw2 = h1.T @ dz2
71
+ db2 = dz2.sum(axis=0)
72
+ dh1 = dz2 @ w2.T * (1 - h1**2)
73
+ dw1 = x.T @ dh1
74
+ db1 = dh1.sum(axis=0)
75
+ w1 -= lr * dw1
76
+ b1 -= lr * db1
77
+ w2 -= lr * dw2
78
+ b2 -= lr * db2
79
+ pred = p.argmax(axis=1)
80
+ acc = float((pred == y).mean())
81
+ hist.append({"epoch": ep + 1, "loss": round(loss, 6), "acc": round(acc, 6)})
82
+ return {"w1": w1, "b1": b1, "w2": w2, "b2": b2, "hist": hist}
83
+
84
+
85
+ def mlp_predict(pack, x: np.ndarray) -> np.ndarray:
86
+ h1 = np.tanh(x @ pack["w1"] + pack["b1"])
87
+ return softmax(h1 @ pack["w2"] + pack["b2"])
88
+
89
+
90
+ def arr(a: np.ndarray):
91
+ return np.asarray(a).astype(float).round(6).tolist()
92
+
93
+
94
+ def noisy(row: list[float], sigma: float = 0.14) -> list[float]:
95
+ x = np.asarray(row, dtype=np.float64) + rng.normal(0, sigma, len(row))
96
+ return np.clip(x, 0, 1).tolist()
97
+
98
+
99
+ def split_xy(rows, labs, frac=0.8):
100
+ x = np.array(rows, dtype=np.float64)
101
+ y = np.array(labs, dtype=np.int64)
102
+ perm = rng.permutation(len(y))
103
+ x, y = x[perm], y[perm]
104
+ cut = int(frac * len(y))
105
+ return x[:cut], y[:cut], x[cut:], y[cut:]
106
+
107
+
108
+ def pack_mlp(trained, xte, yte, labels: list[str], extra: dict | None = None):
109
+ p = mlp_predict(trained, xte)
110
+ pred = p.argmax(1)
111
+ acc = float((pred == yte).mean())
112
+ per = {}
113
+ for i, name in enumerate(labels):
114
+ m = yte == i
115
+ per[name] = float((pred[m] == i).mean()) if m.any() else 0.0
116
+ out = {
117
+ "w1": arr(trained["w1"]),
118
+ "b1": arr(trained["b1"]),
119
+ "w2": arr(trained["w2"]),
120
+ "b2": arr(trained["b2"]),
121
+ "holdoutAcc": round(acc, 4),
122
+ "perClass": {k: round(v, 4) for k, v in per.items()},
123
+ "epochs": len(trained["hist"]),
124
+ "finalLoss": trained["hist"][-1]["loss"],
125
+ "nTrain": int(len(trained["hist"]) and True) and None,
126
+ "curve": trained["hist"][::12],
127
+ "labels": labels,
128
+ }
129
+ if extra:
130
+ out.update(extra)
131
+ out["nTest"] = int(len(yte))
132
+ return out, acc, per
133
+
134
+
135
+ willay_pack = chaski_pack = c5050_pack = cr2_pack = None
136
+ willay_acc = chaski_acc = c5050_acc = cr2_acc = None
137
+ willay_per = chaski_per = c5050_per = cr2_per = None
138
+
139
+ if not KERNELS_ONLY:
140
+ # ---------------------------------------------------------------------------
141
+ # WILLAY — TELL (1) vs SILENCE (0)
142
+ # features: inflate_lean, launder_gguf, lambda_proven, unlabeled_number, receipt
143
+ # Unique cut: the mouth that refuses marketing. Not a mascot.
144
+ # ---------------------------------------------------------------------------
145
+ w_rows, w_labs = [], []
146
+ for _ in range(140):
147
+ w_rows.append(noisy([rng.uniform(0.0, 0.28), rng.uniform(0.0, 0.28), rng.uniform(0.0, 0.22), rng.uniform(0.0, 0.25), rng.uniform(0.72, 1.0)]))
148
+ w_labs.append(1)
149
+ for _ in range(50):
150
+ w_rows.append(noisy([rng.uniform(0.7, 1.0), rng.uniform(0.0, 0.4), rng.uniform(0.0, 0.4), rng.uniform(0.2, 0.8), rng.uniform(0.0, 0.5)]))
151
+ w_labs.append(0)
152
+ for _ in range(50):
153
+ w_rows.append(noisy([rng.uniform(0.0, 0.4), rng.uniform(0.7, 1.0), rng.uniform(0.0, 0.35), rng.uniform(0.2, 0.8), rng.uniform(0.0, 0.55)]))
154
+ w_labs.append(0)
155
+ for _ in range(50):
156
+ w_rows.append(noisy([rng.uniform(0.0, 0.35), rng.uniform(0.0, 0.35), rng.uniform(0.75, 1.0), rng.uniform(0.1, 0.6), rng.uniform(0.0, 0.5)]))
157
+ w_labs.append(0)
158
+ for _ in range(50):
159
+ w_rows.append(noisy([rng.uniform(0.0, 0.4), rng.uniform(0.0, 0.4), rng.uniform(0.0, 0.4), rng.uniform(0.7, 1.0), rng.uniform(0.0, 0.45)]))
160
+ w_labs.append(0)
161
+ for _ in range(40):
162
+ w_rows.append(noisy([rng.uniform(0.35, 0.7), rng.uniform(0.3, 0.65), rng.uniform(0.2, 0.55), rng.uniform(0.3, 0.7), rng.uniform(0.45, 0.75)]))
163
+ w_labs.append(0)
164
+
165
+ wxtr, wytr, wxte, wyte = split_xy(w_rows, w_labs)
166
+ willay = mlp_train(wxtr, wytr, h=8, k=2, epochs=280, lr=0.32)
167
+ willay_pack, willay_acc, willay_per = pack_mlp(
168
+ willay,
169
+ wxte,
170
+ wyte,
171
+ ["SILENCE", "TELL"],
172
+ extra={"nTrain": int(len(wytr)), "features": ["inflate_lean", "launder_gguf", "lambda_proven", "unlabeled_number", "receipt"]},
173
+ )
174
+ print("WILLAY holdout", willay_acc, "per", willay_per, "nTrain", len(wytr), "nTest", len(wyte))
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # CHASKI lineage — CARRY (1) vs DROP (0)
178
+ # Mix is the identity. We do not overwrite R1 with R2.
179
+ # ---------------------------------------------------------------------------
180
+ def chaski_data(carry_n: int, drop_n: int):
181
+ rows, labs = [], []
182
+ for _ in range(carry_n):
183
+ rows.append(noisy([rng.uniform(0.65, 1.0), rng.uniform(0.6, 1.0), rng.uniform(0.0, 0.32), rng.uniform(0.0, 0.7), rng.uniform(0.55, 1.0)]))
184
+ labs.append(1)
185
+ n_unsigned = drop_n // 3
186
+ for _ in range(n_unsigned):
187
+ rows.append(noisy([rng.uniform(0.0, 0.35), rng.uniform(0.3, 1.0), rng.uniform(0.0, 0.6), rng.uniform(0.0, 0.8), rng.uniform(0.0, 0.7)]))
188
+ labs.append(0)
189
+ n_author = drop_n // 3
190
+ for _ in range(n_author):
191
+ rows.append(noisy([rng.uniform(0.3, 0.8), rng.uniform(0.3, 0.9), rng.uniform(0.7, 1.0), rng.uniform(0.2, 0.9), rng.uniform(0.2, 0.8)]))
192
+ labs.append(0)
193
+ for _ in range(drop_n - n_unsigned - n_author):
194
+ rows.append(noisy([rng.uniform(0.2, 0.7), rng.uniform(0.0, 0.28), rng.uniform(0.2, 0.7), rng.uniform(0.0, 0.8), rng.uniform(0.0, 0.6)]))
195
+ labs.append(0)
196
+ return split_xy(rows, labs)
197
+
198
+ def train_courier(name: str, carry_n: int, drop_n: int, mix: str):
199
+ xtr, ytr, xte, yte = chaski_data(carry_n, drop_n)
200
+ pack = mlp_train(xtr, ytr, h=8, k=2, epochs=260, lr=0.33)
201
+ out, acc, per = pack_mlp(
202
+ pack,
203
+ xte,
204
+ yte,
205
+ ["DROP", "CARRY"],
206
+ extra={
207
+ "nTrain": int(len(ytr)),
208
+ "mix": mix,
209
+ "features": ["signed", "payload", "author_verb", "vision", "handle"],
210
+ },
211
+ )
212
+ print(name, "holdout", acc, "per", per, "mix", mix, "nTrain", len(ytr), "nTest", len(yte))
213
+ return out, acc, per
214
+
215
+ chaski_pack, chaski_acc, chaski_per = train_courier("chaski", 180, 80, "70/30 carry/drop")
216
+ c5050_pack, c5050_acc, c5050_per = train_courier("chaski-5050", 130, 130, "50/50 cutting")
217
+ cr2_pack, cr2_acc, cr2_per = train_courier("chaski-r2", 90, 170, "35/65 drop-heavy R2")
218
+
219
+
220
+ # ---------------------------------------------------------------------------
221
+ # Kernel MEASURED smokes — SOFTWARE, not SGD
222
+ # ---------------------------------------------------------------------------
223
+ sys.path.insert(0, str(KERNELS.parent))
224
+ from kernels.yarqa import yarqa_attn, canal_bounds # noqa: E402
225
+ from kernels.maskmod import maskmod_attn # noqa: E402
226
+ from kernels.ouroboros import loop_tax # noqa: E402
227
+ from kernels.nemo_rules import rule_check, RULE_IDS # noqa: E402
228
+ from kernels.blocked import deny_by_default # noqa: E402
229
+ from kernels.receipt_attn import tiled_attn # noqa: E402
230
+ from kernels.block_kv import make_cache, witness_swap # noqa: E402
231
+ from kernels.formulas import run_all, digest_run # noqa: E402
232
+ from kernels.governed_norm import rms_norm # noqa: E402
233
+ from kernels.lambda_gate import check_a1, check_a2, check_a3, check_a4, uniform_weights, wgm # noqa: E402
234
+ from kernels.doctrine import DOCTRINE, proven_trust, advisory # noqa: E402
235
+ from kernels.chain import UnifiedReceiptChain, DIGEST_NOTE # noqa: E402
236
+
237
+ krng = np.random.default_rng(SEED)
238
+ S, D, N = 8, 4, 3
239
+ Q = krng.normal(size=(S, D))
240
+ K = krng.normal(size=(S, D))
241
+ V = krng.normal(size=(S, D))
242
+ _out, _probs, leaked = yarqa_attn(Q, K, V, n_canals=N)
243
+ assert leaked < 1e-12
244
+
245
+ _m_out, _m_probs, future_causal = maskmod_attn(Q, K, V, kind="causal")
246
+ _m_out, _m_probs, future_prefix = maskmod_attn(Q, K, V, kind="prefix")
247
+
248
+ tax = loop_tax([{"ok": False, "ms": 220}, {"ok": True, "ms": 900}], 1300, 4)
249
+
250
+ ok_r1, v_r1 = rule_check("how good is this?", "MMLU 92%")
251
+ ok_r4, v_r4 = rule_check("is lambda proven?", "Λ is a theorem, certified.")
252
+ ok_ok, v_ok = rule_check("how good?", "Unknown. Not yet measured.")
253
+
254
+ gate_block = deny_by_default(allow=False, hard_deny=False, lambda_pass=True)
255
+ gate_deny = deny_by_default(allow=True, hard_deny=True, lambda_pass=True)
256
+ gate_open = deny_by_default(allow=True, hard_deny=False, lambda_pass=True)
257
+
258
+ tiled = tiled_attn(Q, K, V, br=4, bc=4)
259
+ cache = make_cache(n_logical=8, n_physical=6, dim=4, seed=11)
260
+ w_diff = witness_swap(cache, 0, 1)
261
+ cache_same = make_cache(n_logical=8, n_physical=6, dim=4, seed=11)
262
+ w_same = witness_swap(cache_same, 0, 6)
263
+ oob = False
264
+ try:
265
+ make_cache(n_logical=8, n_physical=6, dim=4, seed=11).swap(0, 99)
266
+ except ValueError:
267
+ oob = True
268
+
269
+ rows = run_all(seed=11)
270
+ numeric_rows = [r for r in rows if r["family"] == "numeric"]
271
+ puriq_rows = [r for r in rows if r["family"] == "puriq_locked8"]
272
+ X = krng.normal(0.0, 1.0, size=(4, 8))
273
+ _y, unit_rms, _d = rms_norm(X, np.ones(8))
274
+ axes = 0.2 + krng.random(6) * 0.7
275
+ wts = uniform_weights(6)
276
+ chain = UnifiedReceiptChain()
277
+ chain.emit("khipu", "knot", {"i": 0})
278
+ chain.emit("khipu", "knot", {"i": 1})
279
+ chain_ok, chain_depth, _brk = chain.verify()
280
+
281
+ kernel_measures = {
282
+ "yarqaLeaked": float(leaked),
283
+ "maskmodCausalFutureMass": float(future_causal),
284
+ "maskmodPrefixFutureMass": float(future_prefix),
285
+ "ouroborosModelMs": tax["modelMs"],
286
+ "ouroborosOverheadMs": tax["overheadMs"],
287
+ "ouroborosOverheadLabel": tax["honesty"]["overheadMs"],
288
+ "nemoR1CatchesUnlabeled": (not ok_r1) and ("R1_no_fabrication_label" in v_r1),
289
+ "nemoR4CatchesTheorem": (not ok_r4) and ("R4_lambda_not_theorem" in v_r4),
290
+ "nemoHonestUnknownPasses": ok_ok,
291
+ "denyDefaultBlocks": gate_block["blocked"] is True and gate_block["output"] is None,
292
+ "hardDenyDominates": gate_deny["blocked"] is True,
293
+ "allowOpens": gate_open["blocked"] is False,
294
+ "ruleIds": list(RULE_IDS),
295
+ "bounds": canal_bounds(S, N).tolist(),
296
+ "receiptAttnResidual": float(tiled.residual),
297
+ "blockKvWitnessChanged": bool(w_diff["changed"]),
298
+ "blockKvSamePhysicalUnchanged": not bool(w_same["changed"]),
299
+ "blockKvFailClosed": oob,
300
+ "formulasNumericOk": all(r["ok"] for r in numeric_rows),
301
+ "formulasNumericN": len(numeric_rows),
302
+ "formulasLocked8Ok": all(r["ok"] for r in puriq_rows),
303
+ "formulasLocked8N": len(puriq_rows),
304
+ "formulasLockedIds": [r["id"] for r in puriq_rows],
305
+ "formulasDigest": digest_run(rows),
306
+ "governedNormUnitRms": float(unit_rms),
307
+ "lambdaA1": bool(check_a1(axes, wts)),
308
+ "lambdaA2": bool(check_a2(axes, wts)),
309
+ "lambdaA3": bool(check_a3(wts, 0.55)),
310
+ "lambdaA4": bool(check_a4(axes, wts)),
311
+ "lambdaZeroAxis": float(wgm(axes * np.array([1, 1, 0, 1, 1, 1]), wts)),
312
+ "provenTrust": bool(proven_trust),
313
+ "advisory": bool(advisory),
314
+ "lockedDeclarations": int(DOCTRINE["lockedDeclarations"]),
315
+ "uniqueAxioms": int(DOCTRINE["uniqueAxioms"]),
316
+ "trackedSorries": int(DOCTRINE["trackedSorries"]),
317
+ "digestAlgNote": DIGEST_NOTE,
318
+ "chainVerify": bool(chain_ok),
319
+ "chainDepth": int(chain_depth),
320
+ }
321
+ print("KERNELS", json.dumps({k: v for k, v in kernel_measures.items() if k not in ("bounds", "digestAlgNote", "formulasDigest")}))
322
+
323
+
324
+ # ---------------------------------------------------------------------------
325
+ # Merge into nano-weights.json (do not touch moons / 1.5B)
326
+ # ---------------------------------------------------------------------------
327
+ payload = json.loads(NANO.read_text())
328
+ payload["kernelMeasuredAt"] = datetime.now(timezone.utc).isoformat()
329
+ payload["kernelMeasures"] = kernel_measures
330
+ if not KERNELS_ONLY:
331
+ payload["cohortTrainedAt"] = datetime.now(timezone.utc).isoformat()
332
+ payload["willay"] = willay_pack
333
+ payload["chaski"] = chaski_pack
334
+ payload["chaski5050"] = c5050_pack
335
+ payload["chaskiR2"] = cr2_pack
336
+
337
+ text = json.dumps(payload, indent=2)
338
+ dests = [NANO, SPACE_NANO, KIT_NANO]
339
+ if Path("/workspace/src/lib/nano-weights.json").exists():
340
+ dests.append(Path("/workspace/src/lib/nano-weights.json"))
341
+ dests.append(Path("/workspace/public/space/nano-weights.json"))
342
+ dests.append(Path("/workspace/public/kit/nano-weights.json"))
343
+ seen: set[str] = set()
344
+ for dest in dests:
345
+ key = str(dest.resolve()) if dest.exists() or dest.parent.exists() else str(dest)
346
+ if key in seen:
347
+ continue
348
+ seen.add(key)
349
+ dest.parent.mkdir(parents=True, exist_ok=True)
350
+ dest.write_text(text)
351
+ print("wrote", dest)
352
+
353
+
354
+ def save_mlp(name: str, pack: dict, extra: dict | None = None):
355
+ kw = dict(
356
+ w1=np.asarray(pack["w1"], dtype=np.float64),
357
+ b1=np.asarray(pack["b1"], dtype=np.float64),
358
+ w2=np.asarray(pack["w2"], dtype=np.float64),
359
+ b2=np.asarray(pack["b2"], dtype=np.float64),
360
+ holdoutAcc=np.float64(pack["holdoutAcc"]),
361
+ seed=np.int64(SEED),
362
+ )
363
+ if extra:
364
+ kw.update(extra)
365
+ path = WEIGHTS / name
366
+ np.savez_compressed(path, **kw)
367
+ print("wrote", path, path.stat().st_size)
368
+
369
+
370
+ if not KERNELS_ONLY:
371
+ WEIGHTS.mkdir(parents=True, exist_ok=True)
372
+ save_mlp("willay.npz", willay_pack)
373
+ save_mlp("chaski.npz", chaski_pack)
374
+ save_mlp("chaski_5050.npz", c5050_pack)
375
+ save_mlp("chaski_r2.npz", cr2_pack)
376
+
377
+ summary = {
378
+ "kernelsOnly": KERNELS_ONLY,
379
+ "kernels": {
380
+ "yarqaLeaked": kernel_measures["yarqaLeaked"],
381
+ "maskmodCausalFutureMass": kernel_measures["maskmodCausalFutureMass"],
382
+ "receiptAttnResidual": kernel_measures["receiptAttnResidual"],
383
+ "blockKvWitnessChanged": kernel_measures["blockKvWitnessChanged"],
384
+ "formulasNumericOk": kernel_measures["formulasNumericOk"],
385
+ "formulasLocked8Ok": kernel_measures["formulasLocked8Ok"],
386
+ "governedNormUnitRms": kernel_measures["governedNormUnitRms"],
387
+ "lambdaA1": kernel_measures["lambdaA1"],
388
+ "provenTrust": kernel_measures["provenTrust"],
389
+ "ouroborosModelMs": kernel_measures["ouroborosModelMs"],
390
+ "nemoR1": kernel_measures["nemoR1CatchesUnlabeled"],
391
+ "nemoR4": kernel_measures["nemoR4CatchesTheorem"],
392
+ },
393
+ }
394
+ if not KERNELS_ONLY:
395
+ summary["willay"] = {"holdoutAcc": willay_pack["holdoutAcc"], "perClass": willay_pack["perClass"]}
396
+ summary["chaski"] = {"holdoutAcc": chaski_pack["holdoutAcc"], "perClass": chaski_pack["perClass"], "mix": chaski_pack["mix"]}
397
+ summary["chaski-5050"] = {"holdoutAcc": c5050_pack["holdoutAcc"], "perClass": c5050_pack["perClass"], "mix": c5050_pack["mix"]}
398
+ summary["chaski-r2"] = {"holdoutAcc": cr2_pack["holdoutAcc"], "perClass": cr2_pack["perClass"], "mix": cr2_pack["mix"]}
399
+ metrics_dir = Path("/workspace/space-export")
400
+ if metrics_dir.exists() or Path("/workspace").exists():
401
+ (Path("/workspace/space-export") / "cohort-metrics.json").parent.mkdir(exist_ok=True)
402
+ (Path("/workspace/space-export") / "cohort-metrics.json").write_text(json.dumps(summary, indent=2))
403
+ print("SUMMARY", json.dumps(summary, indent=2))
404
+ print("did not retrain 1.5B. ROADMAP empty. STUB empty. Energy UNAVAILABLE.")