Update modeling_neollm.py
Browse files- 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,
|
|
|
|
|
|
|
| 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 |
-
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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",
|