AbstractPhil commited on
Commit
338daf0
Β·
verified Β·
1 Parent(s): 095ed4f

Create constelation_v11_cross_token_preservation.py

Browse files
constelation_v11_cross_token_preservation.py ADDED
@@ -0,0 +1,562 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hybrid Constellation Relay v2
4
+ ================================
5
+ Fixes from v1:
6
+ - Split gates: fixed_gate (cold, -3.0) + dynamic_gate (warm, -1.0)
7
+ - Balanced: 8 fixed + 8 dynamic per patch
8
+ - Separate dynamic MLP before merge
9
+ - Proper causal intervention test for cross-token routing
10
+ - V-projection: dynamic anchors carry value information, not just position
11
+ """
12
+
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ import numpy as np
17
+ import math
18
+ import time
19
+
20
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
21
+ torch.manual_seed(42)
22
+ torch.backends.cuda.matmul.allow_tf32 = True
23
+ torch.backends.cudnn.allow_tf32 = True
24
+
25
+ HAS_FP8 = hasattr(torch, 'float8_e4m3fn')
26
+
27
+
28
+ def compute_cv(points, n_samples=1500, n_points=5):
29
+ N = points.shape[0]
30
+ if N < n_points: return float('nan')
31
+ points = F.normalize(points.to(DEVICE).float(), dim=-1)
32
+ vols = []
33
+ for _ in range(n_samples):
34
+ idx = torch.randperm(min(N, 10000), device=DEVICE)[:n_points]
35
+ pts = points[idx].unsqueeze(0)
36
+ gram = torch.bmm(pts, pts.transpose(1, 2))
37
+ norms = torch.diagonal(gram, dim1=1, dim2=2)
38
+ d2 = norms.unsqueeze(2) + norms.unsqueeze(1) - 2 * gram
39
+ d2 = F.relu(d2)
40
+ cm = torch.zeros(1, 6, 6, device=DEVICE, dtype=torch.float32)
41
+ cm[:, 0, 1:] = 1; cm[:, 1:, 0] = 1; cm[:, 1:, 1:] = d2
42
+ v2 = -torch.linalg.det(cm) / 9216
43
+ if v2[0].item() > 1e-20:
44
+ vols.append(v2[0].sqrt().cpu())
45
+ if len(vols) < 50: return float('nan')
46
+ vt = torch.stack(vols)
47
+ return (vt.std() / (vt.mean() + 1e-8)).item()
48
+
49
+
50
+ def eff_dim(x):
51
+ x_c = x - x.mean(0, keepdim=True)
52
+ _, S, _ = torch.linalg.svd(x_c[:512].float(), full_matrices=False)
53
+ p = S / S.sum()
54
+ return p.pow(2).sum().reciprocal().item()
55
+
56
+
57
+ def uniform_sphere(n, d):
58
+ return F.normalize(torch.randn(n, d), dim=-1)
59
+
60
+
61
+ # ══════════════════════════════════════════════════════════════════
62
+ # HYBRID CONSTELLATION RELAY v2
63
+ # ══════════════════════════════════════════════════════════════════
64
+
65
+ class HybridRelay(nn.Module):
66
+ """
67
+ Fixed geometric anchors + dynamic cross-token anchors.
68
+ Split processing paths with separate gates.
69
+
70
+ Per patch (d=16):
71
+ Fixed path: A_f anchors Γ— n_phases β†’ fixed_mlp β†’ fixed_out (d)
72
+ Dynamic path: top-k QΒ·K selection β†’ gather V β†’ dynamic_mlp β†’ dyn_out (d)
73
+ Output: fixed_gate * fixed_out + dyn_gate * dyn_out + (1-both) * identity
74
+ """
75
+ def __init__(
76
+ self,
77
+ input_dim,
78
+ patch_dim=16,
79
+ n_fixed=8,
80
+ n_dynamic=8,
81
+ n_phases=3,
82
+ pw_hidden=32,
83
+ fixed_gate_init=-3.0, # sigmoid β‰ˆ 0.047
84
+ dyn_gate_init=-1.0, # sigmoid β‰ˆ 0.269
85
+ ):
86
+ super().__init__()
87
+ assert input_dim % patch_dim == 0
88
+ self.input_dim = input_dim
89
+ self.patch_dim = patch_dim
90
+ self.n_patches = input_dim // patch_dim
91
+ self.n_fixed = n_fixed
92
+ self.n_dynamic = n_dynamic
93
+ self.n_phases = n_phases
94
+
95
+ P, Af, k, d = self.n_patches, n_fixed, n_dynamic, patch_dim
96
+
97
+ # ── Fixed constellation ──
98
+ home = torch.empty(P, Af, d)
99
+ nn.init.xavier_normal_(home.view(P * Af, d))
100
+ home = F.normalize(home.view(P, Af, d), dim=-1)
101
+ self.register_buffer('home', home)
102
+ self.anchors = nn.Parameter(home.clone())
103
+
104
+ # Fixed path MLP: (phases * Af) β†’ d
105
+ fixed_tri_dim = n_phases * Af
106
+ self.fixed_w1 = nn.Parameter(torch.empty(P, fixed_tri_dim, pw_hidden))
107
+ self.fixed_b1 = nn.Parameter(torch.zeros(1, 1, P, pw_hidden))
108
+ self.fixed_w2 = nn.Parameter(torch.empty(P, pw_hidden, d))
109
+ self.fixed_b2 = nn.Parameter(torch.zeros(1, 1, P, d))
110
+ for p in range(P):
111
+ nn.init.xavier_normal_(self.fixed_w1.data[p])
112
+ nn.init.xavier_normal_(self.fixed_w2.data[p])
113
+ self.fixed_norm = nn.LayerNorm(d)
114
+
115
+ # ── Dynamic cross-token path ──
116
+ # Q, K for selection; V for information transfer
117
+ self.q_proj = nn.Parameter(torch.empty(P, d, d))
118
+ self.k_proj = nn.Parameter(torch.empty(P, d, d))
119
+ self.v_proj = nn.Parameter(torch.empty(P, d, d))
120
+ for p in range(P):
121
+ nn.init.xavier_normal_(self.q_proj.data[p])
122
+ nn.init.xavier_normal_(self.k_proj.data[p])
123
+ nn.init.xavier_normal_(self.v_proj.data[p])
124
+
125
+ # Dynamic path MLP: (k * d) β†’ d (reads gathered V values)
126
+ dyn_input_dim = k * d
127
+ self.dyn_w1 = nn.Parameter(torch.empty(P, dyn_input_dim, pw_hidden))
128
+ self.dyn_b1 = nn.Parameter(torch.zeros(1, 1, P, pw_hidden))
129
+ self.dyn_w2 = nn.Parameter(torch.empty(P, pw_hidden, d))
130
+ self.dyn_b2 = nn.Parameter(torch.zeros(1, 1, P, d))
131
+ for p in range(P):
132
+ nn.init.xavier_normal_(self.dyn_w1.data[p])
133
+ nn.init.xavier_normal_(self.dyn_w2.data[p])
134
+ self.dyn_norm = nn.LayerNorm(d)
135
+
136
+ # ── Split gates ──
137
+ self.fixed_gate = nn.Parameter(torch.full((P,), fixed_gate_init))
138
+ self.dyn_gate = nn.Parameter(torch.full((P,), dyn_gate_init))
139
+
140
+ self.norm = nn.LayerNorm(input_dim)
141
+
142
+ def drift(self):
143
+ h = F.normalize(self.home, dim=-1)
144
+ c = F.normalize(self.anchors, dim=-1)
145
+ cos = (h * c).sum(dim=-1).clamp(-1 + 1e-7, 1 - 1e-7)
146
+ return torch.acos(cos)
147
+
148
+ def at_phase(self, t):
149
+ h = F.normalize(self.home, dim=-1)
150
+ c = F.normalize(self.anchors, dim=-1)
151
+ omega = self.drift().unsqueeze(-1)
152
+ sin_omega = omega.sin().clamp(min=1e-7)
153
+ return (torch.sin((1 - t) * omega) / sin_omega * h +
154
+ torch.sin(t * omega) / sin_omega * c)
155
+
156
+ def forward(self, x, return_diagnostics=False):
157
+ """x: (B, S, D)"""
158
+ B, S, D = x.shape
159
+ P, Af, k, d = self.n_patches, self.n_fixed, self.n_dynamic, self.patch_dim
160
+
161
+ x_n = self.norm(x)
162
+ patches = x_n.reshape(B, S, P, d)
163
+ patches_n = F.normalize(patches, dim=-1)
164
+
165
+ # ══════ FIXED PATH ══════
166
+ phases = torch.linspace(0, 1, self.n_phases).tolist()
167
+ fixed_tris = []
168
+ for t in phases:
169
+ anchors_t = F.normalize(self.at_phase(t), dim=-1) # (P, Af, d)
170
+ cos_f = torch.einsum('bspd,pad->bspa', patches_n, anchors_t)
171
+ fixed_tris.append(1.0 - cos_f)
172
+ fixed_tri = torch.cat(fixed_tris, dim=-1) # (B, S, P, Af*phases)
173
+
174
+ h_f = torch.einsum('bspt,pth->bsph', fixed_tri, self.fixed_w1) + self.fixed_b1
175
+ h_f = F.gelu(h_f)
176
+ fixed_out = torch.einsum('bsph,phd->bspd', h_f, self.fixed_w2) + self.fixed_b2
177
+ fixed_out = self.fixed_norm(fixed_out) # (B, S, P, d)
178
+
179
+ # ══════ DYNAMIC PATH ══════
180
+ # Q, K, V projections
181
+ Q = F.normalize(torch.einsum('bspd,pde->bspe', patches_n, self.q_proj), dim=-1)
182
+ K = F.normalize(torch.einsum('bspd,pde->bspe', patches_n, self.k_proj), dim=-1)
183
+ V = torch.einsum('bspd,pde->bspe', patches, self.v_proj) # V not normalized β€” carries magnitude
184
+
185
+ # Relevance: Q_i Β· K_j β†’ (B, P, S, S)
186
+ relevance = torch.einsum('bspd,btpd->bpst', Q, K)
187
+
188
+ # Mask self
189
+ self_mask = torch.eye(S, device=x.device, dtype=torch.bool)
190
+ relevance = relevance.masked_fill(self_mask.unsqueeze(0).unsqueeze(0), -1e9)
191
+
192
+ # Soft top-k: take softmax over keys, then gather top-k
193
+ # This makes gradients flow through the selection
194
+ rel_weights = relevance.softmax(dim=-1) # (B, P, S, S)
195
+
196
+ # Top-k indices for sparse gather
197
+ _, topk_idx = relevance.topk(k, dim=-1) # (B, P, S, k)
198
+
199
+ # Gather top-k weights and re-normalize
200
+ topk_weights = torch.gather(rel_weights, -1, topk_idx) # (B, P, S, k)
201
+ topk_weights = topk_weights / (topk_weights.sum(dim=-1, keepdim=True) + 1e-8)
202
+
203
+ # Gather top-k V vectors: V is (B, S, P, d) β†’ need (B, P, S, d)
204
+ V_perm = V.permute(0, 2, 1, 3) # (B, P, S, d)
205
+ # For each (b, p, s), gather V[b, p, topk_idx[b,p,s,:], :]
206
+ topk_idx_v = topk_idx.unsqueeze(-1).expand(-1, -1, -1, -1, d) # (B, P, S, k, d)
207
+ V_expanded = V_perm.unsqueeze(2).expand(-1, -1, S, -1, -1) # (B, P, S, S, d)
208
+ topk_V = torch.gather(V_expanded, 3, topk_idx_v) # (B, P, S, k, d)
209
+
210
+ # Weighted sum of top-k values
211
+ weighted_V = (topk_weights.unsqueeze(-1) * topk_V).reshape(B, P, S, k * d)
212
+ # β†’ (B, S, P, k*d)
213
+ weighted_V = weighted_V.permute(0, 2, 1, 3)
214
+
215
+ # Dynamic MLP
216
+ h_d = torch.einsum('bspt,pth->bsph', weighted_V, self.dyn_w1) + self.dyn_b1
217
+ h_d = F.gelu(h_d)
218
+ dyn_out = torch.einsum('bsph,phd->bspd', h_d, self.dyn_w2) + self.dyn_b2
219
+ dyn_out = self.dyn_norm(dyn_out) # (B, S, P, d)
220
+
221
+ # ══════ GATED MERGE ══════
222
+ fg = self.fixed_gate.sigmoid().view(1, 1, P, 1)
223
+ dg = self.dyn_gate.sigmoid().view(1, 1, P, 1)
224
+ # Identity weight = 1 - fg - dg (can go negative if both gates high, but sigmoid caps each at 1)
225
+ identity_weight = (1.0 - fg - dg).clamp(min=0.0)
226
+
227
+ blended = fg * fixed_out + dg * dyn_out + identity_weight * patches
228
+ out = blended.reshape(B, S, D)
229
+ result = x + out
230
+
231
+ if return_diagnostics:
232
+ drift = self.drift()
233
+ diag = {
234
+ 'drift_mean': drift.mean().item(),
235
+ 'fixed_gate': self.fixed_gate.sigmoid().mean().item(),
236
+ 'dyn_gate': self.dyn_gate.sigmoid().mean().item(),
237
+ 'identity_weight': identity_weight.mean().item(),
238
+ 'topk_cos_mean': torch.gather(relevance, -1, topk_idx).mean().item(),
239
+ 'topk_cos_max': torch.gather(relevance, -1, topk_idx).max().item(),
240
+ }
241
+ return result, diag
242
+ return result
243
+
244
+
245
+ # ══════════════════════════════════════════════════════════════════
246
+ # COMPARISON MODULES
247
+ # ══════════════════════════════════════════════════════════════════
248
+
249
+ class VanillaAttn(nn.Module):
250
+ def __init__(self, dim, n_heads=4):
251
+ super().__init__()
252
+ self.n_heads = n_heads
253
+ self.head_dim = dim // n_heads
254
+ self.qkv = nn.Linear(dim, 3 * dim, bias=False)
255
+ self.out_proj = nn.Linear(dim, dim, bias=False)
256
+ self.norm = nn.LayerNorm(dim)
257
+
258
+ def forward(self, x):
259
+ B, S, D = x.shape
260
+ x_n = self.norm(x)
261
+ qkv = self.qkv(x_n).reshape(B, S, 3, self.n_heads, self.head_dim)
262
+ qkv = qkv.permute(2, 0, 3, 1, 4)
263
+ q, k, v = qkv[0], qkv[1], qkv[2]
264
+ attn = (q @ k.transpose(-2, -1)) * (self.head_dim ** -0.5)
265
+ attn = attn.softmax(dim=-1)
266
+ out = (attn @ v).transpose(1, 2).reshape(B, S, D)
267
+ return x + self.out_proj(out)
268
+
269
+
270
+ class PureRelay(nn.Module):
271
+ def __init__(self, input_dim, patch_dim=16, n_anchors=16, n_phases=3,
272
+ pw_hidden=32, gate_init=-3.0):
273
+ super().__init__()
274
+ assert input_dim % patch_dim == 0
275
+ P = input_dim // patch_dim
276
+ A, d = n_anchors, patch_dim
277
+ self.input_dim, self.patch_dim, self.n_patches = input_dim, patch_dim, P
278
+ self.n_anchors, self.n_phases = n_anchors, n_phases
279
+
280
+ home = torch.empty(P, A, d)
281
+ nn.init.xavier_normal_(home.view(P * A, d))
282
+ home = F.normalize(home.view(P, A, d), dim=-1)
283
+ self.register_buffer('home', home)
284
+ self.anchors = nn.Parameter(home.clone())
285
+ tri_dim = n_phases * A
286
+ self.pw_w1 = nn.Parameter(torch.empty(P, tri_dim, pw_hidden))
287
+ self.pw_b1 = nn.Parameter(torch.zeros(1, 1, P, pw_hidden))
288
+ self.pw_w2 = nn.Parameter(torch.empty(P, pw_hidden, d))
289
+ self.pw_b2 = nn.Parameter(torch.zeros(1, 1, P, d))
290
+ for p in range(P):
291
+ nn.init.xavier_normal_(self.pw_w1.data[p])
292
+ nn.init.xavier_normal_(self.pw_w2.data[p])
293
+ self.pw_norm = nn.LayerNorm(d)
294
+ self.gates = nn.Parameter(torch.full((P,), gate_init))
295
+ self.norm = nn.LayerNorm(input_dim)
296
+
297
+ def drift(self):
298
+ h = F.normalize(self.home, dim=-1)
299
+ c = F.normalize(self.anchors, dim=-1)
300
+ return torch.acos((h * c).sum(-1).clamp(-1 + 1e-7, 1 - 1e-7))
301
+
302
+ def at_phase(self, t):
303
+ h, c = F.normalize(self.home, dim=-1), F.normalize(self.anchors, dim=-1)
304
+ omega = self.drift().unsqueeze(-1)
305
+ so = omega.sin().clamp(min=1e-7)
306
+ return torch.sin((1-t)*omega)/so * h + torch.sin(t*omega)/so * c
307
+
308
+ def forward(self, x):
309
+ if x.dim() == 2: x = x.unsqueeze(1)
310
+ B, S, D = x.shape
311
+ P, A, d = self.n_patches, self.n_anchors, self.patch_dim
312
+ patches = self.norm(x).reshape(B*S, P, d)
313
+ patches_n = F.normalize(patches, dim=-1)
314
+ tris = []
315
+ for t in torch.linspace(0, 1, self.n_phases).tolist():
316
+ at = F.normalize(self.at_phase(t), dim=-1)
317
+ tris.append(1.0 - torch.einsum('bpd,pad->bpa', patches_n, at))
318
+ tri = torch.cat(tris, dim=-1)
319
+ h = F.gelu(torch.einsum('bpt,pth->bph', tri, self.pw_w1) + self.pw_b1.squeeze(1))
320
+ pw = self.pw_norm(torch.einsum('bph,phd->bpd', h, self.pw_w2) + self.pw_b2.squeeze(1))
321
+ g = self.gates.sigmoid().unsqueeze(0).unsqueeze(-1)
322
+ out = (g * pw + (1-g) * patches).reshape(B, S, D)
323
+ return x + out
324
+
325
+
326
+ # ══════════════════════════════════════════════════════════════════
327
+ # TESTS
328
+ # ══════════════════════════════════════════════════════════════════
329
+
330
+ B = 4
331
+ S = 256
332
+ D = 128
333
+ N_CV = 1500
334
+
335
+ print("=" * 90)
336
+ print("HYBRID CONSTELLATION RELAY v2 β€” SPLIT GATES + CAUSAL TEST")
337
+ print(f" B={B}, S={S}, D={D} = {D//16}p Γ— 16d")
338
+ print(f" Fixed: 8 anchors Γ— 3 phases | Dynamic: 8 top-k with V-projection")
339
+ print(f" Device: {DEVICE}")
340
+ print("=" * 90)
341
+
342
+ configs = {
343
+ 'vanilla_attn': lambda: VanillaAttn(D, 8).to(DEVICE),
344
+ 'pure_relay': lambda: PureRelay(D, 16, 16, 3, 32).to(DEVICE),
345
+ 'hybrid_v2': lambda: HybridRelay(D, 16, 8, 8, 3, 32).to(DEVICE),
346
+ }
347
+
348
+
349
+ # ── TEST 1: Single pass ──
350
+ print(f"\n{'━'*90}")
351
+ print("TEST 1: Single pass")
352
+ print(f"{'━'*90}")
353
+
354
+ x = torch.randn(B, S, D, device=DEVICE)
355
+ x_flat_n = F.normalize(x.reshape(B*S, D), dim=-1)
356
+ cv_base = compute_cv(x_flat_n, N_CV)
357
+ print(f" Baseline CV: {cv_base:.4f}")
358
+ print(f" {'arch':>15} {'params':>8} {'CV_n':>8} {'cos_orig':>10}")
359
+
360
+ for name, builder in configs.items():
361
+ m = builder()
362
+ np_ = sum(p.numel() for p in m.parameters())
363
+ with torch.no_grad():
364
+ out = m(x)
365
+ out_n = F.normalize(out.reshape(B*S, D), dim=-1)
366
+ cv = compute_cv(out_n, N_CV)
367
+ cos = (x_flat_n * out_n).sum(-1).mean().item()
368
+ print(f" {name:>15} {np_:>8,} {cv:>8.4f} {cos:>10.6f}")
369
+
370
+ # Hybrid diagnostics
371
+ hybrid_diag = HybridRelay(D, 16, 8, 8, 3, 32).to(DEVICE)
372
+ with torch.no_grad():
373
+ _, diag = hybrid_diag(x, return_diagnostics=True)
374
+ print(f"\n Hybrid gates: fixed={diag['fixed_gate']:.4f} dyn={diag['dyn_gate']:.4f} "
375
+ f"identity={diag['identity_weight']:.4f}")
376
+
377
+
378
+ # ── TEST 2: Depth sweep ──
379
+ print(f"\n{'━'*90}")
380
+ print("TEST 2: Depth 16")
381
+ print(f"{'━'*90}")
382
+
383
+ x = torch.randn(B, S, D, device=DEVICE)
384
+ x_flat_n = F.normalize(x.reshape(B*S, D), dim=-1)
385
+ checks = [1, 2, 4, 8, 12, 16]
386
+
387
+ for name, builder in configs.items():
388
+ print(f"\n {name}:")
389
+ print(f" {'d':>4} {'CV_n':>8} {'cos':>10} {'eff_d':>8}")
390
+ stack = nn.ModuleList([builder() for _ in range(16)])
391
+ z = x.clone()
392
+ for i, layer in enumerate(stack):
393
+ with torch.no_grad(): z = layer(z)
394
+ if (i+1) in checks:
395
+ zn = F.normalize(z.reshape(B*S, D), dim=-1)
396
+ print(f" {i+1:>4} {compute_cv(zn, N_CV):>8.4f} "
397
+ f"{(x_flat_n * zn).sum(-1).mean().item():>10.6f} "
398
+ f"{eff_dim(z.reshape(B*S, D)):>8.1f}")
399
+
400
+
401
+ # ── TEST 3: Interleaved ──
402
+ print(f"\n{'━'*90}")
403
+ print("TEST 3: Interleaved attn β†’ hybrid β†’ attn β†’ hybrid")
404
+ print(f"{'━'*90}")
405
+
406
+ x = torch.randn(B, S, D, device=DEVICE)
407
+ x_flat_n = F.normalize(x.reshape(B*S, D), dim=-1)
408
+
409
+ attn_l = nn.ModuleList([VanillaAttn(D, 8).to(DEVICE) for _ in range(8)])
410
+ hyb_l = nn.ModuleList([HybridRelay(D, 16, 8, 8, 3, 32).to(DEVICE) for _ in range(8)])
411
+
412
+ print(f" {'step':>4} {'type':>8} {'CV_n':>8} {'cos':>10} {'eff_d':>8}")
413
+ z = x.clone()
414
+ step = 0
415
+ for i in range(8):
416
+ with torch.no_grad(): z = attn_l[i](z)
417
+ step += 1
418
+ if step in checks:
419
+ zn = F.normalize(z.reshape(B*S, D), dim=-1)
420
+ print(f" {step:>4} {'attn':>8} {compute_cv(zn, N_CV):>8.4f} "
421
+ f"{(x_flat_n * zn).sum(-1).mean().item():>10.6f} "
422
+ f"{eff_dim(z.reshape(B*S, D)):>8.1f}")
423
+ with torch.no_grad(): z = hyb_l[i](z)
424
+ step += 1
425
+ if step in checks:
426
+ zn = F.normalize(z.reshape(B*S, D), dim=-1)
427
+ print(f" {step:>4} {'hybrid':>8} {compute_cv(zn, N_CV):>8.4f} "
428
+ f"{(x_flat_n * zn).sum(-1).mean().item():>10.6f} "
429
+ f"{eff_dim(z.reshape(B*S, D)):>8.1f}")
430
+
431
+
432
+ # ── TEST 4: CAUSAL INTERVENTION β€” the real cross-token routing test ──
433
+ print(f"\n{'━'*90}")
434
+ print("TEST 4: Causal intervention β€” does changing token 0 affect other tokens?")
435
+ print(f" Run same sequence twice, swap only token 0. Measure Ξ” on tokens 1-31.")
436
+ print(f"{'━'*90}")
437
+
438
+ S_test = 32
439
+ x_a = torch.randn(1, S_test, D, device=DEVICE)
440
+ x_b = x_a.clone()
441
+ x_b[:, 0] = torch.randn(1, D, device=DEVICE) # only token 0 differs
442
+
443
+ print(f" Token 0 cosine between A and B: "
444
+ f"{F.cosine_similarity(x_a[:, 0], x_b[:, 0]).item():.4f}")
445
+ print(f" Tokens 1-31 identical: "
446
+ f"{(x_a[:, 1:] == x_b[:, 1:]).all().item()}")
447
+
448
+ print(f"\n {'arch':>15} {'other_Ξ”_norm':>12} {'other_Ξ”_cos':>12} {'t0_Ξ”_norm':>10}")
449
+
450
+ for name, builder in configs.items():
451
+ m = builder()
452
+ with torch.no_grad():
453
+ out_a = m(x_a)
454
+ out_b = m(x_b)
455
+
456
+ # How much did tokens 1-31 change?
457
+ delta_others = (out_a[:, 1:] - out_b[:, 1:])
458
+ other_norm = delta_others.norm(dim=-1).mean().item()
459
+ # Cosine change for other tokens
460
+ cos_others = F.cosine_similarity(
461
+ out_a[:, 1:].reshape(-1, D),
462
+ out_b[:, 1:].reshape(-1, D)).mean().item()
463
+ # Token 0 change (sanity β€” should be large for all)
464
+ t0_norm = (out_a[:, 0] - out_b[:, 0]).norm().item()
465
+
466
+ print(f" {name:>15} {other_norm:>12.6f} {1-cos_others:>12.8f} {t0_norm:>10.4f}")
467
+
468
+
469
+ # Run multiple layers to amplify routing signal
470
+ print(f"\n After 4 stacked layers:")
471
+ print(f" {'arch':>15} {'other_Ξ”_norm':>12} {'other_Ξ”_cos':>12}")
472
+
473
+ for name, builder in configs.items():
474
+ layers = nn.ModuleList([builder() for _ in range(4)])
475
+ with torch.no_grad():
476
+ za, zb = x_a.clone(), x_b.clone()
477
+ for layer in layers:
478
+ za = layer(za)
479
+ zb = layer(zb)
480
+
481
+ delta = (za[:, 1:] - zb[:, 1:])
482
+ other_norm = delta.norm(dim=-1).mean().item()
483
+ cos_others = F.cosine_similarity(
484
+ za[:, 1:].reshape(-1, D),
485
+ zb[:, 1:].reshape(-1, D)).mean().item()
486
+ print(f" {name:>15} {other_norm:>12.6f} {1-cos_others:>12.8f}")
487
+
488
+
489
+ # ── TEST 5: Throughput ──
490
+ print(f"\n{'━'*90}")
491
+ print("TEST 5: Throughput")
492
+ print(f"{'━'*90}")
493
+
494
+ x_bench = torch.randn(B, S, D, device=DEVICE)
495
+ print(f" {'arch':>15} {'ms':>8} {'params':>10}")
496
+
497
+ for name, builder in configs.items():
498
+ m = builder()
499
+ np_ = sum(p.numel() for p in m.parameters())
500
+ for _ in range(5):
501
+ with torch.no_grad(): _ = m(x_bench)
502
+ torch.cuda.synchronize()
503
+ t0 = time.time()
504
+ for _ in range(100):
505
+ with torch.no_grad(): _ = m(x_bench)
506
+ torch.cuda.synchronize()
507
+ ms = (time.time() - t0) / 100 * 1000
508
+ print(f" {name:>15} {ms:>8.2f} {np_:>10,}")
509
+
510
+
511
+ # ── TEST 6: Sequence scaling ──
512
+ print(f"\n{'━'*90}")
513
+ print("TEST 6: Sequence length scaling")
514
+ print(f"{'━'*90}")
515
+
516
+ print(f" {'S':>6} {'hybrid_ms':>10} {'attn_ms':>10} {'ratio':>8}")
517
+ for sl in [64, 128, 256, 512, 1024]:
518
+ xs = torch.randn(2, sl, D, device=DEVICE)
519
+ h_m = HybridRelay(D, 16, 8, 8, 3, 32).to(DEVICE)
520
+ a_m = VanillaAttn(D, 8).to(DEVICE)
521
+ with torch.no_grad(): _ = h_m(xs); _ = a_m(xs)
522
+ torch.cuda.synchronize()
523
+
524
+ t0 = time.time()
525
+ for _ in range(50):
526
+ with torch.no_grad(): _ = h_m(xs)
527
+ torch.cuda.synchronize()
528
+ h_ms = (time.time() - t0) / 50 * 1000
529
+
530
+ t0 = time.time()
531
+ for _ in range(50):
532
+ with torch.no_grad(): _ = a_m(xs)
533
+ torch.cuda.synchronize()
534
+ a_ms = (time.time() - t0) / 50 * 1000
535
+ print(f" {sl:>6} {h_ms:>10.2f} {a_ms:>10.2f} {h_ms/a_ms:>8.2f}Γ—")
536
+
537
+
538
+ # ══════════════════════════════════════════════════════════════════
539
+ # SUMMARY
540
+ # ══════════════════════════════════════════════════════════════════
541
+
542
+ print(f"\n{'='*90}")
543
+ print("SUMMARY")
544
+ print(f"{'='*90}")
545
+ print(f"""
546
+ Hybrid v2 architecture per patch:
547
+ Fixed: 8 anchors Γ— 3 phases β†’ MLP β†’ fixed_out (gate β‰ˆ 0.047)
548
+ Dynamic: top-8 QΒ·K β†’ gather V β†’ weighted sum β†’ MLP β†’ dyn_out (gate β‰ˆ 0.269)
549
+ Output: fg*fixed + dg*dynamic + (1-fg-dg)*identity + skip
550
+
551
+ GPT's challenge:
552
+ βœ“ Selective interaction β€” QΒ·K top-k selection
553
+ βœ“ Conditional transformation β€” separate MLPs for fixed/dynamic
554
+ βœ“ Information routing β€” V-projection carries information through geometric channel
555
+
556
+ Key test: Causal intervention (Test 4)
557
+ If other_Ξ”_norm > 0 for hybrid but β‰ˆ 0 for pure_relay,
558
+ cross-token routing is proven.
559
+ """)
560
+ print(f"{'='*90}")
561
+ print("DONE")
562
+ print(f"{'='*90}")