anima-python 0.13.1__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.
@@ -0,0 +1,844 @@
1
+ # ==========================================================================
2
+ # ⛔ ENGINE-INTERNAL TOOL — agent 가 직접 타이핑 금지. 학습/직렬화는 cli/ 단일진입만:
3
+ # anima train | anima serialize (canonical=hexa cli/{train,serialize}.hexa).
4
+ # 단, cli/train.hexa 가 held-out DESCENT 게이트를 `python3 … verify_clm_v2.py descent` 로
5
+ # SUBPROCESS 호출한다(=canonical 경로의 정당한 내부 shell-out) — 그래서 __main__ hard-exit 가드는
6
+ # 두지 않는다(그건 canonical 게이트를 깨뜨림). agent 직접실행 차단은 .harness/enforcement.json
7
+ # H-ANIMA-SINGLE-ENTRY pre_bash 룰이 담당(hexa 내부 subprocess 는 안 건드림). #2603
8
+ # ==========================================================================
9
+ #!/usr/bin/env python3
10
+ """Lane P REMEDY — pure-Python mirror of CORE/clm_decode.hexa's clm_decodable()
11
+ plus a fuller parse, and a byte-roundtrip + golden-reference test harness.
12
+
13
+ This re-parses the raw .clm bytes with the SAME logic as clm_decode.hexa:
14
+ clm_decodable(): magic "CLM\\x01", walk nblk conv blocks via (cout,rest) to find
15
+ the trailer offset, require "CLMX" there.
16
+ parse_clm(): also re-reads block dims and the CLMX ext count (n_ext + each
17
+ ext array's element count), so a round-trip can assert
18
+ structural consistency.
19
+
20
+ No torch / numpy needed for verification (pure stdlib struct). The serializer is
21
+ imported (it guards its torch import) only for the synthetic round-trip.
22
+
23
+ Outputs (verbatim, p7/g63 honest):
24
+ F-CLM-V2-ROUNDTRIP=1 on a clean synthetic round-trip, else =0 + the mismatch.
25
+ golden decodable=true/false + nblk + block0 dims + CLMX-found.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import struct
30
+ import sys
31
+ import os
32
+ import math
33
+
34
+ MAGIC = bytes([67, 76, 77, 1])
35
+ CLMX = bytes([67, 76, 77, 88])
36
+ CLMB = bytes([67, 76, 77, 66]) # bind-readout (Hadamard) extension
37
+
38
+
39
+ def _ru32(b: bytes, off: int) -> int:
40
+ return b[off] + b[off + 1] * 0x100 + b[off + 2] * 0x10000 + b[off + 3] * 0x1000000
41
+
42
+
43
+ def clm_decodable(path_or_bytes) -> bool:
44
+ """Pure-Python mirror of clm_decode.hexa::clm_decodable (lines 43-64)."""
45
+ rb = path_or_bytes if isinstance(path_or_bytes, (bytes, bytearray)) else open(path_or_bytes, "rb").read()
46
+ if len(rb) < 5:
47
+ return False
48
+ if not (rb[0] == 67 and rb[1] == 76 and rb[2] == 77 and rb[3] == 1):
49
+ return False
50
+ nblk = rb[4]
51
+ off = 5
52
+ b = 0
53
+ while b < nblk:
54
+ if off + 8 > len(rb):
55
+ return False
56
+ cout = _ru32(rb, off)
57
+ rest = _ru32(rb, off + 4)
58
+ off += 8
59
+ n = cout * rest
60
+ off += (n + 1) // 2 # int4 nibbles, 2/byte
61
+ off += cout * 4 # per-channel fp32 scale
62
+ b += 1
63
+ if off + 5 > len(rb):
64
+ return False
65
+ return rb[off] == 67 and rb[off + 1] == 76 and rb[off + 2] == 77 and rb[off + 3] == 88
66
+
67
+
68
+ def parse_clm(path_or_bytes) -> dict:
69
+ """Fuller parse: returns block dims + CLMX ext counts. Raises on structural EOF."""
70
+ rb = path_or_bytes if isinstance(path_or_bytes, (bytes, bytearray)) else open(path_or_bytes, "rb").read()
71
+ out = {"len": len(rb), "magic_ok": False, "nblk": None, "blocks": [],
72
+ "clmx_found": False, "n_ext": None, "ext_counts": [], "final_off": None,
73
+ "exact_eof": None, "clmb_found": False, "readout_type": 0,
74
+ "bind_blocks": [], "bind_ext_counts": []}
75
+ if len(rb) < 5:
76
+ return out
77
+ out["magic_ok"] = (rb[0] == 67 and rb[1] == 76 and rb[2] == 77 and rb[3] == 1)
78
+ nblk = rb[4]
79
+ out["nblk"] = nblk
80
+ off = 5
81
+ for _ in range(nblk):
82
+ if off + 8 > len(rb):
83
+ raise ValueError(f"EOF reading block header at off={off}")
84
+ cout = _ru32(rb, off)
85
+ rest = _ru32(rb, off + 4)
86
+ off += 8
87
+ n = cout * rest
88
+ nib = (n + 1) // 2
89
+ off += nib
90
+ off += cout * 4
91
+ out["blocks"].append({"cout": cout, "rest": rest, "nibbles": nib})
92
+ if off > len(rb):
93
+ raise ValueError(f"EOF walking block (cout={cout},rest={rest}) off={off}>len={len(rb)}")
94
+ if off + 5 <= len(rb) and rb[off:off + 4] == CLMX:
95
+ out["clmx_found"] = True
96
+ n_ext = rb[off + 4]
97
+ out["n_ext"] = n_ext
98
+ o2 = off + 5
99
+ for _ in range(n_ext):
100
+ if o2 + 4 > len(rb):
101
+ raise ValueError(f"EOF reading ext count at off={o2}")
102
+ cnt = _ru32(rb, o2)
103
+ o2 += 4
104
+ o2 += cnt * 4
105
+ out["ext_counts"].append(cnt)
106
+ if o2 > len(rb):
107
+ raise ValueError(f"EOF reading ext array (count={cnt}) off={o2}>len={len(rb)}")
108
+ # OPTIONAL CLMB bind-readout trailer: readout_type:u8 + Wa/Wb blocks + 2 ext.
109
+ if o2 + 5 <= len(rb) and rb[o2:o2 + 4] == CLMB:
110
+ out["clmb_found"] = True
111
+ out["readout_type"] = rb[o2 + 4]
112
+ o3 = o2 + 5
113
+ for _ in range(2): # Wa, Wb conv blocks
114
+ cout = _ru32(rb, o3); rest = _ru32(rb, o3 + 4); o3 += 8
115
+ nib = (cout * rest + 1) // 2
116
+ o3 += nib + cout * 4
117
+ out["bind_blocks"].append({"cout": cout, "rest": rest, "nibbles": nib})
118
+ if o3 > len(rb):
119
+ raise ValueError(f"EOF walking CLMB block off={o3}>len={len(rb)}")
120
+ for _ in range(2): # Wa_bias, Wb_bias ext
121
+ cnt = _ru32(rb, o3); o3 += 4 + cnt * 4
122
+ out["bind_ext_counts"].append(cnt)
123
+ if o3 > len(rb):
124
+ raise ValueError(f"EOF reading CLMB ext off={o3}>len={len(rb)}")
125
+ o2 = o3
126
+ out["final_off"] = o2
127
+ out["exact_eof"] = (o2 == len(rb))
128
+ return out
129
+
130
+
131
+ # --------------------------------------------------------------------------- #
132
+ # Synthetic round-trip test (no torch): tiny E=2 / 1-trunk model, d=32,K=3,V=256
133
+ # --------------------------------------------------------------------------- #
134
+ def _build_synthetic(d=32, K=3, V=256, E=2, seed=1234):
135
+ """Legacy L=1/E=2 synthetic — used by the v0.2 byte-eq regression check.
136
+ Returns a logical-slot dict (serialize_v2 accepts logical keys directly)."""
137
+ import numpy as np
138
+ rng = np.random.default_rng(seed)
139
+ def r(*shape):
140
+ return (rng.standard_normal(shape) * 0.1).astype(np.float32)
141
+ sd = {
142
+ # conv blocks: ec/tc/e0/e1 are (cout=d, in=d, K) -> rest=d*K
143
+ "ecW": r(d, d, K),
144
+ "tcW": r(d, d, K),
145
+ "e0W": r(d, d, K),
146
+ "e1W": r(d, d, K),
147
+ "rW": r(E, d, 1), # router conv1d k=1 -> rest=d
148
+ "roW": r(V, d, 1), # readout conv1d k=1 -> rest=d
149
+ # CLMX ext (fp32)
150
+ "embed": r(V, d), # V*d
151
+ "ecB": r(d), "tcB": r(d), "e0B": r(d), "e1B": r(d),
152
+ "rB": r(E), "roB": r(V),
153
+ "tgG": r(d), "tgB": r(d), "noG": r(d), "noB": r(d),
154
+ }
155
+ expect = {
156
+ "nblk": 6,
157
+ "blocks": [
158
+ {"cout": d, "rest": d * K}, {"cout": d, "rest": d * K},
159
+ {"cout": d, "rest": d * K}, {"cout": d, "rest": d * K},
160
+ {"cout": E, "rest": d}, {"cout": V, "rest": d},
161
+ ],
162
+ "n_ext": 11,
163
+ "ext_counts": [V * d, d, d, d, d, E, V, d, d, d, d],
164
+ }
165
+ return sd, expect
166
+
167
+
168
+ def _build_synthetic_general(d, L, E, K=3, V=256, seed=2026):
169
+ """General (L,E) synthetic in TORCH state_dict key layout — used by the
170
+ v0.3 general round-trip test. Keys match model.py's CLMConvMoE so the same
171
+ dict drives serialize_v3. Returns (sd, expect)."""
172
+ import numpy as np
173
+ rng = np.random.default_rng(seed)
174
+ def r(*shape):
175
+ return (rng.standard_normal(shape) * 0.1).astype(np.float32)
176
+ sd = {"embed.weight": r(V, d),
177
+ "embed_conv.conv.weight": r(d, d, K), "embed_conv.conv.bias": r(d)}
178
+ for i in range(L):
179
+ sd[f"trunk.{i}.conv.conv.weight"] = r(d, d, K)
180
+ sd[f"trunk.{i}.conv.conv.bias"] = r(d)
181
+ sd[f"trunk.{i}.norm.weight"] = r(d)
182
+ sd[f"trunk.{i}.norm.bias"] = r(d)
183
+ for j in range(E):
184
+ sd[f"moe.experts.{j}.conv.conv.weight"] = r(d, d, K)
185
+ sd[f"moe.experts.{j}.conv.conv.bias"] = r(d)
186
+ sd["moe.router.weight"] = r(E, d, 1)
187
+ sd["moe.router.bias"] = r(E)
188
+ sd["readout.weight"] = r(V, d, 1)
189
+ sd["readout.bias"] = r(V)
190
+ sd["norm_out.weight"] = r(d)
191
+ sd["norm_out.bias"] = r(d)
192
+ # expected layout: blocks = ecW, tcW*L, eW*E, rW(E), roW(V)
193
+ blocks = [{"cout": d, "rest": d * K}] # ecW
194
+ blocks += [{"cout": d, "rest": d * K} for _ in range(L)] # trunk
195
+ blocks += [{"cout": d, "rest": d * K} for _ in range(E)] # experts
196
+ blocks += [{"cout": E, "rest": d}, {"cout": V, "rest": d}] # router, readout
197
+ # ext = embed, ecB, tcB*L, eB*E, rB, roB, tgG*L, tgB*L, noG, noB
198
+ ext = [V * d, d] + [d] * L + [d] * E + [E, V] + [d] * L + [d] * L + [d, d]
199
+ expect = {"nblk": len(blocks), "blocks": blocks, "n_ext": len(ext), "ext_counts": ext}
200
+ return sd, expect
201
+
202
+
203
+ def _build_synthetic_bind(d, L, E, k, V=256, K=3, seed=4242):
204
+ """General (L,E) BIND synthetic in LOGICAL-slot keys (serialize_v3_bind accepts
205
+ them). Trunk slots match the v3 general order; the readout is Wa/Wb (k,d,1) +
206
+ Wo (V,k,1) — the EXP-3 BindCLM. Returns (sd, expect_main, expect_bind)."""
207
+ import numpy as np
208
+ rng = np.random.default_rng(seed)
209
+ def r(*shape):
210
+ return (rng.standard_normal(shape) * 0.1).astype(np.float32)
211
+ sd = {"embed": r(V, d), "ecW": r(d, d, K), "ecB": r(d)}
212
+ for i in range(L):
213
+ sd[f"tc{i}W"] = r(d, d, K); sd[f"tc{i}B"] = r(d)
214
+ sd[f"tg{i}G"] = r(d); sd[f"tg{i}B"] = r(d)
215
+ for j in range(E):
216
+ sd[f"e{j}W"] = r(d, d, K); sd[f"e{j}B"] = r(d)
217
+ sd["rW"] = r(E, d, 1); sd["rB"] = r(E)
218
+ sd["noG"] = r(d); sd["noB"] = r(d)
219
+ # bind readout: Wa,Wb (k,d,1), Wo (V,k,1)
220
+ sd["Wa"] = r(k, d, 1); sd["WaB"] = r(k)
221
+ sd["Wb"] = r(k, d, 1); sd["WbB"] = r(k)
222
+ sd["Wo"] = r(V, k, 1); sd["WoB"] = r(V)
223
+ # expected MAIN: blocks = ecW, tcW*L, eW*E, rW(E,d), roW(=Wo: V,k)
224
+ blocks = [{"cout": d, "rest": d * K}]
225
+ blocks += [{"cout": d, "rest": d * K} for _ in range(L)]
226
+ blocks += [{"cout": d, "rest": d * K} for _ in range(E)]
227
+ blocks += [{"cout": E, "rest": d}, {"cout": V, "rest": k}] # router, Wo readout
228
+ ext = [V * d, d] + [d] * L + [d] * E + [E, V] + [d] * L + [d] * L + [d, d]
229
+ expect_main = {"nblk": len(blocks), "blocks": blocks, "n_ext": len(ext),
230
+ "ext_counts": ext}
231
+ expect_bind = {"bind_blocks": [{"cout": k, "rest": d}, {"cout": k, "rest": d}],
232
+ "bind_ext_counts": [k, k]}
233
+ return sd, expect_main, expect_bind
234
+
235
+
236
+ def run_roundtrip_bind(tmp_path, d, L, E, k, readout_type, V=256, K=3):
237
+ """v0.3 BIND round-trip via serialize_v3_bind: structural (CLMB) + forward
238
+ sanity (bind decode produces finite logits with the Hadamard/add readout)."""
239
+ import clm_serialize_v2 as S
240
+ import numpy as np
241
+ sd, exp_main, exp_bind = _build_synthetic_bind(d, L, E, k, V=V, K=K)
242
+ S.serialize_v3_bind(sd, n_trunk_layers=L, n_experts=E,
243
+ readout_type=readout_type, out_path=tmp_path)
244
+ rb = open(tmp_path, "rb").read()
245
+ ok, why = _structural_check(rb, exp_main) # main body unchanged vs additive grammar
246
+ if not ok:
247
+ return False, f"main: {why}"
248
+ p = parse_clm(rb)
249
+ if not p["clmb_found"]:
250
+ return False, "CLMB trailer not found"
251
+ if p["readout_type"] != readout_type:
252
+ return False, f"readout_type {p['readout_type']} != {readout_type}"
253
+ bb = [{"cout": x["cout"], "rest": x["rest"]} for x in p["bind_blocks"]]
254
+ if bb != exp_bind["bind_blocks"]:
255
+ return False, f"bind_blocks {bb} != {exp_bind['bind_blocks']}"
256
+ if p["bind_ext_counts"] != exp_bind["bind_ext_counts"]:
257
+ return False, f"bind_ext {p['bind_ext_counts']} != {exp_bind['bind_ext_counts']}"
258
+ if not p["exact_eof"]:
259
+ return False, f"trailing bytes: final_off={p['final_off']} != len={p['len']}"
260
+ # forward sanity: the bind mirror loads + forwards to finite [T,V] logits.
261
+ W = _load_clm_weights(rb)
262
+ if W is None or W["rtype"] != readout_type or W["kbind"] != k:
263
+ return False, f"weights load rtype={W and W['rtype']} kbind={W and W['kbind']}"
264
+ tok = (np.arange(24) % V).astype(float)
265
+ lg = _fwd_logits(W, tok, 24)
266
+ if lg.shape != (24, V) or not np.all(np.isfinite(lg)):
267
+ return False, f"forward shape={lg.shape} finite={np.all(np.isfinite(lg))}"
268
+ return True, f"ok (rtype={readout_type} k={k}, logits {lg.shape} finite)"
269
+
270
+
271
+ def run_roundtrip(tmp_path: str) -> tuple[bool, str]:
272
+ import clm_serialize_v2 as S
273
+ sd, expect = _build_synthetic()
274
+ S.serialize_v2(sd, cfg=None, out_path=tmp_path) # cfg=None: synthetic vouches E=2/L1
275
+ rb = open(tmp_path, "rb").read()
276
+
277
+ if not clm_decodable(rb):
278
+ return False, "clm_decodable(synthetic)=false"
279
+ p = parse_clm(rb)
280
+ if not p["magic_ok"]:
281
+ return False, "magic mismatch"
282
+ if p["nblk"] != expect["nblk"]:
283
+ return False, f"nblk {p['nblk']} != {expect['nblk']}"
284
+ if len(p["blocks"]) != 6:
285
+ return False, f"walked {len(p['blocks'])} blocks != 6"
286
+ for i, (got, exp) in enumerate(zip(p["blocks"], expect["blocks"])):
287
+ if got["cout"] != exp["cout"] or got["rest"] != exp["rest"]:
288
+ return False, (f"block{i} dims got cout={got['cout']},rest={got['rest']} "
289
+ f"!= cout={exp['cout']},rest={exp['rest']}")
290
+ if not p["clmx_found"]:
291
+ return False, "CLMX trailer not found"
292
+ if p["n_ext"] != expect["n_ext"]:
293
+ return False, f"n_ext {p['n_ext']} != {expect['n_ext']}"
294
+ if p["ext_counts"] != expect["ext_counts"]:
295
+ return False, f"ext_counts {p['ext_counts']} != {expect['ext_counts']}"
296
+ if not p["exact_eof"]:
297
+ return False, f"trailing bytes: final_off={p['final_off']} != len={p['len']}"
298
+
299
+ # value round-trip: re-dequant the readout-bias ext and the ecW block, confirm
300
+ # the int4 dequant is within the quant step of the original (sanity on packing).
301
+ import numpy as np
302
+ # check ext embed first element survives fp32 exactly
303
+ # (parse the embed ext payload directly)
304
+ off = 5
305
+ for _ in range(6):
306
+ cout = _ru32(rb, off); rest = _ru32(rb, off + 4); off += 8
307
+ off += (cout * rest + 1) // 2 + cout * 4
308
+ off += 5 # CLMX + n_ext
309
+ cnt = _ru32(rb, off); off += 4
310
+ embed_back = np.frombuffer(rb[off:off + cnt * 4], dtype="<f4")
311
+ if not np.allclose(embed_back, sd["embed"].reshape(-1), atol=0, rtol=0):
312
+ return False, "embed ext fp32 round-trip mismatch (lossless expected)"
313
+
314
+ return True, "ok"
315
+
316
+
317
+ def _structural_check(rb, expect):
318
+ """Shared structural assertions: decodable + nblk + per-block dims + CLMX +
319
+ n_ext + ext_counts + exact_eof. Returns (ok, why)."""
320
+ if not clm_decodable(rb):
321
+ return False, "clm_decodable=false"
322
+ p = parse_clm(rb)
323
+ if not p["magic_ok"]:
324
+ return False, "magic mismatch"
325
+ if p["nblk"] != expect["nblk"]:
326
+ return False, f"nblk {p['nblk']} != {expect['nblk']}"
327
+ if len(p["blocks"]) != expect["nblk"]:
328
+ return False, f"walked {len(p['blocks'])} blocks != {expect['nblk']}"
329
+ for i, (got, exp) in enumerate(zip(p["blocks"], expect["blocks"])):
330
+ if got["cout"] != exp["cout"] or got["rest"] != exp["rest"]:
331
+ return False, (f"block{i} got cout={got['cout']},rest={got['rest']} "
332
+ f"!= cout={exp['cout']},rest={exp['rest']}")
333
+ if not p["clmx_found"]:
334
+ return False, "CLMX trailer not found"
335
+ if p["n_ext"] != expect["n_ext"]:
336
+ return False, f"n_ext {p['n_ext']} != {expect['n_ext']}"
337
+ if p["ext_counts"] != expect["ext_counts"]:
338
+ return False, f"ext_counts {p['ext_counts']} != {expect['ext_counts']}"
339
+ if not p["exact_eof"]:
340
+ return False, f"trailing bytes: final_off={p['final_off']} != len={p['len']}"
341
+ return True, "ok"
342
+
343
+
344
+ def run_roundtrip_general(tmp_path, d, L, E, K=3, V=256):
345
+ """v0.3 general (L,E) round-trip via serialize_v3 (torch-key state_dict)."""
346
+ import clm_serialize_v2 as S
347
+ import numpy as np
348
+ sd, expect = _build_synthetic_general(d, L, E, K=K, V=V)
349
+ S.serialize_v3(sd, n_trunk_layers=L, n_experts=E, out_path=tmp_path)
350
+ rb = open(tmp_path, "rb").read()
351
+ ok, why = _structural_check(rb, expect)
352
+ if not ok:
353
+ return False, why
354
+ # value sanity: embed ext (first ext after CLMX) survives fp32 lossless.
355
+ off = 5
356
+ for blk in expect["blocks"]:
357
+ n = blk["cout"] * blk["rest"]
358
+ off += 8 + (n + 1) // 2 + blk["cout"] * 4
359
+ off += 5 # CLMX + n_ext
360
+ cnt = _ru32(rb, off); off += 4
361
+ embed_back = np.frombuffer(rb[off:off + cnt * 4], dtype="<f4")
362
+ if not np.allclose(embed_back, sd["embed.weight"].reshape(-1), atol=0, rtol=0):
363
+ return False, "embed ext fp32 round-trip mismatch"
364
+ return True, "ok"
365
+
366
+
367
+ def run_v3_byteeq_v2(here):
368
+ """v0.3 byte-eq REGRESSION: serialize_v3(L=1,E=2) must be byte-IDENTICAL to
369
+ serialize_v2 on the same logical weights (no regression on the existing
370
+ format). Builds the legacy L1/E2 synthetic and packs it both ways."""
371
+ import clm_serialize_v2 as S
372
+ sd2, _ = _build_synthetic() # logical-slot keys, L1/E2
373
+ # serialize_v2 (logical keys, cfg=None)
374
+ p2 = os.path.join(here, "_rt_v2.clm")
375
+ S.serialize_v2(sd2, cfg=None, out_path=p2)
376
+ b2 = open(p2, "rb").read()
377
+ # serialize_v3 needs torch-key OR logical-key; the general keymap falls back
378
+ # to logical slot names (_get checks `logical in sd` first). The v3 logical
379
+ # slot names for L1/E2 are ec/tc0/e0/e1... so remap the legacy logical dict.
380
+ sd3 = {"ecW": sd2["ecW"], "tc0W": sd2["tcW"], "e0W": sd2["e0W"], "e1W": sd2["e1W"],
381
+ "rW": sd2["rW"], "roW": sd2["roW"],
382
+ "embed": sd2["embed"], "ecB": sd2["ecB"], "tc0B": sd2["tcB"],
383
+ "e0B": sd2["e0B"], "e1B": sd2["e1B"], "rB": sd2["rB"], "roB": sd2["roB"],
384
+ "tg0G": sd2["tgG"], "tg0B": sd2["tgB"], "noG": sd2["noG"], "noB": sd2["noB"]}
385
+ p3 = os.path.join(here, "_rt_v3.clm")
386
+ S.serialize_v3(sd3, n_trunk_layers=1, n_experts=2, out_path=p3)
387
+ b3 = open(p3, "rb").read()
388
+ for pth in (p2, p3):
389
+ try:
390
+ os.remove(pth)
391
+ except OSError:
392
+ pass
393
+ if b2 == b3:
394
+ return True, f"byte-identical ({len(b2)} bytes)"
395
+ return False, f"v2 ({len(b2)}B) != v3 ({len(b3)}B)"
396
+
397
+
398
+ # --------------------------------------------------------------------------- #
399
+ # HELD-OUT mirror-DESCENT gate (the OVERFIT detector)
400
+ # --------------------------------------------------------------------------- #
401
+ # WHY this exists: the structural round-trips above only prove the .clm is
402
+ # *decodable* (right shape/headers). They say NOTHING about whether the
403
+ # serialized weights actually PREDICT TEXT. A model that memorized a tiny corpus
404
+ # (overfit) is structurally perfect yet useless on held-out text — and the
405
+ # engine's own clm_forward_ce can MISS this, because hexa-lang's dt_ln (atanh
406
+ # series, flame_math.hexa) is numerically wrong away from x=1: dt_ln(256)=4.799
407
+ # (true ln=5.545), dt_ln(1e-6)=-5.14 (true -13.82). That clamps per-position CE
408
+ # at ~5.14 (nn_lib.hexa nn_ce_loss_allpos uses -dt_ln(p_t)), so a broken/overfit
409
+ # model gets a falsely-low engine CE and reads GREEN. (clm303 precedent 2026-06-24:
410
+ # engine model_ce 3.30 < shuffle 4.93 = "GREEN" while the true held-out CE was
411
+ # 7.6-13.7 = worse than random.)
412
+ #
413
+ # This gate is a FAITHFUL pure-numpy mirror of core/clm_decode.hexa's forward
414
+ # (byte-reproduces state/mid_convmoe_fire/clm_decode_mirror.py and the engine
415
+ # clm_forward_ce) BUT uses math.log (correct, dt_ln-immune), reading the .clm
416
+ # BYTES directly. It is build-time SERIALIZE-INTEGRITY tooling, NOT a ρ-AXON reach
417
+ # capability verdict — capability verdicts still go through the single entry
418
+ # `cli/anima.hexa -- eval` (a_engine_native_learning). It exists so a broken or
419
+ # overfit .clm cannot be marked "done" or HF-uploaded.
420
+ #
421
+ # It MUST be evaluated on HELD-OUT text (never the training corpus — that would
422
+ # hide overfitting), and optionally reports the train-vs-held-out gap.
423
+
424
+ INT4_OFF = 8 # nibble code = (byte&0xF) - 8
425
+
426
+
427
+ def _load_clm_weights(path):
428
+ """Pure-numpy load of a .clm (v0.2/v0.3) into a weight dict, matching
429
+ core/clm_decode.hexa::_clmd_load EXACTLY (int4 dequant w=code*scale, general
430
+ (L,E) derivation). Requires numpy. Returns None if not decodable."""
431
+ import numpy as np
432
+ rb = open(path, "rb").read() if not isinstance(path, (bytes, bytearray)) else path
433
+ if not clm_decodable(rb):
434
+ return None
435
+ nblk = rb[4]
436
+ # walk headers for (cout,rest) to derive E,V and block offsets
437
+ off = 5
438
+ hdrs = []
439
+ for _ in range(nblk):
440
+ c = _ru32(rb, off); r = _ru32(rb, off + 4)
441
+ hdrs.append((c, r))
442
+ off += 8 + (c * r + 1) // 2 + c * 4
443
+ E = hdrs[nblk - 2][0]
444
+ V = hdrs[nblk - 1][0]
445
+ L = nblk - E - 3
446
+ d = hdrs[0][0]
447
+ K = hdrs[0][1] // d
448
+
449
+ def rd_block(o):
450
+ cout = _ru32(rb, o); o += 4
451
+ rest = _ru32(rb, o); o += 4
452
+ n = cout * rest
453
+ nb = (n + 1) // 2
454
+ raw = np.frombuffer(rb, dtype=np.uint8, count=nb, offset=o); o += nb
455
+ lo = (raw & 0xF).astype(np.int64) - INT4_OFF
456
+ hi = ((raw >> 4) & 0xF).astype(np.int64) - INT4_OFF
457
+ codes = np.empty(2 * nb, dtype=np.float64)
458
+ codes[0::2] = lo
459
+ codes[1::2] = hi
460
+ codes = codes[:n].reshape(cout, rest)
461
+ scale = np.frombuffer(rb, dtype="<f4", count=cout, offset=o).astype(np.float64)
462
+ o += 4 * cout
463
+ return codes * scale[:, None], o
464
+
465
+ def rd_ext(o):
466
+ n = _ru32(rb, o); o += 4
467
+ arr = np.frombuffer(rb, dtype="<f4", count=n, offset=o).astype(np.float64)
468
+ o += 4 * n
469
+ return arr, o
470
+
471
+ off = 5
472
+ ecW, off = rd_block(off)
473
+ tcW = []
474
+ for _ in range(L):
475
+ w, off = rd_block(off); tcW.append(w)
476
+ eW = []
477
+ for _ in range(E):
478
+ w, off = rd_block(off); eW.append(w)
479
+ rW, off = rd_block(off)
480
+ roW, off = rd_block(off)
481
+ off += 5 # CLMX + n_ext
482
+ embed, off = rd_ext(off)
483
+ ecB, off = rd_ext(off)
484
+ tcB = []
485
+ for _ in range(L):
486
+ a, off = rd_ext(off); tcB.append(a)
487
+ eB = []
488
+ for _ in range(E):
489
+ a, off = rd_ext(off); eB.append(a)
490
+ rB, off = rd_ext(off)
491
+ roB, off = rd_ext(off)
492
+ tgG = []
493
+ for _ in range(L):
494
+ a, off = rd_ext(off); tgG.append(a)
495
+ tgB = []
496
+ for _ in range(L):
497
+ a, off = rd_ext(off); tgB.append(a)
498
+ noG, off = rd_ext(off)
499
+ noB, off = rd_ext(off)
500
+ # OPTIONAL CLMB bind-readout trailer (math.log mirror — dt_ln-immune gate).
501
+ rtype = 0
502
+ Wa = Wb = WaB = WbB = None
503
+ kbind = roW.shape[1]
504
+ if off + 5 <= len(rb) and rb[off] == 67 and rb[off + 1] == 76 \
505
+ and rb[off + 2] == 77 and rb[off + 3] == 66: # "CLMB"
506
+ rtype = rb[off + 4]
507
+ off += 5
508
+ Wa, off = rd_block(off) # [k, d]
509
+ Wb, off = rd_block(off) # [k, d]
510
+ WaB, off = rd_ext(off) # [k]
511
+ WbB, off = rd_ext(off) # [k]
512
+ kbind = Wa.shape[0]
513
+ return dict(d=d, E=E, V=V, K=K, L=L, ecW=ecW, tcW=tcW, eW=eW, rW=rW, roW=roW,
514
+ embed=embed.reshape(V, d), ecB=ecB, tcB=tcB, eB=eB, rB=rB, roB=roB,
515
+ tgG=tgG, tgB=tgB, noG=noG, noB=noB,
516
+ rtype=rtype, kbind=kbind, Wa=Wa, Wb=Wb, WaB=WaB, WbB=WbB)
517
+
518
+
519
+ def _gelu(x):
520
+ import numpy as np
521
+ # tanh approx — matches generator.hexa::_gen_gelu (same form clm_decode uses)
522
+ inner = 0.7978845608 * (x + 0.044715 * x * x * x)
523
+ a = np.clip(inner, -15.0, 15.0)
524
+ e2 = np.exp(2.0 * a)
525
+ return 0.5 * x * (1.0 + (e2 - 1.0) / (e2 + 1.0))
526
+
527
+
528
+ def _conv1d(x, w2d, b, T, Cin, Cout, K, dil):
529
+ import numpy as np
530
+ Kdim = Cin * K
531
+ xcol = np.zeros((T, Kdim))
532
+ idx = np.arange(Cin) * K
533
+ for k in range(K):
534
+ shift = dil * (K - 1 - k)
535
+ if shift == 0:
536
+ xcol[:, idx + k] = x
537
+ elif shift < T:
538
+ xcol[shift:, idx + k] = x[:T - shift]
539
+ return xcol @ w2d.T + b[None, :]
540
+
541
+
542
+ def _gn1(x, g, b):
543
+ import numpy as np
544
+ mu = x.mean(1, keepdims=True)
545
+ var = x.var(1, keepdims=True)
546
+ return (x - mu) / np.sqrt(var + 1e-5) * g[None, :] + b[None, :]
547
+
548
+
549
+ def _fwd_logits(W, tok, T):
550
+ import numpy as np
551
+ d, E, V, K, L = W["d"], W["E"], W["V"], W["K"], W["L"]
552
+ xe = W["embed"][tok.astype(int)]
553
+ xt = _conv1d(xe, W["ecW"], W["ecB"], T, d, d, K, 1)
554
+ dil = 1
555
+ for li in range(L):
556
+ de = min(dil, 512)
557
+ h = _conv1d(xt, W["tcW"][li], W["tcB"][li], T, d, d, K, de)
558
+ xt = xt + _gelu(_gn1(h, W["tgG"][li], W["tgB"][li]))
559
+ dil *= 2
560
+ lr = _conv1d(xt, W["rW"], W["rB"], T, d, E, 1, 1)
561
+ exo = [_gelu(_conv1d(xt, W["eW"][e], W["eB"][e], T, d, d, K, 1)) for e in range(E)]
562
+ p = np.exp(lr - lr.max(1, keepdims=True)); p /= p.sum(1, keepdims=True)
563
+ y = sum(p[:, e:e + 1] * exo[e] for e in range(E))
564
+ yn = _gn1(y, W["noG"], W["noB"])
565
+ rtype = W.get("rtype", 0)
566
+ if rtype:
567
+ k = W["kbind"]
568
+ u = _conv1d(yn, W["Wa"], W["WaB"], T, d, k, 1, 1) # [T,k]
569
+ v = _conv1d(yn, W["Wb"], W["WbB"], T, d, k, 1, 1) # [T,k]
570
+ g = (u * v) if rtype == 1 else (u + v)
571
+ return _conv1d(g, W["roW"], W["roB"], T, k, V, 1, 1) # Wo: [T,V]
572
+ return _conv1d(yn, W["roW"], W["roB"], T, d, V, 1, 1)
573
+
574
+
575
+ def _ce_allpos(logits, tgt, T):
576
+ import numpy as np
577
+ z = logits - logits.max(1, keepdims=True)
578
+ lse = np.log(np.exp(z).sum(1))
579
+ return float((-(z[np.arange(T), tgt.astype(int)] - lse)).sum() / T)
580
+
581
+
582
+ def mirror_ce(clm_path, corpus_path, nwin=64, T=24):
583
+ """Faithful mirror next-byte CE over `nwin` windows of a byte corpus, using
584
+ math.log (correct — dt_ln-immune). Returns (model_ce, shuffle_ce, uniform_ce,
585
+ windows). uniform_ce = ln(V) (the TRUE blind baseline, not the buggy engine
586
+ dt_ln(V))."""
587
+ import numpy as np
588
+ W = _load_clm_weights(clm_path)
589
+ if W is None:
590
+ raise ValueError(f"{clm_path}: not v0.2/v0.3 decodable")
591
+ V = W["V"]
592
+ rb = open(corpus_path, "rb").read()
593
+ n = len(rb)
594
+ stride = max(1, (n - T - 1) // nwin)
595
+ sm = ss = 0.0
596
+ cnt = 0
597
+ for s in range(nwin):
598
+ base = s * stride
599
+ if base + T + 1 <= n:
600
+ tok = np.frombuffer(rb, np.uint8, T, base).astype(float)
601
+ tgt = np.frombuffer(rb, np.uint8, T, base + 1).astype(float)
602
+ lg = _fwd_logits(W, tok, T)
603
+ sm += _ce_allpos(lg, tgt, T)
604
+ ss += _ce_allpos(lg, tgt[::-1].copy(), T)
605
+ cnt += 1
606
+ return sm / cnt, ss / cnt, math.log(V), cnt
607
+
608
+
609
+ def descent_gate(clm_path, heldout_corpus, train_corpus=None, nwin=64,
610
+ max_gap=3.0):
611
+ """The OVERFIT detector. PASS iff the serialized .clm genuinely models
612
+ HELD-OUT text: model_ce < uniform AND model_ce < shuffle. If a `train_corpus`
613
+ is given, also reports the train-vs-held-out gap (large gap = overfit warning)
614
+ and FAILs when held-out fails the descent test (the gap alone is advisory).
615
+
616
+ Returns (ok: bool, report: dict). Frozen bars: uniform=ln(V), shuffle=
617
+ reversed-target control. NOT tunable (a_break_the_wall: frozen-first)."""
618
+ h_model, h_shuf, uni, h_win = mirror_ce(clm_path, heldout_corpus, nwin)
619
+ lt_uniform = h_model < uni
620
+ lt_shuffle = h_model < h_shuf
621
+ ok = lt_uniform and lt_shuffle
622
+ rep = {"heldout_model_ce": round(h_model, 5),
623
+ "heldout_shuffle_ce": round(h_shuf, 5),
624
+ "uniform_ce_lnV": round(uni, 5),
625
+ "heldout_lt_uniform": lt_uniform,
626
+ "heldout_lt_shuffle": lt_shuffle,
627
+ "heldout_windows": h_win,
628
+ "train_model_ce": None, "train_heldout_gap": None,
629
+ "overfit_warning": False}
630
+ if train_corpus and os.path.exists(train_corpus):
631
+ t_model, _, _, _ = mirror_ce(clm_path, train_corpus, nwin)
632
+ gap = h_model - t_model
633
+ rep["train_model_ce"] = round(t_model, 5)
634
+ rep["train_heldout_gap"] = round(gap, 5)
635
+ # Overfit signature: descends on train but the held-out gap is huge.
636
+ rep["overfit_warning"] = (t_model < uni) and (gap > max_gap)
637
+ return ok, rep
638
+
639
+
640
+ def run_descent_gate_cli(clm_path, heldout_corpus, train_corpus=None, nwin=64):
641
+ """Verbatim-output CLI wrapper (p7/c9 honest). Prints F-CLM-DESCENT=1/0 +
642
+ the report, exits 0 on PASS else 1. Used by train.py / train.hexa as the
643
+ post-serialize self-verify (a_clm_gen_pipeline)."""
644
+ try:
645
+ ok, rep = descent_gate(clm_path, heldout_corpus, train_corpus, nwin)
646
+ except Exception as e: # numpy missing / undecodable — honest fail
647
+ print(f"F-CLM-DESCENT=0\nDESCENT_ERROR: {e}")
648
+ return 1
649
+ print(f"F-CLM-DESCENT={'1' if ok else '0'}")
650
+ print("DESCENT " + repr(rep))
651
+ if rep["overfit_warning"]:
652
+ print("OVERFIT_WARNING: descends on train but held-out gap "
653
+ f"{rep['train_heldout_gap']} > 3.0 — the serialized .clm memorized "
654
+ "its corpus and does NOT generalize. Re-train with regularization / "
655
+ "a larger corpus (re-serialization will NOT fix this).")
656
+ if not ok:
657
+ print("DESCENT_FAIL: serialized .clm does NOT model held-out text "
658
+ f"(model_ce {rep['heldout_model_ce']} vs uniform {rep['uniform_ce_lnV']}, "
659
+ f"shuffle {rep['heldout_shuffle_ce']}). Broken or overfit — do NOT "
660
+ "mark done / HF-upload.")
661
+ return 0 if ok else 1
662
+
663
+
664
+ def serialize_self_verify(clm_path, train_corpus, heldout=None, skip=False,
665
+ nwin=64, tail_frac=0.1, tag="CLM"):
666
+ """Post-serialize convenience for the trainers (train.py / train.hexa bridge).
667
+ Runs the held-out mirror-DESCENT gate right after a .clm is written, so a
668
+ broken/overfit artifact can't be marked done. If `heldout` is None, holds out
669
+ a DEEP TAIL slice of `train_corpus` (last `tail_frac`, written to a temp file)
670
+ as a stand-in held-out set — never evaluate the gate on the head the model
671
+ trained on. Returns the gate rc (0=PASS). Honest no-op (rc 0) when skip=True
672
+ or no usable corpus, with a printed reason."""
673
+ if skip:
674
+ print(f"{tag}_DESCENT_GATE=SKIPPED (--no-descent-gate)")
675
+ return 0
676
+ held = heldout
677
+ tmp_made = None
678
+ try:
679
+ if held is None:
680
+ if not train_corpus or not os.path.exists(train_corpus):
681
+ print(f"{tag}_DESCENT_GATE=SKIPPED (no held-out and no readable "
682
+ "train corpus to slice)")
683
+ return 0
684
+ rb = open(train_corpus, "rb").read()
685
+ n = len(rb)
686
+ cut = int(n * (1.0 - tail_frac))
687
+ if n - cut < 4096:
688
+ print(f"{tag}_DESCENT_GATE=SKIPPED (corpus too small to hold out "
689
+ f"a {tail_frac:.0%} tail)")
690
+ return 0
691
+ import tempfile
692
+ fd, tmp_made = tempfile.mkstemp(suffix=".heldtail")
693
+ with os.fdopen(fd, "wb") as f:
694
+ f.write(rb[cut:])
695
+ held = tmp_made
696
+ print(f"{tag}_DESCENT_GATE: holding out deep tail slice "
697
+ f"(last {tail_frac:.0%} = {n - cut} bytes) of {train_corpus}")
698
+ return run_descent_gate_cli(clm_path, held, train_corpus, nwin)
699
+ finally:
700
+ if tmp_made and os.path.exists(tmp_made):
701
+ try:
702
+ os.remove(tmp_made)
703
+ except OSError:
704
+ pass
705
+
706
+
707
+ def run_descent_selftest(here):
708
+ """CI self-test of the DESCENT-gate MACHINERY (no torch, no big artifacts):
709
+ serialize a tiny RANDOM-weight model to .clm, run the held-out mirror gate on
710
+ a small byte corpus, and assert the broken-detector FIRES (random weights ⇒
711
+ NO-DESCENT, F-CLM-DESCENT=0). This proves the gate loads/forwards/scores and
712
+ that a non-modeling .clm is correctly REJECTED — the exact failure the gate
713
+ must catch. (A PASS-side fixture would need a trained model = torch; the
714
+ real PASS path is exercised by the control clm_d768 + the trainers in CI.)"""
715
+ import numpy as np
716
+ sd, _ = _build_synthetic_general(d=24, L=2, E=2, K=3, V=256, seed=7)
717
+ ctmp = os.path.join(here, "_rt_descent.clm")
718
+ import clm_serialize_v2 as S
719
+ S.serialize_v3(sd, n_trunk_layers=2, n_experts=2, out_path=ctmp)
720
+ # small deterministic byte corpus (English-ish) — random weights can't model it
721
+ corp = os.path.join(here, "_rt_descent_corpus.txt")
722
+ with open(corp, "wb") as f:
723
+ f.write((b"the mind is a fire to be kindled not a vessel to be filled. ") * 64)
724
+ try:
725
+ ok, rep = descent_gate(ctmp, corp, None, nwin=16)
726
+ # machinery sanity: produced finite CEs + the random model does NOT descend
727
+ finite = (rep["heldout_model_ce"] == rep["heldout_model_ce"] # not NaN
728
+ and rep["uniform_ce_lnV"] > 5.0)
729
+ fires = (ok is False) # broken-detector must reject random weights
730
+ return (finite and fires), rep
731
+ finally:
732
+ for p in (ctmp, corp):
733
+ try:
734
+ os.remove(p)
735
+ except OSError:
736
+ pass
737
+
738
+
739
+ def main():
740
+ # `verify_clm_v2.py descent <clm> <heldout> [train] [nwin]` runs the
741
+ # held-out overfit gate (the post-serialize self-verify). No args → the
742
+ # structural round-trip self-test suite.
743
+ if len(sys.argv) >= 2 and sys.argv[1] == "descent":
744
+ clm = sys.argv[2]
745
+ heldout = sys.argv[3]
746
+ train = sys.argv[4] if len(sys.argv) > 4 and not sys.argv[4].isdigit() else None
747
+ nwin = int(sys.argv[-1]) if sys.argv[-1].isdigit() else 64
748
+ sys.exit(run_descent_gate_cli(clm, heldout, train, nwin))
749
+
750
+ here = os.path.dirname(os.path.abspath(__file__))
751
+ if here not in sys.path:
752
+ sys.path.insert(0, here)
753
+ tmp = os.path.join(here, "_rt_synth.clm")
754
+
755
+ ok, why = run_roundtrip(tmp)
756
+ if ok:
757
+ print("F-CLM-V2-ROUNDTRIP=1")
758
+ else:
759
+ print("F-CLM-V2-ROUNDTRIP=0")
760
+ print(f"MISMATCH: {why}")
761
+ try:
762
+ os.remove(tmp)
763
+ except OSError:
764
+ pass
765
+
766
+ # v0.3 byte-eq regression (L1/E2: v3 == v2)
767
+ eq_ok, eq_why = run_v3_byteeq_v2(here)
768
+ print(f"F-CLM-V3-BYTEEQ-V2={'1' if eq_ok else '0'} ({eq_why})")
769
+
770
+ # v0.3 general round-trips: a small multi-layer/more-expert config + the
771
+ # actual 3B-class config dims (structure only — no weights materialized at
772
+ # full 3B; the dim-bookkeeping is what the gate proves).
773
+ gtmp = os.path.join(here, "_rt_gen.clm")
774
+ g_ok_small, g_why_small = run_roundtrip_general(gtmp, d=48, L=4, E=6)
775
+ print(f"F-CLM-V3-ROUNDTRIP-SMALL={'1' if g_ok_small else '0'} "
776
+ f"(d=48 L=4 E=6: {g_why_small})")
777
+ g_ok_3b, g_why_3b = run_roundtrip_general(gtmp, d=128, L=30, E=30)
778
+ print(f"F-CLM-V3-ROUNDTRIP-3BDIMS={'1' if g_ok_3b else '0'} "
779
+ f"(d=128 L=30 E=30 [3B block/ext topology, reduced d]: {g_why_3b})")
780
+ try:
781
+ os.remove(gtmp)
782
+ except OSError:
783
+ pass
784
+
785
+ # v0.3 BIND round-trips (CLMB bind-readout codec): Hadamard (rt=1) + linear (rt=2).
786
+ btmp = os.path.join(here, "_rt_bind.clm")
787
+ b_ok_h, b_why_h = run_roundtrip_bind(btmp, d=48, L=2, E=2, k=64, readout_type=1)
788
+ print(f"F-CLM-BIND-ROUNDTRIP-HADAMARD={'1' if b_ok_h else '0'} "
789
+ f"(d=48 L=2 E=2 k=64 rt=1: {b_why_h})")
790
+ b_ok_l, b_why_l = run_roundtrip_bind(btmp, d=48, L=2, E=2, k=64, readout_type=2)
791
+ print(f"F-CLM-BIND-ROUNDTRIP-LINEAR={'1' if b_ok_l else '0'} "
792
+ f"(d=48 L=2 E=2 k=64 rt=2: {b_why_l})")
793
+ # 303M-class bind dims (block/ext topology only; reduced d for the bookkeeping).
794
+ b_ok_303, b_why_303 = run_roundtrip_bind(btmp, d=128, L=4, E=4, k=512, readout_type=1)
795
+ print(f"F-CLM-BIND-ROUNDTRIP-303MDIMS={'1' if b_ok_303 else '0'} "
796
+ f"(d=128 L=4 E=4 k=512 [303M ARM-BIND topology, reduced d]: {b_why_303})")
797
+ try:
798
+ os.remove(btmp)
799
+ except OSError:
800
+ pass
801
+
802
+ # held-out DESCENT-gate machinery self-test (broken-detector must fire on
803
+ # random weights). numpy required; honest SKIP if absent.
804
+ try:
805
+ import numpy as _np # noqa: F401 @root-cause-ok probe whether numpy is importable for the gate self-test
806
+ d_ok, d_rep = run_descent_selftest(here)
807
+ print(f"F-CLM-DESCENT-SELFTEST={'1' if d_ok else '0'} "
808
+ f"(random-weight .clm correctly rejected: heldout_ce="
809
+ f"{d_rep['heldout_model_ce']} >= uniform={d_rep['uniform_ce_lnV']})")
810
+ except ImportError:
811
+ print("F-CLM-DESCENT-SELFTEST=SKIP (numpy unavailable)")
812
+ d_ok = True # don't fail the structural suite on a missing optional dep
813
+
814
+ ok = ok and eq_ok and g_ok_small and g_ok_3b and d_ok and b_ok_h and b_ok_l and b_ok_303
815
+
816
+ # golden-reference parse: prove the mirror matches the REAL flame format.
817
+ golden = None
818
+ for cand in [
819
+ os.environ.get("GOLDEN_CLM", ""),
820
+ os.path.join(here, "..", "..", "state", "laneg_d768_recover", "reexport_d768_v2_fast.clm"),
821
+ "/Users/mini/dancinlab/anima/state/laneg_d768_recover/reexport_d768_v2_fast.clm",
822
+ ]:
823
+ if cand and os.path.exists(cand):
824
+ golden = cand
825
+ break
826
+ if golden is None:
827
+ print("GOLDEN: file not found (skipped) — set GOLDEN_CLM=<path>")
828
+ else:
829
+ dec = clm_decodable(golden)
830
+ g = parse_clm(golden)
831
+ b0 = g["blocks"][0] if g["blocks"] else {}
832
+ print(f"GOLDEN path={golden}")
833
+ print(f"GOLDEN decodable={'true' if dec else 'false'} "
834
+ f"nblk={g['nblk']} "
835
+ f"block0={{cout:{b0.get('cout')},rest:{b0.get('rest')}}} "
836
+ f"CLMX-found={'true' if g['clmx_found'] else 'false'} "
837
+ f"n_ext={g['n_ext']} ext_counts={g['ext_counts']} "
838
+ f"exact_eof={g['exact_eof']}")
839
+
840
+ sys.exit(0 if ok else 1)
841
+
842
+
843
+ if __name__ == "__main__":
844
+ main()