Quazim0t0 commited on
Commit
db5e2b4
·
verified ·
1 Parent(s): 8abb25c

Upload family.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. family.py +446 -0
family.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SpikeWhale / Byrne family traits, ported to Quazimoto-LM (attention backbone).
2
+
3
+ These are the *transformer-native* family blocks (their origin is the transformer
4
+ `modeling_byrne_embed.py`), so unlike the SNN port they operate directly on the
5
+ sequence hidden state [B,T,d] -- no per-step adaptation needed. Each keeps the
6
+ family's safe-at-init contract: a tanh/zero gate makes the block a no-op at start,
7
+ while the content (`up`/`down`) weights are NON-zero so the gate still receives
8
+ gradient (the double-zero saddle would deadlock it). DERF soft_clamp bounds any
9
+ new instability surface, in line with the family's stability discipline.
10
+
11
+ Included: HRMRefinementBlock (signature), MoESwiGLU, MTPHead, JEPAPredictorBlock.
12
+ Engram / ProgSem are bio/SNN-specific and SpikingLinearAttention is the SNN's
13
+ stand-in for the real attention this model already has, so they are omitted.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import math
18
+ import torch
19
+ import torch.nn as nn
20
+ import torch.nn.functional as F
21
+
22
+ import instrument as _viz # live-visualizer capture hooks (no-op unless a recorder is active)
23
+
24
+ # sqrt(pi)/2: soft_clamp is the identity for small inputs and saturates smoothly to
25
+ # +/-bound with a non-zero gradient everywhere (no dead-gradient zones).
26
+ _ERF_K = math.sqrt(math.pi) / 2.0
27
+
28
+
29
+ def soft_clamp(x, bound):
30
+ return bound * torch.erf(x * (_ERF_K / bound))
31
+
32
+
33
+ class RMSNorm(nn.Module):
34
+ def __init__(self, dim, eps=1e-6):
35
+ super().__init__()
36
+ self.eps = eps
37
+ self.weight = nn.Parameter(torch.ones(dim))
38
+
39
+ def forward(self, x):
40
+ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.weight
41
+
42
+
43
+ def sqrtsoftplus(x):
44
+ """Family expert-scoring function: sqrt(softplus(x))."""
45
+ return torch.sqrt(F.softplus(x) + 1e-8)
46
+
47
+
48
+ class HRMRefinementBlock(nn.Module):
49
+ """Signature family block: iterative gated refinement that, in the canonical
50
+ HRM spirit, starts the reasoning from a RANDOM initial state z0 and iterates
51
+ toward a solution conditioned on the input (`anchor`).
52
+
53
+ Unlike the other family traits this block does NOT start as a no-op: the gate
54
+ is initialised OPEN (gate_init_open). With an input-anchored no-op start the
55
+ gate received ~zero gradient and never woke up; a random z0 forces the block
56
+ to actively reconcile the random state against the input, so the open gate
57
+ carries real signal from step 0. To keep the deep trunk intact we contribute
58
+ only the reasoning DELTA (h - z0) as a residual -- z0 itself is never dumped
59
+ into the trunk, and a closed gate (h == z0) degrades cleanly to a no-op."""
60
+
61
+ def __init__(self, hidden_size, refine_dim, steps, eps=1e-3, gate_init_open=0.1):
62
+ super().__init__()
63
+ self.steps = steps
64
+ self.norm = RMSNorm(hidden_size, eps)
65
+ self.down = nn.Linear(hidden_size * 2, refine_dim, bias=False)
66
+ self.up = nn.Linear(refine_dim, hidden_size, bias=False)
67
+ # random initial reasoning state (learnable), broadcast over batch/time
68
+ self.z0 = nn.Parameter(torch.empty(hidden_size))
69
+ nn.init.trunc_normal_(self.z0, std=1.0, a=-2.0, b=2.0)
70
+ # gates start OPEN so the random-state reasoning reaches the output at init
71
+ go = math.atanh(min(gate_init_open, 0.9)) if gate_init_open > 0 else 0.0
72
+ self.gate = nn.Parameter(torch.full((steps,), go))
73
+ nn.init.normal_(self.down.weight, std=0.02)
74
+ nn.init.normal_(self.up.weight, std=0.02)
75
+
76
+ def forward(self, x): # x: [B,T,d]
77
+ B, T, _ = x.shape
78
+ anchor = x
79
+ h = self.z0.expand(B, T, -1) # random initial reasoning state
80
+ for t in range(self.steps):
81
+ inp = torch.cat([self.norm(h), anchor], dim=-1)
82
+ update = soft_clamp(self.up(F.silu(self.down(inp))), 10.0)
83
+ h = h + torch.tanh(self.gate[t]) * update
84
+ return x + (h - self.z0) # add reasoning delta, keep trunk
85
+
86
+
87
+ class ExpertFFN(nn.Module):
88
+ def __init__(self, hidden_size, intermediate_size):
89
+ super().__init__()
90
+ self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
91
+ self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
92
+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
93
+
94
+ def forward(self, x):
95
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
96
+
97
+
98
+ class MoESwiGLU(nn.Module):
99
+ """Shared + top-k routed SwiGLU experts, sqrtsoftplus scoring, norm_topk_prob,
100
+ Switch-style load-balance aux (via `last_aux_loss`). down-projections zero-init
101
+ => no-op at start."""
102
+
103
+ def __init__(self, hidden_size, intermediate_size, n_routed=4, n_shared=1,
104
+ top_k=2, aux_loss_coef=0.01):
105
+ super().__init__()
106
+ self.top_k = min(top_k, n_routed)
107
+ self.n_routed = n_routed
108
+ self.n_shared = n_shared
109
+ self.aux_loss_coef = aux_loss_coef
110
+ self.router = nn.Linear(hidden_size, n_routed, bias=False)
111
+ self.experts = nn.ModuleList([ExpertFFN(hidden_size, intermediate_size)
112
+ for _ in range(n_routed)])
113
+ self.shared = (ExpertFFN(hidden_size, intermediate_size * n_shared)
114
+ if n_shared > 0 else None)
115
+ for e in self.experts:
116
+ nn.init.zeros_(e.down_proj.weight)
117
+ if self.shared is not None:
118
+ nn.init.zeros_(self.shared.down_proj.weight)
119
+ self.last_aux_loss = None
120
+
121
+ def forward(self, x): # x: [B,T,d]
122
+ flat = x.reshape(-1, x.shape[-1])
123
+ out = torch.zeros_like(flat)
124
+ if self.shared is not None:
125
+ s = self.shared(flat)
126
+ out = out + (s / self.n_shared if self.n_shared > 1 else s)
127
+
128
+ logits = self.router(flat)
129
+ scores = sqrtsoftplus(logits)
130
+ topv, topi = scores.topk(self.top_k, dim=-1)
131
+ topv = topv / (topv.sum(-1, keepdim=True) + 1e-8)
132
+ for slot in range(self.top_k):
133
+ idx = topi[:, slot]
134
+ w = topv[:, slot].unsqueeze(-1)
135
+ for e_id, expert in enumerate(self.experts):
136
+ mask = idx == e_id
137
+ if mask.any():
138
+ out[mask] = out[mask] + w[mask] * expert(flat[mask])
139
+
140
+ probs = F.softmax(logits, dim=-1)
141
+ expert_mask = torch.zeros_like(probs)
142
+ expert_mask.scatter_(1, topi, 1.0)
143
+ self.last_aux_loss = (self.n_routed * (expert_mask.mean(0) * probs.mean(0)).sum()
144
+ * self.aux_loss_coef)
145
+ return out.view_as(x)
146
+
147
+
148
+ class MTPHead(nn.Module):
149
+ """Multi-token-prediction head: zero-init d->d residual, reuses the tied readout."""
150
+
151
+ def __init__(self, hidden_size):
152
+ super().__init__()
153
+ self.proj = nn.Linear(hidden_size, hidden_size, bias=False)
154
+ nn.init.zeros_(self.proj.weight)
155
+
156
+ def forward(self, hidden):
157
+ return hidden + self.proj(hidden)
158
+
159
+
160
+ class TokenCompressor(nn.Module):
161
+ """Frozen LSH-style projection (gradient never reaches it through the hash cast)."""
162
+ def __init__(self, hidden_size, compress_dim):
163
+ super().__init__()
164
+ self.proj = nn.Linear(hidden_size, compress_dim, bias=False)
165
+ nn.init.normal_(self.proj.weight, std=0.02)
166
+ self.proj.weight.requires_grad_(False)
167
+
168
+ def forward(self, x):
169
+ return self.proj(x)
170
+
171
+
172
+ class MultiHeadHashLookup(nn.Module):
173
+ """N-gram hash memory: for n=1..max_ngram, hash the n-token compressed window
174
+ into per-head tables and average. Ported from v2 EngramModule."""
175
+ def __init__(self, num_heads, table_size, compress_dim, out_dim, max_ngram=3):
176
+ super().__init__()
177
+ self.num_heads, self.table_size = num_heads, table_size
178
+ self.max_ngram, self.out_dim = max_ngram, out_dim
179
+ self.tables = nn.ModuleList([nn.Embedding(table_size, out_dim) for _ in range(num_heads)])
180
+ for t in self.tables:
181
+ nn.init.normal_(t.weight, std=0.01)
182
+ for n in range(1, max_ngram + 1):
183
+ for k in range(n):
184
+ proj = torch.randn(num_heads, compress_dim)
185
+ proj = proj / (proj.norm(dim=1, keepdim=True) + 1e-8)
186
+ self.register_buffer(f"hash_proj_n{n}_p{k}", proj, persistent=True)
187
+
188
+ def forward(self, compressed):
189
+ B, S, _ = compressed.shape
190
+ dev = compressed.device
191
+ out = torch.zeros(B, S, self.out_dim, device=dev, dtype=compressed.dtype)
192
+ norm = torch.zeros(S, device=dev)
193
+ for n in range(1, self.max_ngram + 1):
194
+ if S < n:
195
+ continue
196
+ valid, start = S - n + 1, n - 1
197
+ h = torch.zeros(B, valid, self.num_heads, device=dev)
198
+ for k in range(n):
199
+ proj = getattr(self, f"hash_proj_n{n}_p{k}")
200
+ h = h + torch.matmul(compressed[:, k:k + valid, :].float(), proj.t())
201
+ idx = h.abs().long() % self.table_size
202
+ for hi, table in enumerate(self.tables):
203
+ out[:, start:, :] = out[:, start:, :] + table(idx[:, :, hi])
204
+ norm[start:] += self.num_heads
205
+ return (out / norm.view(1, -1, 1).clamp(min=1)).to(compressed.dtype)
206
+
207
+
208
+ class DERFContextGate(nn.Module):
209
+ def __init__(self, obs_size, init_bias=-4.0):
210
+ super().__init__()
211
+ self.proj = nn.Linear(obs_size * 2, obs_size)
212
+ self.alpha = nn.Parameter(torch.ones(obs_size))
213
+ self.bias = nn.Parameter(torch.full((obs_size,), init_bias))
214
+ self.gamma = nn.Parameter(torch.ones(obs_size))
215
+
216
+ def forward(self, retrieved, obs):
217
+ logits = self.proj(torch.cat([retrieved, obs], dim=-1))
218
+ gate = self.gamma * ((torch.erf(self.alpha * logits + self.bias) + 1.0) / 2.0)
219
+ return retrieved * gate
220
+
221
+
222
+ class PhaseAttentionRing(nn.Module):
223
+ """Interstitial ATTENTION ring: attends causally over the sequence in
224
+ oscillator-PHASE space ([cos,sin] of the two neighbor oscillator rings) and
225
+ returns an injection current of width m = n_r + n_{r+1} for those neighbors.
226
+ Zero-init gate => no-op at start; soft_clamp bounds the injected drive."""
227
+
228
+ def __init__(self, m, n_heads=4, head_dim=16, bound=10.0):
229
+ super().__init__()
230
+ self.h, self.d, self.bound = n_heads, head_dim, bound
231
+ self.qkv = nn.Linear(2 * m, 3 * n_heads * head_dim, bias=False)
232
+ self.out = nn.Linear(n_heads * head_dim, m, bias=False)
233
+ self.gate = nn.Parameter(torch.zeros(1))
234
+ nn.init.normal_(self.qkv.weight, std=0.02)
235
+ nn.init.normal_(self.out.weight, std=0.02)
236
+
237
+ def forward(self, theta_slice): # [B,T,m] phases of the neighbor rings
238
+ B, T, m = theta_slice.shape
239
+ feat = torch.cat([torch.cos(theta_slice), torch.sin(theta_slice)], dim=-1)
240
+ q, k, v = self.qkv(feat).split(self.h * self.d, dim=-1)
241
+ shp = lambda z: z.view(B, T, self.h, self.d).transpose(1, 2)
242
+ y = F.scaled_dot_product_attention(shp(q), shp(k), shp(v), is_causal=True)
243
+ y = y.transpose(1, 2).reshape(B, T, self.h * self.d)
244
+ return soft_clamp(self.out(y) * torch.tanh(self.gate), self.bound)
245
+
246
+
247
+ class EngramRing(nn.Module):
248
+ """Interstitial ENGRAM ring: absorbs n-gram context from the hidden state via
249
+ hash memory + DERF gate, projected to an injection current of width m for the
250
+ two neighbor oscillator rings. Two no-op gates at init (DERF bias -4 + scale)."""
251
+
252
+ def __init__(self, hidden_size, m, compress_dim=32, num_heads=2,
253
+ table_size=2048, max_ngram=3, bound=10.0):
254
+ super().__init__()
255
+ self.bound = bound
256
+ self.compressor = TokenCompressor(hidden_size, compress_dim)
257
+ self.lookup = MultiHeadHashLookup(num_heads, table_size, compress_dim, m, max_ngram)
258
+ self.to_obs = nn.Linear(hidden_size, m, bias=False)
259
+ self.gate = DERFContextGate(m, init_bias=-4.0)
260
+ self.scale = nn.Parameter(torch.zeros(1)) # extra no-op gate at init
261
+ nn.init.normal_(self.to_obs.weight, std=0.02)
262
+
263
+ def family_reinit(self):
264
+ """Re-apply the inits the model's global self.apply would clobber (table
265
+ std 0.01, frozen-random compressor, DERF bias -4)."""
266
+ for t in self.lookup.tables:
267
+ nn.init.normal_(t.weight, std=0.01)
268
+ nn.init.normal_(self.compressor.proj.weight, std=0.02)
269
+ self.compressor.proj.weight.requires_grad_(False)
270
+ nn.init.constant_(self.gate.bias, -4.0)
271
+
272
+ def forward(self, h): # h: [B,T,hidden]
273
+ retrieved = self.lookup(self.compressor(h.detach()))
274
+ gated = self.gate(retrieved, self.to_obs(h))
275
+ return soft_clamp(gated * torch.tanh(self.scale), self.bound)
276
+
277
+
278
+ class RingController(nn.Module):
279
+ """Tiny per-ring manager that OPTIMIZES ITSELF by a predictive / free-energy rule.
280
+
281
+ Core = a fast-weight linear predictor `W` (a BUFFER, excluded from the global
282
+ optimizer) updated online by the delta rule W += lr * (f - W@prev) outer prev,
283
+ which is exactly one gradient step on the squared prediction error -- the
284
+ controller learns to predict its ring's next state, minimizing surprise, with no
285
+ backprop. A small backprop-trained decoder maps the self-organized feature +
286
+ surprise into ring-control modulations; zero-init => exact no-op at start."""
287
+
288
+ def __init__(self, d_obs=4, feat=384, n_ctrl=4, local_lr=0.01):
289
+ super().__init__()
290
+ self.feat, self.local_lr = feat, local_lr
291
+ self.enc = nn.Linear(d_obs, feat)
292
+ self.dec = nn.Linear(feat + 1, n_ctrl)
293
+ nn.init.zeros_(self.dec.weight)
294
+ nn.init.zeros_(self.dec.bias) # control == 0 at init (no-op)
295
+ self.register_buffer("W", torch.zeros(feat, feat)) # self-organizing fast weights
296
+ self.register_buffer("prev_f", torch.zeros(feat))
297
+
298
+ def family_reinit(self):
299
+ nn.init.zeros_(self.dec.weight)
300
+ nn.init.zeros_(self.dec.bias)
301
+
302
+ def forward(self, obs): # obs: [d_obs] (detached ring stats)
303
+ f = torch.tanh(self.enc(obs)) # [feat]
304
+ pred = self.W @ self.prev_f # predicted current feature
305
+ surprise = F.mse_loss(f.detach(), pred)
306
+ if self.training:
307
+ with torch.no_grad(): # predictive self-organization (no global grad)
308
+ err = f.detach() - pred
309
+ self.W.add_(self.local_lr * torch.outer(err, self.prev_f)).clamp_(-3.0, 3.0)
310
+ self.prev_f.copy_(f.detach())
311
+ ctrl = self.dec(torch.cat([f, surprise.detach().reshape(1)])) # [n_ctrl]
312
+ return ctrl, surprise.detach()
313
+
314
+
315
+ class RingControllerBank(nn.Module):
316
+ """One RingController per oscillator ring (shared across all layers)."""
317
+
318
+ def __init__(self, n_rings, d_obs=4, feat=384, local_lr=0.01):
319
+ super().__init__()
320
+ self.controllers = nn.ModuleList(
321
+ [RingController(d_obs, feat, 4, local_lr) for _ in range(n_rings)])
322
+ self.last_surprise = None
323
+
324
+ def forward(self, obs): # obs: [R, d_obs] -> ctrl [R, 4]
325
+ ctrls, surps = [], []
326
+ for r, c in enumerate(self.controllers):
327
+ ct, sp = c(obs[r])
328
+ ctrls.append(ct)
329
+ surps.append(sp)
330
+ self.last_surprise = torch.stack(surps).mean()
331
+ return torch.stack(ctrls, dim=0)
332
+
333
+
334
+ class RingSpecialists(nn.Module):
335
+ """A MoE-style bank of `n_spec` MINI MEMORY SPECIALISTS for ONE oscillator ring.
336
+
337
+ Each specialist owns two fast-weight stores (test-time-mutable BUFFERS, like
338
+ RingController.W -- excluded from the optimizer):
339
+ * store_in -- a memory of the INPUT context that routes to it, and
340
+ * store_out -- the OUTPUT information it injects back into the ring.
341
+ Tokens are routed to the top-k specialists (a small MoE) by similarity to each
342
+ specialist's address = its learnable identity key + a read of its accumulated
343
+ input memory. The routed store_out is decoded into an injection current for the
344
+ ring, and BOTH stores are written online (train AND inference) by a gated EMA
345
+ rule -- so a generation accumulates an addressable context memory as it runs.
346
+
347
+ Slow (backprop) weights -- q_proj/in_enc/val_enc/out_dec/in_read/key/active --
348
+ learn to route, encode, retrieve and decode; the stores are the fast memory.
349
+ Family contract: zero-init `scale` => exact no-op at start, and an empty
350
+ store_out is zero anyway, so the block is doubly safe until it learns to write
351
+ and open the gate. `active` is a per-specialist usage gate biasing the router."""
352
+
353
+ def __init__(self, ring_size, hidden_size, n_spec=7, key_dim=32, slot_dim=64,
354
+ top_k=2, write_lr=0.1, bound=10.0):
355
+ super().__init__()
356
+ self.n_spec = n_spec
357
+ self.top_k = min(top_k, n_spec)
358
+ self.write_lr, self.bound = write_lr, bound
359
+ self.write_enabled = True
360
+ self.q_proj = nn.Linear(hidden_size, key_dim, bias=False) # router query (learned)
361
+ self.out_dec = nn.Linear(slot_dim, ring_size, bias=False) # retrieved -> injection (learned)
362
+ self.in_read = nn.Linear(slot_dim, key_dim, bias=False) # input-store -> addr (learned)
363
+ # write-side encoders are FROZEN RANDOM projections (cf. EngramRing's frozen
364
+ # compressor): they only ever run inside the no-grad write, so backprop can't
365
+ # train them -- as fixed random features the stores hold a stable encoding the
366
+ # learned read/route/decode path can address.
367
+ self.in_enc = nn.Linear(hidden_size, slot_dim, bias=False) # input -> input-store (frozen)
368
+ self.val_enc = nn.Linear(hidden_size, slot_dim, bias=False) # input -> output-store (frozen)
369
+ self.key = nn.Parameter(torch.randn(n_spec, key_dim) * 0.02) # specialist identity
370
+ self.active = nn.Parameter(torch.zeros(n_spec)) # per-specialist usage gate
371
+ self.scale = nn.Parameter(torch.zeros(1)) # no-op output gate at init
372
+ self.register_buffer("store_in", torch.zeros(n_spec, slot_dim))
373
+ self.register_buffer("store_out", torch.zeros(n_spec, slot_dim))
374
+ for m in (self.q_proj, self.out_dec, self.in_read, self.in_enc, self.val_enc):
375
+ nn.init.normal_(m.weight, std=0.02)
376
+ self.in_enc.weight.requires_grad_(False)
377
+ self.val_enc.weight.requires_grad_(False)
378
+
379
+ def family_reinit(self):
380
+ """Re-apply inits the model's global self.apply clobbers (key, gates, and
381
+ the frozen write encoders)."""
382
+ nn.init.normal_(self.key, std=0.02)
383
+ nn.init.zeros_(self.active)
384
+ nn.init.zeros_(self.scale)
385
+ nn.init.normal_(self.in_enc.weight, std=0.02)
386
+ nn.init.normal_(self.val_enc.weight, std=0.02)
387
+ self.in_enc.weight.requires_grad_(False)
388
+ self.val_enc.weight.requires_grad_(False)
389
+
390
+ def reset_memory(self):
391
+ """Clear both stores -- call between independent prompts/sequences so
392
+ context memory does not bleed across them."""
393
+ self.store_in.zero_()
394
+ self.store_out.zero_()
395
+
396
+ def forward(self, h): # h: [B,T,hidden]
397
+ B, T, _ = h.shape
398
+ # snapshot the fast-weight stores: the graph must hold an immutable copy
399
+ # because we mutate the buffers in-place for the online write below.
400
+ store_in, store_out = self.store_in.clone(), self.store_out.clone()
401
+ q = self.q_proj(h) # [B,T,key_dim]
402
+ addr = self.key + self.in_read(store_in) # [n_spec,key_dim]
403
+ logits = q @ addr.t() # [B,T,n_spec]
404
+ logits = logits + F.logsigmoid(self.active) # usage gate biases routing
405
+ if self.top_k < self.n_spec: # top-k MoE sparsity
406
+ tv = torch.topk(logits, self.top_k, dim=-1).values
407
+ logits = logits.masked_fill(logits < tv[..., [-1]], float("-inf"))
408
+ route = torch.softmax(logits, dim=-1) # [B,T,n_spec]
409
+
410
+ retrieved = route @ store_out # [B,T,slot_dim]
411
+ inject = self.out_dec(retrieved) * torch.tanh(self.scale) # [B,T,ring_size]
412
+
413
+ rec = _viz.get_rec()
414
+ if rec is not None and rec.enabled: # last-token routing
415
+ rec.push_spec(route[0, -1].tolist())
416
+
417
+ # online write: blend this step's input/value into the routed specialists
418
+ if self.write_enabled and self.write_lr > 0:
419
+ with torch.no_grad():
420
+ w = route.reshape(-1, self.n_spec) # [BT,n_spec]
421
+ denom = w.sum(0).clamp(min=1e-3).unsqueeze(1) # [n_spec,1]
422
+ in_info = (w.t() @ self.in_enc(h).reshape(-1, self.in_enc.out_features)) / denom
423
+ val_info = (w.t() @ self.val_enc(h).reshape(-1, self.val_enc.out_features)) / denom
424
+ a = self.write_lr
425
+ self.store_in.mul_(1 - a).add_(a * in_info).clamp_(-self.bound, self.bound)
426
+ self.store_out.mul_(1 - a).add_(a * val_info).clamp_(-self.bound, self.bound)
427
+ return soft_clamp(inject, self.bound)
428
+
429
+
430
+ class JEPAPredictorBlock(nn.Module):
431
+ """Representation-space k-ahead prediction with stop-grad target (JEPA asymmetry).
432
+ Zero-init gate => identity at init; `up` normal so the gate gets gradient."""
433
+
434
+ def __init__(self, dim, pred_dim, horizon, eps=1e-3):
435
+ super().__init__()
436
+ self.horizon = horizon
437
+ self.norm = RMSNorm(dim, eps)
438
+ self.down = nn.Linear(dim, pred_dim, bias=False)
439
+ self.up = nn.Linear(pred_dim, dim, bias=False)
440
+ self.gate = nn.Parameter(torch.zeros(horizon))
441
+ nn.init.normal_(self.down.weight, std=0.02)
442
+ nn.init.normal_(self.up.weight, std=0.02)
443
+
444
+ def forward(self, h, k): # h: [B,T,dim]
445
+ update = self.up(F.silu(self.down(self.norm(h))))
446
+ return h + torch.tanh(self.gate[k - 1]) * update