Kye Gomez commited on
Commit
7916266
·
1 Parent(s): 4ce503f

[FEAT][Main.py]

Browse files
Files changed (3) hide show
  1. .gitignore +1 -0
  2. open_mythos/__init__.py +0 -0
  3. open_mythos/main.py +1078 -0
.gitignore CHANGED
@@ -9,6 +9,7 @@ __pycache__/
9
  # Distribution / packaging
10
  .Python
11
  build/
 
12
  develop-eggs/
13
  dist/
14
  downloads/
 
9
  # Distribution / packaging
10
  .Python
11
  build/
12
+ experimental/
13
  develop-eggs/
14
  dist/
15
  downloads/
open_mythos/__init__.py ADDED
File without changes
open_mythos/main.py ADDED
@@ -0,0 +1,1078 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ OpenMythos v1 — Recurrent-Depth Transformer
3
+ Architecture: Prelude → [Looped Recurrent Block]×T → Coda
4
+ MoE FFN (DeepSeek-style), GQA or MLA, RoPE, RMSNorm, KV cache, LTI-stable injection, ACT halting
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Optional
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+ import torch.nn.functional as F
13
+
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # Config
17
+ # ---------------------------------------------------------------------------
18
+
19
+
20
+ @dataclass
21
+ class MythosConfig:
22
+ """
23
+ Hyperparameter configuration for OpenMythos.
24
+
25
+ Core:
26
+ vocab_size -- token vocabulary size
27
+ dim -- model hidden dimension
28
+ n_heads -- number of query attention heads
29
+ n_kv_heads -- number of key/value heads (GQA; ignored by MLA)
30
+ max_seq_len -- maximum sequence length for RoPE precomputation
31
+ max_loop_iters -- default recurrent loop depth T at inference
32
+ prelude_layers -- number of standard transformer layers before the loop
33
+ coda_layers -- number of standard transformer layers after the loop
34
+
35
+ Attention (attn_type selects between the two):
36
+ attn_type -- "gqa" for Grouped Query Attention, "mla" for Multi-Latent Attention
37
+ kv_lora_rank -- [MLA] compressed KV latent dimension stored in the cache
38
+ q_lora_rank -- [MLA] compressed Q latent dimension
39
+ qk_rope_head_dim-- [MLA] per-head dims that receive RoPE
40
+ qk_nope_head_dim-- [MLA] per-head dims without positional encoding
41
+ v_head_dim -- [MLA] per-head value dimension
42
+
43
+ MoE FFN (used inside the recurrent block):
44
+ n_experts -- total number of routed expert FFNs
45
+ n_shared_experts-- number of always-active shared experts
46
+ n_experts_per_tok-- top-K experts selected per token by the router
47
+ expert_dim -- hidden dimension inside each fine-grained expert
48
+
49
+ Other:
50
+ act_threshold -- ACT halting threshold (cumulative probability to stop looping)
51
+ rope_theta -- RoPE base frequency
52
+ lora_rank -- rank of the per-loop depth-wise LoRA adapter
53
+ """
54
+
55
+ vocab_size: int = 32000
56
+ dim: int = 2048
57
+ n_heads: int = 16
58
+ n_kv_heads: int = 4 # GQA: fewer KV heads than Q heads
59
+ max_seq_len: int = 4096
60
+ max_loop_iters: int = 16 # T — recurrent depth at inference
61
+ prelude_layers: int = 2
62
+ coda_layers: int = 2
63
+ # Attention type: "gqa" | "mla"
64
+ attn_type: str = "mla"
65
+ # MLA params (only used when attn_type="mla")
66
+ kv_lora_rank: int = 512 # compressed KV latent cached instead of full K/V
67
+ q_lora_rank: int = 1536 # compressed Q latent dim
68
+ qk_rope_head_dim: int = 64 # per-head dims that receive RoPE
69
+ qk_nope_head_dim: int = 128 # per-head dims without RoPE
70
+ v_head_dim: int = 128 # per-head value dim
71
+ # MoE
72
+ n_experts: int = 64
73
+ n_shared_experts: int = 2
74
+ n_experts_per_tok: int = 4 # top-K routed
75
+ expert_dim: int = 512 # fine-grained: dim // (n_experts // n_experts_per_tok)
76
+ # ACT halting
77
+ act_threshold: float = 0.99
78
+ # RoPE
79
+ rope_theta: float = 500000.0
80
+ # LoRA depth adaptation
81
+ lora_rank: int = 16
82
+
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # RMSNorm
86
+ # ---------------------------------------------------------------------------
87
+
88
+
89
+ class RMSNorm(nn.Module):
90
+ """
91
+ Root Mean Square Layer Normalization (Zhang & Sennrich, 2019).
92
+
93
+ Normalizes by the RMS of the input rather than mean+variance, with a
94
+ learned per-channel rescaling weight. No bias term. Used in place of
95
+ LayerNorm throughout the model for stability and efficiency.
96
+ """
97
+
98
+ def __init__(self, dim: int, eps: float = 1e-6):
99
+ """
100
+ Args:
101
+ dim -- feature dimension to normalize over
102
+ eps -- small constant added before sqrt for numerical stability
103
+ """
104
+ super().__init__()
105
+ self.eps = eps
106
+ self.weight = nn.Parameter(torch.ones(dim))
107
+
108
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
109
+ """
110
+ Args:
111
+ x -- input tensor of shape (..., dim)
112
+ Returns:
113
+ RMS-normalized tensor of the same shape, rescaled by self.weight
114
+ """
115
+ rms = x.pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()
116
+ return x * rms * self.weight
117
+
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # RoPE
121
+ # ---------------------------------------------------------------------------
122
+
123
+
124
+ def precompute_rope_freqs(
125
+ dim: int, max_len: int, theta: float = 500000.0
126
+ ) -> torch.Tensor:
127
+ """
128
+ Precompute complex-valued RoPE rotation matrices for positions 0..max_len-1.
129
+
130
+ Each position gets a complex phasor e^{i·m·θ_k} for each frequency pair k.
131
+ Stored as a complex tensor so that rotation is a single pointwise multiply.
132
+
133
+ Args:
134
+ dim -- head dimension (must be even); frequencies are computed for dim//2 pairs
135
+ max_len -- maximum sequence length to precompute
136
+ theta -- RoPE base (higher = slower frequency decay; 500k is the LLaMA-3 default)
137
+
138
+ Returns:
139
+ complex64 tensor of shape (max_len, dim//2)
140
+ """
141
+ freqs = 1.0 / (theta ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
142
+ t = torch.arange(max_len, dtype=torch.float32)
143
+ freqs = torch.outer(t, freqs)
144
+ return torch.polar(torch.ones_like(freqs), freqs)
145
+
146
+
147
+ def apply_rope(x: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor:
148
+ """
149
+ Apply rotary positional embeddings to query or key tensors.
150
+
151
+ Interprets each pair of adjacent features as a 2D complex number and
152
+ multiplies by the precomputed phasor for that position, rotating the
153
+ representation in the complex plane without changing its norm.
154
+
155
+ Args:
156
+ x -- tensor of shape (B, T, H, head_dim); head_dim must be even
157
+ freqs_cis -- precomputed complex frequencies of shape (max_len, head_dim//2)
158
+
159
+ Returns:
160
+ Rotated tensor of the same shape and dtype as x
161
+ """
162
+ xc = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
163
+ freqs_cis = freqs_cis[: x.shape[1]].unsqueeze(0).unsqueeze(2)
164
+ return torch.view_as_real(xc * freqs_cis).flatten(-2).to(x.dtype)
165
+
166
+
167
+ # ---------------------------------------------------------------------------
168
+ # Grouped Query Attention with KV cache
169
+ # ---------------------------------------------------------------------------
170
+
171
+
172
+ class GQAttention(nn.Module):
173
+ """
174
+ Grouped Query Attention (Ainslie et al., 2023).
175
+
176
+ Uses fewer KV heads than Q heads (n_kv_heads < n_heads). Each KV head is
177
+ shared across n_heads // n_kv_heads query heads, reducing the KV cache size
178
+ by that factor while keeping full query expressiveness.
179
+
180
+ RoPE is applied to both Q and K. K and V are stored in kv_cache after
181
+ RoPE application so that cached values are already positionally encoded and
182
+ do not need to be re-rotated on retrieval.
183
+ """
184
+
185
+ def __init__(self, cfg: MythosConfig):
186
+ """
187
+ Args:
188
+ cfg -- MythosConfig; uses dim, n_heads, n_kv_heads
189
+ """
190
+ super().__init__()
191
+ self.n_heads = cfg.n_heads
192
+ self.n_kv_heads = cfg.n_kv_heads
193
+ self.head_dim = cfg.dim // cfg.n_heads
194
+ self.groups = cfg.n_heads // cfg.n_kv_heads
195
+
196
+ self.wq = nn.Linear(cfg.dim, cfg.n_heads * self.head_dim, bias=False)
197
+ self.wk = nn.Linear(cfg.dim, cfg.n_kv_heads * self.head_dim, bias=False)
198
+ self.wv = nn.Linear(cfg.dim, cfg.n_kv_heads * self.head_dim, bias=False)
199
+ self.wo = nn.Linear(cfg.n_heads * self.head_dim, cfg.dim, bias=False)
200
+
201
+ def forward(
202
+ self,
203
+ x: torch.Tensor,
204
+ freqs_cis: torch.Tensor,
205
+ mask: Optional[torch.Tensor] = None,
206
+ kv_cache: Optional[dict] = None,
207
+ cache_key: str = "default",
208
+ ) -> torch.Tensor:
209
+ """
210
+ Args:
211
+ x -- input of shape (B, T, dim)
212
+ freqs_cis -- RoPE frequencies for head_dim, shape (T, head_dim//2)
213
+ mask -- additive causal mask of shape (1, 1, T, S) or None
214
+ kv_cache -- dict mutated in-place; stores {"k": ..., "v": ...} per cache_key
215
+ cache_key -- unique key identifying this layer in the cache dict
216
+
217
+ Returns:
218
+ Output tensor of shape (B, T, dim)
219
+ """
220
+ B, T, _ = x.shape
221
+ q = self.wq(x).view(B, T, self.n_heads, self.head_dim)
222
+ k = self.wk(x).view(B, T, self.n_kv_heads, self.head_dim)
223
+ v = self.wv(x).view(B, T, self.n_kv_heads, self.head_dim)
224
+
225
+ q = apply_rope(q, freqs_cis)
226
+ k = apply_rope(k, freqs_cis)
227
+
228
+ if kv_cache is not None:
229
+ if cache_key in kv_cache:
230
+ k = torch.cat([kv_cache[cache_key]["k"], k], dim=1)
231
+ v = torch.cat([kv_cache[cache_key]["v"], v], dim=1)
232
+ kv_cache[cache_key] = {"k": k.detach(), "v": v.detach()}
233
+
234
+ # expand KV to match Q heads
235
+ k = k.repeat_interleave(self.groups, dim=2)
236
+ v = v.repeat_interleave(self.groups, dim=2)
237
+
238
+ q = q.transpose(1, 2) # (B, H, T, head_dim)
239
+ k = k.transpose(1, 2)
240
+ v = v.transpose(1, 2)
241
+
242
+ scale = self.head_dim**-0.5
243
+ attn = torch.matmul(q, k.transpose(-2, -1)) * scale
244
+ if mask is not None:
245
+ attn = attn + mask
246
+ attn = F.softmax(attn, dim=-1)
247
+ out = torch.matmul(attn, v)
248
+ out = out.transpose(1, 2).contiguous().view(B, T, -1)
249
+ return self.wo(out)
250
+
251
+
252
+ # ---------------------------------------------------------------------------
253
+ # Multi-Latent Attention (DeepSeek-V2 style)
254
+ # ---------------------------------------------------------------------------
255
+
256
+
257
+ class MLAttention(nn.Module):
258
+ """
259
+ Multi-Latent Attention (DeepSeek-V2, 2024).
260
+
261
+ The key insight: instead of caching full K and V tensors (each of size
262
+ n_heads × head_dim per token), MLA compresses the KV path through a
263
+ low-rank latent c_kv and only caches that plus the RoPE keys. K_nope and
264
+ V are reconstructed from c_kv at each decoding step, trading a cheap
265
+ linear projection for dramatically smaller cache memory.
266
+
267
+ Q path:
268
+ x → q_down (dim→q_lora_rank) → q_norm
269
+ → q_up_nope (q_lora_rank → n_heads×qk_nope_head_dim) [no RoPE]
270
+ → q_up_rope (q_lora_rank → n_heads×qk_rope_head_dim) [RoPE applied]
271
+ q = cat(q_nope, q_rope) per head
272
+
273
+ KV path:
274
+ x → kv_down (dim → kv_lora_rank + qk_rope_head_dim)
275
+ splits into c_kv (latent, cached) and k_rope_raw (shared across heads)
276
+ k_rope = RoPE(expand(k_rope_raw)) — applied before caching
277
+ c_kv → kv_norm → kv_up → [k_nope | v] — reconstructed each step
278
+ k = cat(k_nope, k_rope) per head
279
+
280
+ Cache stores: c_kv (kv_lora_rank) + k_rope (n_heads × qk_rope_head_dim),
281
+ versus full GQA cache: n_kv_heads × head_dim × 2. At production scale this
282
+ is roughly a 10–20× memory reduction.
283
+ """
284
+
285
+ def __init__(self, cfg: MythosConfig):
286
+ """
287
+ Args:
288
+ cfg -- MythosConfig; uses dim, n_heads, kv_lora_rank, q_lora_rank,
289
+ qk_rope_head_dim, qk_nope_head_dim, v_head_dim
290
+ """
291
+ super().__init__()
292
+ self.n_heads = cfg.n_heads
293
+ self.kv_lora_rank = cfg.kv_lora_rank
294
+ self.qk_rope_dim = cfg.qk_rope_head_dim
295
+ self.qk_nope_dim = cfg.qk_nope_head_dim
296
+ self.v_dim = cfg.v_head_dim
297
+ self.q_head_dim = cfg.qk_nope_head_dim + cfg.qk_rope_head_dim
298
+
299
+ # Q compression
300
+ self.q_down = nn.Linear(cfg.dim, cfg.q_lora_rank, bias=False)
301
+ self.q_norm = RMSNorm(cfg.q_lora_rank)
302
+ self.q_up_nope = nn.Linear(
303
+ cfg.q_lora_rank, cfg.n_heads * cfg.qk_nope_head_dim, bias=False
304
+ )
305
+ self.q_up_rope = nn.Linear(
306
+ cfg.q_lora_rank, cfg.n_heads * cfg.qk_rope_head_dim, bias=False
307
+ )
308
+
309
+ # KV compression: output is [c_kv | k_rope_raw] concatenated
310
+ self.kv_down = nn.Linear(
311
+ cfg.dim, cfg.kv_lora_rank + cfg.qk_rope_head_dim, bias=False
312
+ )
313
+ self.kv_norm = RMSNorm(cfg.kv_lora_rank)
314
+ self.kv_up = nn.Linear(
315
+ cfg.kv_lora_rank,
316
+ cfg.n_heads * (cfg.qk_nope_head_dim + cfg.v_head_dim),
317
+ bias=False,
318
+ )
319
+
320
+ self.wo = nn.Linear(cfg.n_heads * cfg.v_head_dim, cfg.dim, bias=False)
321
+
322
+ def forward(
323
+ self,
324
+ x: torch.Tensor,
325
+ freqs_cis: torch.Tensor,
326
+ mask: Optional[torch.Tensor] = None,
327
+ kv_cache: Optional[dict] = None,
328
+ cache_key: str = "default",
329
+ ) -> torch.Tensor:
330
+ """
331
+ Args:
332
+ x -- input of shape (B, T, dim)
333
+ freqs_cis -- RoPE frequencies sized for qk_rope_head_dim, shape (T, rope_dim//2)
334
+ mask -- additive causal mask of shape (1, 1, T, S) or None
335
+ kv_cache -- dict mutated in-place; stores {"c_kv": ..., "k_rope": ...}
336
+ cache_key -- unique key identifying this layer in the cache dict
337
+
338
+ Returns:
339
+ Output tensor of shape (B, T, dim)
340
+ """
341
+ B, T, _ = x.shape
342
+
343
+ # Q
344
+ c_q = self.q_norm(self.q_down(x))
345
+ q_nope = self.q_up_nope(c_q).view(B, T, self.n_heads, self.qk_nope_dim)
346
+ q_rope = self.q_up_rope(c_q).view(B, T, self.n_heads, self.qk_rope_dim)
347
+ q_rope = apply_rope(q_rope, freqs_cis)
348
+ q = torch.cat([q_nope, q_rope], dim=-1) # (B, T, H, nope+rope)
349
+
350
+ # KV compress
351
+ kv_raw = self.kv_down(x)
352
+ c_kv = kv_raw[..., : self.kv_lora_rank] # (B, T, lora_rank) ← cached
353
+ k_rope = kv_raw[..., self.kv_lora_rank :] # (B, T, rope_dim)
354
+ # expand rope keys across heads and apply RoPE before caching so
355
+ # retrieved keys are already positionally encoded
356
+ k_rope = (
357
+ k_rope.unsqueeze(2)
358
+ .expand(B, T, self.n_heads, self.qk_rope_dim)
359
+ .contiguous()
360
+ )
361
+ k_rope = apply_rope(k_rope, freqs_cis) # (B, T, H, rope_dim) ← cached
362
+
363
+ if kv_cache is not None:
364
+ if cache_key in kv_cache:
365
+ c_kv = torch.cat([kv_cache[cache_key]["c_kv"], c_kv], dim=1)
366
+ k_rope = torch.cat([kv_cache[cache_key]["k_rope"], k_rope], dim=1)
367
+ kv_cache[cache_key] = {"c_kv": c_kv.detach(), "k_rope": k_rope.detach()}
368
+
369
+ S = c_kv.shape[1] # full sequence length including cache
370
+
371
+ # reconstruct K_nope and V from latent (not cached, recomputed each step)
372
+ kv = self.kv_up(self.kv_norm(c_kv)) # (B, S, H*(nope+v))
373
+ kv = kv.view(B, S, self.n_heads, self.qk_nope_dim + self.v_dim)
374
+ k_nope = kv[..., : self.qk_nope_dim] # (B, S, H, nope)
375
+ v = kv[..., self.qk_nope_dim :] # (B, S, H, v_dim)
376
+ k = torch.cat([k_nope, k_rope], dim=-1) # (B, S, H, nope+rope)
377
+
378
+ # attention
379
+ q = q.transpose(1, 2) # (B, H, T, q_head_dim)
380
+ k = k.transpose(1, 2) # (B, H, S, q_head_dim)
381
+ v = v.transpose(1, 2) # (B, H, S, v_dim)
382
+
383
+ scale = self.q_head_dim**-0.5
384
+ attn = torch.matmul(q, k.transpose(-2, -1)) * scale
385
+ if mask is not None:
386
+ attn = attn + mask
387
+ attn = F.softmax(attn, dim=-1)
388
+ out = torch.matmul(attn, v) # (B, H, T, v_dim)
389
+ out = out.transpose(1, 2).contiguous().view(B, T, -1)
390
+ return self.wo(out)
391
+
392
+
393
+ # ---------------------------------------------------------------------------
394
+ # DeepSeek-style MoE FFN
395
+ # ---------------------------------------------------------------------------
396
+
397
+
398
+ class Expert(nn.Module):
399
+ """
400
+ Single SwiGLU feed-forward expert.
401
+
402
+ Implements the gated linear unit variant: output = down(silu(gate(x)) * up(x)).
403
+ Used both as individual routed experts inside MoEFFN and as the standard dense
404
+ FFN in prelude/coda blocks (where expert_dim = dim * 4 // 3).
405
+ """
406
+
407
+ def __init__(self, dim: int, expert_dim: int):
408
+ """
409
+ Args:
410
+ dim -- input and output feature dimension
411
+ expert_dim -- inner (hidden) dimension of the expert
412
+ """
413
+ super().__init__()
414
+ self.gate = nn.Linear(dim, expert_dim, bias=False)
415
+ self.up = nn.Linear(dim, expert_dim, bias=False)
416
+ self.down = nn.Linear(expert_dim, dim, bias=False)
417
+
418
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
419
+ """
420
+ Args:
421
+ x -- input of shape (..., dim)
422
+ Returns:
423
+ Tensor of shape (..., dim)
424
+ """
425
+ return self.down(F.silu(self.gate(x)) * self.up(x))
426
+
427
+
428
+ class MoEFFN(nn.Module):
429
+ """
430
+ Fine-grained Mixture-of-Experts FFN (DeepSeekMoE, Dai et al., 2024).
431
+
432
+ Two classes of experts:
433
+ - Routed experts: n_experts small FFNs; each token activates top-K of them
434
+ via a learned router. A per-expert bias on router logits is updated during
435
+ training to keep load balanced across experts without distorting the loss.
436
+ - Shared experts: n_shared_experts larger FFNs always activated for every token,
437
+ absorbing common cross-domain patterns (syntax, basic reasoning) that would
438
+ otherwise be redundantly learned by many routed experts.
439
+
440
+ Total activated parameters per token ≈ topk/n_experts of routed + all shared,
441
+ keeping compute sparse while the total parameter count stays large.
442
+ """
443
+
444
+ def __init__(self, cfg: MythosConfig):
445
+ """
446
+ Args:
447
+ cfg -- MythosConfig; uses n_experts, n_shared_experts, n_experts_per_tok,
448
+ dim, expert_dim
449
+ """
450
+ super().__init__()
451
+ self.n_experts = cfg.n_experts
452
+ self.n_shared = cfg.n_shared_experts
453
+ self.topk = cfg.n_experts_per_tok
454
+
455
+ self.router = nn.Linear(cfg.dim, cfg.n_experts, bias=False)
456
+ # load-balancing bias adjusted externally during training; not a gradient param
457
+ self.register_buffer("router_bias", torch.zeros(cfg.n_experts))
458
+
459
+ self.routed_experts = nn.ModuleList(
460
+ [Expert(cfg.dim, cfg.expert_dim) for _ in range(cfg.n_experts)]
461
+ )
462
+ self.shared_experts = nn.ModuleList(
463
+ [
464
+ Expert(cfg.dim, cfg.expert_dim * cfg.n_experts_per_tok)
465
+ for _ in range(self.n_shared)
466
+ ]
467
+ )
468
+
469
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
470
+ """
471
+ Args:
472
+ x -- input of shape (B, T, dim)
473
+ Returns:
474
+ Tensor of shape (B, T, dim); shared expert outputs are summed on top
475
+ of the weighted routed expert outputs
476
+ """
477
+ B, T, D = x.shape
478
+ flat = x.view(B * T, D)
479
+
480
+ # router — bias shifts logits for load balancing without touching loss
481
+ logits = self.router(flat) + self.router_bias # (B*T, n_experts)
482
+ scores = F.softmax(logits, dim=-1)
483
+ topk_scores, topk_idx = scores.topk(self.topk, dim=-1)
484
+ topk_scores = topk_scores / topk_scores.sum(dim=-1, keepdim=True) # renorm
485
+
486
+ # routed expert dispatch (token-level scatter)
487
+ out = torch.zeros_like(flat)
488
+ for i in range(self.topk):
489
+ expert_ids = topk_idx[:, i]
490
+ token_scores = topk_scores[:, i].unsqueeze(-1)
491
+ for eid in range(self.n_experts):
492
+ mask = expert_ids == eid
493
+ if not mask.any():
494
+ continue
495
+ out[mask] += token_scores[mask] * self.routed_experts[eid](flat[mask])
496
+
497
+ # shared experts always fire for every token
498
+ for shared in self.shared_experts:
499
+ out = out + shared(flat)
500
+
501
+ return out.view(B, T, D)
502
+
503
+
504
+ # ---------------------------------------------------------------------------
505
+ # Loop-index RoPE (differentiates recurrent block across iterations)
506
+ # ---------------------------------------------------------------------------
507
+
508
+
509
+ def loop_index_embedding(
510
+ h: torch.Tensor, loop_t: int, loop_dim: int, theta: float = 10000.0
511
+ ) -> torch.Tensor:
512
+ """
513
+ Inject a sinusoidal loop-index signal into the first loop_dim channels of h.
514
+
515
+ Analogous to RoPE for sequence position, but applied over recurrence depth
516
+ instead of token position. Without this, the shared recurrent block weights
517
+ must handle both early-stage pattern-matching and late-stage refinement with
518
+ no signal distinguishing which loop they are on. Adding the loop index lets
519
+ the same parameters implement functionally distinct operations per iteration.
520
+
521
+ Args:
522
+ h -- hidden state tensor of shape (B, T, dim)
523
+ loop_t -- current loop iteration index (0-based)
524
+ loop_dim -- number of leading channels to receive the embedding (must be even)
525
+ theta -- sinusoidal base frequency
526
+
527
+ Returns:
528
+ h with a sinusoidal bias added to its first loop_dim channels; same shape
529
+ """
530
+ freqs = 1.0 / (
531
+ theta
532
+ ** (torch.arange(0, loop_dim, 2, device=h.device, dtype=h.dtype) / loop_dim)
533
+ )
534
+ angles = loop_t * freqs # (loop_dim//2,)
535
+ emb = torch.cat([angles.sin(), angles.cos()], dim=-1)[:loop_dim]
536
+ emb_full = torch.zeros(h.shape[-1], device=h.device, dtype=h.dtype)
537
+ emb_full[:loop_dim] = emb
538
+ return h + emb_full.unsqueeze(0).unsqueeze(0)
539
+
540
+
541
+ # ---------------------------------------------------------------------------
542
+ # Depth-wise LoRA adapter (per loop iteration)
543
+ # ---------------------------------------------------------------------------
544
+
545
+
546
+ class LoRAAdapter(nn.Module):
547
+ """
548
+ Depth-wise LoRA adaptation for the recurrent block (Bae et al., 2024).
549
+
550
+ Pure weight-tying (identical weights every loop) limits expressiveness;
551
+ fully distinct weights per loop eliminate parameter savings. This adapter
552
+ sits in between: a shared low-rank down-projection and up-projection matrix B
553
+ are shared across all loops, while a small per-loop scale vector shifts the
554
+ effective transformation at each depth without adding significant parameters.
555
+
556
+ delta(x, t) = (down(x) * scale[t]) @ B
557
+ """
558
+
559
+ def __init__(self, dim: int, rank: int, max_loops: int):
560
+ """
561
+ Args:
562
+ dim -- model hidden dimension (input and output size)
563
+ rank -- low-rank bottleneck dimension
564
+ max_loops -- maximum number of loop iterations (determines embedding table size)
565
+ """
566
+ super().__init__()
567
+ self.down = nn.Linear(dim, rank, bias=False) # shared A: dim → rank
568
+ self.B = nn.Parameter(torch.zeros(rank, dim)) # shared B: rank → dim
569
+ self.scale = nn.Embedding(max_loops, rank) # per-loop element-wise scale
570
+
571
+ def forward(self, x: torch.Tensor, loop_t: int) -> torch.Tensor:
572
+ """
573
+ Args:
574
+ x -- input tensor of shape (B, T, dim)
575
+ loop_t -- current loop index used to look up the per-loop scale
576
+
577
+ Returns:
578
+ Delta tensor of shape (B, T, dim) to be added to the block output
579
+ """
580
+ s = self.scale(torch.tensor(loop_t, device=x.device)) # (rank,)
581
+ down = self.down(x) * s # (B, T, rank)
582
+ return down @ self.B # (B, T, dim)
583
+
584
+
585
+ # ---------------------------------------------------------------------------
586
+ # Single Transformer Block (shared across recurrent loops)
587
+ # ---------------------------------------------------------------------------
588
+
589
+
590
+ class TransformerBlock(nn.Module):
591
+ """
592
+ Standard pre-norm transformer block with swappable attention and optional MoE FFN.
593
+
594
+ Attention is selected by cfg.attn_type:
595
+ "gqa" → GQAttention (Grouped Query Attention, fewer KV heads)
596
+ "mla" → MLAttention (Multi-Latent Attention, compressed KV cache)
597
+
598
+ FFN is selected by use_moe:
599
+ True → MoEFFN (fine-grained routed + shared experts; used in RecurrentBlock)
600
+ False → Expert (dense SwiGLU FFN; used in Prelude and Coda)
601
+ """
602
+
603
+ def __init__(self, cfg: MythosConfig, use_moe: bool = False):
604
+ """
605
+ Args:
606
+ cfg -- MythosConfig; attn_type selects the attention class
607
+ use_moe -- if True, use MoEFFN; otherwise use a dense Expert FFN
608
+ """
609
+ super().__init__()
610
+ self.attn_norm = RMSNorm(cfg.dim)
611
+ self.ffn_norm = RMSNorm(cfg.dim)
612
+ self.attn = MLAttention(cfg) if cfg.attn_type == "mla" else GQAttention(cfg)
613
+ self.ffn = MoEFFN(cfg) if use_moe else Expert(cfg.dim, cfg.dim * 4 // 3)
614
+
615
+ def forward(
616
+ self,
617
+ x: torch.Tensor,
618
+ freqs_cis: torch.Tensor,
619
+ mask: Optional[torch.Tensor] = None,
620
+ kv_cache: Optional[dict] = None,
621
+ cache_key: str = "default",
622
+ ) -> torch.Tensor:
623
+ """
624
+ Args:
625
+ x -- input of shape (B, T, dim)
626
+ freqs_cis -- precomputed RoPE frequencies
627
+ mask -- additive causal mask or None
628
+ kv_cache -- cache dict mutated in-place by the attention layer
629
+ cache_key -- key identifying this layer in the cache
630
+
631
+ Returns:
632
+ Output tensor of shape (B, T, dim)
633
+ """
634
+ x = x + self.attn(self.attn_norm(x), freqs_cis, mask, kv_cache, cache_key)
635
+ x = x + self.ffn(self.ffn_norm(x))
636
+ return x
637
+
638
+
639
+ # ---------------------------------------------------------------------------
640
+ # LTI-stable injection parameters (spectral radius < 1 by construction)
641
+ # ---------------------------------------------------------------------------
642
+
643
+
644
+ class LTIInjection(nn.Module):
645
+ """
646
+ Stable input injection for the recurrent update rule (Parcae, Prairie et al., 2026).
647
+
648
+ The recurrent hidden state evolves as:
649
+ h_{t+1} = A · h_t + B · e + Transformer(h_t, e)
650
+
651
+ where e is the encoded input injected at every loop step to prevent drift.
652
+ Without constraints, A can develop spectral radius ≥ 1, causing the hidden
653
+ state to explode across loop iterations and destabilize training.
654
+
655
+ This class guarantees ρ(A) < 1 by construction via a ZOH discretization:
656
+ A_continuous = Diag(-exp(log_A)) always negative diagonal
657
+ A_discrete = exp(Δt · A_continuous) element-wise, values in (0, 1)
658
+
659
+ where log_A and log_dt are learned parameters and exp ensures positivity.
660
+ This makes looped model training robust to hyperparameter choices and stable
661
+ even at high learning rates.
662
+ """
663
+
664
+ def __init__(self, dim: int):
665
+ """
666
+ Args:
667
+ dim -- hidden state dimension; one scalar per channel for A and B
668
+ """
669
+ super().__init__()
670
+ self.log_A = nn.Parameter(torch.zeros(dim)) # log of A_continuous magnitude
671
+ self.log_dt = nn.Parameter(torch.zeros(1)) # log of discretization step Δt
672
+ self.B = nn.Parameter(torch.ones(dim) * 0.1)
673
+
674
+ def get_A(self) -> torch.Tensor:
675
+ """
676
+ Compute the discretized diagonal state matrix A_discrete.
677
+
678
+ Returns:
679
+ 1-D tensor of shape (dim,) with all values strictly in (0, 1),
680
+ guaranteeing ρ(A) < 1 regardless of learned parameter values.
681
+ """
682
+ A_c = -torch.exp(self.log_A) # continuous diagonal, always < 0
683
+ dt = torch.exp(self.log_dt) # always > 0
684
+ return torch.exp(dt * A_c) # ZOH: values in (0, 1)
685
+
686
+ def forward(
687
+ self, h: torch.Tensor, e: torch.Tensor, transformer_out: torch.Tensor
688
+ ) -> torch.Tensor:
689
+ """
690
+ Compute h_{t+1} = A·h_t + B·e + transformer_out.
691
+
692
+ Args:
693
+ h -- current hidden state (B, T, dim)
694
+ e -- encoded input from Prelude, frozen across loops (B, T, dim)
695
+ transformer_out -- output of the recurrent TransformerBlock at this step (B, T, dim)
696
+
697
+ Returns:
698
+ Updated hidden state of shape (B, T, dim)
699
+ """
700
+ A = self.get_A()
701
+ return A * h + self.B * e + transformer_out
702
+
703
+
704
+ # ---------------------------------------------------------------------------
705
+ # ACT halting (Adaptive Computation Time)
706
+ # ---------------------------------------------------------------------------
707
+
708
+
709
+ class ACTHalting(nn.Module):
710
+ """
711
+ Adaptive Computation Time halting mechanism (Graves, 2016).
712
+
713
+ Learns a per-position halting probability at each loop iteration. Positions
714
+ where the hidden state has converged (high cumulative halting probability)
715
+ stop accumulating updates, while positions still being refined continue.
716
+ This lets easy tokens halt early and hard tokens receive more computation,
717
+ all within the same batch. Also makes the model Turing-complete under
718
+ certain assumptions about the expressiveness of the transformer block.
719
+ """
720
+
721
+ def __init__(self, dim: int):
722
+ """
723
+ Args:
724
+ dim -- hidden state dimension; input to the halting scalar predictor
725
+ """
726
+ super().__init__()
727
+ self.halt = nn.Linear(dim, 1)
728
+
729
+ def forward(self, h: torch.Tensor) -> torch.Tensor:
730
+ """
731
+ Predict per-position halting probability from the current hidden state.
732
+
733
+ Args:
734
+ h -- hidden state of shape (B, T, dim)
735
+
736
+ Returns:
737
+ Halting probability tensor of shape (B, T), values in (0, 1)
738
+ """
739
+ return torch.sigmoid(self.halt(h)).squeeze(-1)
740
+
741
+
742
+ # ---------------------------------------------------------------------------
743
+ # Recurrent Block (one set of weights, looped T times)
744
+ # ---------------------------------------------------------------------------
745
+
746
+
747
+ class RecurrentBlock(nn.Module):
748
+ """
749
+ The core recurrent block of OpenMythos — a single TransformerBlock looped T times.
750
+
751
+ At each loop iteration t, the hidden state h is updated via:
752
+ 1. loop_index_embedding: inject sinusoidal loop-index signal into h
753
+ 2. TransformerBlock: compute attention + MoE FFN on normalized (h + e)
754
+ 3. LoRAAdapter: apply depth-wise LoRA delta to transformer output
755
+ 4. LTIInjection: stable update h = A·h + B·e + transformer_out
756
+ 5. ACTHalting: accumulate per-position halting probabilities;
757
+ positions that have converged stop contributing
758
+
759
+ The encoded input e (output of the Prelude) is injected at every step to keep
760
+ the original input signal alive across arbitrary loop depth, preventing drift.
761
+ The ACT mechanism produces a weighted sum of hidden states across iterations,
762
+ where the weights reflect when each position converged.
763
+
764
+ More loop iterations at inference = deeper reasoning chains, following the
765
+ depth-extrapolation property of looped transformers (Saunshi et al., 2025).
766
+ """
767
+
768
+ def __init__(self, cfg: MythosConfig):
769
+ """
770
+ Args:
771
+ cfg -- MythosConfig; uses dim, lora_rank, max_loop_iters, act_threshold
772
+ """
773
+ super().__init__()
774
+ self.cfg = cfg
775
+ self.block = TransformerBlock(cfg, use_moe=True)
776
+ self.injection = LTIInjection(cfg.dim)
777
+ self.act = ACTHalting(cfg.dim)
778
+ self.lora = LoRAAdapter(cfg.dim, cfg.lora_rank, cfg.max_loop_iters)
779
+ self.norm = RMSNorm(cfg.dim)
780
+ self.loop_dim = (
781
+ cfg.dim // 8
782
+ ) # fraction of channels receiving loop-index embedding
783
+
784
+ def forward(
785
+ self,
786
+ h: torch.Tensor,
787
+ e: torch.Tensor,
788
+ freqs_cis: torch.Tensor,
789
+ mask: Optional[torch.Tensor] = None,
790
+ n_loops: Optional[int] = None,
791
+ kv_cache: Optional[dict] = None,
792
+ ) -> torch.Tensor:
793
+ """
794
+ Run the recurrent loop for up to n_loops iterations with ACT early exit.
795
+
796
+ Args:
797
+ h -- initial hidden state from the Prelude, shape (B, T, dim)
798
+ e -- encoded input frozen for injection each step, shape (B, T, dim)
799
+ freqs_cis-- precomputed RoPE frequencies
800
+ mask -- additive causal mask or None
801
+ n_loops -- number of loop iterations; defaults to cfg.max_loop_iters.
802
+ Can be increased at inference for deeper reasoning (depth extrapolation).
803
+ kv_cache -- cache dict passed through to the inner TransformerBlock;
804
+ each loop iteration uses a separate cache key
805
+
806
+ Returns:
807
+ ACT-weighted sum of hidden states across iterations, shape (B, T, dim)
808
+ """
809
+ n_loops = n_loops or self.cfg.max_loop_iters
810
+ B, T, D = h.shape
811
+
812
+ halted = torch.zeros(B, T, device=h.device, dtype=torch.bool)
813
+ cumulative_p = torch.zeros(B, T, device=h.device)
814
+ h_out = torch.zeros_like(h)
815
+
816
+ for t in range(n_loops):
817
+ h_loop = loop_index_embedding(h, t, self.loop_dim)
818
+ combined = self.norm(h_loop + e)
819
+ cache_key = f"recurrent_loop_{t}"
820
+ trans_out = self.block(combined, freqs_cis, mask, kv_cache, cache_key)
821
+ trans_out = trans_out + self.lora(trans_out, t)
822
+ h = self.injection(h, e, trans_out)
823
+
824
+ p = self.act(h) # (B, T)
825
+ still_running = ~halted
826
+
827
+ # ACT remainder trick: once cumulative_p + p crosses threshold,
828
+ # assign the remaining probability mass as the final weight
829
+ remainder = (1.0 - cumulative_p).clamp(min=0)
830
+ weight = torch.where(
831
+ cumulative_p + p >= self.cfg.act_threshold,
832
+ remainder,
833
+ p,
834
+ )
835
+ h_out = h_out + weight.unsqueeze(-1) * h
836
+
837
+ cumulative_p = cumulative_p + p * still_running.float()
838
+ halted = halted | (cumulative_p >= self.cfg.act_threshold)
839
+
840
+ if halted.all():
841
+ break
842
+
843
+ return h_out
844
+
845
+
846
+ # ---------------------------------------------------------------------------
847
+ # Full Model
848
+ # ---------------------------------------------------------------------------
849
+
850
+
851
+ class OpenMythos(nn.Module):
852
+ """
853
+ OpenMythos — Recurrent-Depth Transformer language model.
854
+
855
+ Implements the hypothesized Claude Mythos architecture as a Recurrent-Depth
856
+ Transformer (RDT). The model divides computation into three functional blocks:
857
+
858
+ Input tokens
859
+ ↓
860
+ [Prelude] — prelude_layers standard transformer blocks, run once
861
+ ↓
862
+ [Recurrent Block] — one transformer block looped T times with input injection
863
+ ↑_______↓ h_{t+1} = A·h_t + B·e + Transformer(h_t, e)
864
+ ↓
865
+ [Coda] — coda_layers standard transformer blocks, run once
866
+ ↓
867
+ Output logits
868
+
869
+ Key properties:
870
+ - Same weights, more loops → deeper reasoning, no parameter growth
871
+ - Depth extrapolation: train on N loops, test on N+k loops (emergent)
872
+ - ACT halting: variable compute per position within a batch
873
+ - MoE FFN in the recurrent block: breadth across domains
874
+ - LTI-stable injection: spectral radius < 1 guaranteed by construction
875
+ - Supports both GQA and MLA attention (set via cfg.attn_type)
876
+ """
877
+
878
+ def __init__(self, cfg: MythosConfig):
879
+ """
880
+ Args:
881
+ cfg -- MythosConfig specifying all architecture hyperparameters
882
+ """
883
+ super().__init__()
884
+ self.cfg = cfg
885
+
886
+ self.embed = nn.Embedding(cfg.vocab_size, cfg.dim)
887
+
888
+ # GQA uses full head_dim for RoPE; MLA uses only qk_rope_head_dim (decoupled)
889
+ freqs = precompute_rope_freqs(
890
+ cfg.dim // cfg.n_heads, cfg.max_seq_len, cfg.rope_theta
891
+ )
892
+ self.register_buffer("freqs_cis", freqs)
893
+ freqs_mla = precompute_rope_freqs(
894
+ cfg.qk_rope_head_dim, cfg.max_seq_len, cfg.rope_theta
895
+ )
896
+ self.register_buffer("freqs_cis_mla", freqs_mla)
897
+
898
+ self.prelude = nn.ModuleList(
899
+ [TransformerBlock(cfg, use_moe=False) for _ in range(cfg.prelude_layers)]
900
+ )
901
+ self.recurrent = RecurrentBlock(cfg)
902
+ self.coda = nn.ModuleList(
903
+ [TransformerBlock(cfg, use_moe=False) for _ in range(cfg.coda_layers)]
904
+ )
905
+
906
+ self.norm = RMSNorm(cfg.dim)
907
+ self.head = nn.Linear(cfg.dim, cfg.vocab_size, bias=False)
908
+ self.head.weight = self.embed.weight # weight tying
909
+
910
+ self._init_weights()
911
+
912
+ def _init_weights(self) -> None:
913
+ """Initialize all linear and embedding weights with N(0, 0.02)."""
914
+ for m in self.modules():
915
+ if isinstance(m, nn.Linear):
916
+ nn.init.normal_(m.weight, std=0.02)
917
+ elif isinstance(m, nn.Embedding):
918
+ nn.init.normal_(m.weight, std=0.02)
919
+
920
+ @staticmethod
921
+ def _causal_mask(seq_len: int, device: torch.device) -> torch.Tensor:
922
+ """
923
+ Build an additive causal mask: 0 on and below the diagonal, -inf above.
924
+
925
+ Args:
926
+ seq_len -- sequence length
927
+ device -- target device
928
+
929
+ Returns:
930
+ Tensor of shape (1, 1, seq_len, seq_len) broadcastable over (B, H, T, S)
931
+ """
932
+ mask = torch.full((1, 1, seq_len, seq_len), float("-inf"), device=device)
933
+ return torch.triu(mask, diagonal=1)
934
+
935
+ def forward(
936
+ self,
937
+ input_ids: torch.Tensor,
938
+ n_loops: Optional[int] = None,
939
+ kv_cache: Optional[dict] = None,
940
+ ) -> torch.Tensor:
941
+ """
942
+ Forward pass through Prelude → Recurrent Block → Coda.
943
+
944
+ Args:
945
+ input_ids -- token indices of shape (B, T)
946
+ n_loops -- recurrent loop depth; defaults to cfg.max_loop_iters.
947
+ Increase at inference to extrapolate to harder problems.
948
+ kv_cache -- dict mutated in-place for autoregressive KV caching;
949
+ pass an empty dict {} and reuse across decode steps
950
+
951
+ Returns:
952
+ Logits of shape (B, T, vocab_size)
953
+ """
954
+ B, T = input_ids.shape
955
+ device = input_ids.device
956
+
957
+ x = self.embed(input_ids)
958
+ freqs_cis = (
959
+ self.freqs_cis_mla if self.cfg.attn_type == "mla" else self.freqs_cis
960
+ )[:T]
961
+ mask = self._causal_mask(T, device) if T > 1 else None
962
+
963
+ for i, layer in enumerate(self.prelude):
964
+ x = layer(x, freqs_cis, mask, kv_cache, cache_key=f"prelude_{i}")
965
+
966
+ e = x # encoded input frozen for injection every loop
967
+ x = self.recurrent(x, e, freqs_cis, mask, n_loops, kv_cache)
968
+
969
+ for i, layer in enumerate(self.coda):
970
+ x = layer(x, freqs_cis, mask, kv_cache, cache_key=f"coda_{i}")
971
+
972
+ return self.head(self.norm(x))
973
+
974
+ @torch.no_grad()
975
+ def generate(
976
+ self,
977
+ input_ids: torch.Tensor,
978
+ max_new_tokens: int = 64,
979
+ n_loops: int = 8,
980
+ temperature: float = 1.0,
981
+ top_k: int = 50,
982
+ ) -> torch.Tensor:
983
+ """
984
+ Autoregressive token generation with KV caching.
985
+
986
+ On step 0 the full prompt is processed. On subsequent steps only the
987
+ last generated token is passed, with all previous keys and values
988
+ retrieved from kv_cache. This keeps decode cost proportional to one
989
+ token per step rather than the full growing sequence.
990
+
991
+ n_loops can be set higher than the training value to extrapolate to
992
+ harder problems at inference time (depth extrapolation property).
993
+
994
+ Args:
995
+ input_ids -- prompt token indices of shape (B, T)
996
+ max_new_tokens -- number of tokens to generate
997
+ n_loops -- recurrent loop depth for each decode step
998
+ temperature -- softmax temperature; lower = more greedy
999
+ top_k -- restrict sampling to top-K logits (0 = disabled)
1000
+
1001
+ Returns:
1002
+ Token indices of shape (B, T + max_new_tokens)
1003
+ """
1004
+ kv_cache: dict = {}
1005
+ for step in range(max_new_tokens):
1006
+ cur_ids = input_ids if step == 0 else input_ids[:, -1:]
1007
+ logits = self.forward(cur_ids, n_loops=n_loops, kv_cache=kv_cache)
1008
+ logits = logits[:, -1, :] / temperature
1009
+ if top_k > 0:
1010
+ v, _ = logits.topk(top_k)
1011
+ logits[logits < v[:, -1:]] = float("-inf")
1012
+ probs = F.softmax(logits, dim=-1)
1013
+ next_tok = torch.multinomial(probs, num_samples=1)
1014
+ input_ids = torch.cat([input_ids, next_tok], dim=1)
1015
+ return input_ids
1016
+
1017
+
1018
+ # ---------------------------------------------------------------------------
1019
+ # Quick smoke test
1020
+ # ---------------------------------------------------------------------------
1021
+
1022
+
1023
+ def _smoke(attn_type: str) -> None:
1024
+ """
1025
+ Instantiate a small OpenMythos model with the given attention type, run a
1026
+ forward pass and a short generation, and verify the LTI spectral radius.
1027
+
1028
+ Args:
1029
+ attn_type -- "gqa" or "mla"
1030
+ """
1031
+ base = dict(
1032
+ vocab_size=1000,
1033
+ dim=256,
1034
+ n_heads=8,
1035
+ max_seq_len=128,
1036
+ max_loop_iters=4,
1037
+ prelude_layers=1,
1038
+ coda_layers=1,
1039
+ n_experts=8,
1040
+ n_shared_experts=1,
1041
+ n_experts_per_tok=2,
1042
+ expert_dim=64,
1043
+ lora_rank=8,
1044
+ attn_type=attn_type,
1045
+ )
1046
+ if attn_type == "gqa":
1047
+ cfg = MythosConfig(**base, n_kv_heads=2)
1048
+ else:
1049
+ cfg = MythosConfig(
1050
+ **base,
1051
+ n_kv_heads=8, # unused by MLA but field must be valid
1052
+ kv_lora_rank=32,
1053
+ q_lora_rank=64,
1054
+ qk_rope_head_dim=16,
1055
+ qk_nope_head_dim=16,
1056
+ v_head_dim=16,
1057
+ )
1058
+
1059
+ model = OpenMythos(cfg)
1060
+ total = sum(p.numel() for p in model.parameters())
1061
+ print(f"\n[{attn_type.upper()}] Parameters: {total:,}")
1062
+
1063
+ ids = torch.randint(0, cfg.vocab_size, (2, 16))
1064
+ logits = model(ids, n_loops=4)
1065
+ print(f"[{attn_type.upper()}] Logits shape: {logits.shape}")
1066
+
1067
+ out = model.generate(ids, max_new_tokens=8, n_loops=8)
1068
+ print(f"[{attn_type.upper()}] Generated shape: {out.shape}")
1069
+
1070
+ A = model.recurrent.injection.get_A()
1071
+ print(
1072
+ f"[{attn_type.upper()}] Spectral radius ρ(A) max: {A.max().item():.4f} (must be < 1)"
1073
+ )
1074
+
1075
+
1076
+ if __name__ == "__main__":
1077
+ _smoke("gqa")
1078
+ _smoke("mla")