KitsuVp commited on
Commit
c945d89
·
verified ·
1 Parent(s): 0396100

Update modeling_neollm.py

Browse files
Files changed (1) hide show
  1. modeling_neollm.py +602 -11
modeling_neollm.py CHANGED
@@ -4,7 +4,9 @@ NeoLLM model with FANformer, SeeDNorm, ResFormer, Learnable Multipliers,
4
  full attention augmented with optional Momentum, MEA, and LUCID operators,
5
  Gated Attention (Qiu et al., 2025) combined with Affine-Scaled Attention
6
  (Bae et al., 2026), an optional Leviathan continuous token embedding
7
- generator, and an optional Leviathan-JTok-M token-indexed modulation module.
 
 
8
 
9
  Attention stack (orthogonal, all active simultaneously when enabled):
10
  1. Gated Attention (use_gated_attention implicit via q_proj gate chunk):
@@ -49,6 +51,10 @@ References:
49
  for Rapid, Resource-Efficient Scientific Computation." arXiv:2505.13315.
50
  JTok / JTok-M: Yang et al. (2026). "JTok: On Token Embedding as Another
51
  Axis of Scaling Law via Joint Token Self-Modulation." arXiv:2602.00800.
 
 
 
 
52
  """
53
 
54
  import math
@@ -165,6 +171,27 @@ class HadamardAnalysis:
165
  alpha_snapshot: Optional[torch.Tensor] = None # self.alpha [D] — learned scale
166
 
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  @dataclass
169
  class AttentionAnalysis:
170
  """
@@ -183,9 +210,15 @@ class AttentionAnalysis:
183
  k_post_norm: Optional[torch.Tensor] = None # K after SeeDNorm k_norm [B,H,S,d]
184
  v_raw: Optional[torch.Tensor] = None # V raw (pre MEA/LUCID) [B,H,S,d]
185
 
186
- # ── RoPE (always active) ──────────────────────────────────────────
187
- q_post_rope: Optional[torch.Tensor] = None # Q after RoPE [B,H,S,d]
188
- k_post_rope: Optional[torch.Tensor] = None # K after RoPE [B,H,S,d]
 
 
 
 
 
 
189
 
190
  # ── Momentum (conditional on use_momentum_attention) ──────────────
191
  q_momentum_delta: Optional[torch.Tensor] = None # causal_first_difference(Q)
@@ -238,6 +271,9 @@ class AttentionAnalysis:
238
  # ── HadamardOProj internals (conditional on use_hadamard_o_proj) ──
239
  hadamard: Optional["HadamardAnalysis"] = None # None when dense o_proj active
240
 
 
 
 
241
 
242
  @dataclass
243
  class MLPAnalysis:
@@ -352,6 +388,20 @@ class AnalysisState:
352
  _ = model(input_ids)
353
  state = model.last_analysis
354
  alpha = state.layers[3].attention.alpha_per_head
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  """
356
  input_ids: Optional[torch.Tensor] = None
357
  embeddings: Optional[torch.Tensor] = None
@@ -1741,12 +1791,170 @@ class HadamardOProj(nn.Module):
1741
  return out
1742
 
1743
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1744
  class NeoLLMAttention(nn.Module):
1745
  """
1746
  Full attention with FANformer, SeeDNorm, ResFormer, Learnable Multipliers,
1747
  optional Momentum, MEA head-level composition, optional LUCID preconditioning,
1748
- optional Affine-Scaled Attention, optional Exclusive Self Attention, and
1749
- optional Directional Routing (Taylor, 2026).
 
1750
 
1751
  Directional Routing inserts at position C — post-XSA, pre-reshape — where
1752
  the output is already normalized (MEAHeadSeeDNorm) and has auto-position
@@ -1754,11 +1962,23 @@ class NeoLLMAttention(nn.Module):
1754
  orthogonal to the self-position already cleaned by XSA.
1755
 
1756
  Pipeline (all active simultaneously when enabled):
1757
- FANLayer → q_proj(gate) → q_norm/k_norm → RoPE → Momentum
1758
  → MEA(K,V) → LUCID(V) → v_ref → Affine-Scaled SDPA
1759
  → MEAHeadSeeDNorm → XSA → Directional Routing → reshape
1760
  → o_proj · sigmoid(gate) → dropout
1761
 
 
 
 
 
 
 
 
 
 
 
 
 
1762
  o_proj variants (controlled by config.use_hadamard_o_proj):
1763
  False (default): dense LinearWithMultipliers — full expressivity,
1764
  develops high κ during training (FP8 risk).
@@ -1769,6 +1989,7 @@ class NeoLLMAttention(nn.Module):
1769
  References:
1770
  Directional Routing: Taylor (2026). arXiv:2603.14923.
1771
  Hadamard o_proj: Aggarwal & Kumar (2026). arXiv:2603.08343.
 
1772
  """
1773
 
1774
  def __init__(self, config: NeoLLMConfig, layer_idx: int):
@@ -1945,6 +2166,53 @@ class NeoLLMAttention(nn.Module):
1945
  self.direction_vecs = None
1946
  self.direction_router = None
1947
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1948
  def _apply_momentum_attention(
1949
  self,
1950
  q: torch.Tensor,
@@ -2113,7 +2381,21 @@ class NeoLLMAttention(nn.Module):
2113
  attn_analysis.v_raw = v.detach()
2114
 
2115
  cos, sin = position_embeddings
2116
- q, k = apply_rotary_pos_emb(q, k, cos, sin)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2117
 
2118
  if attn_analysis is not None:
2119
  attn_analysis.q_post_rope = q.detach()
@@ -2353,7 +2635,7 @@ class NeoLLMMLP(nn.Module):
2353
  config.intermediate_size, config.hidden_size,
2354
  bias=False, use_row_multiplier=True, use_column_multiplier=True,
2355
  )
2356
- self.act_fn = PolyNorm(exclusive_init=0.15)
2357
  self.dropout = nn.Dropout(config.dropout_rate)
2358
 
2359
  def forward(
@@ -2596,6 +2878,246 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
2596
  return outputs
2597
 
2598
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2599
  class NeoLLMPreTrainedModel(PreTrainedModel):
2600
  """
2601
  Base class with custom weight initialization for all NeoLLM components.
@@ -2629,6 +3151,13 @@ class NeoLLMPreTrainedModel(PreTrainedModel):
2629
  per head rather than collapsing to 0 or 1.
2630
  - alpha_ma: zeros — running EMA starts at 0, β starts as −α/N ≈ small
2631
  negative offset; model quickly learns to adjust both.
 
 
 
 
 
 
 
2632
  """
2633
  config: NeoLLMConfig
2634
  base_model_prefix = "model"
@@ -2729,6 +3258,14 @@ class NeoLLMPreTrainedModel(PreTrainedModel):
2729
  module.attn_res_query_attn.data.zero_()
2730
  module.attn_res_query_mlp.data.zero_()
2731
 
 
 
 
 
 
 
 
 
2732
  class NeoLLMModel(NeoLLMPreTrainedModel):
2733
  """
2734
  NeoLLM base decoder-only Transformer.
@@ -2741,11 +3278,30 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
2741
  outputs (or block summaries for Block AttnRes) and passes them to each
2742
  decoder layer, replacing fixed residual accumulation with learned
2743
  depth-wise softmax attention (Kimi Team, 2026, arXiv:2603.15031).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2744
  """
2745
 
2746
  def __init__(self, config: NeoLLMConfig):
2747
  super().__init__(config)
2748
 
 
2749
  if config.use_token_generator:
2750
  self.token_generator = LeviathanGenerator(config)
2751
  else:
@@ -2753,6 +3309,15 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
2753
  config.vocab_size, config.hidden_size, config.pad_token_id
2754
  )
2755
 
 
 
 
 
 
 
 
 
 
2756
  self.layers = nn.ModuleList(
2757
  [NeoLLMDecoderLayer(config, layer_idx)
2758
  for layer_idx in range(config.num_hidden_layers)]
@@ -2764,6 +3329,17 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
2764
 
2765
  self.post_init()
2766
 
 
 
 
 
 
 
 
 
 
 
 
2767
  def get_input_embeddings(self):
2768
  if self.config.use_token_generator:
2769
  return self.token_generator
@@ -2775,7 +3351,7 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
2775
  else:
2776
  self.embed_tokens = value
2777
 
2778
- def _build_layer_analysis(self) -> LayerAnalysis:
2779
  """
2780
  Construct a LayerAnalysis with sub-objects pre-allocated for every
2781
  component that is active in the current config.
@@ -2785,12 +3361,17 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
2785
  Called once per layer per forward when analysis is active.
2786
  """
2787
  cfg = self.config
 
 
 
 
2788
  return LayerAnalysis(
2789
  seednorm_pre_attn = SeeDNormAnalysis(),
2790
  seednorm_post_attn = SeeDNormAnalysis(),
2791
  attention = AttentionAnalysis(
2792
  fan = FANAnalysis(),
2793
  hadamard = HadamardAnalysis() if getattr(cfg, "use_hadamard_o_proj", False) else None,
 
2794
  ),
2795
  mlp = MLPAnalysis(
2796
  fan = FANAnalysis(),
@@ -2856,6 +3437,13 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
2856
  else:
2857
  inputs_embeds = self.embed_tokens(input_ids)
2858
 
 
 
 
 
 
 
 
2859
  if analysis_state is not None:
2860
  analysis_state.embeddings = inputs_embeds.detach()
2861
 
@@ -2928,7 +3516,7 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
2928
  # Build per-layer analysis container (only in eval + analysis mode)
2929
  layer_analysis = None
2930
  if analysis_state is not None:
2931
- layer_analysis = self._build_layer_analysis()
2932
  layer_analysis.layer_idx = layer_idx
2933
  analysis_state.layers.append(layer_analysis)
2934
 
@@ -3239,6 +3827,7 @@ __all__ = [
3239
  "NeoLLMConfig",
3240
  "LeviathanGenerator",
3241
  "LeviathanJTokM",
 
3242
  "FANLayer",
3243
  "SeeDNorm",
3244
  "ScalarMultiplier",
@@ -3246,6 +3835,7 @@ __all__ = [
3246
  "LinearWithMultipliers",
3247
  "MEAHeadSeeDNorm",
3248
  "HadamardOProj",
 
3249
  # Analysis dataclasses — exported so external tools can type-hint against them
3250
  "AnalysisState",
3251
  "LayerAnalysis",
@@ -3256,6 +3846,7 @@ __all__ = [
3256
  "GPASAnalysis",
3257
  "PolyNormAnalysis",
3258
  "HadamardAnalysis",
 
3259
  "JTokMAnalysis",
3260
  "AttnResAnalysis",
3261
  "GeneratorAnalysis",
 
4
  full attention augmented with optional Momentum, MEA, and LUCID operators,
5
  Gated Attention (Qiu et al., 2025) combined with Affine-Scaled Attention
6
  (Bae et al., 2026), an optional Leviathan continuous token embedding
7
+ generator, an optional Leviathan-JTok-M token-indexed modulation module,
8
+ optional Spelling Bee Embeddings (Rabe et al., 2026), and optional Context
9
+ Re-Positioning (Li et al., 2026).
10
 
11
  Attention stack (orthogonal, all active simultaneously when enabled):
12
  1. Gated Attention (use_gated_attention implicit via q_proj gate chunk):
 
51
  for Rapid, Resource-Efficient Scientific Computation." arXiv:2505.13315.
52
  JTok / JTok-M: Yang et al. (2026). "JTok: On Token Embedding as Another
53
  Axis of Scaling Law via Joint Token Self-Modulation." arXiv:2602.00800.
54
+ Spelling Bee Embeddings: Rabe, Clymo & Dong (2026). "Spelling Bee
55
+ Embeddings for Language Modeling." arXiv:2601.18030.
56
+ Context Re-Positioning: Li, Zhao, Cai & Sproat (2026). "REPO: Language
57
+ Models with Context Re-Positioning." arXiv:2512.14391.
58
  """
59
 
60
  import math
 
171
  alpha_snapshot: Optional[torch.Tensor] = None # self.alpha [D] — learned scale
172
 
173
 
174
+ @dataclass
175
+ class REPOAnalysis:
176
+ """
177
+ Internals of a REPOModule forward pass.
178
+ Only populated when use_repo=True and layer_idx >= repo_start_layer.
179
+
180
+ Reference: Li, Zhao, Cai & Sproat (2026). arXiv:2512.14391.
181
+
182
+ positions: continuous per-head positions z [B, H, S] produced by f_ϕ.
183
+ Captures what position pattern the model learned for each head:
184
+ constant (NoPE-like), monotonic (RoPE-like), or hybrid.
185
+ Use this field with the attention interpretability toolkit to
186
+ reproduce the position-pattern analysis of Li et al. (2026) §5.2.
187
+ r_repr: intermediate position representation r [B, S, d_p] — output of
188
+ the SwiGLU sub-layer before the per-head linear W_z.
189
+ Shared across heads within the layer.
190
+ """
191
+ positions: Optional[torch.Tensor] = None # z [B, H, S] — predicted positions
192
+ r_repr: Optional[torch.Tensor] = None # r [B, S, d_p] — shared repr
193
+
194
+
195
  @dataclass
196
  class AttentionAnalysis:
197
  """
 
210
  k_post_norm: Optional[torch.Tensor] = None # K after SeeDNorm k_norm [B,H,S,d]
211
  v_raw: Optional[torch.Tensor] = None # V raw (pre MEA/LUCID) [B,H,S,d]
212
 
213
+ # ── RoPE / REPO (always active) ──────────────────────────────────
214
+ # When use_repo=False or layer_idx < repo_start_layer: standard integer
215
+ # RoPE q_post_rope/k_post_rope are Q/K after apply_rotary_pos_emb.
216
+ # When use_repo=True and layer_idx >= repo_start_layer: REPO path —
217
+ # q_post_rope/k_post_rope are Q/K after _apply_repo_rope with
218
+ # continuous per-head positions z from REPOModule. The positions
219
+ # themselves and the intermediate r_repr are in the .repo sub-object.
220
+ q_post_rope: Optional[torch.Tensor] = None # Q after RoPE/REPO [B,H,S,d]
221
+ k_post_rope: Optional[torch.Tensor] = None # K after RoPE/REPO [B,H,S,d]
222
 
223
  # ── Momentum (conditional on use_momentum_attention) ──────────────
224
  q_momentum_delta: Optional[torch.Tensor] = None # causal_first_difference(Q)
 
271
  # ── HadamardOProj internals (conditional on use_hadamard_o_proj) ──
272
  hadamard: Optional["HadamardAnalysis"] = None # None when dense o_proj active
273
 
274
+ # ── REPO position prediction (conditional on use_repo) ────────────
275
+ repo: Optional["REPOAnalysis"] = None # None when layer_idx < repo_start_layer
276
+
277
 
278
  @dataclass
279
  class MLPAnalysis:
 
388
  _ = model(input_ids)
389
  state = model.last_analysis
390
  alpha = state.layers[3].attention.alpha_per_head
391
+
392
+ REPO access (use_repo=True, layers >= repo_start_layer):
393
+ # Per-head predicted positions [B, H, S] for layer i:
394
+ z = state.layers[i].attention.repo.positions
395
+
396
+ # Shared position representation r [B, S, d_p] for layer i:
397
+ r = state.layers[i].attention.repo.r_repr
398
+
399
+ # Q/K after REPO rotation (identical field name as standard RoPE path):
400
+ q = state.layers[i].attention.q_post_rope # [B, H, S, head_dim]
401
+ k = state.layers[i].attention.k_post_rope # [B, H, S, head_dim]
402
+
403
+ # For layers below repo_start_layer: .repo is None, q/k_post_rope
404
+ # contain standard integer-RoPE rotated Q/K — same field, same shape.
405
  """
406
  input_ids: Optional[torch.Tensor] = None
407
  embeddings: Optional[torch.Tensor] = None
 
1791
  return out
1792
 
1793
 
1794
+ class REPOModule(nn.Module):
1795
+ """
1796
+ Context Re-Positioning module f_ϕ (Li et al., 2026, arXiv:2512.14391).
1797
+
1798
+ Replaces the fixed linear integer indices ``0…L-1`` fed to RoPE with
1799
+ continuous, data-dependent positions ``z_i`` learned end-to-end.
1800
+
1801
+ Architecture (Eq. 4–6 of the paper):
1802
+
1803
+ # Position representation — shared across all heads in this layer
1804
+ r_i = Swish(h_i W_g) ⊙ (h_i W_c) r_i ∈ R^{d_p}
1805
+
1806
+ # Position assignment — independent per head
1807
+ z_i^(h) = r_i w_z^(h) z_i^(h) ∈ R (scalar)
1808
+
1809
+ where ``h_i ∈ R^d`` is the hidden state of token ``i`` entering the
1810
+ decoder layer (pre-FANLayer), and ``d_p = hidden_size // 8`` by default.
1811
+
1812
+ The resulting positions ``z [B, H, S]`` are real-valued and
1813
+ unconstrained. They are used to compute per-head ``cos/sin`` embeddings
1814
+ inline from ``inv_freq``, replacing the standard integer-based
1815
+ ``position_embeddings`` for this layer.
1816
+
1817
+ Design notes:
1818
+ - ``W_g`` and ``W_c`` are shared across heads (parameter efficiency).
1819
+ - ``W_z`` is a single ``[d_p, num_heads]`` matrix; each column is the
1820
+ per-head assignment vector ``w_z^(h)``. Vectorized as one matmul.
1821
+ - The raw hidden state ``h_i`` (not the FAN-augmented or normed variant)
1822
+ is used as input, matching the paper's formulation and avoiding
1823
+ circular dependency with q/k norm.
1824
+ - No bias on any projection — consistent with the paper's Eq. 4–5.
1825
+
1826
+ Reference:
1827
+ Li, H., Zhao, T., Cai, D. & Sproat, R. (2026). "REPO: Language
1828
+ Models with Context Re-Positioning." arXiv:2512.14391.
1829
+ """
1830
+
1831
+ def __init__(self, hidden_size: int, d_p: int, num_heads: int):
1832
+ super().__init__()
1833
+ self.hidden_size = hidden_size
1834
+ self.d_p = d_p
1835
+ self.num_heads = num_heads
1836
+
1837
+ # SwiGLU position representation (shared across heads, Eq. 4)
1838
+ self.W_g = nn.Linear(hidden_size, d_p, bias=False)
1839
+ self.W_c = nn.Linear(hidden_size, d_p, bias=False)
1840
+
1841
+ # Per-head position assignment (vectorized, Eq. 5)
1842
+ # W_z[:, h] is w_z^(h) for head h
1843
+ self.W_z = nn.Linear(d_p, num_heads, bias=False)
1844
+
1845
+ def forward(
1846
+ self,
1847
+ hidden_states: torch.Tensor,
1848
+ repo_analysis: Optional[REPOAnalysis] = None,
1849
+ ) -> torch.Tensor:
1850
+ """
1851
+ Args:
1852
+ hidden_states: [B, S, hidden_size] — residual stream entering the
1853
+ decoder layer, before FANLayer augmentation.
1854
+ repo_analysis: REPOAnalysis container populated when analysis mode
1855
+ is active. None during training (zero overhead).
1856
+
1857
+ Returns:
1858
+ z: [B, H, S] — continuous per-head position scalars.
1859
+ z[:, h, i] is the position assigned to token i by head h.
1860
+ """
1861
+ # Position representation (Eq. 4): Swish(h W_g) ⊙ (h W_c)
1862
+ r = F.silu(self.W_g(hidden_states)) * self.W_c(hidden_states) # [B, S, d_p]
1863
+
1864
+ # Per-head assignment (Eq. 5): z^(h) = r W_z[:, h]
1865
+ # W_z output: [B, S, H] → transpose to [B, H, S]
1866
+ z = self.W_z(r).transpose(1, 2).contiguous() # [B, H, S]
1867
+
1868
+ if repo_analysis is not None:
1869
+ repo_analysis.r_repr = r.detach()
1870
+ repo_analysis.positions = z.detach()
1871
+
1872
+ return z
1873
+
1874
+
1875
+ def _apply_repo_rope(
1876
+ q: torch.Tensor,
1877
+ k: torch.Tensor,
1878
+ z: torch.Tensor,
1879
+ inv_freq: torch.Tensor,
1880
+ attention_scaling: float,
1881
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
1882
+ """
1883
+ Apply RoPE to Q and K using continuous per-head positions from REPO.
1884
+
1885
+ Replaces the standard ``apply_rotary_pos_emb(q, k, cos, sin)`` call for
1886
+ layers where REPO is active. Builds ``cos/sin`` inline from ``z`` and
1887
+ ``inv_freq`` so that the rotation is differentiable w.r.t. ``z`` and
1888
+ therefore w.r.t. the parameters of REPOModule.
1889
+
1890
+ Args:
1891
+ q: [B, H, S, head_dim]
1892
+ k: [B, H_kv, S, head_dim] (GQA: H_kv ≤ H)
1893
+ z: [B, H, S] — per-head positions from REPOModule
1894
+ inv_freq: [rotary_dim/2] — frozen RoPE frequency vector
1895
+ attention_scaling: float — scaling factor from NeoLLMRotaryEmbedding
1896
+
1897
+ Returns:
1898
+ (q_embed, k_embed) with the same shapes as (q, k).
1899
+
1900
+ Implementation note on GQA:
1901
+ Q has ``num_attention_heads`` heads; K/V have ``num_key_value_heads``
1902
+ heads (fewer under GQA). REPO produces one position per Q head.
1903
+ For K we average the positions of the Q heads that map to each KV
1904
+ head (groups of size ``num_key_value_groups``). This is the minimal
1905
+ approach consistent with the paper's per-head independence claim:
1906
+ each KV head receives a position that is representative of the Q
1907
+ heads it serves.
1908
+ """
1909
+ B, H, S = z.shape
1910
+ H_kv = k.shape[1]
1911
+ n_groups = H // H_kv
1912
+ rotary_dim = inv_freq.shape[0] * 2 # inv_freq covers half the rotary dim
1913
+
1914
+ # inv_freq is already float32 on the correct device (registered as buffer
1915
+ # via set_repo_inv_freq) — no .to() needed, no DeviceCopy op.
1916
+ # No autocast barrier: explicit .float() casts on z_q/z_k are sufficient
1917
+ # to maintain float32 precision for the trig ops. Removing the context
1918
+ # manager lets Inductor plan all intermediate tensors as part of a single
1919
+ # static memory graph, eliminating mid-forward allocations that cause
1920
+ # VRAM variance under max-autotune.
1921
+ inv_freq_f = inv_freq
1922
+
1923
+ # z_q: [B, H, S, 1] × inv_freq: [rotary_dim/2] → [B, H, S, rotary_dim/2]
1924
+ z_q = z.float().unsqueeze(-1) # [B, H, S, 1]
1925
+ freqs_q = z_q * inv_freq_f # [B, H, S, r/2]
1926
+ emb_q = torch.cat([freqs_q, freqs_q], dim=-1) # [B, H, S, r]
1927
+ cos_q = (emb_q.cos() * attention_scaling).to(q.dtype)
1928
+ sin_q = (emb_q.sin() * attention_scaling).to(q.dtype)
1929
+
1930
+ # KV positions: mean over the Q heads in each GQA group → [B, H_kv, S]
1931
+ z_k = z.view(B, H_kv, n_groups, S).mean(dim=2) # [B, H_kv, S]
1932
+ z_k = z_k.float().unsqueeze(-1) # [B, H_kv, S, 1]
1933
+ freqs_k = z_k * inv_freq_f # [B, H_kv, S, r/2]
1934
+ emb_k = torch.cat([freqs_k, freqs_k], dim=-1) # [B, H_kv, S, r]
1935
+ cos_k = (emb_k.cos() * attention_scaling).to(k.dtype)
1936
+ sin_k = (emb_k.sin() * attention_scaling).to(k.dtype)
1937
+
1938
+ # Rotate only the first rotary_dim channels; pass the rest through unchanged.
1939
+ q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
1940
+ k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
1941
+
1942
+ q_embed = torch.cat(
1943
+ [(q_rot * cos_q) + (rotate_half(q_rot) * sin_q), q_pass], dim=-1
1944
+ )
1945
+ k_embed = torch.cat(
1946
+ [(k_rot * cos_k) + (rotate_half(k_rot) * sin_k), k_pass], dim=-1
1947
+ )
1948
+ return q_embed, k_embed
1949
+
1950
+
1951
  class NeoLLMAttention(nn.Module):
1952
  """
1953
  Full attention with FANformer, SeeDNorm, ResFormer, Learnable Multipliers,
1954
  optional Momentum, MEA head-level composition, optional LUCID preconditioning,
1955
+ optional Affine-Scaled Attention, optional Exclusive Self Attention,
1956
+ optional Directional Routing (Taylor, 2026), and optional Context
1957
+ Re-Positioning (Li et al., 2026).
1958
 
1959
  Directional Routing inserts at position C — post-XSA, pre-reshape — where
1960
  the output is already normalized (MEAHeadSeeDNorm) and has auto-position
 
1962
  orthogonal to the self-position already cleaned by XSA.
1963
 
1964
  Pipeline (all active simultaneously when enabled):
1965
+ FANLayer → q_proj(gate) → q_norm/k_norm → REPO/RoPE → Momentum
1966
  → MEA(K,V) → LUCID(V) → v_ref → Affine-Scaled SDPA
1967
  → MEAHeadSeeDNorm → XSA → Directional Routing → reshape
1968
  → o_proj · sigmoid(gate) → dropout
1969
 
1970
+ RoPE variants (controlled by config.use_repo and layer_idx):
1971
+ use_repo=False (default): standard integer RoPE via pre-computed
1972
+ position_embeddings — identical to prior behaviour.
1973
+ use_repo=True, layer_idx >= repo_start_layer:
1974
+ REPOModule f_ϕ predicts continuous per-head positions
1975
+ z [B, H, S] from hidden_states. cos/sin are built
1976
+ inline from z and inv_freq so the rotation is
1977
+ differentiable w.r.t. f_ϕ parameters.
1978
+ use_repo=True, layer_idx < repo_start_layer:
1979
+ standard integer RoPE (lower layers capture surface
1980
+ features that benefit less from re-positioning).
1981
+
1982
  o_proj variants (controlled by config.use_hadamard_o_proj):
1983
  False (default): dense LinearWithMultipliers — full expressivity,
1984
  develops high κ during training (FP8 risk).
 
1989
  References:
1990
  Directional Routing: Taylor (2026). arXiv:2603.14923.
1991
  Hadamard o_proj: Aggarwal & Kumar (2026). arXiv:2603.08343.
1992
+ Context Re-Positioning: Li et al. (2026). arXiv:2512.14391.
1993
  """
1994
 
1995
  def __init__(self, config: NeoLLMConfig, layer_idx: int):
 
2166
  self.direction_vecs = None
2167
  self.direction_router = None
2168
 
2169
+ # ── Context Re-Positioning (Li et al., 2026) ──────────���──────────
2170
+ # Active for layers at or above repo_start_layer only.
2171
+ # Layers below repo_start_layer use standard integer RoPE positions.
2172
+ # inv_freq is accessed from the model's rotary_emb at forward time;
2173
+ # stored here as a non-persistent buffer reference set by NeoLLMModel.
2174
+ self.use_repo = (
2175
+ getattr(config, "use_repo", False)
2176
+ and layer_idx >= getattr(config, "repo_start_layer", config.num_hidden_layers // 3)
2177
+ )
2178
+ if self.use_repo:
2179
+ _d_p = getattr(config, "repo_d_p", config.hidden_size // 8)
2180
+ self.repo_module = REPOModule(
2181
+ hidden_size=config.hidden_size,
2182
+ d_p=_d_p,
2183
+ num_heads=config.num_attention_heads,
2184
+ )
2185
+ # _repo_inv_freq is registered as a non-persistent buffer by
2186
+ # set_repo_inv_freq(), called from NeoLLMModel.__init__ after
2187
+ # rotary_emb is built. Declaring it here would conflict.
2188
+ self._repo_attn_scaling: float = 1.0
2189
+ else:
2190
+ self.repo_module = None
2191
+
2192
+ def set_repo_inv_freq(
2193
+ self,
2194
+ inv_freq: torch.Tensor,
2195
+ attention_scaling: float,
2196
+ ) -> None:
2197
+ """
2198
+ Inject the rotary frequency vector from NeoLLMRotaryEmbedding so that
2199
+ REPO can build cos/sin inline from continuous positions.
2200
+
2201
+ Called once by NeoLLMModel.__init__ after rotary_emb is constructed.
2202
+ Only has effect when use_repo=True for this layer.
2203
+
2204
+ Args:
2205
+ inv_freq: [rotary_dim/2] — frozen inv_freq buffer from
2206
+ NeoLLMRotaryEmbedding.
2207
+ attention_scaling: float — attention_scaling from the same module.
2208
+ """
2209
+ if self.use_repo:
2210
+ # Register as non-persistent buffer so .to(device) / .cuda() moves
2211
+ # it automatically — eliminates the DeviceCopy op that splits the
2212
+ # CUDAGraph into 2 partitions when _apply_repo_rope runs.
2213
+ self.register_buffer("_repo_inv_freq", inv_freq.float(), persistent=False)
2214
+ self._repo_attn_scaling = attention_scaling
2215
+
2216
  def _apply_momentum_attention(
2217
  self,
2218
  q: torch.Tensor,
 
2381
  attn_analysis.v_raw = v.detach()
2382
 
2383
  cos, sin = position_embeddings
2384
+ if self.use_repo:
2385
+ # REPO path: f_ϕ predicts continuous per-head positions from the
2386
+ # residual stream, then cos/sin are built inline from those positions
2387
+ # so the rotation is differentiable w.r.t. REPOModule parameters.
2388
+ # (Li et al., 2026, §3.2 — Eq. 6–7)
2389
+ repo_a = attn_analysis.repo if attn_analysis is not None else None
2390
+ z = self.repo_module(hidden_states, repo_analysis=repo_a) # [B, H, S]
2391
+ q, k = _apply_repo_rope(
2392
+ q, k, z,
2393
+ self._repo_inv_freq,
2394
+ self._repo_attn_scaling,
2395
+ )
2396
+ else:
2397
+ # Standard path: integer positions pre-computed by NeoLLMModel.
2398
+ q, k = apply_rotary_pos_emb(q, k, cos, sin)
2399
 
2400
  if attn_analysis is not None:
2401
  attn_analysis.q_post_rope = q.detach()
 
2635
  config.intermediate_size, config.hidden_size,
2636
  bias=False, use_row_multiplier=True, use_column_multiplier=True,
2637
  )
2638
+ self.act_fn = PolyNorm(exclusive_init=0.05)
2639
  self.dropout = nn.Dropout(config.dropout_rate)
2640
 
2641
  def forward(
 
2878
  return outputs
2879
 
2880
 
2881
+ class SpellingBeeEmbedding(nn.Module):
2882
+ """
2883
+ Spelling Bee Embeddings (Rabe et al., 2026, arXiv:2601.18030).
2884
+
2885
+ Augments token embeddings with character-level information derived from
2886
+ the UTF-8 byte sequence of each token. The spelling bee embedding is the
2887
+ mean of the standard token embedding and a character-level summary:
2888
+
2889
+ e_bee(t) = 0.5 * (e_tok(t) + e_chars(t))
2890
+
2891
+ e_chars(t) = inv_sqrt_len(t) * Σ_{i=0}^{15} RoPE(e_byte[b_i], i)
2892
+
2893
+ where inv_sqrt_len = 1/√|t| is precomputed per token type at setup time.
2894
+
2895
+ Key design decisions vs. a naïve per-occurrence implementation:
2896
+
2897
+ 1. **Vocab-level computation** — e_chars is built over the full vocabulary
2898
+ once per forward (shape [V, d]), then gathered by token_ids. A naïve
2899
+ implementation would compute [B*S, 16, d] per step, repeating identical
2900
+ work for every occurrence of a frequent token. This approach reduces
2901
+ the dominant intermediate from O(B·S·16·d) to O(V·16·d), where V ≪ B·S
2902
+ in practice for most batches.
2903
+
2904
+ 2. **Static [256, 16, d] rope_bytes table** — RoPE is applied once over
2905
+ all 256 possible byte values at all 16 positions, producing a table
2906
+ with fully static shapes. torch.compile / max_autotune can fuse the
2907
+ construction of this table (two elementwise ops + concat over fixed
2908
+ dims) into a single kernel. Token-level e_chars is then a gather +
2909
+ sum over this table, also fully static.
2910
+
2911
+ 3. **Precomputed inv_sqrt_lens** — 1/√byte_len is computed once in
2912
+ set_byte_table and stored as a persistent buffer. The per-forward
2913
+ normalisation becomes a single elementwise multiply, with no sqrt or
2914
+ division in the hot path.
2915
+
2916
+ Compatible with both the standard embed_tokens path and the
2917
+ LeviathanGenerator path.
2918
+
2919
+ **Inference cost: zero overhead after baking.**
2920
+ Call ``bake_inference_table(token_embeds_weight)`` once after training to
2921
+ collapse the SBE into a single embedding table indistinguishable from a
2922
+ standard nn.Embedding lookup.
2923
+
2924
+ **Setup: call ``set_byte_table(tokenizer)`` once after model init** (and
2925
+ before any .to(device) / FP8 conversion) before training. The byte table
2926
+ and inv_sqrt_lens are persistent buffers saved in checkpoints.
2927
+
2928
+ References:
2929
+ Rabe, Clymo & Dong (2026). "Spelling Bee Embeddings for Language
2930
+ Modeling." arXiv:2601.18030.
2931
+ """
2932
+
2933
+ MAX_BYTES: int = 16
2934
+
2935
+ def __init__(self, config: "NeoLLMConfig"):
2936
+ super().__init__()
2937
+ d = config.hidden_size
2938
+ base = getattr(config, "rope_theta", 10000.0)
2939
+
2940
+ # 256 × d byte embedding lookup (one per UTF-8 byte value 0..255).
2941
+ self.byte_emb = nn.Embedding(256, d)
2942
+
2943
+ # ── Persistent buffers (saved in checkpoints) ─────────────────────
2944
+ # token_bytes [vocab_size, MAX_BYTES]: UTF-8 byte values per token,
2945
+ # padded with 0x00 up to MAX_BYTES positions.
2946
+ self.register_buffer(
2947
+ "token_bytes",
2948
+ torch.zeros(config.vocab_size, self.MAX_BYTES, dtype=torch.long),
2949
+ persistent=True,
2950
+ )
2951
+ # inv_sqrt_lens [vocab_size]: precomputed 1/sqrt(byte_len) per token.
2952
+ # Replaces the runtime sqrt+division of the naïve implementation.
2953
+ self.register_buffer(
2954
+ "inv_sqrt_lens",
2955
+ torch.ones(config.vocab_size, dtype=torch.float),
2956
+ persistent=True,
2957
+ )
2958
+
2959
+ # ── Non-persistent buffers (recomputed from fixed formula on load) ─
2960
+ # RoPE cos/sin for intra-token positions 0..MAX_BYTES-1.
2961
+ # Shape [MAX_BYTES, d//2] — applied over the 256-type axis in
2962
+ # _build_rope_bytes, not over the batch/sequence axis.
2963
+ half = d // 2
2964
+ theta = 1.0 / (base ** (torch.arange(0, half, dtype=torch.float) * 2.0 / d))
2965
+ pos = torch.arange(self.MAX_BYTES, dtype=torch.float)
2966
+ freqs = torch.outer(pos, theta) # [MAX_BYTES, half]
2967
+ self.register_buffer("intra_cos", freqs.cos(), persistent=False)
2968
+ self.register_buffer("intra_sin", freqs.sin(), persistent=False)
2969
+
2970
+ # Static position index [MAX_BYTES] used as the column index in the
2971
+ # vocab-level gather. Registered as buffer to avoid dynamic tensor
2972
+ # creation inside forward (which would trigger torch.compile retracing).
2973
+ self.register_buffer(
2974
+ "pos_idx",
2975
+ torch.arange(self.MAX_BYTES, dtype=torch.long),
2976
+ persistent=False,
2977
+ )
2978
+
2979
+ # ── Setup ─────────────────────────────────────────────────────────────────
2980
+
2981
+ def set_byte_table(self, tokenizer) -> None:
2982
+ """
2983
+ Precompute the UTF-8 byte table and inv_sqrt_lens from a tokenizer.
2984
+
2985
+ Must be called **once** after model instantiation and **before**
2986
+ ``.to(device)`` / FP8 conversion so the buffers land on the correct
2987
+ device after those transforms. Both buffers are persistent and will
2988
+ be saved/restored from checkpoints automatically.
2989
+
2990
+ Args:
2991
+ tokenizer: Any HuggingFace tokenizer with
2992
+ ``convert_ids_to_tokens(int) -> str | None``.
2993
+ """
2994
+ vocab_size = self.token_bytes.shape[0]
2995
+ byte_ids = torch.zeros(vocab_size, self.MAX_BYTES, dtype=torch.long)
2996
+ inv_sqrt = torch.ones(vocab_size, dtype=torch.float) # default 1/√1
2997
+
2998
+ for token_id in range(vocab_size):
2999
+ token_str = tokenizer.convert_ids_to_tokens(token_id)
3000
+ if token_str is None:
3001
+ continue
3002
+ # Some tokenizers use a special space character (Ġ / ▁); encode
3003
+ # directly to UTF-8 so byte values match raw text bytes.
3004
+ try:
3005
+ raw = token_str.encode("utf-8")
3006
+ except Exception:
3007
+ raw = b"\x00"
3008
+ n = min(len(raw), self.MAX_BYTES)
3009
+ for i in range(n):
3010
+ byte_ids[token_id, i] = raw[i]
3011
+ inv_sqrt[token_id] = 1.0 / math.sqrt(max(n, 1))
3012
+
3013
+ self.token_bytes.copy_(byte_ids.to(self.token_bytes.device))
3014
+ self.inv_sqrt_lens.copy_(inv_sqrt.to(self.inv_sqrt_lens.device))
3015
+
3016
+ # ── Core helpers ──────────────────────────────────────────────────────────
3017
+
3018
+ def _build_rope_bytes(self) -> torch.Tensor:
3019
+ """
3020
+ Build the static [256, MAX_BYTES, d] RoPE-encoded byte table.
3021
+
3022
+ For each of the 256 possible byte values and each of the MAX_BYTES
3023
+ intra-token positions, applies RoPE rotation using the current
3024
+ byte_emb.weight. All shapes are fully static, so torch.compile can
3025
+ fuse this into a single kernel.
3026
+
3027
+ Called once per forward pass; the result is discarded afterward.
3028
+ The cost is two broadcast elementwise ops + one cat over fixed dims.
3029
+
3030
+ Returns:
3031
+ rope_bytes [256, MAX_BYTES, d]
3032
+ """
3033
+ w = self.byte_emb.weight # [256, d]
3034
+ half = w.shape[-1] // 2
3035
+ w1 = w[:, :half].unsqueeze(1) # [256, 1, half]
3036
+ w2 = w[:, half:].unsqueeze(1) # [256, 1, half]
3037
+ cos = self.intra_cos.unsqueeze(0) # [1, MAX_BYTES, half]
3038
+ sin = self.intra_sin.unsqueeze(0) # [1, MAX_BYTES, half]
3039
+ return torch.cat(
3040
+ [w1 * cos - w2 * sin,
3041
+ w1 * sin + w2 * cos],
3042
+ dim=-1,
3043
+ ) # [256, MAX_BYTES, d]
3044
+
3045
+ # ── Forward ───────────────────────────────────────────────────────────────
3046
+
3047
+ def forward(
3048
+ self,
3049
+ token_ids: torch.Tensor, # [B, S] or [N]
3050
+ token_embeds: torch.Tensor, # [B, S, d] or [N, d]
3051
+ ) -> torch.Tensor:
3052
+ """
3053
+ Args:
3054
+ token_ids: integer token indices to look up byte sequences.
3055
+ token_embeds: embeddings from embed_tokens or LeviathanGenerator.
3056
+ Returns:
3057
+ Spelling bee embeddings — same shape as token_embeds.
3058
+ """
3059
+ # ── Step 1: build rope_bytes over 256 byte types × 16 positions ───
3060
+ # Shape [256, MAX_BYTES, d] — fully static, one kernel via compile.
3061
+ rope_bytes = self._build_rope_bytes() # [256, MAX_BYTES, d]
3062
+
3063
+ # ── Step 2: build e_chars over vocab types, not occurrences ────────
3064
+ # token_bytes [V, MAX_BYTES]: byte value at each position per token.
3065
+ # pos_idx [MAX_BYTES]: column selector 0..MAX_BYTES-1.
3066
+ # rope_bytes[token_bytes, pos_idx[None, :], :] selects, for each
3067
+ # vocab token and each position, the RoPE-rotated embedding of that
3068
+ # byte at that position. Result [V, MAX_BYTES, d], then sum → [V, d].
3069
+ e_chars_vocab = rope_bytes[
3070
+ self.token_bytes, # [V, MAX_BYTES] — row index
3071
+ self.pos_idx.unsqueeze(0), # [1, MAX_BYTES] → broadcast [V, MAX_BYTES]
3072
+ ].sum(1) # [V, d]
3073
+
3074
+ # ── Step 3: apply precomputed 1/√byte_len per vocab type ────────────
3075
+ # No sqrt or division in the hot path — pure multiply.
3076
+ e_chars_vocab = e_chars_vocab * self.inv_sqrt_lens.unsqueeze(-1) # [V, d]
3077
+
3078
+ # ── Step 4: gather only the tokens present in this batch ────────────
3079
+ # This is the only B×S operation — a single embedding lookup.
3080
+ e_chars = e_chars_vocab[token_ids] # [B, S, d] or [N, d]
3081
+
3082
+ # ── Step 5: mean with token embeddings ──────────────────────────────
3083
+ return (token_embeds + e_chars) * 0.5
3084
+
3085
+ # ── Inference utility ─────────────────────────────────────────────────────
3086
+
3087
+ @torch.no_grad()
3088
+ def bake_inference_table(
3089
+ self,
3090
+ token_emb_weight: torch.Tensor,
3091
+ ) -> torch.Tensor:
3092
+ """
3093
+ Collapse SBE into a single [vocab_size, d] embedding table.
3094
+
3095
+ After baking, the SBE computation is indistinguishable from a standard
3096
+ nn.Embedding lookup — zero additional overhead at inference time.
3097
+
3098
+ Args:
3099
+ token_emb_weight: [vocab_size, d] — weight matrix of embed_tokens
3100
+ or the equivalent table (e.g. after Leviathan).
3101
+ Returns:
3102
+ [vocab_size, d] — baked spelling bee embedding table.
3103
+
3104
+ Usage::
3105
+
3106
+ baked = model.model.spelling_bee.bake_inference_table(
3107
+ model.model.embed_tokens.weight
3108
+ )
3109
+ model.model.embed_tokens.weight.copy_(baked)
3110
+ # Optionally free byte_emb parameters:
3111
+ # del model.model.spelling_bee
3112
+ """
3113
+ rope_bytes = self._build_rope_bytes() # [256, MAX_BYTES, d]
3114
+ e_chars_vocab = rope_bytes[
3115
+ self.token_bytes,
3116
+ self.pos_idx.unsqueeze(0),
3117
+ ].sum(1) * self.inv_sqrt_lens.unsqueeze(-1) # [V, d]
3118
+ return (token_emb_weight + e_chars_vocab) * 0.5
3119
+
3120
+
3121
  class NeoLLMPreTrainedModel(PreTrainedModel):
3122
  """
3123
  Base class with custom weight initialization for all NeoLLM components.
 
3151
  per head rather than collapsing to 0 or 1.
3152
  - alpha_ma: zeros — running EMA starts at 0, β starts as −α/N ≈ small
3153
  negative offset; model quickly learns to adjust both.
3154
+ REPOModule (Context Re-Positioning):
3155
+ - W_g, W_c, W_z: default normal init from parent _init_weights.
3156
+ No special initialization required — the SwiGLU
3157
+ sub-layer starts near-zero, so z_i ≈ 0 for all tokens
3158
+ at step 0, which is equivalent to constant position
3159
+ assignment (NoPE-like). The model quickly learns to
3160
+ differentiate positions as needed.
3161
  """
3162
  config: NeoLLMConfig
3163
  base_model_prefix = "model"
 
3258
  module.attn_res_query_attn.data.zero_()
3259
  module.attn_res_query_mlp.data.zero_()
3260
 
3261
+ elif isinstance(module, SpellingBeeEmbedding):
3262
+ # byte_emb initialised identically to token embeddings: std=1/√d.
3263
+ # Ensures E[‖e_byte‖²] ≈ 1 at init, matching etok, so the
3264
+ # normalisation factor α = sqrt(byte_len) is calibrated from step 0.
3265
+ d = module.byte_emb.embedding_dim
3266
+ nn.init.normal_(module.byte_emb.weight, mean=0.0, std=1.0 / math.sqrt(d))
3267
+
3268
+
3269
  class NeoLLMModel(NeoLLMPreTrainedModel):
3270
  """
3271
  NeoLLM base decoder-only Transformer.
 
3278
  outputs (or block summaries for Block AttnRes) and passes them to each
3279
  decoder layer, replacing fixed residual accumulation with learned
3280
  depth-wise softmax attention (Kimi Team, 2026, arXiv:2603.15031).
3281
+
3282
+ Spelling Bee Embeddings (Rabe et al., 2026, arXiv:2601.18030):
3283
+ ``use_spelling_bee_embeddings`` is independent of the Leviathan flag:
3284
+ SBE is applied post-embedding regardless of which path produced the
3285
+ token embeddings (embed_tokens or LeviathanGenerator).
3286
+
3287
+ Flag coupling:
3288
+ - use_token_generator=False, use_spelling_bee_embeddings=False
3289
+ → standard embed_tokens, no SBE [default]
3290
+ - use_token_generator=False, use_spelling_bee_embeddings=True
3291
+ → standard embed_tokens + SBE
3292
+ - use_token_generator=True, use_spelling_bee_embeddings=False
3293
+ → LeviathanGenerator only, no SBE
3294
+ - use_token_generator=True, use_spelling_bee_embeddings=True
3295
+ → LeviathanGenerator + SBE
3296
+
3297
+ Setup: call ``model.model.spelling_bee.set_byte_table(tokenizer)``
3298
+ once after model init and before training (and after any .to(device)).
3299
  """
3300
 
3301
  def __init__(self, config: NeoLLMConfig):
3302
  super().__init__(config)
3303
 
3304
+ # ── Embedding path ────────────────────────────────────────────────────
3305
  if config.use_token_generator:
3306
  self.token_generator = LeviathanGenerator(config)
3307
  else:
 
3309
  config.vocab_size, config.hidden_size, config.pad_token_id
3310
  )
3311
 
3312
+ # ── Spelling Bee Embeddings (Rabe et al., 2026) ───────────────────────
3313
+ # Active when use_spelling_bee_embeddings=True, compatible with both
3314
+ # the embed_tokens and LeviathanGenerator paths.
3315
+ use_sbe = getattr(config, "use_spelling_bee_embeddings", False)
3316
+ if use_sbe:
3317
+ self.spelling_bee = SpellingBeeEmbedding(config)
3318
+ else:
3319
+ self.spelling_bee = None
3320
+
3321
  self.layers = nn.ModuleList(
3322
  [NeoLLMDecoderLayer(config, layer_idx)
3323
  for layer_idx in range(config.num_hidden_layers)]
 
3329
 
3330
  self.post_init()
3331
 
3332
+ # ── REPO: inject inv_freq into every attention layer that uses it ─────
3333
+ # Done after post_init so rotary_emb.inv_freq is already initialized.
3334
+ # Layers below repo_start_layer never call set_repo_inv_freq (their
3335
+ # use_repo flag is False) so the call is harmless for those layers.
3336
+ if getattr(config, "use_repo", False):
3337
+ for layer in self.layers:
3338
+ layer.self_attn.set_repo_inv_freq(
3339
+ self.rotary_emb.inv_freq,
3340
+ self.rotary_emb.attention_scaling,
3341
+ )
3342
+
3343
  def get_input_embeddings(self):
3344
  if self.config.use_token_generator:
3345
  return self.token_generator
 
3351
  else:
3352
  self.embed_tokens = value
3353
 
3354
+ def _build_layer_analysis(self, layer_idx: int = 0) -> LayerAnalysis:
3355
  """
3356
  Construct a LayerAnalysis with sub-objects pre-allocated for every
3357
  component that is active in the current config.
 
3361
  Called once per layer per forward when analysis is active.
3362
  """
3363
  cfg = self.config
3364
+ _repo_active = (
3365
+ getattr(cfg, "use_repo", False)
3366
+ and layer_idx >= getattr(cfg, "repo_start_layer", cfg.num_hidden_layers // 3)
3367
+ )
3368
  return LayerAnalysis(
3369
  seednorm_pre_attn = SeeDNormAnalysis(),
3370
  seednorm_post_attn = SeeDNormAnalysis(),
3371
  attention = AttentionAnalysis(
3372
  fan = FANAnalysis(),
3373
  hadamard = HadamardAnalysis() if getattr(cfg, "use_hadamard_o_proj", False) else None,
3374
+ repo = REPOAnalysis() if _repo_active else None,
3375
  ),
3376
  mlp = MLPAnalysis(
3377
  fan = FANAnalysis(),
 
3437
  else:
3438
  inputs_embeds = self.embed_tokens(input_ids)
3439
 
3440
+ # ── Spelling Bee Embeddings (applied post-embedding, pre-decoder) ──────
3441
+ # input_ids may be None when inputs_embeds was passed directly by the
3442
+ # caller; in that case SBE cannot run (no token_ids available) and is
3443
+ # silently skipped — consistent with the standard embedding bypass path.
3444
+ if self.spelling_bee is not None and input_ids is not None:
3445
+ inputs_embeds = self.spelling_bee(input_ids, inputs_embeds)
3446
+
3447
  if analysis_state is not None:
3448
  analysis_state.embeddings = inputs_embeds.detach()
3449
 
 
3516
  # Build per-layer analysis container (only in eval + analysis mode)
3517
  layer_analysis = None
3518
  if analysis_state is not None:
3519
+ layer_analysis = self._build_layer_analysis(layer_idx)
3520
  layer_analysis.layer_idx = layer_idx
3521
  analysis_state.layers.append(layer_analysis)
3522
 
 
3827
  "NeoLLMConfig",
3828
  "LeviathanGenerator",
3829
  "LeviathanJTokM",
3830
+ "SpellingBeeEmbedding",
3831
  "FANLayer",
3832
  "SeeDNorm",
3833
  "ScalarMultiplier",
 
3835
  "LinearWithMultipliers",
3836
  "MEAHeadSeeDNorm",
3837
  "HadamardOProj",
3838
+ "REPOModule",
3839
  # Analysis dataclasses — exported so external tools can type-hint against them
3840
  "AnalysisState",
3841
  "LayerAnalysis",
 
3846
  "GPASAnalysis",
3847
  "PolyNormAnalysis",
3848
  "HadamardAnalysis",
3849
+ "REPOAnalysis",
3850
  "JTokMAnalysis",
3851
  "AttnResAnalysis",
3852
  "GeneratorAnalysis",