AbstractPhil commited on
Commit
f5dc1bc
·
verified ·
1 Parent(s): fded835

exp007_math: math anchors, 4 ask-methods — format lock-in 4/4, no correctness tax, held-out f(x) 0.688->0.938; sep peak 0.496 @L23

Browse files
exp007_math/README.md ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # exp007_math — one adapter stack, four ways of asking for a computation: format locks in with no correctness tax, and held-out function evaluation improves (0.688 → 0.938)
2
+
3
+ **Question.** exp006 left a sharp split on this trunk: the `relay_pw` anchor
4
+ stack locked in every *formal* register (continuation shape, instruct shape,
5
+ tool-call JSON all → 1.0) but a *semantic* constraint — "use these three
6
+ keywords" — did not move at all (0.083 frozen, 0.083 trained). So what does
7
+ the anchor paradigm actually carry: only the **shape** of answers, or
8
+ **computation** itself? This bed asks one arithmetic question four different
9
+ ways and judges FORMAT separately from CORRECTNESS, with a frozen-trunk
10
+ baseline to separate "the trunk can already add" from what the anchor adds,
11
+ and a problem-hash holdout so that held-out correctness is unseen-instance
12
+ computation, not recall.
13
+
14
+ **Answer.** Both. Format locks in everywhere — all four ask-methods reach
15
+ format 1.0, repairing the one register the frozen trunk asks wrong (`nl`
16
+ answers correctly but not in the asked shape, format 0.312) — and
17
+ **correctness never degrades** on any register. Where correctness had
18
+ headroom it *improves*: code-register held-out `f(x)` evaluation goes
19
+ **0.688 → 0.938**, on held-out questions whose exact form and target never
20
+ appear in training — function evaluation improving, not target recall (see
21
+ claim 2 for the exact unseen-instance accounting). Cross-referencing
22
+ exp006 gives the interpretation (not a ledger assertion): the LM loss
23
+ teaches exactly what **target-token predictability** demands. Answer tokens
24
+ are unpredictable without using the question, so correctness moves; exp006's
25
+ keywords were never needed to predict a plausible story, so compliance never
26
+ moved. And the four ask-methods are held apart in sign-code space more
27
+ strongly than any register set ledgered in this line so far: separation
28
+ **0.4711 @ L16 / 0.4957 @ L23**.
29
+
30
+ ## The design
31
+
32
+ **Registers (the four ask-methods).** Every problem is rendered exactly one
33
+ way, deterministically assigned:
34
+
35
+ | register | system instruction asks for | example user turn | example target |
36
+ |---|---|---|---|
37
+ | `nl` | equation + result | "What is 37 + 45?" | `37 + 45 = 82` |
38
+ | `json` | `emit_answer` tool call | "Compute 37 + 45." | `{"expression": "37 + 45", "result": 82}` |
39
+ | `code` | value of a defined Python function | `def f(x): return 3 * x + 2` … "What is f(4)?" | `f(4) = 14` |
40
+ | `word` | one short sentence | "Sam has 37 apples. Sam gets 45 more. …" | `Sam has 82 apples.` |
41
+
42
+ **Problem space.** Fully synthetic — no downloads, ground truth computable:
43
+ `a + b` (1–99), `a − b` (result ≥ 0), `a * b` (2–12), and `f(x) = a·x + b`
44
+ with a 2–9, b 0–20, x 2–9. 4,000 unique problems; `f(x)` problems always
45
+ land in the `code` register (the other kinds round-robin across registers,
46
+ with non-`f(x)` problems in `code` rendered as a one-line function).
47
+ **Holdout is by hash of the rendered question (10%)** — a held-out
48
+ problem's exact question and target never appear in training. For
49
+ `f(x) = a·x + b` problems the `(a, b, x)` combination is additionally
50
+ unseen anywhere in the corpus in any register; a non-`f(x)` problem's
51
+ underlying `a op b` fact *can* appear in training under a different
52
+ register (audited and accounted in claim 2).
53
+
54
+ **Arms.** `frozen` (the untouched trunk, judged with the same prompts) and
55
+ `relay_pw_math`: one exp002-certified `relay_pw` (RelayPatchwork) adapter
56
+ per block of a fully frozen Qwen2.5-0.5B-Instruct trunk — the identical
57
+ adapter layout as exp006 — trained on **all four registers interleaved**
58
+ uniformly per step. 3000 steps, block 512, batch 4, pure Adam lr 1e-3,
59
+ weight decay 0, seed 0. Greedy decoding at eval, max 96 new tokens.
60
+
61
+ **Judges.** The judge splits format from correctness, per register,
62
+ n=16/register, both arms. A generation must close (emit `<|im_end|>` or
63
+ stop) or it fails both. Then:
64
+
65
+ | register | FORMAT passes if | CORRECT passes if |
66
+ |---|---|---|
67
+ | `nl`, `code` | body contains `=` | last integer in body == ground truth |
68
+ | `json` | tool call parses with both keys (`expression`, `result`) | `int(result)` == ground truth |
69
+ | `word` | body ≤ 24 words | last integer in body == ground truth |
70
+
71
+ For `nl`/`code`/`word` the body must also contain at least one integer,
72
+ else both format and correctness fail. Plus per-register held-out
73
+ perplexity, and the register probe carried from
74
+ exp003/exp006: sign-code separation = mean inter-register Hamming distance
75
+ − mean intra-register Hamming distance of the signed winner codes at blocks
76
+ 0 / 8 / 16 / 23 (the frozen arm has no adapters, hence no probe).
77
+
78
+ ## Results (all from `results/ledger.jsonl`, seed 0)
79
+
80
+ **Frozen vs trained, per register** — format, correctness, held-out ppl:
81
+
82
+ | register | format (frozen → trained) | correct (frozen → trained) | held-out ppl (frozen → trained) |
83
+ |---|---|---|---|
84
+ | `nl` | 0.312 → **1.0** | 1.0 → 1.0 | 2.763 → 1.342 |
85
+ | `json` | 1.0 → 1.0 | 1.0 → 1.0 | 4.393 �� 1.061 |
86
+ | `code` | 1.0 → 1.0 | 0.688 → **0.938** | 2.591 → 1.168 |
87
+ | `word` | 1.0 → 1.0 | 0.938 → **1.0** | 3.145 → 1.525 |
88
+
89
+ The frozen trunk is already a competent zero-shot calculator at these
90
+ operand sizes (`nl`/`json` correctness 1.0) — its failures are one register
91
+ asked in the wrong shape (`nl`: "45" or "The equation for 94 - 27 is 67."
92
+ instead of "94 - 27 = 67") and genuine computation misses on `f(x)`
93
+ evaluation (0.688) and word problems (0.938). Training repairs the format
94
+ failure completely and moves both correctness gaps up, degrading nothing.
95
+
96
+ **Register separation** (trained arm; signed winner codes across the four
97
+ ask-methods):
98
+
99
+ | tap | inter | intra | sep |
100
+ |---|---|---|---|
101
+ | L0 | 0.2563 | 0.0418 | 0.2145 |
102
+ | L8 | 0.3293 | 0.0485 | 0.2808 |
103
+ | L16 | 0.8737 | 0.4026 | **0.4711** |
104
+ | L23 | 0.9304 | 0.4347 | **0.4957** |
105
+
106
+ Separation grows monotonically with depth and the peak (0.4957 @ L23) is
107
+ the deepest ledgered in this line so far: exp006's four story registers
108
+ reached 0.3476 / 0.3451 at L16 / L23 and peaked at 0.4619 @ L8 (where this
109
+ run is *lower*, 0.2808), and exp003's instruct tasks stayed ≤ 0.304 at
110
+ L16 / L23 (all cited from their ledgers). Four surface forms of the *same
111
+ underlying computation* are held further apart in sign-code space, at its
112
+ peak, than either previous register set at theirs — with the depth
113
+ *profile* differing across beds.
114
+
115
+ ## What this adds to the line
116
+
117
+ **Claim 1 — format lock-in, with no correctness tax.** All four registers
118
+ reach format 1.0 (from a frozen `nl` format of 0.312), and correctness never
119
+ degrades on any register while it happens: `nl` and `json` hold 1.0, `word`
120
+ moves 0.938 → 1.0. Locking the ask-shape did not cost the anchor any of the
121
+ trunk's arithmetic — the failure mode where format training taxes semantics
122
+ did not appear at this scale.
123
+
124
+ **Claim 2 — computation moves through the adapters.** Code-register
125
+ held-out correctness improves 0.688 → 0.938 on 16 judged problems, every
126
+ one rendered as an `f(x)` question whose exact form and target string never
127
+ appear in training. Exact composition (audited from the deterministic
128
+ generator, seed 0): 5 of the 16 are true `f(x) = a·x + b` problems whose
129
+ `(a, b, x)` combinations appear **nowhere** in the corpus in any register —
130
+ for those, improvement is unseen-instance function evaluation with no
131
+ recall channel at all. The other 11 wrap an `a op b` problem as a one-line
132
+ function; the question and target are still unseen, but for 4 of the 11
133
+ the underlying arithmetic fact appears in training under a *different*
134
+ register, so cross-register fact transfer is a possible channel for those
135
+ four. The improvement therefore cannot be target-string recall, and for the
136
+ `a·x + b` subset it cannot be recall of any kind. The ledgered frozen
137
+ baseline is what makes the sentence honest: the trunk already computes most
138
+ of these, and the anchor's contribution is the measured delta on the part
139
+ it couldn't.
140
+
141
+ **Claim 3 — the predictability principle (cross-experiment interpretation,
142
+ not a ledger assertion).** Placed next to exp006, this run completes a
143
+ clean contrast on the same trunk, adapter, recipe, and step count: math
144
+ correctness improved where exp006's keyword compliance (0.083 → 0.083) did
145
+ not. The parsimonious reading: **the LM loss teaches exactly what
146
+ target-token predictability demands.** Here the answer tokens ("= 82") are
147
+ unpredictable unless the model actually uses the question, so lowering the
148
+ loss forces computation. In exp006 the target stories were predictable
149
+ as stories without honoring the keyword instruction, so nothing pushed
150
+ compliance. This is an interpretation cross-referenced against exp006's
151
+ ledger, not a number asserted from this one; the exp008-class test it
152
+ suggests is a constraint whose target tokens are unpredictable without
153
+ obeying it.
154
+
155
+ **Claim 4 — register separation, deepest peak yet.** Sign-code separation
156
+ across the four ask-methods reaches 0.4711 @ L16 and 0.4957 @ L23 — above
157
+ exp006's story registers at those taps (0.3476 / 0.3451) and above exp006's
158
+ own peak (0.4619 @ L8), and above exp003's instruct tasks (≤ 0.304 at
159
+ L16 / L23). The depth profile is bed-dependent — exp006 peaked shallow, this
160
+ run at L8 sits at 0.2808 — but the peak itself is the deepest ledgered in
161
+ the line. The addressing surface differentiates *how a thing is asked* even
162
+ when *what is computed* is identical — and here does so most strongly deep
163
+ in the trunk.
164
+
165
+ **Caveats.** 1 seed; n=16/register; operands small (2-digit; a*b up to
166
+ 12x12; f(x)=ax+b a 2-9 b 0-20 x 2-9); zero-shot correctness already high
167
+ (nl/json 1.0) so headroom was format + code; of the 16 judged code
168
+ problems only 5 are true a·x+b instances and 4 of the remaining 11 share
169
+ their underlying arithmetic fact with another register's training text
170
+ (claim 2 accounting); register-separation depth profiles differ across
171
+ beds, so "deepest yet" is a claim about the peak, not every tap.
172
+
173
+ ## Files
174
+
175
+ - `qwen_exp007_math.py` — the bed: deterministic problem generator,
176
+ four-register renderer, format/correctness judges, held-out ppl, the
177
+ campaign (`run_exp007`), CPU smoke.
178
+ - `geolip_vitals.py` / `ar_differentiation_bed.py` /
179
+ `exp013_augmentation_bed.py` / `qwen_exp001_relay.py` /
180
+ `qwen_exp002_refine.py` / `qwen_exp003_instruct.py` /
181
+ `qwen_exp006_story.py` — the paste-order harness (trunk wrapper,
182
+ `relay_pw`, tool-call parser, arm loader and register probe; identical to
183
+ the copies shipped with exp006).
184
+ - `repro.py` — smoke / `--run` (full campaign).
185
+ - `build_results.py` — re-asserts every README claim from the ledger.
186
+ - `results/ledger.jsonl` — both rows: frozen baseline + trained arm.
187
+ - `adapters/q7_relay_pw_math_s0.pt` — the trained adapter stack.
188
+
189
+ ## Repro
190
+
191
+ ```bash
192
+ pip install torch transformers
193
+ python repro.py # CPU smoke: problem gen + format/correctness
194
+ # judges exercised in both directions
195
+ python repro.py --run # full campaign (GPU-only verdicts)
196
+ ```
197
+
198
+ Data and caches land in `./data` (override with `GEOLIP_DATA`). The corpus
199
+ is fully synthetic — no dataset downloads; ground truth is computed, so the
200
+ judges need no reference model.
exp007_math/adapters/q7_relay_pw_math_s0.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bc8650497885ea506f2d29ef644f8a8e32557030cf403cdf5f604b5d4df23783
3
+ size 22168167
exp007_math/ar_differentiation_bed.py ADDED
@@ -0,0 +1,487 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ar_differentiation_bed.py — exp012: autoregressive differentiation of the aleph.
2
+
3
+ Differentiation is cultivated by PREDICTIVE pressure along the sequence — the
4
+ address parameterizing the next-byte distribution (Law 2: chain-rule advantage pays
5
+ ONLY where the composed address directly parameterizes the predictive distribution).
6
+ This bed puts the aleph in the autoregressive gradient path and measures what
7
+ differentiates. The head arms enforce the employment law at its maximum: the
8
+ ENTIRE next-byte distribution is parameterized by the address.
9
+
10
+ Byte-level causal LM on wikitext-2-raw (HF parquet, CDN-fast), block 256. ARMS:
11
+ sdpa — standard causal transformer control (matched trunk).
12
+ hub — attention replaced by CAUSAL HUB: linear attention whose feature map
13
+ is the 2K-oriented aleph address, prefix-sum memories (no selection
14
+ event; O(n*K*d)). Differentiation cultivated INSIDE attention.
15
+ addr_head — sdpa trunk, but the OUTPUT HEAD reads ONLY the signed aleph
16
+ coefficient vector w_k = sinh(u_k)/sum_j cosh(u_j) of the final
17
+ hidden state (K -> 256 logits). The address MUST carry every bit of
18
+ next-byte information — the hardest Law-2 bottleneck.
19
+
20
+ JUDGED BY: val bits-per-byte per arm (task) + CULTIVATION VITALS on every aleph
21
+ codebook (readouts, never losses): axis aliveness/hppl, drift-from-init +
22
+ binding fraction @0.29154, winner-|cos| saturation (sign-code emergence), shadow
23
+ path diversity (fixed high-bits hash). Never by recon.
24
+
25
+ Riders: pure Adam wd=0; no BN/Dropout/GAP on geometric paths; orthogonal init;
26
+ Colab-cell-safe (paste-ahead imports, no bare argparse, no __file__ reliance);
27
+ GPU-only for verdict runs.
28
+
29
+ Terminal: python ar_differentiation_bed.py # shapes/parse smoke
30
+ python ar_differentiation_bed.py --train # verdict run
31
+ Colab: paste geolip_vitals.py cell, then this file (smoke auto-runs),
32
+ then train(steps=2000, data_root="/content/data") in the next cell.
33
+ """
34
+ from __future__ import annotations
35
+ import math
36
+ import torch
37
+ import torch.nn as nn
38
+ import torch.nn.functional as F
39
+
40
+ if "anchor_drift" not in globals():
41
+ try:
42
+ from geolip_vitals import anchor_drift, axis_aliveness, path_diversity
43
+ except ImportError:
44
+ _here = globals().get("__file__")
45
+ if _here is not None:
46
+ import sys, pathlib
47
+ sys.path.insert(0, str(pathlib.Path(_here).parent))
48
+ from geolip_vitals import anchor_drift, axis_aliveness, path_diversity
49
+ else:
50
+ raise ImportError(
51
+ "geolip_vitals not found — paste/run its cell first, or "
52
+ "hf_hub_download exp012_ar/geolip_vitals.py from "
53
+ "AbstractPhil/geolip-aleph-differentiation.")
54
+
55
+ VOCAB = 256 # bytes
56
+
57
+
58
+ # ------------------------------------------------------------------ aleph address
59
+ def _super_fibonacci_s3(n: int) -> torch.Tensor:
60
+ """Near-uniform unit quaternions (Alexa CVPR'22) —
61
+ starts the codebook INSIDE the RP^3 attractor basin. D=4 only."""
62
+ PHI, PSI = math.sqrt(2.0), 1.533751168755204288118041
63
+ i = torch.arange(n, dtype=torch.float64)
64
+ s = (i + 0.5) / n
65
+ r, R = torch.sqrt(s), torch.sqrt(1.0 - s)
66
+ a, b = 2 * math.pi * i / PHI, 2 * math.pi * i / PSI
67
+ q = torch.stack([r * torch.sin(a), r * torch.cos(a),
68
+ R * torch.sin(b), R * torch.cos(b)], dim=-1)
69
+ return F.normalize(q, dim=-1).float()
70
+
71
+
72
+ class AlephAddress(nn.Module):
73
+ """Closed-form aleph over 2K oriented half-axes (aleph-void article).
74
+ signed(x): (..., K) w_k = sinh(u_k)/sum_j cosh(u_j) — the Law-2 head feature.
75
+ oriented(x): ((..., K), (..., K)) positive halves of the 2K softmax — HUB map."""
76
+
77
+ def __init__(self, K: int, D: int, tau: float = 0.1, init: str = "random"):
78
+ super().__init__()
79
+ self.K, self.D, self.tau = K, D, tau
80
+ if init == "fibonacci":
81
+ assert D == 4, "fibonacci init lives on S^3 (D=4)"
82
+ A = _super_fibonacci_s3(K)
83
+ else:
84
+ A = F.normalize(torch.randn(K, D), dim=-1)
85
+ self.codebook = nn.Parameter(A)
86
+ self.register_buffer("home", self.codebook.detach().clone())
87
+
88
+ def _u(self, x):
89
+ A = F.normalize(self.codebook, dim=-1)
90
+ return (F.normalize(x, dim=-1) @ A.transpose(-1, -2)) / self.tau
91
+
92
+ def oriented(self, x):
93
+ u = self._u(x)
94
+ m = u.abs().amax(dim=-1, keepdim=True)
95
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
96
+ Z = (ep + en).sum(dim=-1, keepdim=True)
97
+ return ep / Z, en / Z
98
+
99
+ def signed(self, x):
100
+ u = self._u(x)
101
+ m = u.abs().amax(dim=-1, keepdim=True)
102
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
103
+ return (ep - en) / (ep + en).sum(dim=-1, keepdim=True)
104
+
105
+ def signed_at(self, x, taus):
106
+ """Multi-tau stroboscope (rule of 3): signed coefficients at several
107
+ temperatures, concatenated — softer taus keep the vector dense while a
108
+ hard tau supplies the sign-code sharpness. v2 refinement (b)."""
109
+ A = F.normalize(self.codebook, dim=-1)
110
+ cos = F.normalize(x, dim=-1) @ A.transpose(-1, -2)
111
+ outs = []
112
+ for t in taus:
113
+ u = cos / t
114
+ m = u.abs().amax(dim=-1, keepdim=True)
115
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
116
+ outs.append((ep - en) / (ep + en).sum(dim=-1, keepdim=True))
117
+ return torch.cat(outs, dim=-1)
118
+
119
+ def m_hat(self, x):
120
+ """Closed-form soft read (decoders read M_hat, never M). v2 control (c)."""
121
+ u = self._u(x)
122
+ m = u.abs().amax(dim=-1, keepdim=True)
123
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
124
+ A = F.normalize(self.codebook, dim=-1)
125
+ return ((ep - en) @ A) / (ep + en).sum(dim=-1, keepdim=True)
126
+
127
+ def m_hard_ste(self, x):
128
+ """Hard mode (aleph-void article): M_hard = sign(cos_win) * A[win], straight-through to
129
+ the soft read — forward fully discrete SIGN CODE, backward soft gradient.
130
+ Legal per theme A (reconstructive sign code, not a one-hot roster pick)."""
131
+ u = self._u(x)
132
+ soft = self.m_hat(x)
133
+ win = u.abs().argmax(dim=-1)
134
+ A = F.normalize(self.codebook, dim=-1)
135
+ sign = torch.sign(torch.gather(u, -1, win.unsqueeze(-1))).squeeze(-1)
136
+ hard = sign.unsqueeze(-1) * A[win]
137
+ return hard + soft - soft.detach()
138
+
139
+ @torch.no_grad()
140
+ def vitals(self, x_sample) -> dict:
141
+ u = self._u(x_sample.reshape(-1, x_sample.shape[-1]))
142
+ p, n = self.oriented(x_sample.reshape(-1, x_sample.shape[-1]))
143
+ two_k = torch.cat([p, n], dim=-1)
144
+ win = two_k.argmax(dim=-1)
145
+ cos_win = (u.abs().amax(dim=-1) * self.tau) # winner |cos| — sign-code sat.
146
+ d = anchor_drift(self.codebook, self.home)
147
+ return {"drift": round(d["mean"], 4),
148
+ "binding_frac": round(d["binding_fraction"], 4),
149
+ "aliveness": axis_aliveness(two_k),
150
+ "win_cos_mean": round(cos_win.mean().item(), 4),
151
+ "paths": path_diversity(win)}
152
+
153
+
154
+ # ------------------------------------------------------------------------- blocks
155
+ class CausalSDPA(nn.Module):
156
+ def __init__(self, d: int, heads: int = 4):
157
+ super().__init__()
158
+ self.h = heads
159
+ self.qkv = nn.Linear(d, 3 * d, bias=False)
160
+ self.o = nn.Linear(d, d, bias=False)
161
+ nn.init.orthogonal_(self.qkv.weight); nn.init.orthogonal_(self.o.weight)
162
+
163
+ def forward(self, x):
164
+ B, n, d = x.shape
165
+ q, k, v = self.qkv(x).chunk(3, dim=-1)
166
+ q, k, v = (t.view(B, n, self.h, d // self.h).transpose(1, 2) for t in (q, k, v))
167
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
168
+ return self.o(y.transpose(1, 2).reshape(B, n, d))
169
+
170
+
171
+ class CausalHUB(nn.Module):
172
+ """Causal aleph linear attention: prefix-sum memories over the two K-wide
173
+ halves of the oriented address; 2K never materialized; no selection event."""
174
+
175
+ def __init__(self, d: int, K: int = 32, D: int = 4, tau: float = 0.1):
176
+ super().__init__()
177
+ self.addr = AlephAddress(K, D, tau)
178
+ self.q = nn.Linear(d, D, bias=False)
179
+ self.k = nn.Linear(d, D, bias=False)
180
+ self.v = nn.Linear(d, d, bias=False)
181
+ self.o = nn.Linear(d, d, bias=False)
182
+ for m in (self.q, self.k, self.v, self.o):
183
+ nn.init.orthogonal_(m.weight)
184
+
185
+ def forward(self, x):
186
+ qp, qn = self.addr.oriented(self.q(x)) # (B, n, K)
187
+ kp, kn = self.addr.oriented(self.k(x))
188
+ v = self.v(x) # (B, n, d)
189
+ Sp = torch.cumsum(torch.einsum("bnk,bnd->bnkd", kp, v), dim=1)
190
+ Sn = torch.cumsum(torch.einsum("bnk,bnd->bnkd", kn, v), dim=1)
191
+ zp = torch.cumsum(kp, dim=1)
192
+ zn = torch.cumsum(kn, dim=1)
193
+ num = torch.einsum("bnk,bnkd->bnd", qp, Sp) + torch.einsum("bnk,bnkd->bnd", qn, Sn)
194
+ den = (qp * zp).sum(-1, keepdim=True) + (qn * zn).sum(-1, keepdim=True)
195
+ return self.o(num / den.clamp_min(1e-12))
196
+
197
+
198
+ class MslRelay(nn.Module):
199
+ """Depth-composition unit (chain-rule probe): multi-slot M_hat read entering
200
+ the trunk as a NEAR-ZERO gated residual (gate init -3.0, sigma~0.047 — theme D:
201
+ geometry enters as a nudge and grows only if it earns gradient)."""
202
+
203
+ def __init__(self, d: int, n_slots: int = 16, K: int = 64):
204
+ super().__init__()
205
+ self.n_slots = n_slots
206
+ self.proj = nn.Linear(d, n_slots * 4, bias=False)
207
+ self.out = nn.Linear(n_slots * 4, d, bias=False)
208
+ nn.init.orthogonal_(self.proj.weight)
209
+ nn.init.orthogonal_(self.out.weight)
210
+ self.addr = AlephAddress(K, 4)
211
+ self.gate = nn.Parameter(torch.tensor(-3.0))
212
+
213
+ def forward(self, x):
214
+ B, n, _ = x.shape
215
+ slots = self.proj(x).view(B, n, self.n_slots, 4)
216
+ m = self.addr.m_hat(slots).reshape(B, n, -1)
217
+ return x + self.gate.sigmoid() * self.out(m)
218
+
219
+
220
+ class Block(nn.Module):
221
+ def __init__(self, d: int, attn: nn.Module):
222
+ super().__init__()
223
+ self.n1, self.n2 = nn.LayerNorm(d), nn.LayerNorm(d)
224
+ self.attn = attn
225
+ self.mlp = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))
226
+
227
+ def forward(self, x):
228
+ x = x + self.attn(self.n1(x))
229
+ return x + self.mlp(self.n2(x))
230
+
231
+
232
+ class ByteLM(nn.Module):
233
+ def __init__(self, arm: str, d: int = 192, layers: int = 4, block: int = 256,
234
+ K: int = 32, D: int = 4):
235
+ super().__init__()
236
+ # "<arm>_tri" suffix = trigram byte embedding (AlephLM byte_emb x3 lineage):
237
+ # token embedding is the sum of embeddings of bytes t, t-1, t-2.
238
+ self.trigram = arm.endswith("_tri")
239
+ if self.trigram:
240
+ arm = arm[:-4]
241
+ # "_fib" = super-Fibonacci S^3 codebook init (basin test: starts INSIDE
242
+ # the RP^3 attractor; primary observable is init->final geodesic drift).
243
+ self.fib = arm.endswith("_fib")
244
+ if self.fib:
245
+ arm = arm[:-4]
246
+ # "relay*" = stacked addresses in depth: MslRelay after every block.
247
+ # relay -> sdpa trunk + standard head; relay_msl64 -> + addressed head.
248
+ self.use_relay = arm.startswith("relay")
249
+ if arm == "relay":
250
+ arm = "sdpa"
251
+ elif arm == "relay_msl64":
252
+ arm = "addr_msl64"
253
+ self.arm, self.block = arm, block
254
+ self.emb = nn.Embedding(VOCAB, d)
255
+ if self.trigram:
256
+ self.emb1 = nn.Embedding(VOCAB, d)
257
+ self.emb2 = nn.Embedding(VOCAB, d)
258
+ self.pos = nn.Parameter(torch.zeros(1, block, d) + 0.01 * torch.randn(1, block, d))
259
+ mk_attn = (lambda: CausalHUB(d, K, D)) if arm == "hub" else (lambda: CausalSDPA(d))
260
+ self.blocks = nn.ModuleList([Block(d, mk_attn()) for _ in range(layers)])
261
+ if self.use_relay:
262
+ self.relays = nn.ModuleList([MslRelay(d) for _ in range(layers)])
263
+ self.nf = nn.LayerNorm(d)
264
+ if arm == "addr_head":
265
+ self.head_addr = AlephAddress(K, d) # v1: codebook in model dim — COLLAPSED
266
+ self.head = nn.Linear(K, VOCAB, bias=True)
267
+ elif arm in ("addr_d4", "addr_3tau", "addr_mhat"):
268
+ # v2 refinements: LOW-D HOME — learned projection to the native D=4 home
269
+ # before addressing (mirrors the healthy HUB arms), K=64.
270
+ self.head_proj = nn.Linear(d, 4, bias=False)
271
+ nn.init.orthogonal_(self.head_proj.weight)
272
+ self.head_addr = AlephAddress(64, 4)
273
+ if arm == "addr_d4":
274
+ self.head = nn.Linear(64, VOCAB, bias=True) # w alone, D=4 home
275
+ elif arm == "addr_3tau":
276
+ self.taus = (0.05, 0.1, 0.3) # rule-of-3 strobe
277
+ self.head = nn.Linear(64 * 3, VOCAB, bias=True)
278
+ else: # addr_mhat
279
+ self.head = nn.Linear(4, VOCAB, bias=True) # tightest: M_hat
280
+ elif arm.startswith("addr_msl"):
281
+ # v3: MULTI-SLOT heads — the 16s funnel widening: P parallel D=4 slots
282
+ # over a SHARED codebook. addr_msl consumes the reconstructive M_hat per
283
+ # slot (Px4 dims); addr_msl_w consumes signed w per slot (Px64) — tests
284
+ # whether slot-parallel consumption alone rescues the coefficient path.
285
+ # addr_msl<P> = slot-count dose-response. addr_mslh<P> = HARD sign-code
286
+ # consumption (straight-through M_hard per slot).
287
+ self.hard = arm.startswith("addr_mslh")
288
+ if arm in ("addr_msl", "addr_msl_w"):
289
+ self.n_slots = 16
290
+ else:
291
+ self.n_slots = int(arm[len("addr_mslh" if self.hard else "addr_msl"):])
292
+ self.head_proj = nn.Linear(d, self.n_slots * 4, bias=False)
293
+ nn.init.orthogonal_(self.head_proj.weight)
294
+ self.head_addr = AlephAddress(
295
+ 64, 4, init="fibonacci" if self.fib else "random")
296
+ width = self.n_slots * (64 if arm == "addr_msl_w" else 4)
297
+ self.head = nn.Linear(width, VOCAB, bias=True)
298
+ elif arm == "addr_3tau_mhat":
299
+ # v3: combine the two v2 winners — 3-tau stroboscope + reconstructive read.
300
+ self.head_proj = nn.Linear(d, 4, bias=False)
301
+ nn.init.orthogonal_(self.head_proj.weight)
302
+ self.head_addr = AlephAddress(64, 4)
303
+ self.taus = (0.05, 0.1, 0.3)
304
+ self.head = nn.Linear(64 * 3 + 4, VOCAB, bias=True)
305
+ else:
306
+ self.head = nn.Linear(d, VOCAB, bias=True)
307
+ self._last_h = None
308
+
309
+ def forward(self, idx):
310
+ x = self.emb(idx)
311
+ if self.trigram: # past-only shifts — causality preserved
312
+ x = x + self.emb1(F.pad(idx, (1, 0), value=0)[:, :-1]) \
313
+ + self.emb2(F.pad(idx, (2, 0), value=0)[:, :-2])
314
+ x = x + self.pos[:, : idx.shape[1]]
315
+ if self.use_relay:
316
+ for b, r in zip(self.blocks, self.relays):
317
+ x = r(b(x))
318
+ else:
319
+ for b in self.blocks:
320
+ x = b(x)
321
+ h = self.nf(x)
322
+ self._last_h = h.detach()
323
+ if self.arm == "addr_head":
324
+ return self.head(self.head_addr.signed(h))
325
+ if self.arm == "addr_d4":
326
+ return self.head(self.head_addr.signed(self.head_proj(h)))
327
+ if self.arm == "addr_3tau":
328
+ return self.head(self.head_addr.signed_at(self.head_proj(h), self.taus))
329
+ if self.arm == "addr_mhat":
330
+ return self.head(self.head_addr.m_hat(self.head_proj(h)))
331
+ if self.arm.startswith("addr_msl"):
332
+ B, n, _ = h.shape
333
+ slots = self.head_proj(h).view(B, n, self.n_slots, 4)
334
+ if self.arm == "addr_msl_w":
335
+ feats = self.head_addr.signed(slots).reshape(B, n, -1)
336
+ elif getattr(self, "hard", False):
337
+ feats = self.head_addr.m_hard_ste(slots).reshape(B, n, -1)
338
+ else:
339
+ feats = self.head_addr.m_hat(slots).reshape(B, n, -1)
340
+ return self.head(feats)
341
+ if self.arm == "addr_3tau_mhat":
342
+ p = self.head_proj(h)
343
+ feats = torch.cat([self.head_addr.signed_at(p, self.taus),
344
+ self.head_addr.m_hat(p)], dim=-1)
345
+ return self.head(feats)
346
+ return self.head(h)
347
+
348
+ @torch.no_grad()
349
+ def vitals(self) -> dict:
350
+ out = {}
351
+ if self.arm == "hub":
352
+ for i, b in enumerate(self.blocks):
353
+ if self._last_h is not None:
354
+ out[f"L{i}"] = b.attn.addr.vitals(b.attn.q(self._last_h[:2]))
355
+ elif self.arm == "addr_head" and self._last_h is not None:
356
+ out["head"] = self.head_addr.vitals(self._last_h[:2])
357
+ elif self.arm in ("addr_d4", "addr_3tau", "addr_mhat",
358
+ "addr_3tau_mhat") and self._last_h is not None:
359
+ out["head"] = self.head_addr.vitals(self.head_proj(self._last_h[:2]))
360
+ elif self.arm.startswith("addr_msl") and self._last_h is not None:
361
+ slots = self.head_proj(self._last_h[:2])
362
+ out["head"] = self.head_addr.vitals(
363
+ slots.reshape(*slots.shape[:-1], self.n_slots, 4))
364
+ if self.use_relay and self._last_h is not None:
365
+ for i, r in enumerate(self.relays):
366
+ s = r.proj(self._last_h[:2])
367
+ v = r.addr.vitals(s.reshape(*s.shape[:-1], r.n_slots, 4))
368
+ out[f"relay{i}"] = {"gate": round(r.gate.sigmoid().item(), 4),
369
+ "drift": v["drift"],
370
+ "binding_frac": v["binding_frac"],
371
+ "ppl": round(v["aliveness"]["usage_ppl"], 1)}
372
+ return out
373
+
374
+
375
+ # --------------------------------------------------------------------------- data
376
+ def _wikitext_bytes(data_root: str):
377
+ """wikitext-2-raw as flat uint8 tensors via the HF parquet CDN."""
378
+ from huggingface_hub import hf_hub_download
379
+ import pyarrow.parquet as pq
380
+
381
+ def load(split):
382
+ p = hf_hub_download("Salesforce/wikitext",
383
+ f"wikitext-2-raw-v1/{split}-00000-of-00001.parquet",
384
+ repo_type="dataset", local_dir=data_root)
385
+ text = "".join(pq.read_table(p).column("text").to_pylist())
386
+ return torch.frombuffer(bytearray(text.encode("utf-8")), dtype=torch.uint8).clone()
387
+
388
+ return load("train"), load("validation")
389
+
390
+
391
+ def _batch(data: torch.Tensor, batch: int, block: int, device, g: torch.Generator):
392
+ ix = torch.randint(0, data.numel() - block - 1, (batch,), generator=g)
393
+ x = torch.stack([data[i:i + block] for i in ix]).long().to(device)
394
+ y = torch.stack([data[i + 1:i + block + 1] for i in ix]).long().to(device)
395
+ return x, y
396
+
397
+
398
+ # -------------------------------------------------------------------- train/smoke
399
+ def train(arms=("sdpa", "hub", "addr_head"), steps: int = 2000, batch: int = 32,
400
+ block: int = 256, device: str = "cuda", data_root: str = "./data",
401
+ seed: int = 0, eval_every: int = 500, save: bool = True):
402
+ """Verdict run — GPU only. Pure Adam wd=0. Reports val bits-per-byte + vitals.
403
+ save=True writes {data_root}/ar_ckpts/{arm}_s{seed}_t{steps}.pt per arm —
404
+ the cultivated codebooks are SPECIMENS for the projective reading instruments."""
405
+ import os
406
+ if device == "cuda" and not torch.cuda.is_available():
407
+ raise RuntimeError("Verdict runs are GPU-only (never CPU-train for accuracy).")
408
+ ckpt_dir = os.path.join(data_root, "ar_ckpts")
409
+ os.makedirs(ckpt_dir, exist_ok=True)
410
+ tr, va = _wikitext_bytes(data_root)
411
+ print(f"data ready: train {tr.numel():,} bytes, val {va.numel():,} bytes", flush=True)
412
+ results = {}
413
+ for arm in arms:
414
+ torch.manual_seed(seed)
415
+ g = torch.Generator().manual_seed(seed)
416
+ model = ByteLM(arm, block=block).to(device)
417
+ n_params = sum(p.numel() for p in model.parameters())
418
+ opt = torch.optim.Adam(model.parameters(), lr=3e-4, weight_decay=0.0)
419
+ for step in range(1, steps + 1):
420
+ x, y = _batch(tr, batch, block, device, g)
421
+ logits = model(x)
422
+ loss = F.cross_entropy(logits.reshape(-1, VOCAB), y.reshape(-1))
423
+ opt.zero_grad(set_to_none=True)
424
+ loss.backward()
425
+ opt.step()
426
+ if step % eval_every == 0 or step == steps:
427
+ model.eval()
428
+ with torch.no_grad():
429
+ losses = []
430
+ for _ in range(20):
431
+ xv, yv = _batch(va, batch, block, device, g)
432
+ lv = F.cross_entropy(model(xv).reshape(-1, VOCAB),
433
+ yv.reshape(-1))
434
+ losses.append(lv.item())
435
+ bpb = sum(losses) / len(losses) / math.log(2)
436
+ print(f"[{arm}] step {step} val_bpb={bpb:.4f} vitals={model.vitals()}",
437
+ flush=True)
438
+ model.train()
439
+ results[arm] = {"val_bpb": bpb, "params": n_params, "vitals": model.vitals()}
440
+ if save:
441
+ path = os.path.join(ckpt_dir, f"{arm}_s{seed}_t{steps}.pt")
442
+ torch.save({"arm": arm, "seed": seed, "steps": steps, "val_bpb": bpb,
443
+ "state_dict": {k: v.cpu() for k, v in
444
+ model.state_dict().items()}}, path)
445
+ print(f"saved specimen: {path}", flush=True)
446
+ print(results, flush=True)
447
+ return results
448
+
449
+
450
+ def smoke():
451
+ """Shapes/parse only — no accuracy claims."""
452
+ x = torch.randint(0, VOCAB, (2, 64))
453
+ for arm in ("sdpa", "hub", "addr_head"):
454
+ m = ByteLM(arm, d=96, layers=2, block=64, K=16)
455
+ logits = m(x)
456
+ assert logits.shape == (2, 64, VOCAB)
457
+ logits.sum().backward()
458
+ # causality check: future byte must not affect past logits
459
+ with torch.no_grad():
460
+ a = m(x)[0, 10]
461
+ x2 = x.clone(); x2[0, 40] = (x2[0, 40] + 7) % 256
462
+ b = m(x2)[0, 10]
463
+ assert torch.allclose(a, b, atol=1e-4), f"{arm} leaks future context"
464
+ print(f"{arm}: OK params={sum(p.numel() for p in m.parameters()):,} "
465
+ f"vitals={m.vitals()}", flush=True)
466
+ print("OK — AR bed smoke passed (verdict run: train() on GPU)", flush=True)
467
+
468
+
469
+ def _in_notebook() -> bool:
470
+ try:
471
+ get_ipython() # type: ignore[name-defined] # noqa: F821
472
+ return True
473
+ except NameError:
474
+ return False
475
+
476
+
477
+ if __name__ == "__main__":
478
+ if _in_notebook():
479
+ smoke()
480
+ print("Notebook mode: call train(steps=2000) in the next cell (GPU).")
481
+ else:
482
+ import argparse
483
+ ap = argparse.ArgumentParser()
484
+ ap.add_argument("--train", action="store_true")
485
+ ap.add_argument("--steps", type=int, default=2000)
486
+ a, _ = ap.parse_known_args()
487
+ train(steps=a.steps) if a.train else smoke()
exp007_math/build_results.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """build_results.py — exp007_math: read results/ledger.jsonl and RE-ASSERT
2
+ every claim in the README. The ledger carries two rows: the FROZEN trunk
3
+ baseline (separates "the trunk can already add" from what the anchor adds)
4
+ and the trained relay_pw_math arm (one relay_pw stack, all four ask-method
5
+ registers interleaved). Run from inside this folder: python build_results.py
6
+ """
7
+ import json
8
+ import os
9
+
10
+ HERE = os.path.dirname(os.path.abspath(__file__))
11
+ rows = [json.loads(l) for l in
12
+ open(os.path.join(HERE, "results", "ledger.jsonl"), encoding="utf-8")]
13
+ assert all(r["exp"] == "q7" for r in rows) and len(rows) == 2
14
+
15
+ frozen = next(r for r in rows if r["arm"] == "frozen")
16
+ trained = next(r for r in rows if r["arm"] == "relay_pw_math")
17
+ assert frozen["seed"] == 0 and frozen["steps"] == 0
18
+ assert trained["seed"] == 0 and trained["steps"] == 3000
19
+ REGS = ("nl", "json", "code", "word")
20
+
21
+ # same synthetic corpus under both arms (train-side cache stats identical)
22
+ assert frozen["cache_stats"] == trained["cache_stats"] == \
23
+ {"nl": 696, "json": 702, "code": 1496, "word": 718}
24
+
25
+ fv, tv = frozen["validity"], trained["validity"]
26
+
27
+ # claim 1a: format lock-in — the frozen trunk fails FORMAT only on nl
28
+ # (0.312: it answers correctly but not in the asked shape; json/code/word
29
+ # already 1.0); after training all four registers reach format 1.0.
30
+ assert fv["nl"]["format"] == 0.312
31
+ assert fv["json"]["format"] == 1.0
32
+ assert fv["code"]["format"] == 1.0
33
+ assert fv["word"]["format"] == 1.0
34
+ assert all(tv[r]["format"] == 1.0 for r in REGS)
35
+
36
+ # claim 1b: no correctness tax — correctness never degrades on any register:
37
+ # nl and json hold 1.0 -> 1.0, word 0.938 -> 1.0, code improves (claim 2).
38
+ assert fv["nl"]["correct"] == 1.0 and tv["nl"]["correct"] == 1.0
39
+ assert fv["json"]["correct"] == 1.0 and tv["json"]["correct"] == 1.0
40
+ assert fv["word"]["correct"] == 0.938 and tv["word"]["correct"] == 1.0
41
+ assert all(tv[r]["correct"] >= fv[r]["correct"] for r in REGS)
42
+
43
+ # claim 2: computation through the adapters — code-register held-out
44
+ # correctness 0.688 -> 0.938 on 16 f(x)-rendered questions never seen in
45
+ # training (audited: 5/16 are true a*x+b instances unseen anywhere in the
46
+ # corpus; 4/16 wrapped-arithmetic items share their a-op-b fact with
47
+ # another register's training text — see README claim 2). Not target
48
+ # recall. Frozen baseline ledgered.
49
+ assert fv["code"]["correct"] == 0.688
50
+ assert tv["code"]["correct"] == 0.938
51
+
52
+ # claim 3 (the predictability principle) is a CROSS-EXPERIMENT
53
+ # interpretation — exp006's keyword compliance (0.083 -> 0.083, exp006
54
+ # ledger) vs correctness moving here — and is NOT asserted from this
55
+ # ledger. Its local anchor IS asserted: held-out LM loss improved on
56
+ # every register (the loss had something to teach, and correctness moved
57
+ # exactly where target tokens are unpredictable without the question).
58
+ assert frozen["ppl"] == {"nl": 2.763, "json": 4.393,
59
+ "code": 2.591, "word": 3.145}
60
+ assert trained["ppl"] == {"nl": 1.342, "json": 1.061,
61
+ "code": 1.168, "word": 1.525}
62
+ assert all(trained["ppl"][r] < frozen["ppl"][r] for r in REGS)
63
+
64
+ # claim 4: register separation, deepest PEAK yet in the line — sign-code
65
+ # sep across the 4 ask-methods 0.4711 @ L16 and 0.4957 @ L23 (exp006 story
66
+ # registers 0.3476 / 0.3451 at those taps, own peak 0.4619 @ L8; exp003
67
+ # instruct arms <= 0.304 at L16/L23 — cited from their ledgers, not
68
+ # asserted here). Separation grows monotonically with depth in THIS run;
69
+ # depth profiles differ across beds. Frozen arm has no adapters, no probe.
70
+ sep = trained["register_sep"]
71
+ assert sep["L16"]["sep"] == 0.4711
72
+ assert sep["L23"]["sep"] == 0.4957
73
+ assert sep["L0"]["sep"] == 0.2145 and sep["L8"]["sep"] == 0.2808
74
+ assert sep["L0"]["sep"] < sep["L8"]["sep"] < sep["L16"]["sep"] \
75
+ < sep["L23"]["sep"]
76
+ assert frozen["register_sep"] is None
77
+
78
+ out = {"frozen": {"ppl": frozen["ppl"], "validity": fv},
79
+ "relay_pw_math": {"steps": trained["steps"],
80
+ "ppl": trained["ppl"],
81
+ "validity": tv,
82
+ "register_sep": sep},
83
+ "cache_stats": trained["cache_stats"],
84
+ "n_rows": len(rows)}
85
+ json.dump(out, open(os.path.join(HERE, "results", "results.json"), "w",
86
+ encoding="utf-8"), indent=1)
87
+ print(f"{len(rows)} rows -> results/results.json")
88
+ print("all README claims asserted OK")
exp007_math/exp013_augmentation_bed.py ADDED
@@ -0,0 +1,506 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """exp013_augmentation_bed.py — augmenting pretrained models with the aleph.
2
+ Three tracks, one file (sequel to exp012's ar_differentiation_bed):
3
+
4
+ A. AUTOREGRESSION FROM CLIP-L: next-token prediction over CLIP-tokenized text,
5
+ reading FROZEN openai/clip-vit-large-patch14 text-tower hidden states.
6
+ FACTOR: extraction layer in {final, penultimate} (the last two layers — the
7
+ penultimate is what diffusion stacks consume). Heads at ~matched params:
8
+ linear | mlp | aleph multi-slot M_hat (P=64, D=4, shared K=64 — the exp012
9
+ certified construction) | sign-code (straight-through).
10
+ B. JOINT-FAILURE PROBES on frozen pooled embeddings (CLIP-L both layers + BERT):
11
+ b1 SPELLING-AR — decode a word's characters from ONLY the head's read of its
12
+ pooled embedding (GATE: linear/mlp must fail <50% exact first);
13
+ b2 ORDER — original-vs-shuffled discrimination (secondary; info may be absent).
14
+ C. GPT-2 (124M) AUGMENTATION: frozen trunk + trainable adapters after every block —
15
+ aleph MslRelay adapters vs param-matched MLP adapters vs frozen baseline;
16
+ gate growth by depth is a first-class readout (exp012 depth-gradient law).
17
+
18
+ Riders: pure Adam wd=0; no contrastive/InfoNCE into address paths; vitals are
19
+ readouts; GPU-only for verdict runs; caches/specimens live OUTSIDE the repo.
20
+ Colab: paste geolip_vitals.py, then ar_differentiation_bed.py, then this file.
21
+ """
22
+ from __future__ import annotations
23
+ import json
24
+ import math
25
+ import os
26
+ import re
27
+ import torch
28
+ import torch.nn as nn
29
+ import torch.nn.functional as F
30
+
31
+ # ---- paste-ahead imports (notebook-safe) -----------------------------------------
32
+ if "anchor_drift" not in globals():
33
+ try:
34
+ from geolip_vitals import anchor_drift, axis_aliveness, path_diversity
35
+ except ImportError:
36
+ _here = globals().get("__file__")
37
+ if _here is None:
38
+ raise ImportError("paste/run geolip_vitals.py first")
39
+ import sys, pathlib
40
+ sys.path.insert(0, str(pathlib.Path(_here).parent))
41
+ from geolip_vitals import anchor_drift, axis_aliveness, path_diversity
42
+ if "AlephAddress" not in globals():
43
+ try:
44
+ from ar_differentiation_bed import AlephAddress, MslRelay, _wikitext_bytes
45
+ except ImportError:
46
+ _here = globals().get("__file__")
47
+ if _here is None:
48
+ raise ImportError("paste/run ar_differentiation_bed.py first")
49
+ from ar_differentiation_bed import AlephAddress, MslRelay, _wikitext_bytes
50
+
51
+ DATA_ROOT = os.environ.get("GEOLIP_DATA", "./data")
52
+
53
+
54
+ class SquaredReLU(nn.Module):
55
+ def forward(self, x):
56
+ return F.relu(x) ** 2
57
+
58
+
59
+ # =============================================================== head arm family ==
60
+ class HeadArm(nn.Module):
61
+ """Conditioning transform d_in -> F_OUT (256), then a shared-shape task head.
62
+ Arms: linear | mlp | aleph (multi-slot M_hat) | sign (straight-through M_hard).
63
+ The aleph arms are the exp012-certified construction: P=64 slots x D=4 over one
64
+ shared K=64 codebook."""
65
+ F_OUT = 256
66
+
67
+ def __init__(self, arm: str, d_in: int, mlp_hidden: int = 192):
68
+ # mlp_hidden=192 param-matches the aleph arm at d_in=768:
69
+ # mlp ~ 192*(768+256)+LN ~ 197K vs aleph proj 768*256 + codebook = 196.9K
70
+ super().__init__()
71
+ self.arm = arm
72
+ if arm == "linear":
73
+ self.net = nn.Linear(d_in, self.F_OUT, bias=False)
74
+ elif arm == "mlp":
75
+ self.net = nn.Sequential(nn.Linear(d_in, mlp_hidden), SquaredReLU(),
76
+ nn.LayerNorm(mlp_hidden),
77
+ nn.Linear(mlp_hidden, self.F_OUT))
78
+ elif arm in ("aleph", "sign"):
79
+ self.proj = nn.Linear(d_in, 64 * 4, bias=False)
80
+ nn.init.orthogonal_(self.proj.weight)
81
+ self.addr = AlephAddress(64, 4)
82
+ else:
83
+ raise ValueError(arm)
84
+
85
+ def forward(self, x):
86
+ if self.arm in ("linear", "mlp"):
87
+ return self.net(x)
88
+ slots = self.proj(x).reshape(*x.shape[:-1], 64, 4)
89
+ read = self.addr.m_hard_ste(slots) if self.arm == "sign" \
90
+ else self.addr.m_hat(slots)
91
+ return read.reshape(*x.shape[:-1], 256)
92
+
93
+ @torch.no_grad()
94
+ def vitals(self, x_sample) -> dict:
95
+ if self.arm in ("linear", "mlp"):
96
+ return {}
97
+ slots = self.proj(x_sample).reshape(*x_sample.shape[:-1], 64, 4)
98
+ p, n = self.addr.oriented(slots)
99
+ two_k = torch.cat([p, n], -1).reshape(-1, 128)
100
+ d = anchor_drift(self.addr.codebook, self.addr.home)
101
+ return {"drift": round(d["mean"], 4),
102
+ "binding_frac": round(d["binding_fraction"], 4),
103
+ "usage_ppl": round(axis_aliveness(two_k)["usage_ppl"], 1),
104
+ "paths": path_diversity(two_k.argmax(-1))["unique_hashed"]}
105
+
106
+ def param_count(self):
107
+ return sum(p.numel() for p in self.parameters())
108
+
109
+
110
+ # ================================================================== caches ========
111
+ def _wikitext_lines(data_root, min_chars=40, max_lines=None):
112
+ from huggingface_hub import hf_hub_download
113
+ import pyarrow.parquet as pq
114
+ out = {}
115
+ for split, cap in (("train", max_lines), ("validation", None)):
116
+ p = hf_hub_download("Salesforce/wikitext",
117
+ f"wikitext-2-raw-v1/{split}-00000-of-00001.parquet",
118
+ repo_type="dataset", local_dir=data_root)
119
+ lines = [t.strip() for t in pq.read_table(p).column("text").to_pylist()
120
+ if len(t.strip()) >= min_chars]
121
+ out[split] = lines[:cap] if cap else lines
122
+ return out["train"], out["validation"]
123
+
124
+
125
+ @torch.no_grad()
126
+ def cache_clip(data_root, n_train=12000, n_val=1500, device="cuda", batch=64):
127
+ """CLIP-L text tower over wikitext lines; caches BOTH of the last two layers.
128
+ hidden_states[-1] == the final encoder layer output (pre final-LN),
129
+ last_hidden_state == final-LN(final layer). We cache:
130
+ 'final' = last_hidden_state (what the projection head consumes),
131
+ 'penult' = hidden_states[-2] (the layer diffusion stacks consume).
132
+ Also caches pooled (EOS-position) vectors for both layers, and token ids."""
133
+ from transformers import CLIPTextModel, CLIPTokenizerFast
134
+ path = os.path.join(data_root, "exp013", "clip_cache.pt")
135
+ if os.path.exists(path):
136
+ return torch.load(path, map_location="cpu", weights_only=True)
137
+ os.makedirs(os.path.dirname(path), exist_ok=True)
138
+ tok = CLIPTokenizerFast.from_pretrained("openai/clip-vit-large-patch14")
139
+ model = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14").to(device).eval()
140
+ tr_lines, va_lines = _wikitext_lines(data_root, max_lines=n_train)
141
+ va_lines = va_lines[:n_val]
142
+ def encode(lines):
143
+ H_f, H_p, IDS, EOS = [], [], [], []
144
+ for i in range(0, len(lines), batch):
145
+ enc = tok(lines[i:i + batch], padding="max_length", truncation=True,
146
+ max_length=77, return_tensors="pt").to(device)
147
+ out = model(**enc, output_hidden_states=True)
148
+ H_f.append(out.last_hidden_state.half().cpu())
149
+ H_p.append(out.hidden_states[-2].half().cpu())
150
+ IDS.append(enc.input_ids.cpu())
151
+ EOS.append(enc.input_ids.argmax(-1).cpu()) # EOT id is the max token id
152
+ return (torch.cat(H_f), torch.cat(H_p), torch.cat(IDS), torch.cat(EOS))
153
+ tr = encode(tr_lines)
154
+ va = encode(va_lines)
155
+ blob = {"train": {"final": tr[0], "penult": tr[1], "ids": tr[2], "eos": tr[3]},
156
+ "val": {"final": va[0], "penult": va[1], "ids": va[2], "eos": va[3]},
157
+ "vocab": tok.vocab_size}
158
+ torch.save(blob, path)
159
+ print(f"clip cache: train {tr[0].shape}, val {va[0].shape} -> {path}", flush=True)
160
+ return blob
161
+
162
+
163
+ @torch.no_grad()
164
+ def cache_word_embeddings(data_root, n_words=10000, device="cuda", batch=256):
165
+ """Pooled embeddings of frequent wikitext words for the spelling probe:
166
+ CLIP-L final + penultimate (EOS-pooled) and BERT (CLS + mean of last layer)."""
167
+ from transformers import (CLIPTextModel, CLIPTokenizerFast,
168
+ BertModel, BertTokenizerFast)
169
+ path = os.path.join(data_root, "exp013", "word_cache.pt")
170
+ if os.path.exists(path):
171
+ return torch.load(path, map_location="cpu", weights_only=True)
172
+ os.makedirs(os.path.dirname(path), exist_ok=True)
173
+ tr_lines, _ = _wikitext_lines(data_root)
174
+ from collections import Counter
175
+ cnt = Counter(w for l in tr_lines for w in re.findall(r"[a-z]{3,12}", l.lower()))
176
+ words = [w for w, _ in cnt.most_common(n_words)]
177
+ blob = {"words": words}
178
+ ct = CLIPTokenizerFast.from_pretrained("openai/clip-vit-large-patch14")
179
+ cm = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14").to(device).eval()
180
+ fin, pen = [], []
181
+ for i in range(0, len(words), batch):
182
+ enc = ct(words[i:i + batch], padding="max_length", truncation=True,
183
+ max_length=77, return_tensors="pt").to(device)
184
+ out = cm(**enc, output_hidden_states=True)
185
+ eos = enc.input_ids.argmax(-1)
186
+ idx = torch.arange(eos.numel(), device=device)
187
+ fin.append(out.last_hidden_state[idx, eos].half().cpu())
188
+ pen.append(out.hidden_states[-2][idx, eos].half().cpu())
189
+ blob["clip_final"], blob["clip_penult"] = torch.cat(fin), torch.cat(pen)
190
+ del cm
191
+ bt = BertTokenizerFast.from_pretrained("bert-base-uncased")
192
+ bm = BertModel.from_pretrained("bert-base-uncased").to(device).eval()
193
+ cls, mean = [], []
194
+ for i in range(0, len(words), batch):
195
+ enc = bt(words[i:i + batch], padding=True, truncation=True,
196
+ max_length=16, return_tensors="pt").to(device)
197
+ out = bm(**enc).last_hidden_state
198
+ m = enc.attention_mask.unsqueeze(-1)
199
+ cls.append(out[:, 0].half().cpu())
200
+ mean.append(((out * m).sum(1) / m.sum(1)).half().cpu())
201
+ blob["bert_cls"], blob["bert_mean"] = torch.cat(cls), torch.cat(mean)
202
+ torch.save(blob, path)
203
+ print(f"word cache: {len(words)} words -> {path}", flush=True)
204
+ return blob
205
+
206
+
207
+ # ============================================================ track A =============
208
+ def track_a(arms=("linear", "mlp", "aleph", "sign"), layers=("final", "penult"),
209
+ steps=1500, batch=64, seed=0, device="cuda",
210
+ data_root=DATA_ROOT, eval_every=500, save=True):
211
+ """Next-CLIP-token prediction from frozen CLIP-L hidden states."""
212
+ if not torch.cuda.is_available():
213
+ raise RuntimeError("verdict runs are GPU-only")
214
+ blob = cache_clip(data_root, device=device)
215
+ V = blob["vocab"]
216
+ results = {}
217
+ ck_dir = os.path.join(data_root, "exp013", "ckpts")
218
+ os.makedirs(ck_dir, exist_ok=True)
219
+ for layer in layers:
220
+ Htr = blob["train"][layer].float()
221
+ ids_tr = blob["train"]["ids"]
222
+ Hva = blob["val"][layer].float()
223
+ ids_va = blob["val"]["ids"]
224
+ for arm in arms:
225
+ torch.manual_seed(seed)
226
+ g = torch.Generator().manual_seed(seed)
227
+ head = HeadArm(arm, Htr.shape[-1]).to(device)
228
+ out_proj = nn.Linear(HeadArm.F_OUT, V).to(device)
229
+ params = list(head.parameters()) + list(out_proj.parameters())
230
+ opt = torch.optim.Adam(params, lr=3e-4, weight_decay=0.0)
231
+ n_par = sum(p.numel() for p in params)
232
+ for step in range(1, steps + 1):
233
+ ix = torch.randint(0, Htr.shape[0], (batch,), generator=g)
234
+ h = Htr[ix].to(device)
235
+ y = ids_tr[ix].to(device)
236
+ logits = out_proj(head(h[:, :-1]))
237
+ loss = F.cross_entropy(logits.reshape(-1, V), y[:, 1:].reshape(-1))
238
+ opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
239
+ if step % eval_every == 0 or step == steps:
240
+ with torch.no_grad():
241
+ ls = []
242
+ for j in range(0, min(1024, Hva.shape[0]), batch):
243
+ h = Hva[j:j + batch].to(device)
244
+ y = ids_va[j:j + batch].to(device)
245
+ lg = out_proj(head(h[:, :-1]))
246
+ ls.append(F.cross_entropy(
247
+ lg.reshape(-1, V), y[:, 1:].reshape(-1)).item())
248
+ ce = sum(ls) / len(ls)
249
+ vit = head.vitals(Hva[:2, :8].to(device))
250
+ print(f"[A {layer} {arm} s{seed}] step {step} val_ce={ce:.4f} "
251
+ f"params={n_par:,} vitals={vit}", flush=True)
252
+ results[f"{layer}/{arm}/s{seed}"] = {"val_ce": ce, "params": n_par,
253
+ "vitals": vit}
254
+ if save and arm in ("aleph", "sign"):
255
+ torch.save({"track": "A", "layer": layer, "arm": arm, "seed": seed,
256
+ "val_ce": ce, "state_dict": {k: v.cpu() for k, v in
257
+ head.state_dict().items()}},
258
+ os.path.join(ck_dir, f"A_{layer}_{arm}_s{seed}.pt"))
259
+ print(results, flush=True)
260
+ return results
261
+
262
+
263
+ # ============================================================ track B =============
264
+ class CharDecoder(nn.Module):
265
+ """Tiny GRU char decoder conditioned ONLY on the arm's 256-d read."""
266
+ CHARS = "abcdefghijklmnopqrstuvwxyz"
267
+ def __init__(self, cond_dim=256, hidden=256):
268
+ super().__init__()
269
+ self.V = len(self.CHARS) + 2 # +BOS +EOS
270
+ self.emb = nn.Embedding(self.V, 64)
271
+ self.init = nn.Linear(cond_dim, hidden)
272
+ self.gru = nn.GRU(64, hidden, batch_first=True)
273
+ self.out = nn.Linear(hidden, self.V)
274
+
275
+ def encode_word(self, w):
276
+ return [1] + [2 + self.CHARS.index(c) for c in w] + [0] # BOS..EOS(0)
277
+
278
+ def forward(self, cond, tgt): # tgt: (B, L) int, teacher-forced
279
+ h0 = torch.tanh(self.init(cond)).unsqueeze(0)
280
+ x = self.emb(tgt[:, :-1])
281
+ y, _ = self.gru(x, h0)
282
+ return self.out(y) # predict tgt[:,1:]
283
+
284
+ @torch.no_grad()
285
+ def greedy(self, cond, max_len=14):
286
+ B = cond.shape[0]
287
+ h = torch.tanh(self.init(cond)).unsqueeze(0)
288
+ t = torch.ones(B, 1, dtype=torch.long, device=cond.device)
289
+ done = torch.zeros(B, dtype=torch.bool, device=cond.device)
290
+ outs = []
291
+ for _ in range(max_len):
292
+ y, h = self.gru(self.emb(t), h)
293
+ t = self.out(y).argmax(-1)
294
+ outs.append(t)
295
+ done |= (t.squeeze(1) == 0)
296
+ if done.all():
297
+ break
298
+ return torch.cat(outs, 1)
299
+
300
+
301
+ def track_b1(arms=("linear", "mlp"), substrates=("clip_final", "clip_penult",
302
+ "bert_cls", "bert_mean"), steps=3000, batch=128, seed=0,
303
+ device="cuda", data_root=DATA_ROOT, save=True):
304
+ """Spelling-AR from pooled embeddings. Run baselines first (the GATE:
305
+ qualify the task only if linear/mlp exact-match < 0.50), then aleph/sign."""
306
+ if not torch.cuda.is_available():
307
+ raise RuntimeError("verdict runs are GPU-only")
308
+ blob = cache_word_embeddings(data_root, device=device)
309
+ words = blob["words"]
310
+ dec_tpl = CharDecoder()
311
+ enc = [dec_tpl.encode_word(w) for w in words]
312
+ L = max(len(e) for e in enc)
313
+ tgt = torch.zeros(len(enc), L, dtype=torch.long)
314
+ for i, e in enumerate(enc):
315
+ tgt[i, :len(e)] = torch.tensor(e)
316
+ g0 = torch.Generator().manual_seed(1234) # fixed split across arms
317
+ perm = torch.randperm(len(words), generator=g0)
318
+ tr_ix, va_ix = perm[:9000], perm[9000:]
319
+ ck_dir = os.path.join(data_root, "exp013", "ckpts")
320
+ os.makedirs(ck_dir, exist_ok=True)
321
+ results = {}
322
+ for sub in substrates:
323
+ E = blob[sub].float()
324
+ for arm in arms:
325
+ torch.manual_seed(seed)
326
+ g = torch.Generator().manual_seed(seed)
327
+ head = HeadArm(arm, E.shape[-1]).to(device)
328
+ dec = CharDecoder().to(device)
329
+ params = list(head.parameters()) + list(dec.parameters())
330
+ opt = torch.optim.Adam(params, lr=1e-3, weight_decay=0.0)
331
+ for step in range(1, steps + 1):
332
+ ix = tr_ix[torch.randint(0, tr_ix.numel(), (batch,), generator=g)]
333
+ cond = head(E[ix].to(device))
334
+ t = tgt[ix].to(device)
335
+ lg = dec(cond, t)
336
+ mask = (t[:, 1:] != 0) | (torch.cumsum(t[:, 1:] == 0, 1) == 1)
337
+ loss = F.cross_entropy(lg[mask], t[:, 1:][mask])
338
+ opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
339
+ with torch.no_grad():
340
+ cond = head(E[va_ix].to(device))
341
+ pred = dec.greedy(cond)
342
+ t = tgt[va_ix, 1:].to(device)
343
+ n = min(pred.shape[1], t.shape[1])
344
+ pad_ok = torch.ones_like(t[:, :n], dtype=torch.bool)
345
+ seen_eos = torch.cumsum(t[:, :n] == 0, 1) > 0
346
+ match = ((pred[:, :n] == t[:, :n]) | seen_eos).all(-1)
347
+ exact = match.float().mean().item()
348
+ vit = head.vitals(E[va_ix[:16]].to(device))
349
+ print(f"[B1 {sub} {arm} s{seed}] exact={exact:.4f} vitals={vit}", flush=True)
350
+ results[f"{sub}/{arm}/s{seed}"] = {"exact": exact, "vitals": vit}
351
+ if save and arm in ("aleph", "sign"):
352
+ torch.save({"track": "B1", "sub": sub, "arm": arm, "seed": seed,
353
+ "exact": exact, "state_dict": {k: v.cpu() for k, v in
354
+ head.state_dict().items()}},
355
+ os.path.join(ck_dir, f"B1_{sub}_{arm}_s{seed}.pt"))
356
+ print(results, flush=True)
357
+ return results
358
+
359
+
360
+ # ============================================================ track C =============
361
+ class MLPAdapter(nn.Module):
362
+ """Param-matched plain adapter (the ablation twin of MslRelay).
363
+ hidden=64 matches MslRelay at d=768 (2*768*64=98.3K vs 98.6K incl codebook).
364
+ Output layer ZERO-INIT (standard adapter stabilization — the first version
365
+ diverged at lr 1e-3 with random init; the aleph relay needed no such aid,
366
+ which is itself a datapoint, but the control gets its best shot)."""
367
+ def __init__(self, d, hidden=64):
368
+ super().__init__()
369
+ out = nn.Linear(hidden, d)
370
+ nn.init.zeros_(out.weight)
371
+ nn.init.zeros_(out.bias)
372
+ self.net = nn.Sequential(nn.Linear(d, hidden), SquaredReLU(), out)
373
+ self.gate = nn.Parameter(torch.tensor(-3.0))
374
+
375
+ def forward(self, x):
376
+ return x + self.gate.sigmoid() * self.net(x)
377
+
378
+
379
+ class _BlockWithAdapter(nn.Module):
380
+ def __init__(self, block, adapter):
381
+ super().__init__()
382
+ self.block, self.adapter = block, adapter
383
+
384
+ def forward(self, *a, **k):
385
+ out = self.block(*a, **k)
386
+ if isinstance(out, tuple):
387
+ return (self.adapter(out[0]),) + out[1:]
388
+ return self.adapter(out)
389
+
390
+
391
+ def track_c(arms=("frozen", "aleph", "mlp"), steps=1500, batch=8, block=256,
392
+ seed=0, device="cuda", data_root=DATA_ROOT,
393
+ eval_every=500, save=True):
394
+ """GPT-2 124M frozen; adapters after every block; train adapters only."""
395
+ from transformers import GPT2LMHeadModel, GPT2TokenizerFast
396
+ if not torch.cuda.is_available():
397
+ raise RuntimeError("verdict runs are GPU-only")
398
+ tok = GPT2TokenizerFast.from_pretrained("gpt2")
399
+ tr_lines, va_lines = _wikitext_lines(data_root)
400
+ def to_stream(lines):
401
+ ids = tok("\n\n".join(lines), return_tensors="pt").input_ids[0]
402
+ return ids
403
+ stream_tr = to_stream(tr_lines[:8000])
404
+ stream_va = to_stream(va_lines[:1000])
405
+ ck_dir = os.path.join(data_root, "exp013", "ckpts")
406
+ os.makedirs(ck_dir, exist_ok=True)
407
+ results = {}
408
+ for arm in arms:
409
+ torch.manual_seed(seed)
410
+ g = torch.Generator().manual_seed(seed)
411
+ model = GPT2LMHeadModel.from_pretrained("gpt2").to(device)
412
+ for p in model.parameters():
413
+ p.requires_grad_(False)
414
+ adapters = []
415
+ if arm != "frozen":
416
+ d = model.config.n_embd
417
+ for i, blk in enumerate(model.transformer.h):
418
+ ad = (MslRelay(d) if arm == "aleph" else MLPAdapter(d)).to(device)
419
+ model.transformer.h[i] = _BlockWithAdapter(blk, ad)
420
+ adapters.append(ad)
421
+ params = [p for ad in adapters for p in ad.parameters()]
422
+ n_par = sum(p.numel() for p in params)
423
+ opt = torch.optim.Adam(params, lr=1e-3, weight_decay=0.0)
424
+ else:
425
+ params, n_par = [], 0
426
+ def eval_ppl():
427
+ model.eval()
428
+ with torch.no_grad():
429
+ ls = []
430
+ for j in range(0, stream_va.numel() - block - 1, block * 4):
431
+ x = stream_va[j:j + block].unsqueeze(0).to(device)
432
+ out = model(x, labels=x)
433
+ ls.append(out.loss.item())
434
+ model.train()
435
+ return math.exp(sum(ls) / len(ls))
436
+ if arm == "frozen":
437
+ ppl = eval_ppl()
438
+ print(f"[C frozen] ppl={ppl:.3f}", flush=True)
439
+ results["frozen"] = {"ppl": ppl}
440
+ continue
441
+ for step in range(1, steps + 1):
442
+ ix = torch.randint(0, stream_tr.numel() - block - 1, (batch,), generator=g)
443
+ x = torch.stack([stream_tr[i:i + block] for i in ix]).to(device)
444
+ loss = model(x, labels=x).loss
445
+ opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
446
+ if step % eval_every == 0 or step == steps:
447
+ ppl = eval_ppl()
448
+ gates = [round(ad.gate.sigmoid().item(), 4) for ad in adapters]
449
+ vit = {}
450
+ if arm == "aleph":
451
+ drifts = [round(anchor_drift(ad.addr.codebook, ad.addr.home)
452
+ ["mean"], 3) for ad in adapters]
453
+ vit = {"drift_by_depth": drifts}
454
+ print(f"[C {arm} s{seed}] step {step} ppl={ppl:.3f} "
455
+ f"params={n_par:,} gates={gates} {vit}", flush=True)
456
+ results[f"{arm}/s{seed}"] = {"ppl": ppl, "params": n_par, "gates": gates,
457
+ **vit}
458
+ if save and arm == "aleph":
459
+ torch.save({"track": "C", "arm": arm, "seed": seed, "ppl": ppl,
460
+ "state_dict": {f"relay{i}.{k}": v.cpu()
461
+ for i, ad in enumerate(adapters)
462
+ for k, v in ad.state_dict().items()}},
463
+ os.path.join(ck_dir, f"C_{arm}_s{seed}.pt"))
464
+ print(results, flush=True)
465
+ return results
466
+
467
+
468
+ # ================================================================ smoke ===========
469
+ def smoke():
470
+ """Shapes/parse only — no substrates, no training."""
471
+ for arm in ("linear", "mlp", "aleph", "sign"):
472
+ h = HeadArm(arm, 768)
473
+ y = h(torch.randn(2, 10, 768))
474
+ assert y.shape == (2, 10, 256)
475
+ y.sum().backward()
476
+ print(arm, "OK", f"{h.param_count():,}", h.vitals(torch.randn(2, 4, 768)))
477
+ dec = CharDecoder()
478
+ t = torch.tensor([dec.encode_word("hello") + [0] * 3,
479
+ dec.encode_word("worlds") + [0] * 2])
480
+ lg = dec(torch.randn(2, 256), t)
481
+ assert lg.shape[:2] == (2, t.shape[1] - 1)
482
+ print("decoder OK; greedy:", dec.greedy(torch.randn(2, 256)).shape)
483
+ ad = MLPAdapter(768)
484
+ assert ad(torch.randn(2, 4, 768)).shape == (2, 4, 768)
485
+ print("adapter OK — exp013 smoke passed (caches+tracks need GPU+transformers)")
486
+
487
+
488
+ def _in_notebook():
489
+ try:
490
+ get_ipython() # type: ignore[name-defined] # noqa: F821
491
+ return True
492
+ except NameError:
493
+ return False
494
+
495
+
496
+ if __name__ == "__main__":
497
+ if _in_notebook():
498
+ smoke()
499
+ print("Notebook: cache_clip()/cache_word_embeddings() then "
500
+ "track_b1() gate -> track_a() -> track_c().")
501
+ else:
502
+ import argparse
503
+ ap = argparse.ArgumentParser()
504
+ ap.add_argument("--track", default="smoke")
505
+ a, _ = ap.parse_known_args()
506
+ {"smoke": smoke, "a": track_a, "b1": track_b1, "c": track_c}[a.track]()
exp007_math/geolip_vitals.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """geolip_vitals.py — shared diagnostic harness for the GeoLIP aleph experiments.
2
+ ALL functions are READOUTS: no gradients, no losses. CV is a readout, never a
3
+ force. Addressing is judged by drift->0.29154 and CV->0.20, never by recon cosine
4
+ (judgment criteria per the aleph-void article: https://huggingface.co/blog/AbstractPhil/geometric-vocabulary-patchwork-aleph-void).
5
+
6
+ Vitals provided:
7
+ anchor_drift — geodesic drift of anchors from init; binding fraction @0.29154
8
+ pentachoron_cv — CM 4-volume CV over random 5-row subsets (geovocab2 import)
9
+ axis_aliveness — oriented-address usage: axes alive, hppl, collapse flag
10
+ gate_stats — gate means vs the 0.012-0.03 band
11
+ path_diversity — unique-path counting, FIXED high-bits hash (low-16 bug is the
12
+ retracted artifact — never use the low bits)
13
+ grad_norm_spread — gradient democracy monitor (orders-of-magnitude spread)
14
+ CVScreen — CV@1000-batch early band screen (<0.30 LOW / .35-.50 MID / >.80 HIGH)
15
+
16
+ Smoke on a torch-capable env: python geolip_vitals.py
17
+ """
18
+ from __future__ import annotations
19
+ import math
20
+ import torch
21
+
22
+ BINDING = 0.29154 # radians; the binding/separation constant
23
+ CV_BAND = (0.13, 0.30) # CM CV band (discovery_catalog #4)
24
+ GATE_BAND = (0.012, 0.03) # live invariant candidate (acd_campaign)
25
+ KNUTH32 = 2654435761
26
+
27
+
28
+ # ----------------------------------------------------------------------------- drift
29
+ @torch.no_grad()
30
+ def anchor_drift(current: torch.Tensor, init: torch.Tensor, tol: float = 0.05) -> dict:
31
+ """Geodesic drift (radians) of each row of `current` from its row in `init`,
32
+ both row-normalized. Returns mean/std/per-row drift and the fraction of rows
33
+ within +/-tol of BINDING (the GLFM '46%' readout)."""
34
+ a = torch.nn.functional.normalize(current.float(), dim=-1)
35
+ b = torch.nn.functional.normalize(init.float(), dim=-1)
36
+ cos = (a * b).sum(-1).clamp(-1.0, 1.0)
37
+ drift = torch.arccos(cos)
38
+ frac = ((drift - BINDING).abs() <= tol).float().mean()
39
+ return {"mean": drift.mean().item(), "std": drift.std().item(),
40
+ "per_row": drift, "binding_fraction": frac.item()}
41
+
42
+
43
+ # -------------------------------------------------------------------------------- cv
44
+ @torch.no_grad()
45
+ def _pentachoron_volumes(pts: torch.Tensor) -> torch.Tensor:
46
+ """Batched Cayley-Menger 4-simplex volumes. pts: (B, 5, D) -> (B,) volumes.
47
+ One float64 det over all samples (vol^2 = -det(CM)/9216 for n=4). Built-in
48
+ for speed (the per-sample reference path is ~260x slower in a vitals loop);
49
+ geovocab2 remains the formula's reference implementation, parity-checked
50
+ via cv_reference_check()."""
51
+ B = pts.shape[0]
52
+ d2 = torch.cdist(pts.double(), pts.double()).pow(2) # (B,5,5)
53
+ cm = torch.ones(B, 6, 6, dtype=torch.float64, device=pts.device)
54
+ cm[:, 0, 0] = 0.0
55
+ cm[:, 1:, 1:] = d2
56
+ det = torch.linalg.det(cm)
57
+ return (-det / 9216.0).clamp_min(0.0).sqrt().float()
58
+
59
+
60
+ @torch.no_grad()
61
+ def pentachoron_cv(rows: torch.Tensor, n_samples: int = 200,
62
+ generator: torch.Generator | None = None) -> float:
63
+ """CV (std/mean) of Cayley-Menger 4-simplex volumes over n_samples random
64
+ 5-row subsets. Rows are row-normalized before measurement. Uses the built-in
65
+ batched CM (float64 det); validate against geovocab2 with
66
+ cv_reference_check() after any change to the volume math."""
67
+ x = torch.nn.functional.normalize(rows.float(), dim=-1)
68
+ n = x.shape[0]
69
+ if n < 5:
70
+ raise ValueError(f"pentachoron_cv needs >=5 rows, got {n}")
71
+ g = generator or torch.Generator(device="cpu").manual_seed(0)
72
+ idx = torch.stack([torch.randperm(n, generator=g)[:5]
73
+ for _ in range(n_samples)]) # (B,5)
74
+ v = _pentachoron_volumes(x[idx].cpu())
75
+ return (v.std() / v.mean().clamp_min(1e-12)).item()
76
+
77
+
78
+ @torch.no_grad()
79
+ def cv_reference_check(n_trials: int = 50, tol: float = 1e-5) -> float:
80
+ """Parity check of the built-in batched CM against geovocab2's reference
81
+ implementation (the formula's source of truth). Returns max |rel diff|;
82
+ raises if geovocab2 is absent or parity fails. Run after touching
83
+ _pentachoron_volumes."""
84
+ try:
85
+ from geovocab2.shapes.formula.symbolic.cayley_menger import (
86
+ CayleyMengerFromSimplex)
87
+ except Exception as e: # pragma: no cover
88
+ raise ImportError(
89
+ "cv_reference_check requires geovocab2 (install via the geolip-svae "
90
+ "umbrella: pip install git+https://github.com/AbstractEyes/"
91
+ "geolip-svae).") from e
92
+ ref = CayleyMengerFromSimplex()
93
+ g = torch.Generator().manual_seed(0)
94
+ pts = torch.nn.functional.normalize(
95
+ torch.randn(n_trials, 5, 4, generator=g), dim=-1)
96
+ mine = _pentachoron_volumes(pts)
97
+ # compare at float64: the reference computes in the INPUT dtype, and fp32
98
+ # dets lose up to ~4% on near-degenerate pentachora (measured 2026-07-11)
99
+ theirs = torch.stack([ref.forward(p.double())["volume"].float() for p in pts])
100
+ rel = ((mine - theirs).abs() / theirs.abs().clamp_min(1e-12)).max().item()
101
+ if rel > tol:
102
+ raise AssertionError(f"CM parity vs geovocab2 failed: max rel {rel}")
103
+ return rel
104
+
105
+
106
+ # ------------------------------------------------------------------------- aliveness
107
+ @torch.no_grad()
108
+ def axis_aliveness(oriented_weights: torch.Tensor, alive_thresh: float = 1e-3) -> dict:
109
+ """`oriented_weights`: (..., 2K) nonnegative oriented-softmax address rows
110
+ (sum to 1 on the last dim). Returns axes-alive count, mean-usage perplexity
111
+ (hppl analogue; healthy hosted reference 125-126/128), and a collapse flag.
112
+ Reference behavior: near-uniform aliveness at div_weight=0 (discovery #22)."""
113
+ w = oriented_weights.reshape(-1, oriented_weights.shape[-1]).float()
114
+ usage = w.mean(0)
115
+ usage = usage / usage.sum().clamp_min(1e-12)
116
+ # an axis is alive if its mean usage exceeds alive_thresh x the uniform share
117
+ alive = int((usage > alive_thresh * (1.0 / usage.numel())).sum())
118
+ ent = -(usage.clamp_min(1e-12) * usage.clamp_min(1e-12).log()).sum()
119
+ ppl = float(ent.exp())
120
+ return {"axes_total": usage.numel(), "axes_alive": alive, "usage_ppl": ppl,
121
+ "collapsed": ppl < 0.05 * usage.numel()}
122
+
123
+
124
+ # ------------------------------------------------------------------------------ gates
125
+ @torch.no_grad()
126
+ def gate_stats(gates: torch.Tensor) -> dict:
127
+ """Gate values (post-sigmoid/clamp). Reports mean and whether it sits in the
128
+ 0.012-0.03 band (read-only — the band is a candidate invariant, never a target)."""
129
+ g = gates.float().flatten()
130
+ m = g.mean().item()
131
+ return {"mean": m, "std": g.std().item(),
132
+ "in_band": GATE_BAND[0] <= m <= GATE_BAND[1]}
133
+
134
+
135
+ # ------------------------------------------------------------------------------ paths
136
+ @torch.no_grad()
137
+ def path_diversity(ids: torch.Tensor) -> dict:
138
+ """Unique-path counting with the FIXED multiplicative hash:
139
+ ((ids * 2654435761) % 2^32) >> 16 — Knuth needs the HIGH bits; the low-16
140
+ variant produced a retracted ~1,500 path ceiling in a prior campaign.
141
+ `ids`: integer tensor, one composed path id per row (any shape)."""
142
+ x = ids.reshape(-1).to(torch.int64)
143
+ hashed = ((x * KNUTH32) % (1 << 32)) >> 16
144
+ return {"n": int(x.numel()),
145
+ "unique_raw": int(torch.unique(x).numel()),
146
+ "unique_hashed": int(torch.unique(hashed).numel())}
147
+
148
+
149
+ @torch.no_grad()
150
+ def compose_path_ids(stage_indices: list[torch.Tensor], radix: int) -> torch.Tensor:
151
+ """Compose per-stage discrete indices (each (...,) int in [0, radix)) into a
152
+ single path id, positional base-`radix` — construction, not hashing."""
153
+ out = torch.zeros_like(stage_indices[0], dtype=torch.int64)
154
+ for s in stage_indices:
155
+ out = out * radix + s.to(torch.int64)
156
+ return out
157
+
158
+
159
+ # --------------------------------------------------------------------- grad democracy
160
+ @torch.no_grad()
161
+ def grad_norm_spread(groups: dict[str, list[torch.nn.Parameter]]) -> dict:
162
+ """Gradient-democracy monitor. `groups`: name -> params of one parallel member
163
+ (tower/expert). Reports per-group grad norms and the orders-of-magnitude spread.
164
+ Reference: unequalized heterogeneous towers spread ~20 orders (fibonacci dead at
165
+ 2.25e-21 under helix); equalized ~0.0 (geofractal gradient-democracy result)."""
166
+ norms = {}
167
+ for name, params in groups.items():
168
+ gs = [p.grad for p in params if p.grad is not None]
169
+ norms[name] = float(torch.sqrt(sum((g.float() ** 2).sum() for g in gs)).item()) \
170
+ if gs else 0.0
171
+ vals = [v for v in norms.values() if v > 0]
172
+ spread = (math.log10(max(vals)) - math.log10(min(vals))) if len(vals) >= 2 else 0.0
173
+ return {"norms": norms, "spread_orders": spread, "dead": [k for k, v in norms.items() if v == 0.0]}
174
+
175
+
176
+ # ----------------------------------------------------------------------------- screen
177
+ class CVScreen:
178
+ """CV@N early band screen (tri-band ft1): record pentachoron CV at `step_mark`
179
+ batches; classify <0.30 LOW / 0.35-0.50 MID / >0.80 HIGH. Turns ~2h/config
180
+ into ~7min. Readout only."""
181
+ def __init__(self, step_mark: int = 1000):
182
+ self.step_mark = step_mark
183
+ self.recorded: float | None = None
184
+
185
+ def maybe_record(self, step: int, rows: torch.Tensor) -> float | None:
186
+ if self.recorded is None and step >= self.step_mark:
187
+ self.recorded = pentachoron_cv(rows)
188
+ return self.recorded
189
+
190
+ @property
191
+ def band(self) -> str | None:
192
+ c = self.recorded
193
+ if c is None:
194
+ return None
195
+ if c < 0.30:
196
+ return "LOW"
197
+ if 0.35 <= c <= 0.50:
198
+ return "MID"
199
+ if c > 0.80:
200
+ return "HIGH"
201
+ return "BETWEEN"
202
+
203
+
204
+ # ------------------------------------------------------------------------------ smoke
205
+ if __name__ == "__main__": # shapes/parse smoke ONLY — no training, ever.
206
+ g = torch.Generator().manual_seed(0)
207
+ K, D = 64, 4
208
+ init = torch.nn.functional.normalize(torch.randn(K, D, generator=g), dim=-1)
209
+ cur = torch.nn.functional.normalize(init + 0.29 * torch.randn(K, D, generator=g), dim=-1)
210
+ print("drift:", {k: v for k, v in anchor_drift(cur, init).items() if k != "per_row"})
211
+ w = torch.softmax(torch.randn(32, 2 * K, generator=g), dim=-1)
212
+ print("aliveness:", axis_aliveness(w))
213
+ print("gates:", gate_stats(torch.full((8,), 0.024)))
214
+ ids = compose_path_ids([torch.randint(0, 16, (4096,), generator=g) for _ in range(4)], 16)
215
+ print("paths:", path_diversity(ids))
216
+ lin = torch.nn.Linear(8, 8)
217
+ lin(torch.randn(4, 8)).sum().backward()
218
+ print("democracy:", grad_norm_spread({"a": list(lin.parameters())}))
219
+ print("OK — vitals smoke passed (pentachoron_cv needs geovocab2; run on GPU env)")
exp007_math/qwen_exp001_relay.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """qwen_exp001_relay.py — geolip-aleph-qwen EXPERIMENT 1: the relay retrofit
2
+ at 0.5B. The certified exp013 Track-C recipe (frozen trunk + near-zero-gated
3
+ multi-slot M_hat relays after every block, vs the param-matched zero-init MLP
4
+ adapter ablation) at its next scale rung: Qwen2.5-0.5B (24 blocks, d=896,
5
+ ~494M frozen params; relay stack ~2.8M trainable, <0.6%). First campaign of
6
+ the generation-stake line — judged on ppl, the GATE MECHANISM (do the gates
7
+ grow? exp013: aleph gates grew ~3x while MLP gates shrank), relay codebook
8
+ vitals, and GENERATED SAMPLES (the deliverable is a model that generates).
9
+
10
+ Arms (x2 seeds): frozen (eval-only baseline) | relay (MslRelay(896) after
11
+ every block) | mlp (param-matched MLPAdapter(896, hidden=64), zero-init out —
12
+ the exp013 ablation, 114,688 vs 114,944 params per adapter, 0.2%).
13
+ Corpus: wikitext-103-raw-v1 (HF parquet), ~12M-token cache; block 512,
14
+ batch 4, 3000 steps adapters-only, pure Adam lr 1e-3 wd 0 (the exp013
15
+ relay-training regime).
16
+ Riders: trunk FROZEN throughout; pure Adam wd=0; GPU-only verdicts; >=2
17
+ seeds; drift-check before any freeze claim; Colab-safe. Paste order:
18
+ geolip_vitals -> ar_differentiation_bed -> exp013_augmentation_bed -> this.
19
+ """
20
+ from __future__ import annotations
21
+ import json
22
+ import math
23
+ import os
24
+ import torch
25
+ import torch.nn as nn
26
+ import torch.nn.functional as F
27
+
28
+ if "MslRelay" not in globals():
29
+ try:
30
+ from ar_differentiation_bed import MslRelay
31
+ from exp013_augmentation_bed import MLPAdapter
32
+ from geolip_vitals import anchor_drift, axis_aliveness
33
+ except ImportError:
34
+ _here = globals().get("__file__")
35
+ if _here is None:
36
+ raise ImportError("paste geolip_vitals + ar_differentiation_bed + "
37
+ "exp013_augmentation_bed first")
38
+ import sys, pathlib
39
+ sys.path.insert(0, str(pathlib.Path(_here).parent))
40
+ from ar_differentiation_bed import MslRelay
41
+ from exp013_augmentation_bed import MLPAdapter
42
+ from geolip_vitals import anchor_drift, axis_aliveness
43
+
44
+ DATA_ROOT = os.environ.get("GEOLIP_DATA", "./data")
45
+ EXP_DIR = os.path.join(DATA_ROOT, "qwen_exp001")
46
+ BASE_MODEL = "Qwen/Qwen2.5-0.5B"
47
+ BLOCK, BATCH, STEPS, LR = 512, 4, 3000, 1e-3
48
+ MAX_TOKENS = 12_000_000
49
+ PROMPTS = ("The history of mathematics begins",
50
+ "In a small village by the sea,",
51
+ "The most important principle of engineering is")
52
+
53
+
54
+ class _QwenBlockWithAdapter(nn.Module):
55
+ """Wrap a Qwen2 decoder layer: adapter applied to the hidden-states output,
56
+ all other outputs and kwargs passed through untouched."""
57
+
58
+ def __init__(self, block, adapter):
59
+ super().__init__()
60
+ self.block, self.adapter = block, adapter
61
+
62
+ def forward(self, *args, **kwargs):
63
+ out = self.block(*args, **kwargs)
64
+ if isinstance(out, tuple):
65
+ return (self.adapter(out[0]),) + out[1:]
66
+ return self.adapter(out)
67
+
68
+
69
+ def _token_cache(device="cpu"):
70
+ """~12M-token wikitext-103 train stream + val stream, cached to disk."""
71
+ os.makedirs(EXP_DIR, exist_ok=True)
72
+ path = os.path.join(EXP_DIR, "tok_cache.pt")
73
+ if os.path.exists(path):
74
+ blob = torch.load(path, map_location="cpu", weights_only=True)
75
+ return blob["train"], blob["val"]
76
+ from huggingface_hub import hf_hub_download
77
+ import pyarrow.parquet as pq
78
+ from transformers import AutoTokenizer
79
+ tok = AutoTokenizer.from_pretrained(BASE_MODEL)
80
+
81
+ def stream(fname, cap):
82
+ p = hf_hub_download("Salesforce/wikitext", fname, repo_type="dataset")
83
+ lines = [t for t in pq.read_table(p).column("text").to_pylist()
84
+ if t and len(t.strip()) > 40]
85
+ ids = []
86
+ total = 0
87
+ chunk = []
88
+ csz = 0
89
+ for ln in lines:
90
+ chunk.append(ln)
91
+ csz += len(ln)
92
+ if csz > 500_000:
93
+ e = tok("".join(chunk), return_tensors="pt").input_ids[0]
94
+ ids.append(e)
95
+ total += e.numel()
96
+ chunk, csz = [], 0
97
+ if total >= cap:
98
+ break
99
+ if chunk and total < cap:
100
+ e = tok("".join(chunk), return_tensors="pt").input_ids[0]
101
+ ids.append(e)
102
+ return torch.cat(ids)[:cap]
103
+
104
+ tr = stream("wikitext-103-raw-v1/train-00000-of-00002.parquet", MAX_TOKENS)
105
+ va = stream("wikitext-103-raw-v1/validation-00000-of-00001.parquet",
106
+ 600_000)
107
+ torch.save({"train": tr, "val": va}, path)
108
+ print(f"token cache: train {tr.numel():,} val {va.numel():,}", flush=True)
109
+ return tr, va
110
+
111
+
112
+ def _batch(stream, batch, block, device, g):
113
+ ix = torch.randint(0, stream.numel() - block - 1, (batch,), generator=g)
114
+ x = torch.stack([stream[i:i + block] for i in ix]).to(device)
115
+ y = torch.stack([stream[i + 1:i + block + 1] for i in ix]).to(device)
116
+ return x.long(), y.long()
117
+
118
+
119
+ def load_qwen(arm: str, seed: int = 0, device="cuda"):
120
+ from transformers import AutoModelForCausalLM
121
+ torch.manual_seed(seed)
122
+ model = AutoModelForCausalLM.from_pretrained(BASE_MODEL,
123
+ torch_dtype=torch.float32)
124
+ model.config.use_cache = False
125
+ for p in model.parameters():
126
+ p.requires_grad_(False)
127
+ adapters = None
128
+ if arm != "frozen":
129
+ d = model.config.hidden_size
130
+ mk = (lambda: MslRelay(d)) if arm == "relay" else (lambda: MLPAdapter(d))
131
+ adapters = nn.ModuleList([mk() for _ in model.model.layers])
132
+ model.model.layers = nn.ModuleList(
133
+ [_QwenBlockWithAdapter(b, a)
134
+ for b, a in zip(model.model.layers, adapters)])
135
+ return model.to(device), adapters
136
+
137
+
138
+ @torch.no_grad()
139
+ def eval_ppl(model, va, device="cuda", n=40, g=None):
140
+ g = g or torch.Generator().manual_seed(0)
141
+ model.eval()
142
+ ls = []
143
+ for _ in range(n):
144
+ x, y = _batch(va, BATCH, BLOCK, device, g)
145
+ logits = model(input_ids=x).logits
146
+ ls.append(F.cross_entropy(logits.reshape(-1, logits.shape[-1]),
147
+ y.reshape(-1)).item())
148
+ return math.exp(sum(ls) / len(ls))
149
+
150
+
151
+ @torch.no_grad()
152
+ def sample(model, device="cuda", max_new=80):
153
+ from transformers import AutoTokenizer
154
+ tok = AutoTokenizer.from_pretrained(BASE_MODEL)
155
+ model.eval()
156
+ model.config.use_cache = True
157
+ outs = {}
158
+ for p in PROMPTS:
159
+ ids = tok(p, return_tensors="pt").input_ids.to(device)
160
+ out = model.generate(ids, max_new_tokens=max_new, do_sample=False,
161
+ pad_token_id=tok.eos_token_id)
162
+ outs[p] = tok.decode(out[0][ids.shape[1]:], skip_special_tokens=True)
163
+ model.config.use_cache = False
164
+ return outs
165
+
166
+
167
+ def adapter_vitals(adapters):
168
+ if adapters is None:
169
+ return None
170
+ gates = [round(torch.sigmoid(a.gate).item(), 4) for a in adapters
171
+ if hasattr(a, "gate")]
172
+ drifts = []
173
+ for a in adapters:
174
+ ad = getattr(a, "addr", None)
175
+ if ad is not None:
176
+ drifts.append(round(anchor_drift(ad.codebook, ad.home)["mean"], 3))
177
+ return {"gates": gates[:6] + ["..."] if len(gates) > 6 else gates,
178
+ "gate_mean": round(sum(gates) / max(len(gates), 1), 4),
179
+ "drift": drifts[:6] + ["..."] if len(drifts) > 6 else drifts}
180
+
181
+
182
+ def run_arm(arm: str, seed: int = 0, steps=STEPS, device="cuda"):
183
+ if not torch.cuda.is_available():
184
+ raise RuntimeError("verdict runs are GPU-only")
185
+ os.makedirs(EXP_DIR, exist_ok=True)
186
+ tr, va = _token_cache()
187
+ model, adapters = load_qwen(arm, seed=seed, device=device)
188
+ ledger = open(os.path.join(EXP_DIR, "ledger.jsonl"), "a", encoding="utf-8")
189
+ if arm == "frozen":
190
+ ppl = eval_ppl(model, va, device=device)
191
+ rec = {"exp": "q1", "arm": arm, "seed": seed, "ppl": round(ppl, 3),
192
+ "trainable": 0, "samples": sample(model, device=device)}
193
+ ledger.write(json.dumps(rec) + "\n"); ledger.flush()
194
+ print(f"[q1 frozen s{seed}] FINAL ppl={ppl:.3f}", flush=True)
195
+ ledger.close()
196
+ return ppl
197
+ g = torch.Generator().manual_seed(seed)
198
+ params = [p for p in adapters.parameters()]
199
+ n_train = sum(p.numel() for p in params)
200
+ opt = torch.optim.Adam(params, lr=LR, weight_decay=0.0)
201
+ model.train()
202
+ for step in range(1, steps + 1):
203
+ x, y = _batch(tr, BATCH, BLOCK, device, g)
204
+ logits = model(input_ids=x).logits
205
+ loss = F.cross_entropy(logits.reshape(-1, logits.shape[-1]),
206
+ y.reshape(-1))
207
+ opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
208
+ if step % 500 == 0:
209
+ v = adapter_vitals(adapters)
210
+ print(f"[q1 {arm} s{seed} step {step}] loss={loss.item():.3f} "
211
+ f"gate_mean={v['gate_mean']}", flush=True)
212
+ ppl = eval_ppl(model, va, device=device)
213
+ rec = {"exp": "q1", "arm": arm, "seed": seed, "ppl": round(ppl, 3),
214
+ "steps": steps, "trainable": n_train,
215
+ "vitals": adapter_vitals(adapters),
216
+ "samples": sample(model, device=device)}
217
+ ledger.write(json.dumps(rec) + "\n"); ledger.flush()
218
+ print(f"[q1 {arm} s{seed}] FINAL ppl={ppl:.3f} "
219
+ f"gate_mean={rec['vitals']['gate_mean']} trainable={n_train:,}",
220
+ flush=True)
221
+ torch.save({"arm": arm, "seed": seed,
222
+ "adapters": {k: v.cpu() for k, v in
223
+ adapters.state_dict().items()}},
224
+ os.path.join(EXP_DIR, f"q1_{arm}_s{seed}.pt"))
225
+ ledger.close()
226
+ del model, adapters
227
+ torch.cuda.empty_cache()
228
+ return ppl
229
+
230
+
231
+ def run_exp001(seeds=(0, 1), device="cuda"):
232
+ run_arm("frozen", seed=0, device=device) # baseline once (no training)
233
+ for seed in seeds:
234
+ for arm in ("relay", "mlp"):
235
+ run_arm(arm, seed=seed, device=device)
236
+ print("=== qwen exp001 COMPLETE ===", flush=True)
237
+
238
+
239
+ def smoke():
240
+ """Shapes/parse only — no substrate download, no training."""
241
+ r = MslRelay(896)
242
+ m = MLPAdapter(896)
243
+ x = torch.randn(2, 8, 896)
244
+ assert r(x).shape == x.shape and m(x).shape == x.shape
245
+ rp = sum(p.numel() for p in r.parameters())
246
+ mp = sum(p.numel() for p in m.parameters())
247
+ assert abs(rp - mp) / rp < 0.01, (rp, mp) # param-matched adapters
248
+ blk = nn.Linear(896, 896) # tuple-passthrough check
249
+ class TupBlock(nn.Module):
250
+ def forward(self, h, **kw):
251
+ return (blk(h), "aux")
252
+ w = _QwenBlockWithAdapter(TupBlock(), r)
253
+ out = w(x, position_ids=None)
254
+ assert isinstance(out, tuple) and out[0].shape == x.shape and out[1] == "aux"
255
+ (out[0].sum()).backward()
256
+ assert r.addr.codebook.grad is not None
257
+ print(f"qwen exp001 smoke passed (relay {rp:,} ~ mlp {mp:,} params/adapter;"
258
+ " full run needs GPU + transformers + the 0.5B download)")
259
+
260
+
261
+ def _in_notebook():
262
+ try:
263
+ get_ipython() # type: ignore[name-defined] # noqa: F821
264
+ return True
265
+ except NameError:
266
+ return False
267
+
268
+
269
+ if __name__ == "__main__":
270
+ smoke() if not _in_notebook() else (smoke(),
271
+ print("Notebook: run_exp001() on GPU."))
exp007_math/qwen_exp002_refine.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """qwen_exp002_refine.py — geolip-aleph-qwen EXPERIMENT 2: refine the relay.
2
+ exp001 verdict shape: the relay retrofit works at 0.5B (frozen 17.80 ->
3
+ ~14.09, gates grow 0.047 -> 0.081 = trunk opt-in) but the param-matched MLP
4
+ adapter is at parity-or-ahead on ppl — the certified GPT-2 ordering did not
5
+ transfer as-is. exp002 races PROTOTYPICAL ENHANCEMENTS of the relay, each
6
+ grounded in a certified law, at an equalized WIDER budget (~230K/adapter),
7
+ against the honest wide MLP control.
8
+
9
+ Arms (frozen Qwen2.5-0.5B trunk, adapters after every block unless noted):
10
+ relay32 — MslRelay with n_slots=32 (slot dose-response: exp012 L-AR3,
11
+ task monotone in slot width)
12
+ relay_3tau — multi-slot read at 3 temperatures (0.05/0.1/0.3), concatenated
13
+ (the certified rule-of-3 stroboscope, exp012 v2: -1.08 bpb)
14
+ relay_pw — slots -> M_hat -> PATCHWORK consumer Linear(64,178) ->
15
+ SquaredReLU -> LN -> Linear(178,896) (the constellation
16
+ consumption spec replaces the bare linear out)
17
+ relay_deep — exp001's TOTAL budget concentrated on the last 12 blocks
18
+ (n_slots=32 there, nothing on blocks 0-11): the depth-gradient
19
+ law (cultivation concentrates near the prediction gradient)
20
+ as an architecture decision; total params == exp001 relay
21
+ mlp_wide — MLPAdapter hidden=128 (the wide-budget capacity control)
22
+ All near-zero gated (init -3.0). Training identical to exp001: block 512,
23
+ batch 4, 3000 steps, pure Adam lr 1e-3 wd 0, adapters only. Judged: val ppl
24
+ vs exp001's relay (14.093/14.091) and mlp (13.927/...) + gate mechanism +
25
+ vitals + generated samples. Seed 0 sweep first; seed 1 for the top arms.
26
+ Riders: trunk frozen; >=2 seeds before any claim; GPU-only; Colab-safe.
27
+ Paste order: geolip_vitals -> ar_differentiation_bed ->
28
+ exp013_augmentation_bed -> qwen_exp001_relay -> this file.
29
+ """
30
+ from __future__ import annotations
31
+ import json
32
+ import math
33
+ import os
34
+ import torch
35
+ import torch.nn as nn
36
+ import torch.nn.functional as F
37
+
38
+ if "run_arm" not in globals():
39
+ try:
40
+ from ar_differentiation_bed import AlephAddress, MslRelay
41
+ from exp013_augmentation_bed import MLPAdapter
42
+ from geolip_vitals import anchor_drift
43
+ from qwen_exp001_relay import (_QwenBlockWithAdapter, _token_cache,
44
+ _batch, eval_ppl, sample, EXP_DIR as
45
+ Q1_DIR, BASE_MODEL, BLOCK, BATCH, LR)
46
+ except ImportError:
47
+ _here = globals().get("__file__")
48
+ if _here is None:
49
+ raise ImportError("paste the qwen stack first")
50
+ import sys, pathlib
51
+ sys.path.insert(0, str(pathlib.Path(_here).parent))
52
+ from ar_differentiation_bed import AlephAddress, MslRelay
53
+ from exp013_augmentation_bed import MLPAdapter
54
+ from geolip_vitals import anchor_drift
55
+ from qwen_exp001_relay import (_QwenBlockWithAdapter, _token_cache,
56
+ _batch, eval_ppl, sample, EXP_DIR as
57
+ Q1_DIR, BASE_MODEL, BLOCK, BATCH, LR)
58
+
59
+ EXP2_DIR = os.path.join(os.path.dirname(Q1_DIR), "qwen_exp002")
60
+ STEPS = 3000
61
+
62
+
63
+ class SquaredReLU(nn.Module):
64
+ def forward(self, x):
65
+ return F.relu(x) ** 2
66
+
67
+
68
+ class Relay3Tau(nn.Module):
69
+ """Multi-slot M_hat at 3 temperatures, concatenated (rule-of-3 strobe)."""
70
+ TAUS = (0.05, 0.1, 0.3)
71
+
72
+ def __init__(self, d: int, n_slots: int = 16, K: int = 64):
73
+ super().__init__()
74
+ self.n_slots = n_slots
75
+ self.proj = nn.Linear(d, n_slots * 4, bias=False)
76
+ self.out = nn.Linear(n_slots * 4 * len(self.TAUS), d, bias=False)
77
+ nn.init.orthogonal_(self.proj.weight)
78
+ nn.init.orthogonal_(self.out.weight)
79
+ self.addr = AlephAddress(K, 4)
80
+ self.gate = nn.Parameter(torch.tensor(-3.0))
81
+
82
+ def forward(self, x):
83
+ B, n, _ = x.shape
84
+ slots = self.proj(x).view(B, n, self.n_slots, 4)
85
+ feats = []
86
+ for t in self.TAUS:
87
+ u = self.addr._u(slots) * (self.addr.tau / t)
88
+ m = u.abs().amax(dim=-1, keepdim=True)
89
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
90
+ A = F.normalize(self.addr.codebook, dim=-1)
91
+ feats.append((((ep - en) @ A)
92
+ / (ep + en).sum(dim=-1, keepdim=True)).reshape(B, n, -1))
93
+ return x + torch.sigmoid(self.gate) * self.out(torch.cat(feats, -1))
94
+
95
+
96
+ class RelayPatchwork(nn.Module):
97
+ """Multi-slot M_hat -> constellation-spec consumer (SquaredReLU patchwork)."""
98
+
99
+ def __init__(self, d: int, n_slots: int = 16, K: int = 64, hidden: int = 178):
100
+ super().__init__()
101
+ self.n_slots = n_slots
102
+ self.proj = nn.Linear(d, n_slots * 4, bias=False)
103
+ nn.init.orthogonal_(self.proj.weight)
104
+ self.addr = AlephAddress(K, 4)
105
+ self.consume = nn.Sequential(
106
+ nn.Linear(n_slots * 4, hidden), SquaredReLU(),
107
+ nn.LayerNorm(hidden), nn.Linear(hidden, d))
108
+ nn.init.zeros_(self.consume[-1].weight) # zero-init out (theme D)
109
+ self.gate = nn.Parameter(torch.tensor(-3.0))
110
+
111
+ def forward(self, x):
112
+ B, n, _ = x.shape
113
+ slots = self.proj(x).view(B, n, self.n_slots, 4)
114
+ feats = self.addr.m_hat(slots).reshape(B, n, -1)
115
+ return x + torch.sigmoid(self.gate) * self.consume(feats)
116
+
117
+
118
+ def build_adapters(arm: str, model):
119
+ d = model.config.hidden_size
120
+ L = len(model.model.layers)
121
+ if arm == "relay32":
122
+ mk = [lambda: MslRelay(d, n_slots=32) for _ in range(L)]
123
+ elif arm == "relay_3tau":
124
+ mk = [lambda: Relay3Tau(d) for _ in range(L)]
125
+ elif arm == "relay_pw":
126
+ mk = [lambda: RelayPatchwork(d) for _ in range(L)]
127
+ elif arm == "relay_deep":
128
+ mk = [None] * (L // 2) + [lambda: MslRelay(d, n_slots=32)
129
+ for _ in range(L - L // 2)]
130
+ elif arm == "mlp_wide":
131
+ mk = [lambda: MLPAdapter(d, hidden=128) for _ in range(L)]
132
+ else:
133
+ raise ValueError(arm)
134
+ adapters = nn.ModuleList([m() if m else nn.Identity() for m in mk])
135
+ model.model.layers = nn.ModuleList(
136
+ [_QwenBlockWithAdapter(b, a) if not isinstance(a, nn.Identity) else b
137
+ for b, a in zip(model.model.layers, adapters)])
138
+ return adapters
139
+
140
+
141
+ def adapter_vitals(adapters):
142
+ gates, drifts = [], []
143
+ for a in adapters:
144
+ if hasattr(a, "gate"):
145
+ gates.append(round(torch.sigmoid(a.gate).item(), 4))
146
+ ad = getattr(a, "addr", None)
147
+ if ad is not None:
148
+ drifts.append(round(anchor_drift(ad.codebook, ad.home)["mean"], 3))
149
+ return {"gate_mean": round(sum(gates) / max(len(gates), 1), 4),
150
+ "gates_head_tail": gates[:3] + gates[-3:],
151
+ "drift_head_tail": (drifts[:3] + drifts[-3:]) if drifts else None}
152
+
153
+
154
+ def run_arm2(arm: str, seed: int = 0, steps=STEPS, device="cuda"):
155
+ if not torch.cuda.is_available():
156
+ raise RuntimeError("verdict runs are GPU-only")
157
+ os.makedirs(EXP2_DIR, exist_ok=True)
158
+ from transformers import AutoModelForCausalLM
159
+ tr, va = _token_cache()
160
+ torch.manual_seed(seed)
161
+ model = AutoModelForCausalLM.from_pretrained(BASE_MODEL,
162
+ torch_dtype=torch.float32)
163
+ model.config.use_cache = False
164
+ for p in model.parameters():
165
+ p.requires_grad_(False)
166
+ adapters = build_adapters(arm, model)
167
+ model = model.to(device)
168
+ n_train = sum(p.numel() for p in adapters.parameters())
169
+ g = torch.Generator().manual_seed(seed)
170
+ opt = torch.optim.Adam(adapters.parameters(), lr=LR, weight_decay=0.0)
171
+ model.train()
172
+ for step in range(1, steps + 1):
173
+ x, y = _batch(tr, BATCH, BLOCK, device, g)
174
+ logits = model(input_ids=x).logits
175
+ loss = F.cross_entropy(logits.reshape(-1, logits.shape[-1]),
176
+ y.reshape(-1))
177
+ opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
178
+ if step % 1000 == 0:
179
+ print(f"[q2 {arm} s{seed} step {step}] loss={loss.item():.3f}",
180
+ flush=True)
181
+ ppl = eval_ppl(model, va, device=device)
182
+ v = adapter_vitals(adapters)
183
+ rec = {"exp": "q2", "arm": arm, "seed": seed, "ppl": round(ppl, 3),
184
+ "steps": steps, "trainable": n_train, "vitals": v,
185
+ "samples": sample(model, device=device)}
186
+ ledger = open(os.path.join(EXP2_DIR, "ledger.jsonl"), "a", encoding="utf-8")
187
+ ledger.write(json.dumps(rec) + "\n"); ledger.close()
188
+ print(f"[q2 {arm} s{seed}] FINAL ppl={ppl:.3f} gate_mean={v['gate_mean']} "
189
+ f"trainable={n_train:,}", flush=True)
190
+ torch.save({"arm": arm, "seed": seed,
191
+ "adapters": {k: v2.cpu() for k, v2 in
192
+ adapters.state_dict().items()}},
193
+ os.path.join(EXP2_DIR, f"q2_{arm}_s{seed}.pt"))
194
+ del model, adapters
195
+ torch.cuda.empty_cache()
196
+ return ppl
197
+
198
+
199
+ ARMS = ("relay32", "relay_3tau", "relay_pw", "relay_deep", "mlp_wide")
200
+
201
+
202
+ def run_wave_a(arms=ARMS, seed=0, device="cuda"):
203
+ for arm in arms:
204
+ run_arm2(arm, seed=seed, device=device)
205
+ print("=== qwen exp002 WAVE A COMPLETE ===", flush=True)
206
+
207
+
208
+ def smoke():
209
+ x = torch.randn(2, 8, 896)
210
+ for cls, kw in ((Relay3Tau, {}), (RelayPatchwork, {}),):
211
+ a = cls(896, **kw)
212
+ y = a(x)
213
+ assert y.shape == x.shape
214
+ y.sum().backward()
215
+ assert a.addr.codebook.grad is not None
216
+ a.zero_grad()
217
+ p32 = sum(p.numel() for p in MslRelay(896, n_slots=32).parameters())
218
+ p3t = sum(p.numel() for p in Relay3Tau(896).parameters())
219
+ ppw = sum(p.numel() for p in RelayPatchwork(896).parameters())
220
+ pmw = sum(p.numel() for p in MLPAdapter(896, hidden=128).parameters())
221
+ lo, hi = min(p32, p3t, ppw, pmw), max(p32, p3t, ppw, pmw)
222
+ assert (hi - lo) / hi < 0.12, (p32, p3t, ppw, pmw) # budget-equalized ~10%
223
+ print(f"qwen exp002 smoke passed (relay32 {p32:,} | 3tau {p3t:,} | "
224
+ f"pw {ppw:,} | mlp_wide {pmw:,} per adapter)")
225
+
226
+
227
+ def _in_notebook():
228
+ try:
229
+ get_ipython() # type: ignore[name-defined] # noqa: F821
230
+ return True
231
+ except NameError:
232
+ return False
233
+
234
+
235
+ if __name__ == "__main__":
236
+ smoke() if not _in_notebook() else (smoke(),
237
+ print("Notebook: run_wave_a() on GPU."))
exp007_math/qwen_exp003_instruct.py ADDED
@@ -0,0 +1,451 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """qwen_exp003_instruct.py — geolip-aleph-qwen EXPERIMENT 3: instruct-task
2
+ adapters + register differentiation on the discrete surface.
3
+ Hypothesis under test (Phil): continuation-trained adapters "double up"
4
+ values — aligning REGISTERS to overlapping internal states; instruct-task
5
+ training should differentiate them cleanly. The json-coco-format dataset
6
+ (AbstractPhil/json-coco-format) is the ideal instrument: the SAME 22k COCO
7
+ captions pass through THREE structurally distinct output registers —
8
+ task_1 hallucination_reduction (grounded literal extraction), task_2
9
+ useful_generalization (bracketed generics), task_3 generic_symbolism
10
+ (positional placeholders) — in native messages+tools shape.
11
+
12
+ Substrate: Qwen2.5-0.5B-INSTRUCT, frozen; adapters = the exp002-certified
13
+ relay_pw (aleph addressing + constellation patchwork consumer) after every
14
+ block. Arms (x2 seeds): frozen (zero-shot) | relay_pw_task1 (the
15
+ hallucination-reduction cell — Phil's prior task-1 adapter, reproduced on the
16
+ relay stack) | relay_pw_all (multi-task on all three) | mlp_all
17
+ (MLPAdapter(128), budget-matched control; its 1/2-seed divergence rate at
18
+ this width is itself a standing datum).
19
+
20
+ JUDGES:
21
+ 1. held-out per-task PPL (split BY CAPTION hash — no caption leaks across
22
+ tasks or splits);
23
+ 2. TASK VALIDITY on generated outputs (validators re-implemented from the
24
+ dataset README: JSON parse + schema keys; task_1 per-leaf grounding
25
+ substring check; task_2 ^\\[[a-z_]+\\]$; task_3 typed monotonic
26
+ placeholders) — hallucination reduction as a measured rate;
27
+ 3. THE REGISTER PROBE: sign-codes from the relay stack for the same held-out
28
+ captions under all four registers (3 task system-prompts + plain
29
+ continuation); separation = mean inter-register Hamming minus mean
30
+ intra-register Hamming, per layer — measured on BOTH the instruct-trained
31
+ stack and the exp002 WIKITEXT-trained stack (the overlap-vs-differentiate
32
+ comparison the hypothesis asks for).
33
+ Training: block 512, batch 4, 3000 steps, adapters only, pure Adam lr 1e-3
34
+ wd 0 (the certified regime). Full-conversation LM loss (no prompt masking) —
35
+ consistent with the line's stream training; noted as an instrument property.
36
+ Riders: trunk frozen; >=2 seeds; GPU-only; Colab-safe. Paste order:
37
+ geolip_vitals -> ar_differentiation_bed -> exp013_augmentation_bed ->
38
+ qwen_exp001_relay -> qwen_exp002_refine -> this file.
39
+ """
40
+ from __future__ import annotations
41
+ import json
42
+ import math
43
+ import os
44
+ import re
45
+ import torch
46
+ import torch.nn as nn
47
+ import torch.nn.functional as F
48
+
49
+ if "RelayPatchwork" not in globals():
50
+ try:
51
+ from exp013_augmentation_bed import MLPAdapter
52
+ from qwen_exp001_relay import _QwenBlockWithAdapter, _batch
53
+ from qwen_exp002_refine import RelayPatchwork
54
+ except ImportError:
55
+ _here = globals().get("__file__")
56
+ if _here is None:
57
+ raise ImportError("paste the qwen stack first")
58
+ import sys, pathlib
59
+ sys.path.insert(0, str(pathlib.Path(_here).parent))
60
+ from exp013_augmentation_bed import MLPAdapter
61
+ from qwen_exp001_relay import _QwenBlockWithAdapter, _batch
62
+ from qwen_exp002_refine import RelayPatchwork
63
+
64
+ DATA_ROOT = os.environ.get("GEOLIP_DATA", "./data")
65
+ EXP3_DIR = os.path.join(DATA_ROOT, "qwen_exp003")
66
+ DATASET = "AbstractPhil/json-coco-format"
67
+ INSTRUCT_MODEL = "Qwen/Qwen2.5-0.5B-Instruct"
68
+ BLOCK, BATCH, STEPS, LR = 512, 4, 3000, 1e-3
69
+ TASKS = ("task_1", "task_2", "task_3")
70
+ HOLDOUT_MOD = 50 # caption-hash % 50 == 0 -> held out (~2%)
71
+
72
+
73
+ # ================================ data ============================================
74
+ def _rows(task):
75
+ from huggingface_hub import hf_hub_download
76
+ p = hf_hub_download(DATASET, f"data/{task}.jsonl", repo_type="dataset")
77
+ return [json.loads(l) for l in open(p, encoding="utf-8")]
78
+
79
+
80
+ def _render(tok, row, gen_prompt=False):
81
+ """Chat-template render; stringify tool arguments if the template needs it."""
82
+ msgs = row["messages"][:2] if gen_prompt else row["messages"]
83
+ try:
84
+ return tok.apply_chat_template(msgs, tools=row["tools"],
85
+ tokenize=False,
86
+ add_generation_prompt=gen_prompt)
87
+ except Exception:
88
+ msgs = json.loads(json.dumps(msgs))
89
+ for m in msgs:
90
+ for tc in m.get("tool_calls", []) or []:
91
+ a = tc["function"]["arguments"]
92
+ if isinstance(a, dict):
93
+ tc["function"]["arguments"] = json.dumps(a)
94
+ return tok.apply_chat_template(msgs, tools=row["tools"],
95
+ tokenize=False,
96
+ add_generation_prompt=gen_prompt)
97
+
98
+
99
+ def _is_holdout(prompt: str) -> bool:
100
+ import hashlib
101
+ return int(hashlib.md5(prompt.encode()).hexdigest(), 16) % HOLDOUT_MOD == 0
102
+
103
+
104
+ def build_caches():
105
+ """Token streams per task split (train/holdout) + eval row lists, cached."""
106
+ os.makedirs(EXP3_DIR, exist_ok=True)
107
+ path = os.path.join(EXP3_DIR, "instruct_cache.pt")
108
+ if os.path.exists(path):
109
+ return torch.load(path, map_location="cpu", weights_only=False)
110
+ from transformers import AutoTokenizer
111
+ tok = AutoTokenizer.from_pretrained(INSTRUCT_MODEL)
112
+ blob = {"train": {}, "hold": {}, "eval_rows": {}}
113
+ for task in TASKS:
114
+ rows = _rows(task)
115
+ tr_txt, ho_txt, ev = [], [], []
116
+ for r in rows:
117
+ if _is_holdout(r["prompt"]):
118
+ ho_txt.append(_render(tok, r))
119
+ if len(ev) < 64:
120
+ ev.append({"prompt": r["prompt"],
121
+ "gen_prompt": _render(tok, r, gen_prompt=True)})
122
+ else:
123
+ tr_txt.append(_render(tok, r))
124
+ def stream(txts):
125
+ ids = tok("\n".join(txts), return_tensors="pt").input_ids[0]
126
+ return ids
127
+ blob["train"][task] = stream(tr_txt)
128
+ blob["hold"][task] = stream(ho_txt)
129
+ blob["eval_rows"][task] = ev
130
+ print(f"[cache] {task}: train {blob['train'][task].numel():,} tok, "
131
+ f"hold {blob['hold'][task].numel():,} tok, eval {len(ev)} rows",
132
+ flush=True)
133
+ torch.save(blob, path)
134
+ return blob
135
+
136
+
137
+ # ============================ validators (per the dataset README) =================
138
+ # core content keys; style/mood are constant-null in the data and models may
139
+ # legitimately omit them (validators only read subjects/actions/setting)
140
+ SCHEMA_KEYS = {"subjects", "actions", "setting"}
141
+
142
+
143
+ def parse_tool_json(text: str):
144
+ # GREEDY inner match — the arguments JSON is nested; a non-greedy match
145
+ # truncates at the first '}' (the v1 parser bug, caught by a perfect
146
+ # generation scoring 0)
147
+ m = re.search(r"<tool_call>\s*(\{.*\})\s*</tool_call>", text, re.S)
148
+ raw = m.group(1) if m else None
149
+ if raw is None:
150
+ m = re.search(r"\{.*\}", text, re.S)
151
+ raw = m.group(0) if m else None
152
+ if raw is None:
153
+ return None
154
+ try:
155
+ obj = json.loads(raw)
156
+ except Exception:
157
+ return None
158
+ if isinstance(obj, dict) and "arguments" in obj:
159
+ obj = obj["arguments"]
160
+ if isinstance(obj, str):
161
+ try:
162
+ obj = json.loads(obj)
163
+ except Exception:
164
+ return None
165
+ return obj if isinstance(obj, dict) else None
166
+
167
+
168
+ def _leaves(args, with_setting=True):
169
+ for s in args.get("subjects") or []:
170
+ if isinstance(s, dict):
171
+ if s.get("name"):
172
+ yield str(s["name"])
173
+ for a in s.get("attributes") or []:
174
+ yield str(a)
175
+ for a in args.get("actions") or []:
176
+ yield str(a)
177
+ # setting is an ENUM categorization in task_1 (not verbatim-grounded);
178
+ # format checks in task_2/3 do cover it
179
+ if with_setting and args.get("setting"):
180
+ yield str(args["setting"])
181
+
182
+
183
+ def validate(task: str, args: dict, caption: str):
184
+ if args is None or not SCHEMA_KEYS.issubset(args.keys()):
185
+ return False
186
+ leaves = list(_leaves(args, with_setting=(task != "task_1")))
187
+ if not leaves:
188
+ return False
189
+ if task == "task_1":
190
+ cap = caption.lower()
191
+ return all(all(w in cap for w in lf.lower().split()) for lf in leaves)
192
+ if task == "task_2":
193
+ return all(re.fullmatch(r"\[[a-z_]+\]", lf) for lf in leaves)
194
+ if task == "task_3":
195
+ ok, counters = True, {}
196
+ for lf in leaves:
197
+ m = re.fullmatch(r"\[(ENTITY|ATTRIBUTE|ACTION|[A-Z_]+?)(?:_(\d+))?\]", lf)
198
+ if not m:
199
+ return False
200
+ if m.group(2):
201
+ k, n = m.group(1), int(m.group(2))
202
+ ok &= n == counters.get(k, 0) + 1 or n <= counters.get(k, 0) + 1
203
+ counters[k] = max(counters.get(k, 0), n)
204
+ return ok
205
+ return False
206
+
207
+
208
+ # ================================ arms ============================================
209
+ def load_instruct(arm: str, seed: int = 0, device="cuda"):
210
+ from transformers import AutoModelForCausalLM
211
+ torch.manual_seed(seed)
212
+ model = AutoModelForCausalLM.from_pretrained(INSTRUCT_MODEL,
213
+ dtype=torch.float32)
214
+ model.config.use_cache = False
215
+ for p in model.parameters():
216
+ p.requires_grad_(False)
217
+ adapters = None
218
+ if arm != "frozen":
219
+ d = model.config.hidden_size
220
+ mk = (lambda: RelayPatchwork(d)) if arm.startswith("relay_pw") \
221
+ else (lambda: MLPAdapter(d, hidden=128))
222
+ adapters = nn.ModuleList([mk() for _ in model.model.layers])
223
+ model.model.layers = nn.ModuleList(
224
+ [_QwenBlockWithAdapter(b, a)
225
+ for b, a in zip(model.model.layers, adapters)])
226
+ return model.to(device), adapters
227
+
228
+
229
+ @torch.no_grad()
230
+ def ppl_per_task(model, blob, device="cuda", n=25):
231
+ out = {}
232
+ for task in TASKS:
233
+ g = torch.Generator().manual_seed(0)
234
+ ls = []
235
+ for _ in range(n):
236
+ x, y = _batch(blob["hold"][task], BATCH, BLOCK, device, g)
237
+ logits = model(input_ids=x).logits
238
+ ls.append(F.cross_entropy(logits.reshape(-1, logits.shape[-1]),
239
+ y.reshape(-1)).item())
240
+ out[task] = round(math.exp(sum(ls) / len(ls)), 3)
241
+ return out
242
+
243
+
244
+ @torch.no_grad()
245
+ def task_validity(model, blob, device="cuda", n_eval=48, max_new=200):
246
+ from transformers import AutoTokenizer
247
+ tok = AutoTokenizer.from_pretrained(INSTRUCT_MODEL)
248
+ model.eval()
249
+ model.config.use_cache = True
250
+ out = {}
251
+ for task in TASKS:
252
+ rows = blob["eval_rows"][task][:n_eval]
253
+ js = va = 0
254
+ for r in rows:
255
+ ids = tok(r["gen_prompt"], return_tensors="pt").input_ids.to(device)
256
+ gen = model.generate(ids, max_new_tokens=max_new, do_sample=False,
257
+ pad_token_id=tok.eos_token_id)
258
+ txt = tok.decode(gen[0][ids.shape[1]:], skip_special_tokens=False)
259
+ args = parse_tool_json(txt)
260
+ if args is not None and SCHEMA_KEYS.issubset(args.keys()):
261
+ js += 1
262
+ if validate(task, args, r["prompt"]):
263
+ va += 1
264
+ out[task] = {"json_valid": round(js / len(rows), 4),
265
+ "task_valid": round(va / len(rows), 4)}
266
+ model.config.use_cache = False
267
+ return out
268
+
269
+
270
+ # ============================ the register probe ==================================
271
+ @torch.no_grad()
272
+ def register_probe(model, adapters, blob, device="cuda", n_caps=24):
273
+ """Sign-codes per relay layer for the SAME captions under 4 registers
274
+ (task_1/2/3 gen-prompts + plain continuation). Separation per layer =
275
+ mean inter-register Hamming - mean intra-register Hamming."""
276
+ from transformers import AutoTokenizer
277
+ tok = AutoTokenizer.from_pretrained(INSTRUCT_MODEL)
278
+ model.eval()
279
+ caps = [r["prompt"] for r in blob["eval_rows"]["task_1"][:n_caps]]
280
+ regs = {}
281
+ for task in TASKS:
282
+ by_prompt = {r["prompt"]: r["gen_prompt"]
283
+ for r in blob["eval_rows"][task]}
284
+ regs[task] = [by_prompt.get(c, "") for c in caps]
285
+ regs["plain"] = [c for c in caps]
286
+ layer_ids = [0, 8, 16, 23]
287
+ # hook-based collection (one pass per register, hooks read the codes)
288
+ codes = {reg: {i: [] for i in layer_ids} for reg in regs}
289
+ for reg, prompts in regs.items():
290
+ hooks, store = [], {i: [] for i in layer_ids}
291
+
292
+ def mk(i, a):
293
+ def h(mod, inp, out):
294
+ x = inp[0][:, -1:]
295
+ slots = a.proj(x).view(*x.shape[:-1], a.n_slots, 4)
296
+ A = F.normalize(a.addr.codebook, dim=-1)
297
+ cos = F.normalize(slots, dim=-1) @ A.T
298
+ win = cos.abs().argmax(-1)
299
+ sgn = (torch.gather(cos, -1, win.unsqueeze(-1))
300
+ .squeeze(-1) > 0).long()
301
+ store[i].append((win * 2 + sgn).reshape(-1).cpu())
302
+ return h
303
+ for i in layer_ids:
304
+ hooks.append(adapters[i].register_forward_hook(mk(i, adapters[i])))
305
+ for pr in prompts:
306
+ if pr:
307
+ ids = tok(pr, return_tensors="pt").input_ids.to(device)
308
+ model(input_ids=ids)
309
+ for h in hooks:
310
+ h.remove()
311
+ for i in layer_ids:
312
+ codes[reg][i] = torch.stack(store[i]) if store[i] else None
313
+ sep = {}
314
+ for i in layer_ids:
315
+ mats = {r: codes[r][i] for r in codes if codes[r][i] is not None}
316
+ names = list(mats)
317
+ def ham(a, b):
318
+ return (a.unsqueeze(1) != b.unsqueeze(0)).float().mean().item()
319
+ intra = sum(ham(mats[r], mats[r]) for r in names) / len(names)
320
+ pairs = [(a, b) for ai, a in enumerate(names) for b in names[ai + 1:]]
321
+ inter = sum(ham(mats[a], mats[b]) for a, b in pairs) / len(pairs)
322
+ sep[f"L{i}"] = {"inter": round(inter, 4), "intra": round(intra, 4),
323
+ "sep": round(inter - intra, 4)}
324
+ return sep
325
+
326
+
327
+ # ================================ runner ==========================================
328
+ def train_arm3(arm: str, seed: int = 0, steps=STEPS, device="cuda"):
329
+ if not torch.cuda.is_available():
330
+ raise RuntimeError("verdict runs are GPU-only")
331
+ os.makedirs(EXP3_DIR, exist_ok=True)
332
+ blob = build_caches()
333
+ model, adapters = load_instruct(arm, seed=seed, device=device)
334
+ ledger = open(os.path.join(EXP3_DIR, "ledger.jsonl"), "a", encoding="utf-8")
335
+ if arm == "frozen":
336
+ rec = {"exp": "q3", "arm": arm, "seed": seed,
337
+ "ppl": ppl_per_task(model, blob, device=device),
338
+ "validity": task_validity(model, blob, device=device)}
339
+ ledger.write(json.dumps(rec) + "\n"); ledger.close()
340
+ print(f"[q3 frozen] FINAL {rec['ppl']} {rec['validity']}", flush=True)
341
+ return
342
+ # training stream: task_1 only, or all three interleaved
343
+ tasks = ("task_1",) if arm.endswith("task1") else TASKS
344
+ g = torch.Generator().manual_seed(seed)
345
+ opt = torch.optim.Adam(adapters.parameters(), lr=LR, weight_decay=0.0)
346
+ model.train()
347
+ for step in range(1, steps + 1):
348
+ task = tasks[int(torch.randint(len(tasks), (1,), generator=g))]
349
+ x, y = _batch(blob["train"][task], BATCH, BLOCK, device, g)
350
+ logits = model(input_ids=x).logits
351
+ loss = F.cross_entropy(logits.reshape(-1, logits.shape[-1]),
352
+ y.reshape(-1))
353
+ opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
354
+ if step % 1000 == 0:
355
+ print(f"[q3 {arm} s{seed} step {step}] loss={loss.item():.3f}",
356
+ flush=True)
357
+ rec = {"exp": "q3", "arm": arm, "seed": seed, "steps": steps,
358
+ "trainable": sum(p.numel() for p in adapters.parameters()),
359
+ "ppl": ppl_per_task(model, blob, device=device),
360
+ "validity": task_validity(model, blob, device=device)}
361
+ if arm.startswith("relay_pw"):
362
+ rec["register_sep"] = register_probe(model, adapters, blob,
363
+ device=device)
364
+ ledger.write(json.dumps(rec) + "\n"); ledger.close()
365
+ print(f"[q3 {arm} s{seed}] FINAL ppl={rec['ppl']} "
366
+ f"validity={rec['validity']} sep={rec.get('register_sep')}",
367
+ flush=True)
368
+ torch.save({"arm": arm, "seed": seed,
369
+ "adapters": {k: v.cpu() for k, v in
370
+ adapters.state_dict().items()}},
371
+ os.path.join(EXP3_DIR, f"q3_{arm}_s{seed}.pt"))
372
+ del model, adapters
373
+ torch.cuda.empty_cache()
374
+
375
+
376
+ def wikitext_stack_probe(device="cuda"):
377
+ """The comparison cell: the exp002 WIKITEXT-trained relay_pw stack probed
378
+ on the same registers (overlap prediction) — loaded onto the BASE model it
379
+ was trained on, prompts rendered with the instruct template regardless
380
+ (identical probe inputs across stacks)."""
381
+ from transformers import AutoModelForCausalLM
382
+ blob = build_caches()
383
+ model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-0.5B",
384
+ dtype=torch.float32)
385
+ model.config.use_cache = False
386
+ for p in model.parameters():
387
+ p.requires_grad_(False)
388
+ d = model.config.hidden_size
389
+ adapters = nn.ModuleList([RelayPatchwork(d) for _ in model.model.layers])
390
+ ck = torch.load(os.path.join(DATA_ROOT, "qwen_exp002", "q2_relay_pw_s0.pt"),
391
+ map_location="cpu", weights_only=True)
392
+ adapters.load_state_dict(ck["adapters"])
393
+ model.model.layers = nn.ModuleList(
394
+ [_QwenBlockWithAdapter(b, a)
395
+ for b, a in zip(model.model.layers, adapters)])
396
+ model = model.to(device).eval()
397
+ sep = register_probe(model, adapters, blob, device=device)
398
+ ledger = open(os.path.join(EXP3_DIR, "ledger.jsonl"), "a", encoding="utf-8")
399
+ ledger.write(json.dumps({"exp": "q3", "arm": "wikitext_stack_probe",
400
+ "seed": 0, "register_sep": sep}) + "\n")
401
+ ledger.close()
402
+ print(f"[q3 wikitext_stack_probe] sep={sep}", flush=True)
403
+ del model, adapters
404
+ torch.cuda.empty_cache()
405
+
406
+
407
+ def run_exp003(seeds=(0, 1), device="cuda"):
408
+ train_arm3("frozen", seed=0, device=device)
409
+ wikitext_stack_probe(device=device)
410
+ for seed in seeds:
411
+ for arm in ("relay_pw_task1", "relay_pw_all", "mlp_all"):
412
+ train_arm3(arm, seed=seed, device=device)
413
+ print("=== qwen exp003 COMPLETE ===", flush=True)
414
+
415
+
416
+ def smoke():
417
+ ok = parse_tool_json('x <tool_call>\n{"name":"emit_caption_schema",'
418
+ '"arguments":{"subjects":[{"name":"cat","attributes":'
419
+ '["black"]}],"actions":["sitting"],"setting":"indoor",'
420
+ '"style":null,"mood":null}}\n</tool_call>')
421
+ assert ok and validate("task_1", ok, "A black cat sitting indoor scene.")
422
+ assert not validate("task_1", ok, "A dog on grass.") # ungrounded
423
+ t2 = {"subjects": [{"name": "[pet]", "attributes": ["[color]"]}],
424
+ "actions": ["[resting]"], "setting": "[indoor]", "style": None,
425
+ "mood": None}
426
+ assert validate("task_2", t2, "")
427
+ t3 = {"subjects": [{"name": "[ENTITY_1]", "attributes": ["[ATTRIBUTE_1]"]}],
428
+ "actions": ["[ACTION_1]"], "setting": "[INDOOR]", "style": None,
429
+ "mood": None}
430
+ assert validate("task_3", t3, "")
431
+ assert not validate("task_2", t3, "") # cross-task
432
+ r = RelayPatchwork(896)
433
+ m = MLPAdapter(896, hidden=128)
434
+ rp = sum(p.numel() for p in r.parameters())
435
+ mp = sum(p.numel() for p in m.parameters())
436
+ assert abs(rp - mp) / rp < 0.01, (rp, mp) # budget-matched
437
+ print(f"qwen exp003 smoke passed (validators OK; relay_pw {rp:,} ~ "
438
+ f"mlp128 {mp:,}; full run needs GPU + instruct download)")
439
+
440
+
441
+ def _in_notebook():
442
+ try:
443
+ get_ipython() # type: ignore[name-defined] # noqa: F821
444
+ return True
445
+ except NameError:
446
+ return False
447
+
448
+
449
+ if __name__ == "__main__":
450
+ smoke() if not _in_notebook() else (smoke(),
451
+ print("Notebook: run_exp003() on GPU."))
exp007_math/qwen_exp006_story.py ADDED
@@ -0,0 +1,470 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """qwen_exp006_story.py — geolip-aleph-qwen EXPERIMENT 6: story anchors —
2
+ one relay_pw stack, FOUR methods of requesting a story (Phil's overnight
3
+ directive 2026-07-12: "tinystories with multiple methods of requesting
4
+ stories"). The register-differentiation program moves from extraction
5
+ registers (exp003) and composite emission (exp004) to open-ended GENERATION
6
+ with a per-register measurable constraint.
7
+
8
+ DATA: roneneldan/TinyStories validation parquet (single 10MB file), stories
9
+ 40-180 words, ~4.8k after shuffle, story-hash holdout 10%. From EACH story
10
+ four request registers are derived deterministically:
11
+ cont — continuation: user gives the opening 2 sentences, assistant
12
+ completes the story (no tools);
13
+ inst — instruct: "Tell me a short story about {topic}" (topic = 2 salient
14
+ content words), assistant tells the full story;
15
+ kw — keyword-constrained: "Write a short story using these words:
16
+ w1, w2, w3" (3 content words hash-sampled from the story) — the
17
+ constraint is CHECKABLE at judge time;
18
+ json — tool call: emit_story({title, story}) — the structured register.
19
+
20
+ ARMS: frozen (zero-shot baseline) | relay_pw_story (ONE stack, all four
21
+ registers interleaved — exp003 showed multi-task resolves capture). 3000
22
+ steps, block 512, batch 4, pure Adam lr 1e-3 wd 0.
23
+
24
+ JUDGES: per-register held-out ppl; GENERATION validity n=12/register
25
+ (terminates before cap + register-specific checks: cont/inst/kw word counts
26
+ and sentence shape, kw = all 3 keywords present, json = parse + keys +
27
+ story length; novelty check — the continuation must not just parrot its
28
+ prompt); the REGISTER PROBE (sign-code separation across the 4 request
29
+ methods, exp003 gauge). Riders: trunk frozen; GPU-only verdicts; Colab-safe;
30
+ step-50 peak_mem telemetry (the WDDM sysmem-spill law).
31
+ Paste order: geolip_vitals -> ar_differentiation_bed ->
32
+ exp013_augmentation_bed -> qwen_exp001_relay -> qwen_exp002_refine ->
33
+ qwen_exp003_instruct -> this file.
34
+ """
35
+ from __future__ import annotations
36
+ import hashlib
37
+ import json
38
+ import math
39
+ import os
40
+ import re
41
+ import torch
42
+ import torch.nn as nn
43
+ import torch.nn.functional as F
44
+
45
+ if "parse_tool_json" not in globals():
46
+ try:
47
+ from qwen_exp001_relay import _QwenBlockWithAdapter, _batch
48
+ from qwen_exp002_refine import RelayPatchwork
49
+ from qwen_exp003_instruct import parse_tool_json, INSTRUCT_MODEL
50
+ except ImportError:
51
+ _here = globals().get("__file__")
52
+ if _here is None:
53
+ raise ImportError("paste the qwen stack first")
54
+ import sys, pathlib
55
+ sys.path.insert(0, str(pathlib.Path(_here).parent))
56
+ from qwen_exp001_relay import _QwenBlockWithAdapter, _batch
57
+ from qwen_exp002_refine import RelayPatchwork
58
+ from qwen_exp003_instruct import parse_tool_json, INSTRUCT_MODEL
59
+
60
+ DATA_ROOT = os.environ.get("GEOLIP_DATA", "./data")
61
+ EXP6_DIR = os.path.join(DATA_ROOT, "qwen_exp006")
62
+ BLOCK, BATCH, STEPS, LR = 512, 4, 3000, 1e-3
63
+ MAX_NEW, N_EVAL = 320, 12
64
+ REGS = ("cont", "inst", "kw", "json")
65
+
66
+ STORY_SRC = ("roneneldan/TinyStories",
67
+ "data/validation-00000-of-00001-869c898b519ad725.parquet")
68
+
69
+ TOOL = [{
70
+ "type": "function",
71
+ "function": {
72
+ "name": "emit_story",
73
+ "description": "Return a short story with a title.",
74
+ "parameters": {
75
+ "type": "object",
76
+ "properties": {
77
+ "title": {"type": "string"},
78
+ "story": {"type": "string"},
79
+ },
80
+ "required": ["title", "story"],
81
+ },
82
+ },
83
+ }]
84
+
85
+ SYS = {
86
+ "cont": "Continue the story the user started. Keep the same characters "
87
+ "and simple words. Finish the story.",
88
+ "inst": "You are a storyteller. Tell one short story in simple words. "
89
+ "Give it a beginning, a middle, and an end.",
90
+ "kw": "You are a storyteller. Write one short story in simple words "
91
+ "that uses every word the user requires.",
92
+ "json": "Return one short story by calling the emit_story tool with a "
93
+ "title and the story text.",
94
+ }
95
+
96
+ _STOP = set("the a an and or but so then was were is are had has have he she "
97
+ "it they them his her its their there this that with very to of "
98
+ "in on at for said says say not too you i we one day once upon "
99
+ "time when all out up down back went go going got get".split())
100
+
101
+ _SENT_RE = re.compile(r"(?<=[.!?])\s+")
102
+
103
+
104
+ def _sentences(story: str):
105
+ return [s.strip() for s in _SENT_RE.split(story.strip()) if s.strip()]
106
+
107
+
108
+ def _content_words(story: str):
109
+ seen, out = set(), []
110
+ for w in re.findall(r"[A-Za-z']+", story):
111
+ lw = w.lower()
112
+ if len(lw) > 3 and lw not in _STOP and lw not in seen:
113
+ seen.add(lw)
114
+ out.append(lw)
115
+ return out
116
+
117
+
118
+ def _h(story: str) -> int:
119
+ return int(hashlib.md5(story.encode()).hexdigest(), 16)
120
+
121
+
122
+ def derive_registers(story: str):
123
+ """Deterministic 4-register derivation for one story; None if the story
124
+ can't support a register (too few sentences/content words)."""
125
+ sents = _sentences(story)
126
+ words = _content_words(story)
127
+ if len(sents) < 4 or len(words) < 5:
128
+ return None
129
+ h = _h(story)
130
+ opening = " ".join(sents[:2])
131
+ rest = " ".join(sents[2:])
132
+ topic = " and ".join(words[:2])
133
+ kws = [words[(h >> (8 * i)) % len(words)] for i in range(3)]
134
+ if len(set(kws)) < 3: # hash collision -> first three
135
+ kws = words[:3]
136
+ title = " ".join(story.split()[:5]) + "..."
137
+ return {
138
+ "cont": {"user": opening, "target": rest},
139
+ "inst": {"user": f"Tell me a short story about {topic}.",
140
+ "target": story},
141
+ "kw": {"user": "Write a short story using these words: "
142
+ f"{kws[0]}, {kws[1]}, {kws[2]}.",
143
+ "target": story, "kws": kws},
144
+ "json": {"user": f"Tell me a short story about {topic}.",
145
+ "target": {"title": title, "story": story}},
146
+ }
147
+
148
+
149
+ def _render(tok, reg, user, target=None, gen_prompt=False):
150
+ msgs = [{"role": "system", "content": SYS[reg]},
151
+ {"role": "user", "content": user}]
152
+ tools = TOOL if reg == "json" else None
153
+ if not gen_prompt:
154
+ if reg == "json":
155
+ msgs.append({"role": "assistant", "tool_calls": [{
156
+ "type": "function",
157
+ "function": {"name": "emit_story", "arguments": target}}]})
158
+ else:
159
+ msgs.append({"role": "assistant", "content": target})
160
+ try:
161
+ return tok.apply_chat_template(msgs, tools=tools, tokenize=False,
162
+ add_generation_prompt=gen_prompt)
163
+ except Exception:
164
+ msgs2 = json.loads(json.dumps(msgs))
165
+ for m in msgs2:
166
+ for tc in m.get("tool_calls", []) or []:
167
+ if isinstance(tc["function"]["arguments"], dict):
168
+ tc["function"]["arguments"] = json.dumps(
169
+ tc["function"]["arguments"])
170
+ return tok.apply_chat_template(msgs2, tools=tools, tokenize=False,
171
+ add_generation_prompt=gen_prompt)
172
+
173
+
174
+ def build_caches():
175
+ os.makedirs(EXP6_DIR, exist_ok=True)
176
+ path = os.path.join(EXP6_DIR, "story_cache.pt")
177
+ if os.path.exists(path):
178
+ return torch.load(path, map_location="cpu", weights_only=False)
179
+ from huggingface_hub import hf_hub_download
180
+ import pyarrow.parquet as pq
181
+ from transformers import AutoTokenizer
182
+ tok = AutoTokenizer.from_pretrained(INSTRUCT_MODEL)
183
+ p = hf_hub_download(STORY_SRC[0], STORY_SRC[1], repo_type="dataset")
184
+ texts = pq.read_table(p, columns=["text"]).column("text").to_pylist()
185
+ stories = [t.strip() for t in texts
186
+ if t and 40 <= len(t.split()) <= 180]
187
+ g = torch.Generator().manual_seed(0)
188
+ stories = [stories[i] for i in
189
+ torch.randperm(len(stories), generator=g).tolist()][:4800]
190
+ train_txt = {r: [] for r in REGS}
191
+ hold_txt = {r: [] for r in REGS}
192
+ eval_rows = {r: [] for r in REGS}
193
+ n_used = 0
194
+ for st in stories:
195
+ d = derive_registers(st)
196
+ if d is None:
197
+ continue
198
+ n_used += 1
199
+ holdout = _h(st) % 10 == 0
200
+ for r in REGS:
201
+ txt = _render(tok, r, d[r]["user"], d[r]["target"])
202
+ if holdout:
203
+ hold_txt[r].append(txt)
204
+ if len(eval_rows[r]) < 48:
205
+ eval_rows[r].append({
206
+ "user": d[r]["user"],
207
+ "gen_prompt": _render(tok, r, d[r]["user"],
208
+ gen_prompt=True),
209
+ "kws": d[r].get("kws"),
210
+ "opening": d["cont"]["user"] if r == "cont" else None})
211
+ else:
212
+ train_txt[r].append(txt)
213
+ blob = {
214
+ "train": {r: tok("\n".join(train_txt[r]),
215
+ return_tensors="pt").input_ids[0] for r in REGS},
216
+ "hold": {r: tok("\n".join(hold_txt[r]),
217
+ return_tensors="pt").input_ids[0] for r in REGS},
218
+ "eval_rows": eval_rows,
219
+ "stats": {"n_stories": n_used,
220
+ "n_train": len(train_txt["cont"]),
221
+ "n_hold": len(hold_txt["cont"])},
222
+ }
223
+ torch.save(blob, path)
224
+ print(f"[cache q6] {blob['stats']}", flush=True)
225
+ return blob
226
+
227
+
228
+ # ================================ arms ============================================
229
+ def load_arm6(arm: str, seed: int = 0, device="cuda"):
230
+ from transformers import AutoModelForCausalLM
231
+ torch.manual_seed(seed)
232
+ model = AutoModelForCausalLM.from_pretrained(INSTRUCT_MODEL,
233
+ dtype=torch.float32)
234
+ model.config.use_cache = False
235
+ for p in model.parameters():
236
+ p.requires_grad_(False)
237
+ adapters = None
238
+ if arm != "frozen":
239
+ d = model.config.hidden_size
240
+ adapters = nn.ModuleList([RelayPatchwork(d)
241
+ for _ in model.model.layers])
242
+ model.model.layers = nn.ModuleList(
243
+ [_QwenBlockWithAdapter(b, a)
244
+ for b, a in zip(model.model.layers, adapters)])
245
+ return model.to(device), adapters
246
+
247
+
248
+ # ================================ judges ==========================================
249
+ @torch.no_grad()
250
+ def ppl_per_reg(model, blob, device="cuda", n=15):
251
+ g = torch.Generator().manual_seed(0)
252
+ model.eval()
253
+ out = {}
254
+ for r in REGS:
255
+ ls = []
256
+ for _ in range(n):
257
+ x, y = _batch(blob["hold"][r], BATCH, BLOCK, device, g)
258
+ logits = model(input_ids=x).logits
259
+ ls.append(F.cross_entropy(logits.reshape(-1, logits.shape[-1]),
260
+ y.reshape(-1)).item())
261
+ out[r] = round(math.exp(sum(ls) / len(ls)), 3)
262
+ return out
263
+
264
+
265
+ def _word_count(t: str) -> int:
266
+ return len(re.findall(r"[A-Za-z']+", t))
267
+
268
+
269
+ def check_gen(reg: str, row: dict, txt: str, closed: bool):
270
+ """Register-specific validity for one generation. Returns (ok, reason)."""
271
+ if not closed:
272
+ return False, "no_eos"
273
+ body = txt.split("<|im_end|>")[0].strip()
274
+ if reg == "json":
275
+ args = parse_tool_json(body)
276
+ if args is None:
277
+ return False, "json_error"
278
+ if not isinstance(args.get("title"), str) or \
279
+ not isinstance(args.get("story"), str):
280
+ return False, "missing_keys"
281
+ if _word_count(args["story"]) < 30:
282
+ return False, "too_short"
283
+ return True, "ok"
284
+ if _word_count(body) < (40 if reg == "inst" else 30):
285
+ return False, "too_short"
286
+ if len(_sentences(body)) < 2:
287
+ return False, "not_story_shaped"
288
+ if reg == "kw":
289
+ low = body.lower()
290
+ if not all(k in low for k in row["kws"]):
291
+ return False, "missing_keyword"
292
+ if reg == "cont":
293
+ # novelty: the continuation must not just parrot its opening
294
+ op_words = set(row["opening"].lower().split())
295
+ new = [w for w in body.lower().split() if w not in op_words]
296
+ if len(new) < 15:
297
+ return False, "parrot"
298
+ return True, "ok"
299
+
300
+
301
+ @torch.no_grad()
302
+ def story_validity(model, blob, device="cuda", n_eval=N_EVAL):
303
+ from transformers import AutoTokenizer
304
+ tok = AutoTokenizer.from_pretrained(INSTRUCT_MODEL)
305
+ model.eval()
306
+ model.config.use_cache = True
307
+ out = {}
308
+ for r in REGS:
309
+ oks, reasons, samples = 0, {}, []
310
+ rows = blob["eval_rows"][r][:n_eval]
311
+ for i, row in enumerate(rows):
312
+ ids = tok(row["gen_prompt"],
313
+ return_tensors="pt").input_ids.to(device)
314
+ gen = model.generate(ids, max_new_tokens=MAX_NEW, do_sample=False,
315
+ pad_token_id=tok.eos_token_id)
316
+ txt = tok.decode(gen[0][ids.shape[1]:], skip_special_tokens=False)
317
+ closed = len(gen[0]) - ids.shape[1] < MAX_NEW or \
318
+ "<|im_end|>" in txt
319
+ ok, why = check_gen(r, row, txt, closed)
320
+ oks += int(ok)
321
+ reasons[why] = reasons.get(why, 0) + 1
322
+ if i < 2:
323
+ samples.append(txt[:300])
324
+ out[r] = {"valid": round(oks / len(rows), 3), "reasons": reasons,
325
+ "samples": samples}
326
+ model.config.use_cache = False
327
+ return out
328
+
329
+
330
+ @torch.no_grad()
331
+ def register_probe6(model, adapters, blob, device="cuda", n=24):
332
+ """exp003 gauge on the 4 request registers: sign-code separation per
333
+ relay layer = mean inter-register Hamming - mean intra-register."""
334
+ from transformers import AutoTokenizer
335
+ tok = AutoTokenizer.from_pretrained(INSTRUCT_MODEL)
336
+ model.eval()
337
+ layer_ids = [0, 8, 16, 23]
338
+ codes = {r: {i: [] for i in layer_ids} for r in REGS}
339
+ for r in REGS:
340
+ prompts = [row["gen_prompt"] for row in blob["eval_rows"][r][:n]]
341
+ store = {i: [] for i in layer_ids}
342
+ hooks = []
343
+
344
+ def mk(i, a):
345
+ def h(mod, inp, out):
346
+ x = inp[0][:, -1:]
347
+ slots = a.proj(x).view(*x.shape[:-1], a.n_slots, 4)
348
+ A = F.normalize(a.addr.codebook, dim=-1)
349
+ cos = F.normalize(slots, dim=-1) @ A.T
350
+ win = cos.abs().argmax(-1)
351
+ sgn = (torch.gather(cos, -1, win.unsqueeze(-1))
352
+ .squeeze(-1) > 0).long()
353
+ store[i].append((win * 2 + sgn).reshape(-1).cpu())
354
+ return h
355
+ for i in layer_ids:
356
+ hooks.append(adapters[i].register_forward_hook(
357
+ mk(i, adapters[i])))
358
+ for pr in prompts:
359
+ ids = tok(pr, return_tensors="pt").input_ids.to(device)
360
+ model(input_ids=ids)
361
+ for h in hooks:
362
+ h.remove()
363
+ for i in layer_ids:
364
+ codes[r][i] = torch.stack(store[i])
365
+ sep = {}
366
+ for i in layer_ids:
367
+ def ham(a, b):
368
+ return (a.unsqueeze(1) != b.unsqueeze(0)).float().mean().item()
369
+ intra = sum(ham(codes[r][i], codes[r][i]) for r in REGS) / len(REGS)
370
+ pairs = [(a, b) for ai, a in enumerate(REGS) for b in REGS[ai + 1:]]
371
+ inter = sum(ham(codes[a][i], codes[b][i]) for a, b in pairs) \
372
+ / len(pairs)
373
+ sep[f"L{i}"] = {"inter": round(inter, 4), "intra": round(intra, 4),
374
+ "sep": round(inter - intra, 4)}
375
+ return sep
376
+
377
+
378
+ # ================================ runner ==========================================
379
+ def run_arm6(arm: str, seed: int = 0, steps=STEPS, device="cuda"):
380
+ if not torch.cuda.is_available():
381
+ raise RuntimeError("verdict runs are GPU-only")
382
+ os.makedirs(EXP6_DIR, exist_ok=True)
383
+ blob = build_caches()
384
+ model, adapters = load_arm6(arm, seed=seed, device=device)
385
+ ledger = open(os.path.join(EXP6_DIR, "ledger.jsonl"), "a",
386
+ encoding="utf-8")
387
+ if arm != "frozen":
388
+ g = torch.Generator().manual_seed(seed)
389
+ opt = torch.optim.Adam(adapters.parameters(), lr=LR, weight_decay=0.0)
390
+ model.train()
391
+ for step in range(1, steps + 1):
392
+ r = REGS[int(torch.randint(len(REGS), (1,), generator=g))]
393
+ x, y = _batch(blob["train"][r], BATCH, BLOCK, device, g)
394
+ logits = model(input_ids=x).logits
395
+ loss = F.cross_entropy(logits.reshape(-1, logits.shape[-1]),
396
+ y.reshape(-1))
397
+ opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
398
+ if step == 50 or step % 1000 == 0:
399
+ mem = torch.cuda.max_memory_allocated() / 2**30 \
400
+ if device == "cuda" else 0.0
401
+ print(f"[q6 {arm} s{seed} step {step}] "
402
+ f"loss={loss.item():.3f} peak_mem={mem:.1f}GB",
403
+ flush=True)
404
+ rec = {"exp": "q6", "arm": arm, "seed": seed,
405
+ "steps": steps if arm != "frozen" else 0,
406
+ "ppl": ppl_per_reg(model, blob, device=device),
407
+ "validity": story_validity(model, blob, device=device),
408
+ "register_sep": register_probe6(model, adapters, blob,
409
+ device=device)
410
+ if adapters is not None else None,
411
+ "cache_stats": blob["stats"]}
412
+ ledger.write(json.dumps(rec) + "\n"); ledger.close()
413
+ v = {r: rec["validity"][r]["valid"] for r in REGS}
414
+ print(f"[q6 {arm} s{seed}] FINAL ppl={rec['ppl']} valid={v} "
415
+ f"sep={rec['register_sep']}", flush=True)
416
+ if adapters is not None:
417
+ torch.save({"arm": arm, "seed": seed,
418
+ "adapters": {k: t.cpu() for k, t in
419
+ adapters.state_dict().items()}},
420
+ os.path.join(EXP6_DIR, f"q6_{arm}_s{seed}.pt"))
421
+ del model, adapters
422
+ torch.cuda.empty_cache()
423
+
424
+
425
+ def run_exp006(device="cuda"):
426
+ run_arm6("frozen", seed=0, device=device)
427
+ run_arm6("relay_pw_story", seed=0, device=device)
428
+ print("=== qwen exp006 COMPLETE ===", flush=True)
429
+
430
+
431
+ def smoke():
432
+ st = ("Once upon a time there was a little dog named Rex. Rex loved to "
433
+ "play in the park every day. One day Rex found a shiny red ball "
434
+ "under a big tree. He picked it up and ran to show his friend "
435
+ "Anna. Anna smiled and they played with the ball until the sun "
436
+ "went down. Rex was very happy that day.")
437
+ d = derive_registers(st)
438
+ assert d is not None
439
+ assert len(d["kw"]["kws"]) == 3 and len(set(d["kw"]["kws"])) == 3
440
+ assert d["cont"]["user"].startswith("Once upon")
441
+ assert d["json"]["target"]["title"].endswith("...")
442
+ ok, why = check_gen("kw", {"kws": d["kw"]["kws"]},
443
+ st + "<|im_end|>", True)
444
+ assert ok, why
445
+ ok, why = check_gen("kw", {"kws": ["zebra", "quantum", "xylophone"]},
446
+ st + "<|im_end|>", True)
447
+ assert not ok and why == "missing_keyword"
448
+ ok, why = check_gen("cont", {"opening": st}, st + "<|im_end|>", True)
449
+ assert not ok and why == "parrot"
450
+ ok, why = check_gen("json", {}, "<tool_call>\n" + json.dumps(
451
+ {"name": "emit_story", "arguments":
452
+ {"title": "T...", "story": st}}) + "\n</tool_call><|im_end|>", True)
453
+ assert ok, why
454
+ assert check_gen("inst", {}, "Hi.<|im_end|>", True)[1] == "too_short"
455
+ assert check_gen("inst", {}, st, False)[1] == "no_eos"
456
+ print("qwen exp006 smoke passed (register derivation + validators "
457
+ "both directions)")
458
+
459
+
460
+ def _in_notebook():
461
+ try:
462
+ get_ipython() # type: ignore[name-defined] # noqa: F821
463
+ return True
464
+ except NameError:
465
+ return False
466
+
467
+
468
+ if __name__ == "__main__":
469
+ smoke() if not _in_notebook() else (smoke(),
470
+ print("Notebook: run_exp006() on GPU."))
exp007_math/qwen_exp007_math.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """qwen_exp007_math.py — geolip-aleph-qwen EXPERIMENT 7: math anchors —
2
+ one relay_pw stack, FOUR methods of asking for a computation (Phil's
3
+ overnight directive 2026-07-12: "math functions with multiple methods of
4
+ asking for functions"). The judge splits FORMAT from CORRECTNESS: does the
5
+ anchor paradigm carry COMPUTATION on held-out problems, or only the shape
6
+ of answers? Fully synthetic — no downloads; ground truth computable.
7
+
8
+ REGISTERS (each problem rendered one way, deterministically assigned):
9
+ nl — "What is 37 + 45?" -> "37 + 45 = 82"
10
+ json — emit_answer tool call -> {"expression": "37 + 45",
11
+ "result": 82}
12
+ code — "def f(x): return 3*x + 2 ... What is f(4)?" -> "f(4) = 14"
13
+ word — templated word problem -> "Sam has 82 apples."
14
+
15
+ PROBLEM SPACE: a+b (1-99), a-b (result>=0), a*b (2-12), f(x)=a*x+b with
16
+ a 2-9, b 0-20, x 2-9. 4,000 problems, problem-hash holdout 10% — held-out
17
+ CORRECTNESS is unseen-instance computation, not recall.
18
+
19
+ ARMS: frozen | relay_pw_math (all four registers interleaved). 3000 steps,
20
+ block 512, batch 4, pure Adam lr 1e-3 wd 0.
21
+
22
+ JUDGES: per-register held-out ppl; per-register FORMAT validity and
23
+ CORRECTNESS (n=16/register, both arms — the frozen baseline separates
24
+ "the trunk can already add" from what the anchor adds); the register probe
25
+ (sign-code separation across the 4 ask-methods). Riders: trunk frozen;
26
+ GPU-only verdicts; Colab-safe; step-50 peak_mem telemetry.
27
+ Paste order: geolip_vitals -> ar_differentiation_bed ->
28
+ exp013_augmentation_bed -> qwen_exp001_relay -> qwen_exp002_refine ->
29
+ qwen_exp003_instruct -> qwen_exp006_story -> this file.
30
+ """
31
+ from __future__ import annotations
32
+ import hashlib
33
+ import json
34
+ import math
35
+ import os
36
+ import re
37
+ import torch
38
+ import torch.nn as nn
39
+ import torch.nn.functional as F
40
+
41
+ if "parse_tool_json" not in globals():
42
+ try:
43
+ from qwen_exp001_relay import _QwenBlockWithAdapter, _batch
44
+ from qwen_exp002_refine import RelayPatchwork
45
+ from qwen_exp003_instruct import parse_tool_json, INSTRUCT_MODEL
46
+ from qwen_exp006_story import load_arm6, register_probe6
47
+ except ImportError:
48
+ _here = globals().get("__file__")
49
+ if _here is None:
50
+ raise ImportError("paste the qwen stack first")
51
+ import sys, pathlib
52
+ sys.path.insert(0, str(pathlib.Path(_here).parent))
53
+ from qwen_exp001_relay import _QwenBlockWithAdapter, _batch
54
+ from qwen_exp002_refine import RelayPatchwork
55
+ from qwen_exp003_instruct import parse_tool_json, INSTRUCT_MODEL
56
+ from qwen_exp006_story import load_arm6, register_probe6
57
+
58
+ DATA_ROOT = os.environ.get("GEOLIP_DATA", "./data")
59
+ EXP7_DIR = os.path.join(DATA_ROOT, "qwen_exp007")
60
+ BLOCK, BATCH, STEPS, LR = 512, 4, 3000, 1e-3
61
+ MAX_NEW, N_EVAL = 96, 16
62
+ REGS = ("nl", "json", "code", "word")
63
+
64
+ TOOL = [{
65
+ "type": "function",
66
+ "function": {
67
+ "name": "emit_answer",
68
+ "description": "Return the result of a computation.",
69
+ "parameters": {
70
+ "type": "object",
71
+ "properties": {
72
+ "expression": {"type": "string"},
73
+ "result": {"type": "integer"},
74
+ },
75
+ "required": ["expression", "result"],
76
+ },
77
+ },
78
+ }]
79
+
80
+ SYS = {
81
+ "nl": "Answer the arithmetic question. Reply with the equation and its "
82
+ "result, like: 12 + 5 = 17",
83
+ "json": "Compute the expression and return it by calling the "
84
+ "emit_answer tool.",
85
+ "code": "The user defines a Python function and asks for its value. "
86
+ "Reply like: f(3) = 11",
87
+ "word": "Solve the word problem. Reply with one short sentence "
88
+ "containing the answer.",
89
+ }
90
+
91
+ _NAMES = ("Sam", "Anna", "Ben", "Mia", "Tom", "Lily", "Max", "Zoe")
92
+ _THINGS = ("apples", "pens", "books", "coins", "shells", "cards",
93
+ "marbles", "stickers")
94
+
95
+
96
+ def _h(s: str) -> int:
97
+ return int(hashlib.md5(s.encode()).hexdigest(), 16)
98
+
99
+
100
+ def gen_problems(n=4000, seed=0):
101
+ """Deterministic synthetic problems: (register, user, target, truth)."""
102
+ g = torch.Generator().manual_seed(seed)
103
+
104
+ def ri(lo, hi):
105
+ return int(torch.randint(lo, hi + 1, (1,), generator=g))
106
+
107
+ out, seen = [], set()
108
+ while len(out) < n:
109
+ kind = ri(0, 3)
110
+ if kind == 0: # a + b
111
+ a, b = ri(1, 99), ri(1, 99)
112
+ expr, truth = f"{a} + {b}", a + b
113
+ elif kind == 1: # a - b >= 0
114
+ a, b = ri(1, 99), ri(1, 99)
115
+ a, b = max(a, b), min(a, b)
116
+ expr, truth = f"{a} - {b}", a - b
117
+ elif kind == 2: # a * b
118
+ a, b = ri(2, 12), ri(2, 12)
119
+ expr, truth = f"{a} * {b}", a * b
120
+ else: # f(x)=a*x+b
121
+ a, b, x = ri(2, 9), ri(0, 20), ri(2, 9)
122
+ expr, truth = f"f{a}x{b}@{x}", a * x + b
123
+ reg = REGS[len(out) % 4] if kind != 3 else "code"
124
+ if kind == 3:
125
+ user = (f"def f(x):\n return {a} * x + {b}\n\n"
126
+ f"What is f({x})?")
127
+ target = f"f({x}) = {truth}"
128
+ elif reg == "nl":
129
+ user = f"What is {expr}?"
130
+ target = f"{expr} = {truth}"
131
+ elif reg == "json":
132
+ user = f"Compute {expr}."
133
+ target = {"expression": expr, "result": truth}
134
+ elif reg == "code": # non-f(x) problem in code reg
135
+ user = (f"def f(x):\n return x {expr.split(' ', 1)[1]}\n\n"
136
+ f"What is f({expr.split(' ')[0]})?")
137
+ target = f"f({expr.split(' ')[0]}) = {truth}"
138
+ else: # word
139
+ nm = _NAMES[_h(expr) % len(_NAMES)]
140
+ th = _THINGS[_h(expr + "t") % len(_THINGS)]
141
+ a_, op, b_ = expr.split(" ")
142
+ if op == "+":
143
+ user = (f"{nm} has {a_} {th}. {nm} gets {b_} more. "
144
+ f"How many {th} does {nm} have now?")
145
+ elif op == "-":
146
+ user = (f"{nm} has {a_} {th}. {nm} gives away {b_}. "
147
+ f"How many {th} does {nm} have left?")
148
+ else:
149
+ user = (f"Each box holds {b_} {th}. There are {a_} boxes. "
150
+ f"How many {th} in all?")
151
+ target = f"{nm} has {truth} {th}." if op != "*" else \
152
+ f"There are {truth} {th}."
153
+ key = (reg, user)
154
+ if key in seen:
155
+ continue
156
+ seen.add(key)
157
+ out.append({"reg": reg, "user": user, "target": target,
158
+ "truth": truth})
159
+ return out
160
+
161
+
162
+ def _render(tok, reg, user, target=None, gen_prompt=False):
163
+ msgs = [{"role": "system", "content": SYS[reg]},
164
+ {"role": "user", "content": user}]
165
+ tools = TOOL if reg == "json" else None
166
+ if not gen_prompt:
167
+ if reg == "json":
168
+ msgs.append({"role": "assistant", "tool_calls": [{
169
+ "type": "function",
170
+ "function": {"name": "emit_answer", "arguments": target}}]})
171
+ else:
172
+ msgs.append({"role": "assistant", "content": target})
173
+ try:
174
+ return tok.apply_chat_template(msgs, tools=tools, tokenize=False,
175
+ add_generation_prompt=gen_prompt)
176
+ except Exception:
177
+ msgs2 = json.loads(json.dumps(msgs))
178
+ for m in msgs2:
179
+ for tc in m.get("tool_calls", []) or []:
180
+ if isinstance(tc["function"]["arguments"], dict):
181
+ tc["function"]["arguments"] = json.dumps(
182
+ tc["function"]["arguments"])
183
+ return tok.apply_chat_template(msgs2, tools=tools, tokenize=False,
184
+ add_generation_prompt=gen_prompt)
185
+
186
+
187
+ def build_caches7():
188
+ os.makedirs(EXP7_DIR, exist_ok=True)
189
+ path = os.path.join(EXP7_DIR, "math_cache.pt")
190
+ if os.path.exists(path):
191
+ return torch.load(path, map_location="cpu", weights_only=False)
192
+ from transformers import AutoTokenizer
193
+ tok = AutoTokenizer.from_pretrained(INSTRUCT_MODEL)
194
+ probs = gen_problems()
195
+ train_txt = {r: [] for r in REGS}
196
+ hold_txt = {r: [] for r in REGS}
197
+ eval_rows = {r: [] for r in REGS}
198
+ for p in probs:
199
+ r = p["reg"]
200
+ txt = _render(tok, r, p["user"], p["target"])
201
+ if _h(p["user"]) % 10 == 0:
202
+ hold_txt[r].append(txt)
203
+ if len(eval_rows[r]) < 48:
204
+ eval_rows[r].append({
205
+ "user": p["user"], "truth": p["truth"],
206
+ "gen_prompt": _render(tok, r, p["user"],
207
+ gen_prompt=True)})
208
+ else:
209
+ train_txt[r].append(txt)
210
+ blob = {
211
+ "train": {r: tok("\n".join(train_txt[r]),
212
+ return_tensors="pt").input_ids[0] for r in REGS},
213
+ "hold": {r: tok("\n".join(hold_txt[r]),
214
+ return_tensors="pt").input_ids[0] for r in REGS},
215
+ "eval_rows": eval_rows,
216
+ "stats": {r: len(train_txt[r]) for r in REGS},
217
+ }
218
+ torch.save(blob, path)
219
+ print(f"[cache q7] {blob['stats']}", flush=True)
220
+ return blob
221
+
222
+
223
+ # ================================ judges ==========================================
224
+ _NUM_RE = re.compile(r"-?\d+")
225
+
226
+
227
+ def check_math(reg: str, row: dict, txt: str, closed: bool):
228
+ """Returns (format_ok, correct, reason)."""
229
+ if not closed:
230
+ return False, False, "no_eos"
231
+ body = txt.split("<|im_end|>")[0].strip()
232
+ if reg == "json":
233
+ args = parse_tool_json(body)
234
+ if args is None:
235
+ return False, False, "json_error"
236
+ if "expression" not in args or "result" not in args:
237
+ return False, False, "missing_keys"
238
+ try:
239
+ return True, int(args["result"]) == row["truth"], "ok"
240
+ except (TypeError, ValueError):
241
+ return True, False, "non_int_result"
242
+ nums = _NUM_RE.findall(body)
243
+ if not nums:
244
+ return False, False, "no_number"
245
+ fmt = ("=" in body) if reg in ("nl", "code") else \
246
+ (len(body.split()) <= 24)
247
+ return fmt, int(nums[-1]) == row["truth"], "ok"
248
+
249
+
250
+ @torch.no_grad()
251
+ def math_validity(model, blob, device="cuda", n_eval=N_EVAL):
252
+ from transformers import AutoTokenizer
253
+ tok = AutoTokenizer.from_pretrained(INSTRUCT_MODEL)
254
+ model.eval()
255
+ model.config.use_cache = True
256
+ out = {}
257
+ for r in REGS:
258
+ fmts, cors, samples = 0, 0, []
259
+ rows = blob["eval_rows"][r][:n_eval]
260
+ for i, row in enumerate(rows):
261
+ ids = tok(row["gen_prompt"],
262
+ return_tensors="pt").input_ids.to(device)
263
+ gen = model.generate(ids, max_new_tokens=MAX_NEW, do_sample=False,
264
+ pad_token_id=tok.eos_token_id)
265
+ txt = tok.decode(gen[0][ids.shape[1]:], skip_special_tokens=False)
266
+ closed = len(gen[0]) - ids.shape[1] < MAX_NEW or \
267
+ "<|im_end|>" in txt
268
+ fmt, cor, _ = check_math(r, row, txt, closed)
269
+ fmts += int(fmt)
270
+ cors += int(cor)
271
+ if i < 2:
272
+ samples.append(txt[:160])
273
+ out[r] = {"format": round(fmts / len(rows), 3),
274
+ "correct": round(cors / len(rows), 3),
275
+ "samples": samples}
276
+ model.config.use_cache = False
277
+ return out
278
+
279
+
280
+ @torch.no_grad()
281
+ def ppl_per_reg7(model, blob, device="cuda", n=15):
282
+ g = torch.Generator().manual_seed(0)
283
+ model.eval()
284
+ out = {}
285
+ for r in REGS:
286
+ ls = []
287
+ for _ in range(n):
288
+ x, y = _batch(blob["hold"][r], BATCH, BLOCK, device, g)
289
+ logits = model(input_ids=x).logits
290
+ ls.append(F.cross_entropy(logits.reshape(-1, logits.shape[-1]),
291
+ y.reshape(-1)).item())
292
+ out[r] = round(math.exp(sum(ls) / len(ls)), 3)
293
+ return out
294
+
295
+
296
+ # ================================ runner ==========================================
297
+ def run_arm7(arm: str, seed: int = 0, steps=STEPS, device="cuda"):
298
+ if not torch.cuda.is_available():
299
+ raise RuntimeError("verdict runs are GPU-only")
300
+ os.makedirs(EXP7_DIR, exist_ok=True)
301
+ blob = build_caches7()
302
+ model, adapters = load_arm6(arm, seed=seed, device=device)
303
+ ledger = open(os.path.join(EXP7_DIR, "ledger.jsonl"), "a",
304
+ encoding="utf-8")
305
+ if arm != "frozen":
306
+ g = torch.Generator().manual_seed(seed)
307
+ opt = torch.optim.Adam(adapters.parameters(), lr=LR, weight_decay=0.0)
308
+ model.train()
309
+ for step in range(1, steps + 1):
310
+ r = REGS[int(torch.randint(len(REGS), (1,), generator=g))]
311
+ x, y = _batch(blob["train"][r], BATCH, BLOCK, device, g)
312
+ logits = model(input_ids=x).logits
313
+ loss = F.cross_entropy(logits.reshape(-1, logits.shape[-1]),
314
+ y.reshape(-1))
315
+ opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
316
+ if step == 50 or step % 1000 == 0:
317
+ mem = torch.cuda.max_memory_allocated() / 2**30 \
318
+ if device == "cuda" else 0.0
319
+ print(f"[q7 {arm} s{seed} step {step}] "
320
+ f"loss={loss.item():.3f} peak_mem={mem:.1f}GB",
321
+ flush=True)
322
+ # the probe reuses exp006's gauge (identical adapter anatomy)
323
+ import qwen_exp006_story as q6mod
324
+ q6_regs_save = q6mod.REGS
325
+ q6mod.REGS = REGS
326
+ try:
327
+ sep = register_probe6(model, adapters, blob, device=device) \
328
+ if adapters is not None else None
329
+ finally:
330
+ q6mod.REGS = q6_regs_save
331
+ rec = {"exp": "q7", "arm": arm, "seed": seed,
332
+ "steps": steps if arm != "frozen" else 0,
333
+ "ppl": ppl_per_reg7(model, blob, device=device),
334
+ "validity": math_validity(model, blob, device=device),
335
+ "register_sep": sep,
336
+ "cache_stats": blob["stats"]}
337
+ ledger.write(json.dumps(rec) + "\n"); ledger.close()
338
+ v = {r: (rec["validity"][r]["format"], rec["validity"][r]["correct"])
339
+ for r in REGS}
340
+ print(f"[q7 {arm} s{seed}] FINAL ppl={rec['ppl']} fmt/cor={v} "
341
+ f"sep={rec['register_sep']}", flush=True)
342
+ if adapters is not None:
343
+ torch.save({"arm": arm, "seed": seed,
344
+ "adapters": {k: t.cpu() for k, t in
345
+ adapters.state_dict().items()}},
346
+ os.path.join(EXP7_DIR, f"q7_{arm}_s{seed}.pt"))
347
+ del model, adapters
348
+ torch.cuda.empty_cache()
349
+
350
+
351
+ def run_exp007(device="cuda"):
352
+ run_arm7("frozen", seed=0, device=device)
353
+ run_arm7("relay_pw_math", seed=0, device=device)
354
+ print("=== qwen exp007 COMPLETE ===", flush=True)
355
+
356
+
357
+ def smoke():
358
+ probs = gen_problems(n=400)
359
+ assert len(probs) == 400
360
+ regs = {p["reg"] for p in probs}
361
+ assert regs == set(REGS), regs
362
+ for p in probs[:50]:
363
+ if p["reg"] == "json":
364
+ assert p["target"]["result"] == p["truth"]
365
+ elif p["reg"] in ("nl", "code"):
366
+ assert str(p["truth"]) in p["target"]
367
+ f, c, _ = check_math("nl", {"truth": 82}, "37 + 45 = 82<|im_end|>", True)
368
+ assert f and c
369
+ f, c, _ = check_math("nl", {"truth": 82}, "37 + 45 = 83<|im_end|>", True)
370
+ assert f and not c
371
+ f, c, _ = check_math("json", {"truth": 14}, "<tool_call>\n" + json.dumps(
372
+ {"name": "emit_answer",
373
+ "arguments": {"expression": "3*4+2", "result": 14}}) +
374
+ "\n</tool_call><|im_end|>", True)
375
+ assert f and c
376
+ f, c, _ = check_math("word", {"truth": 42},
377
+ "There are 42 pens.<|im_end|>", True)
378
+ assert f and c
379
+ f, c, why = check_math("nl", {"truth": 5}, "37 + 45 = 82", False)
380
+ assert not f and why == "no_eos"
381
+ print("qwen exp007 smoke passed (problem gen + format/correctness "
382
+ "judges both directions)")
383
+
384
+
385
+ def _in_notebook():
386
+ try:
387
+ get_ipython() # type: ignore[name-defined] # noqa: F821
388
+ return True
389
+ except NameError:
390
+ return False
391
+
392
+
393
+ if __name__ == "__main__":
394
+ smoke() if not _in_notebook() else (smoke(),
395
+ print("Notebook: run_exp007() on GPU."))
exp007_math/repro.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """repro.py — standalone loader/runner for exp007. Code dependencies live in
2
+ THIS folder. python repro.py = CPU smoke; python repro.py --run = full
3
+ campaign (frozen baseline + trained arm; GPU ~1.2h). Data + caches land in
4
+ ./data (override with GEOLIP_DATA)."""
5
+ import os
6
+ import sys
7
+
8
+ HERE = os.path.dirname(os.path.abspath(__file__))
9
+ sys.path.insert(0, HERE)
10
+
11
+ if __name__ == "__main__":
12
+ import geolip_vitals # noqa: F401 (paste order)
13
+ import ar_differentiation_bed # noqa: F401
14
+ import exp013_augmentation_bed # noqa: F401
15
+ import qwen_exp001_relay # noqa: F401
16
+ import qwen_exp002_refine # noqa: F401
17
+ import qwen_exp003_instruct # noqa: F401
18
+ import qwen_exp007_math as m
19
+ if "--run" in sys.argv[1:]:
20
+ m.run_exp007()
21
+ else:
22
+ m.smoke()
23
+ print("repro smoke passed — --run for the campaign (GPU)")
exp007_math/results/ledger.jsonl ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ {"exp": "q7", "arm": "frozen", "seed": 0, "steps": 0, "ppl": {"nl": 2.763, "json": 4.393, "code": 2.591, "word": 3.145}, "validity": {"nl": {"format": 0.312, "correct": 1.0, "samples": ["45<|im_end|>", "The equation for 94 - 27 is 67.<|im_end|>"]}, "json": {"format": 1.0, "correct": 1.0, "samples": ["<tool_call>\n{\"name\": \"emit_answer\", \"arguments\": {\"expression\": \"2 * 12\", \"result\": 24}}\n</tool_call><|im_end|>", "<tool_call>\n{\"name\": \"emit_answer\", \"arguments\": {\"expression\": \"84 + 73\", \"result\": 157}}\n</tool_call><|im_end|>"]}, "code": {"format": 1.0, "correct": 0.688, "samples": ["f(77) = 60<|im_end|>", "f(5) = 15<|im_end|>"]}, "word": {"format": 1.0, "correct": 0.938, "samples": ["Max now has 20 marbles.<|im_end|>", "Zoe has 41 shells left.<|im_end|>"]}}, "register_sep": null, "cache_stats": {"nl": 696, "json": 702, "code": 1496, "word": 718}}
2
+ {"exp": "q7", "arm": "relay_pw_math", "seed": 0, "steps": 3000, "ppl": {"nl": 1.342, "json": 1.061, "code": 1.168, "word": 1.525}, "validity": {"nl": {"format": 1.0, "correct": 1.0, "samples": ["5 * 9 = 45<|im_end|>", "94 - 27 = 67<|im_end|>"]}, "json": {"format": 1.0, "correct": 1.0, "samples": ["<tool_call>\n{\"name\": \"emit_answer\", \"arguments\": {\"expression\": \"2 * 12\", \"result\": 24}}\n</tool_call><|im_end|>", "<tool_call>\n{\"name\": \"emit_answer\", \"arguments\": {\"expression\": \"84 + 73\", \"result\": 157}}\n</tool_call><|im_end|>"]}, "code": {"format": 1.0, "correct": 0.938, "samples": ["f(77) = 6<|im_end|>", "f(5) = 15<|im_end|>"]}, "word": {"format": 1.0, "correct": 1.0, "samples": ["Max has 72 marbles.<|im_end|>", "Zoe has 41 shells.<|im_end|>"]}}, "register_sep": {"L0": {"inter": 0.2563, "intra": 0.0418, "sep": 0.2145}, "L8": {"inter": 0.3293, "intra": 0.0485, "sep": 0.2808}, "L16": {"inter": 0.8737, "intra": 0.4026, "sep": 0.4711}, "L23": {"inter": 0.9304, "intra": 0.4347, "sep": 0.4957}}, "cache_stats": {"nl": 696, "json": 702, "code": 1496, "word": 718}}
exp007_math/results/results.json ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "frozen": {
3
+ "ppl": {
4
+ "nl": 2.763,
5
+ "json": 4.393,
6
+ "code": 2.591,
7
+ "word": 3.145
8
+ },
9
+ "validity": {
10
+ "nl": {
11
+ "format": 0.312,
12
+ "correct": 1.0,
13
+ "samples": [
14
+ "45<|im_end|>",
15
+ "The equation for 94 - 27 is 67.<|im_end|>"
16
+ ]
17
+ },
18
+ "json": {
19
+ "format": 1.0,
20
+ "correct": 1.0,
21
+ "samples": [
22
+ "<tool_call>\n{\"name\": \"emit_answer\", \"arguments\": {\"expression\": \"2 * 12\", \"result\": 24}}\n</tool_call><|im_end|>",
23
+ "<tool_call>\n{\"name\": \"emit_answer\", \"arguments\": {\"expression\": \"84 + 73\", \"result\": 157}}\n</tool_call><|im_end|>"
24
+ ]
25
+ },
26
+ "code": {
27
+ "format": 1.0,
28
+ "correct": 0.688,
29
+ "samples": [
30
+ "f(77) = 60<|im_end|>",
31
+ "f(5) = 15<|im_end|>"
32
+ ]
33
+ },
34
+ "word": {
35
+ "format": 1.0,
36
+ "correct": 0.938,
37
+ "samples": [
38
+ "Max now has 20 marbles.<|im_end|>",
39
+ "Zoe has 41 shells left.<|im_end|>"
40
+ ]
41
+ }
42
+ }
43
+ },
44
+ "relay_pw_math": {
45
+ "steps": 3000,
46
+ "ppl": {
47
+ "nl": 1.342,
48
+ "json": 1.061,
49
+ "code": 1.168,
50
+ "word": 1.525
51
+ },
52
+ "validity": {
53
+ "nl": {
54
+ "format": 1.0,
55
+ "correct": 1.0,
56
+ "samples": [
57
+ "5 * 9 = 45<|im_end|>",
58
+ "94 - 27 = 67<|im_end|>"
59
+ ]
60
+ },
61
+ "json": {
62
+ "format": 1.0,
63
+ "correct": 1.0,
64
+ "samples": [
65
+ "<tool_call>\n{\"name\": \"emit_answer\", \"arguments\": {\"expression\": \"2 * 12\", \"result\": 24}}\n</tool_call><|im_end|>",
66
+ "<tool_call>\n{\"name\": \"emit_answer\", \"arguments\": {\"expression\": \"84 + 73\", \"result\": 157}}\n</tool_call><|im_end|>"
67
+ ]
68
+ },
69
+ "code": {
70
+ "format": 1.0,
71
+ "correct": 0.938,
72
+ "samples": [
73
+ "f(77) = 6<|im_end|>",
74
+ "f(5) = 15<|im_end|>"
75
+ ]
76
+ },
77
+ "word": {
78
+ "format": 1.0,
79
+ "correct": 1.0,
80
+ "samples": [
81
+ "Max has 72 marbles.<|im_end|>",
82
+ "Zoe has 41 shells.<|im_end|>"
83
+ ]
84
+ }
85
+ },
86
+ "register_sep": {
87
+ "L0": {
88
+ "inter": 0.2563,
89
+ "intra": 0.0418,
90
+ "sep": 0.2145
91
+ },
92
+ "L8": {
93
+ "inter": 0.3293,
94
+ "intra": 0.0485,
95
+ "sep": 0.2808
96
+ },
97
+ "L16": {
98
+ "inter": 0.8737,
99
+ "intra": 0.4026,
100
+ "sep": 0.4711
101
+ },
102
+ "L23": {
103
+ "inter": 0.9304,
104
+ "intra": 0.4347,
105
+ "sep": 0.4957
106
+ }
107
+ }
108
+ },
109
+ "cache_stats": {
110
+ "nl": 696,
111
+ "json": 702,
112
+ "code": 1496,
113
+ "word": 718
114
+ },
115
+ "n_rows": 2
116
+ }