AbstractPhil commited on
Commit
8e22bad
Β·
verified Β·
1 Parent(s): 2dd0777

Create aleph_routed_attention.py

Browse files
experiments/exp_007_aleph_routed_attention/aleph_routed_attention.py ADDED
@@ -0,0 +1,851 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # aleph_routed_attention.py
2
+ """
3
+ Aleph-Routed Attention β€” routing attention through a learned projective codebook
4
+ =================================================================================
5
+
6
+ Two variants of attention whose routing medium is the aleph signed-projective
7
+ address (geolip-svae aleph_model.py lineage):
8
+
9
+ HUB : linear attention whose feature map IS the aleph address.
10
+ score(i,j) = <addr(q_i), addr(k_j)> over 2K oriented axes [+A; -A].
11
+ Factors through two K-wide memories (the antipodal closed-form trick:
12
+ the 2K tensor is never materialized). O(n*K*d), PURE GEMM β€” no gathers.
13
+ Denominator is a dot product of strictly positive distributions, so it
14
+ cannot vanish or flip sign (structurally stabler than elu+1 feature maps).
15
+ Attention-matrix rank is bounded by 2K: K is the bandwidth knob,
16
+ tau is the hardness knob.
17
+
18
+ BUCKET : hard address. Each token's winner oriented half-axis is its bucket;
19
+ exact softmax attention within sorted equal-width blocks (Reformer-style
20
+ sort-and-window), masked to same-bucket pairs. One gather-bound mode for
21
+ the A/B against the GEMM mode. Codebook receives gradient through a
22
+ differentiable address-agreement bias added to the scores (hard argmax
23
+ alone is gradient-dead w.r.t. the codebook).
24
+
25
+ Shared geometric discipline (geolip-svae invariants honored):
26
+ - q/k address rows are sphere-normalized onto S^(D_addr-1) (geometric premise)
27
+ - nn.init.orthogonal_ on the address projections (load-bearing)
28
+ - no BatchNorm, no Dropout on the geometric path, no GAP
29
+ - codebook init: 'random' | 'fibonacci' (super-Fibonacci S^3 at D=4) | (K,D) array
30
+ β€” 'custom' array supports TRANSPLANTING a trained AlephModel codebook.
31
+
32
+ Preregistered basin test (decide before running):
33
+ Train the routing codebook from scratch on a sequence task, then run the
34
+ geolip-svae antipodal-collapse extraction on export_codebook().
35
+ CLEAN (|deviation| < 0.05 on RP^(D-1)) -> cross-objective attractor evidence.
36
+ DIRTY -> the attractor is reconstruction-specific.
37
+ Either answer is data.
38
+
39
+ Compile discipline (Phil's rule): forward() returns a single Tensor. All
40
+ diagnostics (perplexity, margin, bucket load, confidence) live in the separate
41
+ no-grad address_stats() method β€” never in the compiled hot path.
42
+
43
+ Prior-art honesty for the writeup: hub is the linear-transformer/Performer
44
+ family (kernel feature maps) crossed with Set-Transformer inducing points;
45
+ bucket rhymes with Reformer/Routing Transformer. Novel content: the signed
46
+ antipodal closed form as feature map, spherical D-space addresses, codebook
47
+ transplant from reconstruction alephs, and the attractor test.
48
+
49
+ Author: AbstractPhil + Mirel
50
+ Date: 2026-06-09
51
+ License: MIT
52
+ """
53
+
54
+ from __future__ import annotations
55
+
56
+ import math
57
+ from dataclasses import dataclass
58
+ from typing import Optional, Tuple, Dict
59
+
60
+ import torch
61
+ import torch.nn as nn
62
+ import torch.nn.functional as F
63
+ from torch import Tensor
64
+
65
+
66
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
67
+ # Config
68
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
69
+
70
+ @dataclass
71
+ class AlephAttentionConfig:
72
+ """Configuration for AlephRoutedAttention.
73
+
74
+ Args:
75
+ dim: model dimension
76
+ num_heads: attention heads
77
+ mode: 'hub' (linear, GEMM-only) | 'bucket' (hard-address cliques)
78
+ K: codebook axes (oriented axes = 2K). Rank/bandwidth knob.
79
+ D_addr: address-space dimension (rows live on S^(D_addr-1))
80
+ tau: address temperature. Small -> near-discrete routing,
81
+ large -> mean-pool collapse. aleph reference: 0.1
82
+ codebook_init: 'random' | 'fibonacci' | (K, D_addr) tensor/array
83
+ (transplant a trained AlephModel codebook here)
84
+ freeze_codebook: register codebook as a buffer (no gradient). Only safe
85
+ once a drift check confirms the init IS the attractor.
86
+ causal: autoregressive masking (both modes)
87
+ chunk_size: hub-causal chunk width (exact chunked linear attention)
88
+ block_size: bucket-mode sorted-window width W (keys window = 2W
89
+ via 1-block lookback)
90
+ bucket_bias_scale_init: init of the learnable scale on the differentiable
91
+ address-agreement bias (the codebook's gradient path in
92
+ bucket mode)
93
+ confidence_gate: multiply head outputs by aleph address confidence
94
+ ||(p+ - p-) @ A|| (experimental; default off)
95
+ qkv_bias / out_bias: projection biases
96
+ dropout: output-projection dropout ONLY (never on the geometric path)
97
+ eps: numerical floor for denominators
98
+ """
99
+ dim: int = 512
100
+ num_heads: int = 8
101
+ mode: str = "hub" # 'hub' | 'bucket'
102
+ K: int = 64
103
+ D_addr: int = 4
104
+ tau: float = 0.1
105
+ codebook_init: object = "fibonacci"
106
+ freeze_codebook: bool = False
107
+ causal: bool = False
108
+ chunk_size: int = 128
109
+ block_size: int = 64
110
+ bucket_bias_scale_init: float = 1.0
111
+ confidence_gate: bool = False
112
+ tied_address: bool = False # share q/k address projection. EMPIRICAL (2026-06-09
113
+ # CPU recall A/B): tying HURTS β€” sharp self-affinity
114
+ # at low tau structurally biases routing to self
115
+ # (same family as softmax(1/d) collapse). Keep False.
116
+ qkv_bias: bool = False
117
+ out_bias: bool = True
118
+ dropout: float = 0.0
119
+ eps: float = 1e-8
120
+
121
+ def __post_init__(self):
122
+ assert self.mode in ("hub", "bucket"), f"mode must be 'hub'|'bucket', got {self.mode!r}"
123
+ assert self.dim % self.num_heads == 0, \
124
+ f"dim ({self.dim}) must be divisible by num_heads ({self.num_heads})"
125
+ self.head_dim = self.dim // self.num_heads
126
+ assert self.K >= 2 and self.D_addr >= 2
127
+ assert self.tau > 0 and self.eps > 0
128
+ assert self.chunk_size > 0 and self.block_size > 0
129
+
130
+
131
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
132
+ # Codebook init (ported from geolip-svae aleph_model.py β€” self-contained)
133
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
134
+
135
+ def _super_fibonacci_s3(n: int, dtype=torch.float32) -> Tensor:
136
+ """n near-uniform unit quaternions on S^3 via super-Fibonacci spirals
137
+ (Alexa, CVPR 2022). Deterministic, low-discrepancy. Returns (n, 4)."""
138
+ PHI = math.sqrt(2.0)
139
+ PSI = 1.533751168755204288118041
140
+ i = torch.arange(n, dtype=torch.float64) + 0.5
141
+ s = i / n
142
+ r = torch.sqrt(s)
143
+ R = torch.sqrt(1.0 - s)
144
+ alpha = 2.0 * math.pi * i / PHI
145
+ beta = 2.0 * math.pi * i / PSI
146
+ q = torch.stack([r * torch.sin(alpha), r * torch.cos(alpha),
147
+ R * torch.sin(beta), R * torch.cos(beta)], dim=-1)
148
+ return q.to(dtype)
149
+
150
+
151
+ def _init_codebook(K: int, D: int, init, dtype=torch.float32) -> Tensor:
152
+ """'random' Gaussian | 'fibonacci' near-uniform spread (exact at D=4,
153
+ seeded-normalized fallback otherwise) | caller (K, D) array, row-normalized
154
+ β€” the transplant path for a trained AlephModel codebook."""
155
+ if isinstance(init, str):
156
+ if init == "random":
157
+ return torch.randn(K, D, dtype=dtype)
158
+ if init == "fibonacci":
159
+ if D == 4:
160
+ return F.normalize(_super_fibonacci_s3(K, dtype=dtype), dim=-1)
161
+ g = torch.Generator().manual_seed(0)
162
+ return F.normalize(torch.randn(K, D, generator=g, dtype=dtype), dim=-1)
163
+ raise ValueError(f"unknown codebook_init '{init}'")
164
+ A = torch.as_tensor(init, dtype=dtype)
165
+ if tuple(A.shape) != (K, D):
166
+ raise ValueError(f"codebook_init array shape {tuple(A.shape)} != ({K}, {D})")
167
+ return F.normalize(A, dim=-1)
168
+
169
+
170
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
171
+ # Main module
172
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
173
+
174
+ class AlephRoutedAttention(nn.Module):
175
+ """Attention routed through a learned (K, D_addr) projective codebook.
176
+
177
+ Per head, queries and keys are projected to D_addr and sphere-normalized.
178
+ The aleph address p(x) = softmax([u; -u]), u = (x_hat @ A^T) / tau
179
+ is the routing medium:
180
+
181
+ mode='hub' tokens communicate THROUGH the codebook β€” linear attention
182
+ with p as the feature map, antipodal-factored to K-wide ops.
183
+ mode='bucket' tokens attend only to same-winner-half-axis peers β€” exact
184
+ softmax within sorted blocks.
185
+
186
+ forward(x, attn_mask=None) -> Tensor (B, S, dim). Diagnostics: address_stats().
187
+ """
188
+
189
+ def __init__(self, config: AlephAttentionConfig):
190
+ super().__init__()
191
+ self.cfg = config
192
+ c = config
193
+ self.dim, self.H, self.hd = c.dim, c.num_heads, c.head_dim
194
+ self.K, self.Da, self.tau = c.K, c.D_addr, c.tau
195
+
196
+ # ── projections ──
197
+ # address projections: per-head D_addr rows for q and k (the routing space)
198
+ self.q_addr = nn.Linear(c.dim, self.H * self.Da, bias=c.qkv_bias)
199
+ nn.init.orthogonal_(self.q_addr.weight) # load-bearing convention
200
+ if c.tied_address:
201
+ self.k_addr = self.q_addr # one routing space
202
+ else:
203
+ self.k_addr = nn.Linear(c.dim, self.H * self.Da, bias=c.qkv_bias)
204
+ nn.init.orthogonal_(self.k_addr.weight)
205
+ # value projection: full head_dim payload
206
+ self.v_proj = nn.Linear(c.dim, c.dim, bias=c.qkv_bias)
207
+ self.out_proj = nn.Linear(c.dim, c.dim, bias=c.out_bias)
208
+ self.dropout = nn.Dropout(c.dropout) # output path only
209
+
210
+ # bucket mode additionally scores with full-width q/k (payload attention
211
+ # inside the clique); hub routes purely through the address
212
+ if c.mode == "bucket":
213
+ self.q_proj = nn.Linear(c.dim, c.dim, bias=c.qkv_bias)
214
+ self.k_proj = nn.Linear(c.dim, c.dim, bias=c.qkv_bias)
215
+ nn.init.orthogonal_(self.q_proj.weight)
216
+ nn.init.orthogonal_(self.k_proj.weight)
217
+ self.bucket_bias_scale = nn.Parameter(
218
+ torch.tensor(float(c.bucket_bias_scale_init)))
219
+ self.scale = 1.0 / math.sqrt(self.hd)
220
+
221
+ # ── the aleph codebook ──
222
+ A0 = _init_codebook(c.K, c.D_addr, c.codebook_init)
223
+ if c.freeze_codebook:
224
+ self.register_buffer("codebook", A0)
225
+ else:
226
+ self.codebook = nn.Parameter(A0)
227
+
228
+ # diversity-loss hook: stash the mean address (WITH grad) when armed
229
+ self.emit_diversity: bool = False
230
+ self._mean_address: Optional[Tensor] = None # (2K,) when armed
231
+
232
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
233
+ # Address machinery (shared)
234
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
235
+
236
+ def oriented_codebook(self) -> Tensor:
237
+ """(2K, D_addr) oriented half-axes [+A; -A], unit rows."""
238
+ A = F.normalize(self.codebook, dim=-1)
239
+ return torch.cat([A, -A], dim=0)
240
+
241
+ def export_codebook(self) -> Tensor:
242
+ """Normalized (K, D_addr) axes for the geolip-svae antipodal-collapse
243
+ extraction β€” the preregistered basin test entry point."""
244
+ return F.normalize(self.codebook.detach(), dim=-1).cpu()
245
+
246
+ def _split_addr(self, t: Tensor, B: int, S: int) -> Tensor:
247
+ """(B, S, H*Da) -> (B, H, S, Da), rows sphere-normalized."""
248
+ t = t.view(B, S, self.H, self.Da).transpose(1, 2)
249
+ return F.normalize(t, dim=-1) # S^(D_addr-1): the premise
250
+
251
+ def _address(self, x_hat: Tensor) -> Tuple[Tensor, Tensor]:
252
+ """Aleph address of unit rows x_hat (..., Da) against the codebook.
253
+
254
+ Returns (p_plus, p_minus), each (..., K), with
255
+ p_plus_k = e^{ u_k} / Z, p_minus_k = e^{-u_k} / Z,
256
+ Z = sum_k (e^{u_k} + e^{-u_k}), u = (x_hat @ A^T)/tau
257
+ i.e. the exact softmax over the 2K oriented axes, antipodally factored:
258
+ the 2K tensor is never materialized. Stable via max|u| subtraction
259
+ (at least one exponent is exactly e^0, so Z' >= 1)."""
260
+ A = F.normalize(self.codebook, dim=-1) # (K, Da)
261
+ u = (x_hat @ A.t()) * (1.0 / self.tau) # (..., K) signed
262
+ m = u.abs().amax(dim=-1, keepdim=True)
263
+ ep = torch.exp(u - m) # ∝ e^{+u}
264
+ en = torch.exp(-u - m) # ∝ e^{-u}
265
+ Z = (ep + en).sum(dim=-1, keepdim=True) # >= 1 by construction
266
+ return ep / Z, en / Z
267
+
268
+ def _confidence(self, pq_p: Tensor, pq_m: Tensor) -> Tensor:
269
+ """Aleph address confidence ||(p+ - p-) @ A|| in (0, 1] β€” the norm of the
270
+ soft codebook reconstruction (the hub analogue of ||M_hat||)."""
271
+ A = F.normalize(self.codebook, dim=-1)
272
+ return ((pq_p - pq_m) @ A).norm(dim=-1) # (..., )
273
+
274
+ def _stash_diversity(self, pk_p: Tensor, pk_m: Tensor,
275
+ mask: Optional[Tensor]) -> None:
276
+ """Mean address over valid key rows -> (2K,) with grad, for diversity_loss()."""
277
+ if not (self.emit_diversity and self.training):
278
+ return
279
+ if mask is not None:
280
+ w = mask[:, None, :, None].to(pk_p.dtype) # (B,1,S,1)
281
+ n = w.sum().clamp_min(1.0) * self.H
282
+ mp = (pk_p * w).sum(dim=(0, 1, 2)) / n
283
+ mm = (pk_m * w).sum(dim=(0, 1, 2)) / n
284
+ else:
285
+ mp = pk_p.mean(dim=(0, 1, 2))
286
+ mm = pk_m.mean(dim=(0, 1, 2))
287
+ self._mean_address = torch.cat([mp, mm], dim=0) # (2K,)
288
+
289
+ def diversity_loss(self) -> Tensor:
290
+ """Anti-collapse term (train_aleph div_weight semantics):
291
+ log(2K) - H(mean address). Zero at uniform usage. Arm with
292
+ model.emit_diversity = True; read after forward; weight ~0.01."""
293
+ if self._mean_address is None:
294
+ return torch.zeros((), device=self.codebook.device)
295
+ p = self._mean_address.clamp_min(1e-12)
296
+ H = -(p * p.log()).sum()
297
+ return math.log(2 * self.K) - H
298
+
299
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
300
+ # HUB mode β€” linear attention through the codebook (pure GEMM)
301
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
302
+
303
+ def _hub_full(self, pq_p, pq_m, pk_p, pk_m, v) -> Tensor:
304
+ """Non-causal hub. p*: (B,H,S,K), v: (B,H,S,hd) -> (B,H,S,hd).
305
+
306
+ score(i,j) = pq+(i)Β·pk+(j) + pq-(i)Β·pk-(j) factors through two K-wide
307
+ memories; out_i = num_i / den_i with den strictly positive."""
308
+ Mp = torch.einsum('bhsk,bhsd->bhkd', pk_p, v) # (B,H,K,hd)
309
+ Mm = torch.einsum('bhsk,bhsd->bhkd', pk_m, v)
310
+ zp = pk_p.sum(dim=2) # (B,H,K)
311
+ zm = pk_m.sum(dim=2)
312
+ num = torch.einsum('bhsk,bhkd->bhsd', pq_p, Mp) \
313
+ + torch.einsum('bhsk,bhkd->bhsd', pq_m, Mm)
314
+ den = torch.einsum('bhsk,bhk->bhs', pq_p, zp) \
315
+ + torch.einsum('bhsk,bhk->bhs', pq_m, zm)
316
+ return num / den.unsqueeze(-1).clamp_min(self.cfg.eps)
317
+
318
+ def _hub_causal(self, pq_p, pq_m, pk_p, pk_m, v,
319
+ state: Optional[Tuple[Tensor, ...]] = None
320
+ ) -> Tuple[Tensor, Tuple[Tensor, ...]]:
321
+ """Exact chunked causal hub: running K-wide state across chunks +
322
+ lower-triangular intra-chunk correction. Loop count = S/chunk_size
323
+ (the standard chunked linear-attention recurrence β€” not a per-token loop).
324
+
325
+ `state` = (Mp, Mm, zp, zm) carried from previous segments. The state is
326
+ constant-size β€” (B,H,K,hd)+(B,H,K) per sign β€” regardless of how much
327
+ past it summarizes: Mp/Mm are what has been written to each oriented
328
+ codebook axis so far. Returns (out, final_state) for streaming."""
329
+ B, H, S, _ = v.shape
330
+ C = min(self.cfg.chunk_size, S)
331
+ if state is None:
332
+ Mp = v.new_zeros(B, H, self.K, self.hd)
333
+ Mm = v.new_zeros(B, H, self.K, self.hd)
334
+ zp = v.new_zeros(B, H, self.K)
335
+ zm = v.new_zeros(B, H, self.K)
336
+ else:
337
+ Mp, Mm, zp, zm = state
338
+ outs = []
339
+ tri_cache: Dict[int, Tensor] = {}
340
+ for s0 in range(0, S, C):
341
+ s1 = min(s0 + C, S)
342
+ qp, qm = pq_p[:, :, s0:s1], pq_m[:, :, s0:s1]
343
+ kp, km = pk_p[:, :, s0:s1], pk_m[:, :, s0:s1]
344
+ vc = v[:, :, s0:s1]
345
+ c = s1 - s0
346
+ if c not in tri_cache:
347
+ tri_cache[c] = torch.tril(
348
+ torch.ones(c, c, device=v.device, dtype=v.dtype))
349
+ tri = tri_cache[c]
350
+ # intra-chunk (causal) scores β€” strictly positive entries pre-mask
351
+ intra = (torch.einsum('bhik,bhjk->bhij', qp, kp)
352
+ + torch.einsum('bhik,bhjk->bhij', qm, km)) * tri
353
+ num = intra @ vc \
354
+ + torch.einsum('bhsk,bhkd->bhsd', qp, Mp) \
355
+ + torch.einsum('bhsk,bhkd->bhsd', qm, Mm)
356
+ den = intra.sum(dim=-1) \
357
+ + torch.einsum('bhsk,bhk->bhs', qp, zp) \
358
+ + torch.einsum('bhsk,bhk->bhs', qm, zm)
359
+ outs.append(num / den.unsqueeze(-1).clamp_min(self.cfg.eps))
360
+ # state update (inclusive of this chunk, for the next one)
361
+ Mp = Mp + torch.einsum('bhck,bhcd->bhkd', kp, vc)
362
+ Mm = Mm + torch.einsum('bhck,bhcd->bhkd', km, vc)
363
+ zp = zp + kp.sum(dim=2)
364
+ zm = zm + km.sum(dim=2)
365
+ return torch.cat(outs, dim=2), (Mp, Mm, zp, zm)
366
+
367
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
368
+ # BUCKET mode β€” hard-address cliques (sort + windowed exact attention)
369
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
370
+
371
+ @staticmethod
372
+ def _take(t: Tensor, idx: Tensor) -> Tensor:
373
+ """Gather along dim=2. t: (B,H,S,X) or (B,H,S); idx: (B,H,S)."""
374
+ if t.dim() == 3:
375
+ return torch.gather(t, 2, idx)
376
+ return torch.gather(t, 2, idx.unsqueeze(-1).expand(-1, -1, -1, t.shape[-1]))
377
+
378
+ @staticmethod
379
+ def _window(t: Tensor, nb: int, W: int) -> Tensor:
380
+ """Blocked tensor (B,H,nb,W,...) -> (B,H,nb,2W,...) keys window =
381
+ [previous block ; this block]. Block 0's previous half is junk β€”
382
+ callers must kill it via the validity window."""
383
+ prev = torch.cat([torch.zeros_like(t[:, :, :1]), t[:, :, :-1]], dim=2)
384
+ return torch.cat([prev, t], dim=3)
385
+
386
+ def _bucket_attend(self, q, k, v, pq_p, pq_m, pk_p, pk_m,
387
+ mask: Optional[Tensor]) -> Tensor:
388
+ """q,k,v: (B,H,S,hd); p*: (B,H,S,K); mask: (B,S) 1=valid or None.
389
+
390
+ 1. bucket = winner oriented half-axis (argmax |u|, sign-resolved)
391
+ 2. stable-sort tokens by bucket; pad S to a multiple of W
392
+ 3. exact softmax attention within [prev block ; block] windows,
393
+ masked to same-bucket, valid, (and causal by original position)
394
+ 4. differentiable codebook path: scores += scale * address-agreement
395
+ 5. inverse-permute, un-pad."""
396
+ cfg = self.cfg
397
+ B, H, S, hd = q.shape
398
+ W = min(cfg.block_size, max(8, S))
399
+ dev = q.device
400
+
401
+ # ── 1. hard bucket ids ── (recover signed u from the address: u = (log ep - log en)/2
402
+ # is unnecessary β€” argmax of p_plus vs p_minus IS argmax |u| with sign)
403
+ win_p, idx_p = pq_p.max(dim=-1) # query side unused for ids
404
+ # bucket from the KEY/QUERY shared address rows: use each token's own address
405
+ # (q-side and k-side addresses may differ; routing identity = q-address for
406
+ # queries, k-address for keys β€” a token can listen in one clique and speak in
407
+ # another. We bucket by the K-side address for keys and Q-side for queries,
408
+ # then require equality β€” implemented by bucketing each side independently.)
409
+ def hard_ids(pp: Tensor, pm: Tensor) -> Tensor:
410
+ vp, ip = pp.max(dim=-1)
411
+ vm, im = pm.max(dim=-1)
412
+ plus_wins = vp >= vm
413
+ return torch.where(plus_wins, ip, im + self.K) # (B,H,S) in [0, 2K)
414
+
415
+ bq = hard_ids(pq_p, pq_m)
416
+ bk = hard_ids(pk_p, pk_m)
417
+ valid = (mask if mask is not None
418
+ else torch.ones(B, S, device=dev, dtype=torch.bool))
419
+ valid = valid.bool()[:, None, :].expand(B, H, S)
420
+ JUNK = 2 * self.K + 1
421
+ bq = torch.where(valid, bq, torch.full_like(bq, JUNK))
422
+ bk = torch.where(valid, bk, torch.full_like(bk, JUNK))
423
+
424
+ # ── 2. pad to multiple of W, sort by key-bucket ──
425
+ pad = (-S) % W
426
+ if pad:
427
+ def padS(t, fill=0.0):
428
+ shape = list(t.shape); shape[2] = pad
429
+ return torch.cat([t, t.new_full(shape, fill)], dim=2)
430
+ q, k, v = padS(q), padS(k), padS(v)
431
+ pq_p, pq_m, pk_p, pk_m = padS(pq_p), padS(pq_m), padS(pk_p), padS(pk_m)
432
+ bq, bk = padS(bq, JUNK), padS(bk, JUNK)
433
+ valid = padS(valid, False)
434
+ Sp = S + pad
435
+ nb = Sp // W
436
+ pos = torch.arange(Sp, device=dev).view(1, 1, Sp).expand(B, H, Sp)
437
+
438
+ sort_idx = bk.argsort(dim=-1, stable=True) # cluster keys by bucket
439
+ inv_idx = sort_idx.argsort(dim=-1)
440
+ gq, gk, gv = self._take(q, sort_idx), self._take(k, sort_idx), self._take(v, sort_idx)
441
+ gpq_p, gpq_m = self._take(pq_p, sort_idx), self._take(pq_m, sort_idx)
442
+ gpk_p, gpk_m = self._take(pk_p, sort_idx), self._take(pk_m, sort_idx)
443
+ gbq, gbk = self._take(bq, sort_idx), self._take(bk, sort_idx)
444
+ gvalid, gpos = self._take(valid.long(), sort_idx).bool(), self._take(pos, sort_idx)
445
+
446
+ def blk(t):
447
+ return t.view(B, H, nb, W, *t.shape[3:])
448
+ q_b, v_b = blk(gq), blk(gv)
449
+ k_w = self._window(blk(gk), nb, W) # (B,H,nb,2W,hd)
450
+ v_w = self._window(blk(gv), nb, W)
451
+ pkp_w = self._window(blk(gpk_p), nb, W)
452
+ pkm_w = self._window(blk(gpk_m), nb, W)
453
+ bq_b = blk(gbq)
454
+ bk_w = self._window(blk(gbk).unsqueeze(-1), nb, W).squeeze(-1)
455
+ val_w = self._window(blk(gvalid.long()).unsqueeze(-1), nb, W).squeeze(-1).bool()
456
+ pos_b = blk(gpos)
457
+ pos_w = self._window(blk(gpos).unsqueeze(-1), nb, W).squeeze(-1)
458
+ val_w[:, :, 0, :W] = False # block 0 has no previous
459
+
460
+ # ── 3. scores: payload qΒ·k within the window ──
461
+ scores = torch.einsum('bhnwd,bhnud->bhnwu', q_b, k_w) * self.scale
462
+
463
+ # ── 4. differentiable address-agreement bias (codebook gradient path) ──
464
+ pqp_b, pqm_b = blk(gpq_p), blk(gpq_m)
465
+ agreement = torch.einsum('bhnwk,bhnuk->bhnwu', pqp_b, pkp_w) \
466
+ + torch.einsum('bhnwk,bhnuk->bhnwu', pqm_b, pkm_w)
467
+ scores = scores + self.bucket_bias_scale * agreement
468
+
469
+ # ── masks: same bucket, valid, causal ──
470
+ same = bq_b.unsqueeze(-1) == bk_w.unsqueeze(-2) # (B,H,nb,W,2W)
471
+ keep = same & val_w.unsqueeze(-2)
472
+ if cfg.causal:
473
+ keep = keep & (pos_w.unsqueeze(-2) <= pos_b.unsqueeze(-1))
474
+ scores = scores.masked_fill(~keep, float('-inf'))
475
+ attn = F.softmax(scores, dim=-1)
476
+ attn = torch.nan_to_num(attn, nan=0.0) # all-masked rows = pads only
477
+ out_b = torch.einsum('bhnwu,bhnud->bhnwd', attn, v_w)
478
+
479
+ # ── 5. inverse permute, un-pad ─��
480
+ out = out_b.reshape(B, H, Sp, hd)
481
+ out = self._take(out, inv_idx)
482
+ return out[:, :, :S]
483
+
484
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
485
+ # Forward (returns a single Tensor β€” compile-rule compliant)
486
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
487
+
488
+ def forward(self, x: Tensor, attn_mask: Optional[Tensor] = None) -> Tensor:
489
+ """x: (B, S, dim); attn_mask: (B, S) with 1 = valid, 0 = padding.
490
+ Returns (B, S, dim)."""
491
+ B, S, _ = x.shape
492
+ cfg = self.cfg
493
+
494
+ qh = self._split_addr(self.q_addr(x), B, S) # (B,H,S,Da) on the sphere
495
+ kh = self._split_addr(self.k_addr(x), B, S)
496
+ v = self.v_proj(x).view(B, S, self.H, self.hd).transpose(1, 2)
497
+
498
+ pq_p, pq_m = self._address(qh) # (B,H,S,K) each
499
+ pk_p, pk_m = self._address(kh)
500
+ self._stash_diversity(pk_p, pk_m, attn_mask)
501
+
502
+ if attn_mask is not None:
503
+ mk = attn_mask[:, None, :, None].to(v.dtype) # kill masked KEYS
504
+ pk_p, pk_m, v_in = pk_p * mk, pk_m * mk, v * mk
505
+ else:
506
+ v_in = v
507
+
508
+ if cfg.mode == "hub":
509
+ if cfg.causal:
510
+ out, _ = self._hub_causal(pq_p, pq_m, pk_p, pk_m, v_in)
511
+ else:
512
+ out = self._hub_full(pq_p, pq_m, pk_p, pk_m, v_in)
513
+ else: # bucket
514
+ qf = self.q_proj(x).view(B, S, self.H, self.hd).transpose(1, 2)
515
+ kf = self.k_proj(x).view(B, S, self.H, self.hd).transpose(1, 2)
516
+ out = self._bucket_attend(qf, kf, v, pq_p, pq_m, pk_p, pk_m, attn_mask)
517
+
518
+ if cfg.confidence_gate:
519
+ out = out * self._confidence(pq_p, pq_m).unsqueeze(-1)
520
+
521
+ out = out.transpose(1, 2).reshape(B, S, self.dim)
522
+ return self.dropout(self.out_proj(out))
523
+
524
+ def forward_stream(self, x: Tensor,
525
+ state: Optional[Tuple[Tensor, ...]] = None,
526
+ attn_mask: Optional[Tensor] = None
527
+ ) -> Tuple[Tensor, Tuple[Tensor, ...]]:
528
+ """Segment-recurrent forward (mode='hub', causal=True only).
529
+
530
+ Processes a segment with the codebook memory carried in `state`
531
+ (init None = empty past), returns (out, new_state). Context is
532
+ unbounded at constant memory: state is (Mp, Mm, zp, zm), shape
533
+ (B,H,K,hd)x2 + (B,H,K)x2, independent of total past length.
534
+ TBPTT discipline: .detach() each state tensor between backward
535
+ passes β€” graphs are freed per segment."""
536
+ assert self.cfg.mode == "hub" and self.cfg.causal, \
537
+ "forward_stream requires mode='hub', causal=True (bucket sorts globally)"
538
+ B, S, _ = x.shape
539
+ qh = self._split_addr(self.q_addr(x), B, S)
540
+ kh = self._split_addr(self.k_addr(x), B, S)
541
+ v = self.v_proj(x).view(B, S, self.H, self.hd).transpose(1, 2)
542
+ pq_p, pq_m = self._address(qh)
543
+ pk_p, pk_m = self._address(kh)
544
+ self._stash_diversity(pk_p, pk_m, attn_mask)
545
+ if attn_mask is not None:
546
+ mk = attn_mask[:, None, :, None].to(v.dtype)
547
+ pk_p, pk_m, v = pk_p * mk, pk_m * mk, v * mk
548
+ out, new_state = self._hub_causal(pq_p, pq_m, pk_p, pk_m, v, state)
549
+ if self.cfg.confidence_gate:
550
+ out = out * self._confidence(pq_p, pq_m).unsqueeze(-1)
551
+ out = out.transpose(1, 2).reshape(B, S, self.dim)
552
+ return self.dropout(self.out_proj(out)), new_state
553
+
554
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
555
+ # Diagnostics (eval-only; never in the hot path)
556
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
557
+
558
+ @torch.no_grad()
559
+ def address_stats(self, x: Tensor,
560
+ attn_mask: Optional[Tensor] = None,
561
+ max_rows: Optional[int] = None) -> Dict[str, float]:
562
+ """Codebook-health monitors (train_aleph semantics):
563
+ perplexity : exp(H(mean address)) β€” effective oriented axes in use,
564
+ in [1, 2K]. The collapse detector.
565
+ margin : mean (top1 - top2) of per-row address β€” decisiveness.
566
+ confidence : mean ||(p+ - p-) @ A|| β€” address sharpness in (0, 1].
567
+ bucket_cv : coefficient of variation of hard-bucket occupancy
568
+ (load-balance; bucket mode's health metric).
569
+ """
570
+ B, S, _ = x.shape
571
+ kh = self._split_addr(self.k_addr(x), B, S)
572
+ pp, pm = self._address(kh)
573
+ if attn_mask is not None:
574
+ m = attn_mask.bool()[:, None, :].expand(B, self.H, S)
575
+ pp = pp[m]; pm = pm[m] # (R, K)
576
+ else:
577
+ pp = pp.reshape(-1, self.K); pm = pm.reshape(-1, self.K)
578
+ full = torch.cat([pp, pm], dim=-1) # (R, 2K)
579
+ if max_rows is not None and full.shape[0] > max_rows:
580
+ full = full[torch.randperm(full.shape[0])[:max_rows]]
581
+ pp, pm = full[:, :self.K], full[:, self.K:]
582
+
583
+ mean_addr = full.mean(dim=0).clamp_min(1e-12)
584
+ H = -(mean_addr * mean_addr.log()).sum()
585
+ perplexity = H.exp().item()
586
+
587
+ top2 = full.topk(2, dim=-1).values
588
+ margin = (top2[:, 0] - top2[:, 1]).mean().item()
589
+
590
+ A = F.normalize(self.codebook, dim=-1)
591
+ confidence = ((pp - pm) @ A).norm(dim=-1).mean().item()
592
+
593
+ ids = full.argmax(dim=-1)
594
+ occ = torch.bincount(ids, minlength=2 * self.K).float()
595
+ bucket_cv = (occ.std(unbiased=False) / occ.mean().clamp_min(1e-12)).item()
596
+
597
+ return {"perplexity": perplexity, "margin": margin,
598
+ "confidence": confidence, "bucket_cv": bucket_cv,
599
+ "max_perplexity": float(2 * self.K)}
600
+
601
+ def extra_repr(self) -> str:
602
+ c = self.cfg
603
+ return (f"dim={c.dim}, heads={c.num_heads}, mode={c.mode}, "
604
+ f"K={c.K} (2K={2*c.K} oriented), D_addr={c.D_addr}, "
605
+ f"tau={c.tau}, causal={c.causal}")
606
+
607
+
608
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
609
+ # Reference baseline (for the harness A/B)
610
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
611
+
612
+ class StandardAttention(nn.Module):
613
+ """Plain softmax attention, same I/O contract, for the A/B."""
614
+
615
+ def __init__(self, dim: int, num_heads: int, causal: bool = False):
616
+ super().__init__()
617
+ assert dim % num_heads == 0
618
+ self.H, self.hd, self.causal = num_heads, dim // num_heads, causal
619
+ self.qkv = nn.Linear(dim, 3 * dim, bias=False)
620
+ self.out_proj = nn.Linear(dim, dim)
621
+ self.scale = 1.0 / math.sqrt(self.hd)
622
+
623
+ def forward(self, x: Tensor, attn_mask: Optional[Tensor] = None) -> Tensor:
624
+ B, S, D = x.shape
625
+ q, k, v = self.qkv(x).view(B, S, 3, self.H, self.hd) \
626
+ .permute(2, 0, 3, 1, 4).unbind(0)
627
+ scores = (q @ k.transpose(-2, -1)) * self.scale
628
+ if attn_mask is not None:
629
+ scores = scores.masked_fill(
630
+ ~attn_mask.bool()[:, None, None, :], float('-inf'))
631
+ if self.causal:
632
+ tri = torch.ones(S, S, device=x.device, dtype=torch.bool).tril()
633
+ scores = scores.masked_fill(~tri, float('-inf'))
634
+ out = torch.nan_to_num(F.softmax(scores, dim=-1), nan=0.0) @ v
635
+ return self.out_proj(out.transpose(1, 2).reshape(B, S, D))
636
+
637
+
638
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
639
+ # Harness β€” associative recall (routing-sensitive synthetic task)
640
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
641
+ #
642
+ # Sequence = [k1 v1 k2 v2 ... kn vn Q kq] -> predict the value paired with kq.
643
+ # Solvable only by routing the query token to the matching key token: a task
644
+ # where the routing medium IS the bottleneck. Trained with pure Adam (never
645
+ # AdamW β€” weight decay fights the geometric basin).
646
+
647
+ class TinyRecallModel(nn.Module):
648
+ def __init__(self, vocab: int, dim: int, attn: nn.Module, n_layers: int = 2,
649
+ attn_factory=None):
650
+ super().__init__()
651
+ self.emb = nn.Embedding(vocab, dim)
652
+ self.pos = nn.Parameter(0.02 * torch.randn(1, 512, dim))
653
+ layers = []
654
+ for i in range(n_layers):
655
+ a = attn if (i == 0 and attn_factory is None) else attn_factory()
656
+ layers.append(nn.ModuleDict({
657
+ "norm1": nn.LayerNorm(dim), "attn": a,
658
+ "norm2": nn.LayerNorm(dim),
659
+ "mlp": nn.Sequential(nn.Linear(dim, 2 * dim), nn.GELU(),
660
+ nn.Linear(2 * dim, dim)),
661
+ }))
662
+ self.layers = nn.ModuleList(layers)
663
+ self.head = nn.Linear(dim, vocab)
664
+
665
+ def forward(self, ids: Tensor) -> Tensor:
666
+ x = self.emb(ids) + self.pos[:, :ids.shape[1]]
667
+ for L in self.layers:
668
+ x = x + L["attn"](L["norm1"](x))
669
+ x = x + L["mlp"](L["norm2"](x))
670
+ return self.head(x[:, -1]) # predict from final token
671
+
672
+
673
+ def make_recall_batch(B: int, n_pairs: int, n_keys: int, n_vals: int,
674
+ device) -> Tuple[Tensor, Tensor]:
675
+ """Tokens: [0, n_keys) keys | [n_keys, n_keys+n_vals) values | Q = last id."""
676
+ Q = n_keys + n_vals
677
+ keys = torch.stack([torch.randperm(n_keys, device=device)[:n_pairs]
678
+ for _ in range(B)]) # unique keys per row
679
+ vals = torch.randint(0, n_vals, (B, n_pairs), device=device) + n_keys
680
+ seq = torch.stack([keys, vals], dim=-1).reshape(B, 2 * n_pairs)
681
+ qi = torch.randint(0, n_pairs, (B,), device=device)
682
+ kq = keys.gather(1, qi[:, None])
683
+ target = vals.gather(1, qi[:, None]).squeeze(1)
684
+ ids = torch.cat([seq, torch.full((B, 1), Q, device=device), kq], dim=1)
685
+ return ids, target
686
+
687
+
688
+ def run_harness(mode: str, steps: int = 300, device: str = "cpu",
689
+ seed: int = 1234, log_every: int = 50,
690
+ dim: int = 128, n_heads: int = 4, K: int = 32, D_addr: int = 4,
691
+ n_pairs: int = 12, n_keys: int = 48, n_vals: int = 24,
692
+ batch: int = 64, lr: float = 3e-4,
693
+ div_weight: float = 0.0, tied_address: bool = False,
694
+ codebook_init="fibonacci", lr_decay: bool = True,
695
+ snapshot_codebook: bool = False) -> Dict[str, float]:
696
+ torch.manual_seed(seed)
697
+ vocab = n_keys + n_vals + 1
698
+ if mode == "standard":
699
+ attn_factory = lambda: StandardAttention(dim, n_heads)
700
+ first = attn_factory()
701
+ else:
702
+ cfg = AlephAttentionConfig(dim=dim, num_heads=n_heads, mode=mode,
703
+ K=K, D_addr=D_addr, tau=0.1,
704
+ tied_address=tied_address,
705
+ codebook_init=codebook_init)
706
+ attn_factory = lambda: AlephRoutedAttention(cfg)
707
+ first = attn_factory()
708
+ model = TinyRecallModel(vocab, dim, first, n_layers=2,
709
+ attn_factory=attn_factory).to(device)
710
+ opt = torch.optim.Adam(model.parameters(), lr=lr) # pure Adam, never AdamW
711
+ sched = (torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=steps,
712
+ eta_min=lr * 0.1) if lr_decay else None)
713
+
714
+ aleph_layers = [m for m in model.modules() if isinstance(m, AlephRoutedAttention)]
715
+ for a in aleph_layers:
716
+ a.emit_diversity = div_weight > 0
717
+
718
+ print(f"\n=== mode={mode} params={sum(p.numel() for p in model.parameters()):,} ===")
719
+ final = {}
720
+ snapshots = [] # (step, (K,D)) trajectory
721
+ if snapshot_codebook and aleph_layers:
722
+ snapshots.append((0, aleph_layers[0].export_codebook()))
723
+ for step in range(1, steps + 1):
724
+ ids, target = make_recall_batch(batch, n_pairs, n_keys, n_vals, device)
725
+ logits = model(ids)
726
+ loss = F.cross_entropy(logits, target)
727
+ if div_weight > 0:
728
+ loss = loss + div_weight * sum(a.diversity_loss() for a in aleph_layers)
729
+ opt.zero_grad(set_to_none=True)
730
+ loss.backward()
731
+ gnorm = torch.nn.utils.clip_grad_norm_(
732
+ model.parameters(), max(loss.item(), 1.0)) # Phil's clip rule
733
+ opt.step()
734
+ if sched is not None:
735
+ sched.step()
736
+
737
+ if step % log_every == 0 or step == steps:
738
+ with torch.no_grad():
739
+ acc = (logits.argmax(-1) == target).float().mean().item()
740
+ line = f" step {step:4d} loss {loss.item():.4f} acc {acc:.3f} |g| {gnorm:.2f}"
741
+ if aleph_layers:
742
+ model.eval()
743
+ x_probe = model.emb(ids) + model.pos[:, :ids.shape[1]]
744
+ st = aleph_layers[0].address_stats(x_probe)
745
+ model.train()
746
+ line += (f" ppl {st['perplexity']:.1f}/{st['max_perplexity']:.0f}"
747
+ f" margin {st['margin']:.3f} conf {st['confidence']:.3f}"
748
+ f" bktCV {st['bucket_cv']:.2f}")
749
+ final.update(st)
750
+ print(line)
751
+ final.update({"loss": loss.item(), "acc": acc})
752
+ if snapshot_codebook and aleph_layers:
753
+ snapshots.append((step, aleph_layers[0].export_codebook()))
754
+ if snapshots:
755
+ final["codebook_snapshots"] = snapshots
756
+ return final
757
+
758
+
759
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
760
+ # Smoke tests + activation
761
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
762
+
763
+ def _smoke():
764
+ torch.manual_seed(0)
765
+ print("=" * 70)
766
+ print("AlephRoutedAttention β€” smoke tests")
767
+ print("=" * 70)
768
+
769
+ for mode in ("hub", "bucket"):
770
+ for causal in (False, True):
771
+ cfg = AlephAttentionConfig(dim=64, num_heads=4, mode=mode, K=16,
772
+ D_addr=4, causal=causal, block_size=16,
773
+ chunk_size=32)
774
+ m = AlephRoutedAttention(cfg)
775
+ x = torch.randn(2, 50, 64, requires_grad=True) # odd S: pad path
776
+ mask = torch.ones(2, 50); mask[1, 40:] = 0
777
+ y = m(x, attn_mask=mask)
778
+ assert y.shape == (2, 50, 64), y.shape
779
+ assert torch.isfinite(y).all()
780
+ y.sum().backward()
781
+ assert torch.isfinite(x.grad).all()
782
+ assert m.codebook.grad is not None and torch.isfinite(m.codebook.grad).all(), \
783
+ f"codebook got no/bad gradient in mode={mode}"
784
+ cb_g = m.codebook.grad.norm().item()
785
+ print(f" βœ“ mode={mode:6s} causal={causal!s:5s} out {tuple(y.shape)} "
786
+ f"codebook |grad|={cb_g:.4f}")
787
+ x.grad = None
788
+
789
+ # hub causal == hub full restricted? sanity: causal output at position i must
790
+ # not depend on tokens > i. Perturb a late token; early outputs must not move.
791
+ cfg = AlephAttentionConfig(dim=64, num_heads=4, mode="hub", K=16, D_addr=4,
792
+ causal=True, chunk_size=16)
793
+ m = AlephRoutedAttention(cfg).eval()
794
+ x = torch.randn(1, 40, 64)
795
+ y1 = m(x)
796
+ x2 = x.clone(); x2[0, 35] += 10.0
797
+ y2 = m(x2)
798
+ assert torch.allclose(y1[0, :35], y2[0, :35], atol=1e-5), "causality leak!"
799
+ print(" βœ“ hub causal: no future leakage (perturbation test)")
800
+
801
+ # stats sanity
802
+ st = m.address_stats(x)
803
+ assert 1.0 <= st["perplexity"] <= st["max_perplexity"] + 1e-3
804
+ print(f" βœ“ stats: {st}")
805
+
806
+ # diversity hook
807
+ m2 = AlephRoutedAttention(AlephAttentionConfig(dim=64, num_heads=4, K=16))
808
+ m2.train(); m2.emit_diversity = True
809
+ _ = m2(torch.randn(2, 20, 64))
810
+ d = m2.diversity_loss()
811
+ assert d.requires_grad and torch.isfinite(d)
812
+ print(f" βœ“ diversity_loss = {d.item():.4f} (grad-carrying)")
813
+
814
+ # fibonacci init at D=4 is unit + deterministic
815
+ A = _init_codebook(32, 4, "fibonacci")
816
+ assert torch.allclose(A.norm(dim=-1), torch.ones(32), atol=1e-5)
817
+ print(" βœ“ super-Fibonacci codebook init (D=4) unit rows")
818
+ print("All smoke tests passed.\n")
819
+
820
+
821
+ #if __name__ == "__main__":
822
+ # import argparse
823
+ # ap = argparse.ArgumentParser(description="Aleph-routed attention β€” smoke + A/B harness")
824
+ # ap.add_argument("--steps", type=int, default=300)
825
+ # ap.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
826
+ # ap.add_argument("--modes", nargs="+", default=["hub", "bucket", "standard"])
827
+ # ap.add_argument("--div-weight", type=float, default=0.0)
828
+ # ap.add_argument("--K", type=int, default=32)
829
+ # ap.add_argument("--tau", type=float, default=0.1)
830
+ # ap.add_argument("--smoke-only", action="store_true")
831
+ # # parse_known_args: ignore foreign argv (e.g. Jupyter/Colab injects
832
+ # # `-f /.../kernel-*.json`), so the module runs in notebooks unchanged
833
+ # args, _unknown = ap.parse_known_args()
834
+ #
835
+ # _smoke()
836
+ # if not args.smoke_only:
837
+ # results = {}
838
+ # for mode in args.modes:
839
+ # results[mode] = run_harness(mode, steps=args.steps, device=args.device,
840
+ # K=args.K, div_weight=args.div_weight)
841
+ # print("\n" + "=" * 70)
842
+ # print("A/B summary (associative recall)")
843
+ # for mode, r in results.items():
844
+ # extra = (f" ppl {r.get('perplexity', float('nan')):.1f}"
845
+ # f" margin {r.get('margin', float('nan')):.3f}"
846
+ # if "perplexity" in r else "")
847
+ # print(f" {mode:9s} loss {r['loss']:.4f} acc {r['acc']:.3f}{extra}")
848
+ # print("=" * 70)
849
+ # print("\nBasin test entry point: model.export_codebook() -> feed to the")
850
+ # print("geolip-svae antipodal-collapse extraction. Preregistered criterion:")
851
+ # print("|deviation| < 0.05 on RP^(D-1) = cross-objective attractor evidence.")