Update modeling_neollm.py
Browse files- modeling_neollm.py +903 -79
modeling_neollm.py
CHANGED
|
@@ -20,7 +20,17 @@ Attention stack (orthogonal, all active simultaneously when enabled):
|
|
| 20 |
moving-average bias that prevents collapse. Reduces first-token bias,
|
| 21 |
increases attention entropy, and is complementary to Gated Attention
|
| 22 |
(Bae et al. 2026, Table 2: Affine-Scaled + Gated > either alone).
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
References:
|
| 26 |
FANformer: "FANformer: Improving Large Language Models Through Effective
|
|
@@ -42,7 +52,8 @@ References:
|
|
| 42 |
"""
|
| 43 |
|
| 44 |
import math
|
| 45 |
-
from
|
|
|
|
| 46 |
|
| 47 |
import torch
|
| 48 |
import torch.nn.functional as F
|
|
@@ -60,14 +71,277 @@ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_u
|
|
| 60 |
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
|
| 61 |
from transformers.processing_utils import Unpack
|
| 62 |
from transformers.utils import TransformersKwargs, logging
|
| 63 |
-
from
|
| 64 |
|
| 65 |
from transformers import AutoConfig, AutoModel, AutoModelForCausalLM
|
| 66 |
|
| 67 |
logger = logging.get_logger(__name__)
|
| 68 |
|
| 69 |
|
| 70 |
-
# ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
class ScalarMultiplier(nn.Module):
|
| 73 |
"""
|
|
@@ -454,6 +728,7 @@ class LeviathanGenerator(nn.Module):
|
|
| 454 |
self,
|
| 455 |
token_ids: torch.Tensor,
|
| 456 |
return_internals: bool = False,
|
|
|
|
| 457 |
) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor, torch.Tensor]]:
|
| 458 |
"""
|
| 459 |
Generate embeddings from discrete token indices.
|
|
@@ -478,6 +753,9 @@ class LeviathanGenerator(nn.Module):
|
|
| 478 |
token_ids: (batch, seq_len) or (seq_len,)
|
| 479 |
return_internals: if True, also return z_tilde and B_vals for
|
| 480 |
reuse by JTok-M surfaces in every decoder layer.
|
|
|
|
|
|
|
|
|
|
| 481 |
Returns:
|
| 482 |
embeddings [*token_ids.shape, hidden_size],
|
| 483 |
or (embeddings, z_tilde [N, d_seed], B_vals [N, d_seed, n_knots])
|
|
@@ -494,12 +772,19 @@ class LeviathanGenerator(nn.Module):
|
|
| 494 |
for r in range(self.k):
|
| 495 |
z = z + self.codebooks[r][coords_flat[:, r]]
|
| 496 |
|
|
|
|
|
|
|
|
|
|
| 497 |
# ── JTok-M shared path ────────────────────────────────────────────
|
| 498 |
# Produces z_tilde and B_vals consumed by every decoder layer's
|
| 499 |
# JTok-M module. This path is unchanged and uses fixed scalar scale.
|
| 500 |
z_tilde = torch.sigmoid(self.seed_norm(self.seed_proj(z))) # [N, d_seed]
|
| 501 |
B_vals = self._bspline_basis(z_tilde.clamp(0.0, 1.0)) # [N, d_seed, n_knots]
|
| 502 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
# ── Per-head generator path (fully vectorized, 6 kernels) ────────
|
| 504 |
# All 8 heads are processed simultaneously. No Python loop.
|
| 505 |
# Maximum intermediate tensor [N, M, d_seed, n_knots] appears once.
|
|
@@ -509,6 +794,9 @@ class LeviathanGenerator(nn.Module):
|
|
| 509 |
z_all = F.linear(z.to(target_dtype), self.head_proj_weight)
|
| 510 |
z_all = z_all.view(N, self.num_modes, self.d_seed) # [N, M, d_seed]
|
| 511 |
|
|
|
|
|
|
|
|
|
|
| 512 |
# Kernel 2: per-head LayerNorm + sigmoid(x/2)
|
| 513 |
# Manual LN over last dim with independent weight/bias per head.
|
| 514 |
# Mathematically identical to 8 separate nn.LayerNorm(d_seed).
|
|
@@ -520,6 +808,9 @@ class LeviathanGenerator(nn.Module):
|
|
| 520 |
+ self.head_norm_bias.unsqueeze(0)
|
| 521 |
z_all = torch.sigmoid(z_all / 2.0) # [N, M, d_seed]
|
| 522 |
|
|
|
|
|
|
|
|
|
|
| 523 |
# Kernel 3: vectorized B-spline basis for all heads
|
| 524 |
# head_scale [M, d_seed] is used inside _bspline_basis_all_heads
|
| 525 |
B_all = self._bspline_basis_all_heads(
|
|
@@ -529,6 +820,9 @@ class LeviathanGenerator(nn.Module):
|
|
| 529 |
# Kernel 4: vectorized KHRONOS tensor product for all heads
|
| 530 |
modes_all = self._khronos_all_heads(B_all) # [N, M, krank]
|
| 531 |
|
|
|
|
|
|
|
|
|
|
| 532 |
# Kernel 5: project all heads to hidden_size and sum
|
| 533 |
# einsum: token n, head m, krank k → hidden d (summed over m)
|
| 534 |
# head_out_weight [M, krank, hidden_size]
|
|
@@ -541,6 +835,9 @@ class LeviathanGenerator(nn.Module):
|
|
| 541 |
# No W_res — confirmed absent in the authors' implementation
|
| 542 |
e = e.reshape(*orig_shape, self.hidden_size)
|
| 543 |
|
|
|
|
|
|
|
|
|
|
| 544 |
if return_internals:
|
| 545 |
return e, z_tilde, B_vals
|
| 546 |
return e
|
|
@@ -627,6 +924,7 @@ class LeviathanJTokM(nn.Module):
|
|
| 627 |
B_vals: torch.Tensor,
|
| 628 |
z_tilde: torch.Tensor,
|
| 629 |
target_dtype: torch.dtype,
|
|
|
|
| 630 |
) -> torch.Tensor:
|
| 631 |
"""
|
| 632 |
Evaluate all n_e surfaces vectorized over the full token batch.
|
|
@@ -638,9 +936,10 @@ class LeviathanJTokM(nn.Module):
|
|
| 638 |
All shapes are static → torch.compile compatible.
|
| 639 |
|
| 640 |
Args:
|
| 641 |
-
B_vals:
|
| 642 |
-
z_tilde:
|
| 643 |
target_dtype: model dtype
|
|
|
|
| 644 |
Returns:
|
| 645 |
surfaces: [N, n_e, D]
|
| 646 |
"""
|
|
@@ -669,7 +968,12 @@ class LeviathanJTokM(nn.Module):
|
|
| 669 |
z = z_tilde.to(target_dtype)
|
| 670 |
out_res = torch.einsum("nd,idc->nic", z, self.W_res.to(target_dtype))
|
| 671 |
|
| 672 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
|
| 674 |
# ── Router ────────────────────────────────────────────────────────────
|
| 675 |
|
|
@@ -680,6 +984,7 @@ class LeviathanJTokM(nn.Module):
|
|
| 680 |
self,
|
| 681 |
h_tilde: torch.Tensor,
|
| 682 |
surfaces: torch.Tensor,
|
|
|
|
| 683 |
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 684 |
"""
|
| 685 |
Context-dependent routing over h_tilde (hidden state after attention).
|
|
@@ -698,6 +1003,7 @@ class LeviathanJTokM(nn.Module):
|
|
| 698 |
Args:
|
| 699 |
h_tilde: [N, D] — hidden state after attention (before MLP)
|
| 700 |
surfaces: [N, n_e, D]
|
|
|
|
| 701 |
Returns:
|
| 702 |
mixed: [N, D]
|
| 703 |
aux_stats: (p_sum [n_e], f_sum [n_e], N) for loss accumulation
|
|
@@ -720,6 +1026,12 @@ class LeviathanJTokM(nn.Module):
|
|
| 720 |
selected = surfaces.gather(dim=1, index=idx_exp) # [N, K, D]
|
| 721 |
mixed = (w.unsqueeze(-1) * selected).sum(dim=1) # [N, D]
|
| 722 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
# Load-balancing statistics for aux loss (Appendix B, Yang et al. 2026)
|
| 724 |
# p_i = mean routing probability over batch
|
| 725 |
# f_i = fraction of tokens actually routed to i
|
|
@@ -741,31 +1053,41 @@ class LeviathanJTokM(nn.Module):
|
|
| 741 |
h_tilde: torch.Tensor,
|
| 742 |
z_tilde: torch.Tensor,
|
| 743 |
B_vals: torch.Tensor,
|
|
|
|
| 744 |
) -> Tuple[torch.Tensor, Tuple]:
|
| 745 |
"""
|
| 746 |
Compute additive JTok-M residual for one decoder layer.
|
| 747 |
|
| 748 |
Args:
|
| 749 |
-
h_tilde:
|
| 750 |
-
z_tilde:
|
| 751 |
-
B_vals:
|
|
|
|
|
|
|
| 752 |
Returns:
|
| 753 |
-
delta_r:
|
| 754 |
aux_stats: tuple for accumulating load-balance loss
|
| 755 |
"""
|
| 756 |
target_dtype = h_tilde.dtype
|
| 757 |
|
| 758 |
# All n_e surfaces in one vectorized pass
|
| 759 |
-
surfaces = self._eval_surfaces(B_vals, z_tilde, target_dtype
|
| 760 |
|
| 761 |
# Context-dependent routing
|
| 762 |
-
mixed, aux_stats = self._route_and_mix(h_tilde, surfaces
|
| 763 |
|
| 764 |
# Normalise direction, apply scaler, scale with 1/√(2ℓ)
|
| 765 |
# Norm_ε decouples direction from magnitude (JTok Appendix D.2)
|
| 766 |
mixed_norm = mixed / (mixed.norm(dim=-1, keepdim=True) + self.norm_eps)
|
| 767 |
delta_r = self.lns_scale * self.scaler * mixed_norm # [N, D]
|
| 768 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 769 |
return delta_r, aux_stats
|
| 770 |
|
| 771 |
|
|
@@ -825,10 +1147,20 @@ class FANLayer(nn.Module):
|
|
| 825 |
if self.input_linear.bias is not None:
|
| 826 |
nn.init.zeros_(self.input_linear.bias)
|
| 827 |
|
| 828 |
-
def forward(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 829 |
pg = self.input_linear(x)
|
| 830 |
p, g = torch.split(pg, [self.p_output_dim, self.g_output_dim], dim=-1)
|
| 831 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
|
| 833 |
|
| 834 |
class LNS(nn.Module):
|
|
@@ -853,8 +1185,17 @@ class GPAS(nn.Module):
|
|
| 853 |
self.d_model = d_model
|
| 854 |
self.alpha = nn.Parameter(torch.zeros(1))
|
| 855 |
|
| 856 |
-
def forward(
|
| 857 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 858 |
|
| 859 |
|
| 860 |
class SeeDNorm(nn.Module):
|
|
@@ -883,7 +1224,11 @@ class SeeDNorm(nn.Module):
|
|
| 883 |
def _rms_norm(self, x: torch.Tensor) -> torch.Tensor:
|
| 884 |
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
| 885 |
|
| 886 |
-
def forward(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 887 |
x_for_dynamic = F.dropout(x, p=self.dropout_input)
|
| 888 |
rescale_factor = torch.tanh(
|
| 889 |
torch.sum(x_for_dynamic * self.beta, dim=-1, keepdim=True)
|
|
@@ -891,7 +1236,13 @@ class SeeDNorm(nn.Module):
|
|
| 891 |
dynamic_scale = rescale_factor * self.alpha + self.gamma
|
| 892 |
x_normalized = self._rms_norm(x.float())
|
| 893 |
x_normalized = F.dropout(x_normalized, p=self.dropout_hidden)
|
| 894 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 895 |
|
| 896 |
def extra_repr(self) -> str:
|
| 897 |
return (f"dim={self.dim}, eps={self.eps}, "
|
|
@@ -1107,6 +1458,7 @@ def affine_scaled_eager_attention_forward(
|
|
| 1107 |
alpha: torch.Tensor,
|
| 1108 |
beta: torch.Tensor,
|
| 1109 |
dropout: float = 0.0,
|
|
|
|
| 1110 |
**kwargs: Unpack[TransformersKwargs],
|
| 1111 |
):
|
| 1112 |
"""
|
|
@@ -1129,8 +1481,10 @@ def affine_scaled_eager_attention_forward(
|
|
| 1129 |
Reference: Bae et al. (2026), Affine-Scaled Attention, Eq. 6–8.
|
| 1130 |
|
| 1131 |
Args:
|
| 1132 |
-
alpha:
|
| 1133 |
-
beta:
|
|
|
|
|
|
|
| 1134 |
"""
|
| 1135 |
key_states = repeat_kv(key, module.num_key_value_groups)
|
| 1136 |
value_states = repeat_kv(value, module.num_key_value_groups)
|
|
@@ -1139,19 +1493,137 @@ def affine_scaled_eager_attention_forward(
|
|
| 1139 |
if attention_mask is not None:
|
| 1140 |
attn_weights = attn_weights + attention_mask[:, :, :, : key_states.shape[-2]]
|
| 1141 |
|
| 1142 |
-
|
| 1143 |
attn_weights, dim=-1, dtype=torch.float32
|
| 1144 |
).to(query.dtype)
|
| 1145 |
|
|
|
|
|
|
|
|
|
|
| 1146 |
# Affine reweighting: relaxes the unit-sum constraint of softmax.
|
| 1147 |
# α ∈ [0, 1] scales down the softmax distribution (input-adaptively per head).
|
| 1148 |
# β offsets to prevent collapse when α deviates from its running mean.
|
| 1149 |
# Shapes: α, β are [B, H, S_q, 1], attn_weights is [B, H, S_q, S_k] → broadcast.
|
| 1150 |
-
|
| 1151 |
|
| 1152 |
-
|
| 1153 |
-
|
| 1154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1155 |
|
| 1156 |
|
| 1157 |
class NeoLLMAttention(nn.Module):
|
|
@@ -1336,19 +1808,49 @@ class NeoLLMAttention(nn.Module):
|
|
| 1336 |
self.direction_vecs = None
|
| 1337 |
self.direction_router = None
|
| 1338 |
|
| 1339 |
-
def _apply_momentum_attention(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1340 |
if not self.use_momentum_attention or self.momentum_gamma == 0.0:
|
| 1341 |
return q, k
|
| 1342 |
-
|
| 1343 |
-
|
| 1344 |
-
|
| 1345 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1346 |
if not self.use_mea_attention:
|
| 1347 |
return k, v
|
| 1348 |
-
|
| 1349 |
-
|
| 1350 |
-
|
| 1351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1352 |
if not self.use_lucid_attention:
|
| 1353 |
return v.contiguous()
|
| 1354 |
key_rn = rms_key_unit_norm(k, eps=self.lucid_attention_eps)
|
|
@@ -1360,15 +1862,20 @@ class NeoLLMAttention(nn.Module):
|
|
| 1360 |
eye = torch.eye(prec.shape[-1], device=prec.device, dtype=prec.dtype).view(
|
| 1361 |
1, 1, prec.shape[-1], prec.shape[-1]
|
| 1362 |
)
|
| 1363 |
-
prec
|
| 1364 |
-
|
| 1365 |
prec, v.float(), upper=False, unitriangular=True
|
| 1366 |
).to(v.dtype).contiguous()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1367 |
|
| 1368 |
def _apply_directional_routing(
|
| 1369 |
self,
|
| 1370 |
attn_out: torch.Tensor,
|
| 1371 |
hidden_states: torch.Tensor,
|
|
|
|
| 1372 |
) -> torch.Tensor:
|
| 1373 |
"""
|
| 1374 |
Directional suppression at position C (post-XSA, pre-reshape).
|
|
@@ -1377,6 +1884,7 @@ class NeoLLMAttention(nn.Module):
|
|
| 1377 |
attn_out: [B, S, H, d_head] — output after XSA and SeeDNorm.
|
| 1378 |
hidden_states: [B, S, hidden_size] — pre-FAN residual stream,
|
| 1379 |
used as router input (same as paper's x_i).
|
|
|
|
| 1380 |
Returns:
|
| 1381 |
[B, S, H, d_head] with selected directional components suppressed.
|
| 1382 |
"""
|
|
@@ -1416,8 +1924,17 @@ class NeoLLMAttention(nn.Module):
|
|
| 1416 |
# Σ_k weighted_{h,k} · d_{h,k}:
|
| 1417 |
# weighted [B, S, H, K] × d [H, K, D] → [B, S, H, D]
|
| 1418 |
suppression = torch.einsum("bshk,hkd->bshd", weighted, d)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1419 |
|
| 1420 |
-
return
|
| 1421 |
|
| 1422 |
def forward(
|
| 1423 |
self,
|
|
@@ -1425,11 +1942,13 @@ class NeoLLMAttention(nn.Module):
|
|
| 1425 |
position_embeddings: tuple[torch.Tensor, torch.Tensor],
|
| 1426 |
attention_mask: Optional[torch.Tensor] = None,
|
| 1427 |
first_layer_fan: Optional[torch.Tensor] = None,
|
|
|
|
| 1428 |
**kwargs: Unpack[FlashAttentionKwargs],
|
| 1429 |
) -> tuple[torch.Tensor, Optional[torch.Tensor], Optional[torch.Tensor]]:
|
| 1430 |
input_shape = hidden_states.shape[:-1]
|
| 1431 |
|
| 1432 |
-
|
|
|
|
| 1433 |
if first_layer_fan is not None:
|
| 1434 |
h_fan = self.lambda_1 * first_layer_fan + self.lambda_2 * h_fan
|
| 1435 |
current_layer_fan = h_fan.clone()
|
|
@@ -1443,27 +1962,42 @@ class NeoLLMAttention(nn.Module):
|
|
| 1443 |
)
|
| 1444 |
gate = gate.reshape(*input_shape, -1)
|
| 1445 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1446 |
q = self.q_norm(q_raw.view(query_shape)).transpose(1, 2)
|
| 1447 |
k = self.k_norm(self.k_proj(h_fan).view(kv_shape)).transpose(1, 2)
|
| 1448 |
v = self.v_proj(h_fan).view(kv_shape).transpose(1, 2)
|
| 1449 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1450 |
cos, sin = position_embeddings
|
| 1451 |
q, k = apply_rotary_pos_emb(q, k, cos, sin)
|
| 1452 |
-
|
| 1453 |
-
|
| 1454 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1455 |
|
| 1456 |
# Capture v_ref for XSA after MEA mixing and LUCID preconditioning.
|
| 1457 |
# This is the vector that actually participated in SDPA aggregation.
|
| 1458 |
v_ref = v if self.use_xsa else None
|
| 1459 |
|
| 1460 |
# ── Affine-Scaled Attention ───────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1461 |
alpha = None
|
| 1462 |
beta = None
|
| 1463 |
-
use_affine =
|
| 1464 |
-
self.use_affine_scaled_attention
|
| 1465 |
-
and self.config._attn_implementation == "eager"
|
| 1466 |
-
)
|
| 1467 |
if use_affine:
|
| 1468 |
alpha = linear_clipping(self.alpha_proj(hidden_states)) # [B, S, H]
|
| 1469 |
alpha = alpha.permute(0, 2, 1).unsqueeze(-1) # [B, H, S, 1]
|
|
@@ -1476,14 +2010,31 @@ class NeoLLMAttention(nn.Module):
|
|
| 1476 |
self.affine_momentum * self.alpha_ma
|
| 1477 |
+ (1.0 - self.affine_momentum) * batch_mean
|
| 1478 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1479 |
|
| 1480 |
if use_affine:
|
| 1481 |
-
|
| 1482 |
-
|
| 1483 |
-
|
| 1484 |
-
|
| 1485 |
-
|
| 1486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1487 |
else:
|
| 1488 |
attn_fn = eager_attention_forward
|
| 1489 |
if self.config._attn_implementation != "eager":
|
|
@@ -1493,10 +2044,19 @@ class NeoLLMAttention(nn.Module):
|
|
| 1493 |
dropout=0.0 if not self.training else self.attention_dropout,
|
| 1494 |
scaling=self.scaling, **kwargs,
|
| 1495 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1496 |
|
| 1497 |
attn_out = attn_out.reshape(*input_shape, -1, self.head_dim)
|
| 1498 |
if self.use_mea_attention:
|
| 1499 |
attn_out = self.mea_output_norm(attn_out)
|
|
|
|
|
|
|
| 1500 |
|
| 1501 |
# ── Exclusive Self Attention (position B, pre-routing) ────────────
|
| 1502 |
# Removes auto-position component before directional routing so that
|
|
@@ -1507,7 +2067,11 @@ class NeoLLMAttention(nn.Module):
|
|
| 1507 |
v_ref_t = v_ref_t.to(attn_out.dtype)
|
| 1508 |
proj = (attn_out * v_ref_t).sum(dim=-1, keepdim=True)
|
| 1509 |
norm_sq = (v_ref_t * v_ref_t).sum(dim=-1, keepdim=True).clamp(min=self.xsa_eps)
|
| 1510 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1511 |
|
| 1512 |
# ── Directional Routing (position C, post-XSA, pre-reshape) ──────
|
| 1513 |
# Suppresses cross-domain interference directions from the head output.
|
|
@@ -1515,14 +2079,26 @@ class NeoLLMAttention(nn.Module):
|
|
| 1515 |
# When use_xsa=False: directions span full head-space (no XSA pre-clean).
|
| 1516 |
# When use_directional_routing=False: this block is skipped entirely.
|
| 1517 |
if self.use_directional_routing:
|
| 1518 |
-
attn_out = self._apply_directional_routing(
|
|
|
|
|
|
|
| 1519 |
|
| 1520 |
# ── Reshape → o_proj → Gated Attention gate → dropout ────────────
|
| 1521 |
-
|
| 1522 |
-
|
| 1523 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1524 |
|
| 1525 |
-
|
|
|
|
|
|
|
|
|
|
| 1526 |
class PolyNorm(nn.Module):
|
| 1527 |
def __init__(
|
| 1528 |
self,
|
|
@@ -1561,7 +2137,11 @@ class PolyNorm(nn.Module):
|
|
| 1561 |
out = branch - alpha.to(branch.dtype) * proj_coeff * ref
|
| 1562 |
return self._norm(out)
|
| 1563 |
|
| 1564 |
-
def forward(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1565 |
# Caché de potencias: x_sq reutilizado en x1 y x2; x_cu = x·x_sq evita pow(3)
|
| 1566 |
x_sq = x.pow(2)
|
| 1567 |
x_cu = x * x_sq
|
|
@@ -1571,9 +2151,20 @@ class PolyNorm(nn.Module):
|
|
| 1571 |
x2 = x_sq * (x_sq * x_sq).mean(-1, keepdim=True).add(self.eps).rsqrt()
|
| 1572 |
x3 = x_cu * (x_cu * x_cu).mean(-1, keepdim=True).add(self.eps).rsqrt()
|
| 1573 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1574 |
# Fuerzas exclusivas aprendibles
|
| 1575 |
alpha2, alpha3 = torch.sigmoid(self.exclusive_logits).unbind()
|
| 1576 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1577 |
# Precalcular ref (x1) en fp32 y su norma al cuadrado — compartido por x2 y x3
|
| 1578 |
x1_f = x1.float()
|
| 1579 |
ref_norm_sq = x1_f.pow(2).sum(-1, keepdim=True).clamp_min(self.proj_eps)
|
|
@@ -1582,13 +2173,22 @@ class PolyNorm(nn.Module):
|
|
| 1582 |
x2 = self._exclusive(x2, x1, alpha2, x1_f, ref_norm_sq)
|
| 1583 |
x3 = self._exclusive(x3, x1, alpha3, x1_f, ref_norm_sq)
|
| 1584 |
|
| 1585 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1586 |
self.weight[0] * x3
|
| 1587 |
+ self.weight[1] * x2
|
| 1588 |
+ self.weight[2] * x1
|
| 1589 |
+ self.bias
|
| 1590 |
)
|
| 1591 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1592 |
|
| 1593 |
class NeoLLMMLP(nn.Module):
|
| 1594 |
"""MLP with FANformer integration and Learnable Multipliers."""
|
|
@@ -1614,11 +2214,34 @@ class NeoLLMMLP(nn.Module):
|
|
| 1614 |
self.act_fn = PolyNorm(exclusive_init=0.05)
|
| 1615 |
self.dropout = nn.Dropout(config.dropout_rate)
|
| 1616 |
|
| 1617 |
-
def forward(
|
| 1618 |
-
|
| 1619 |
-
|
| 1620 |
-
|
| 1621 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1622 |
|
| 1623 |
|
| 1624 |
class NeoLLMDecoderLayer(GradientCheckpointingLayer):
|
|
@@ -1681,6 +2304,8 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
|
|
| 1681 |
sources: list,
|
| 1682 |
partial: torch.Tensor,
|
| 1683 |
query: torch.Tensor,
|
|
|
|
|
|
|
| 1684 |
) -> torch.Tensor:
|
| 1685 |
"""
|
| 1686 |
Depth-wise softmax attention over preceding layer outputs.
|
|
@@ -1722,9 +2347,14 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
|
|
| 1722 |
B_vals: Optional[torch.Tensor] = None,
|
| 1723 |
attn_res_sources: Optional[list] = None,
|
| 1724 |
attn_res_partial: Optional[torch.Tensor] = None,
|
|
|
|
| 1725 |
output_attentions: Optional[bool] = False,
|
| 1726 |
**kwargs: Unpack[FlashAttentionKwargs],
|
| 1727 |
) -> Tuple:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1728 |
# ── Attention Residuals: compute pre-attention input ──────────────
|
| 1729 |
# When active, the input to the attention sublayer is no longer the
|
| 1730 |
# raw hidden_states (accumulated residual) but a softmax-weighted
|
|
@@ -1732,41 +2362,68 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
|
|
| 1732 |
# attn_res_partial carries the intra-block standard residual that
|
| 1733 |
# connects the attention and MLP sublayers within this layer.
|
| 1734 |
# When inactive, flow is identical to the original.
|
|
|
|
| 1735 |
if self.use_attn_res and attn_res_sources is not None and attn_res_partial is not None:
|
| 1736 |
-
h_attn = self._attn_res(
|
| 1737 |
-
|
|
|
|
|
|
|
| 1738 |
residual_attn = attn_res_partial
|
| 1739 |
else:
|
| 1740 |
h_attn = hidden_states
|
| 1741 |
residual_attn = hidden_states
|
| 1742 |
|
| 1743 |
# ── Attention block ───────────────────────────────────────────────
|
| 1744 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1745 |
|
| 1746 |
hidden_states, attn_weights, self.current_layer_fan = self.self_attn(
|
| 1747 |
-
hidden_states=
|
| 1748 |
attention_mask=attention_mask,
|
| 1749 |
position_embeddings=position_embeddings,
|
| 1750 |
first_layer_fan=first_layer_fan,
|
|
|
|
| 1751 |
**kwargs,
|
| 1752 |
)
|
| 1753 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1754 |
|
| 1755 |
# ── Attention Residuals: compute pre-MLP input ────────────────────
|
| 1756 |
# After attention, the partial sum is updated with h_tilde.
|
| 1757 |
# The pre-MLP AttnRes attends over the same sources but with h_tilde
|
| 1758 |
# as the current partial — capturing the within-layer attention output.
|
| 1759 |
if self.use_attn_res and attn_res_sources is not None:
|
| 1760 |
-
h_mlp = self._attn_res(
|
| 1761 |
-
|
|
|
|
|
|
|
| 1762 |
residual_mlp = h_tilde
|
| 1763 |
else:
|
| 1764 |
h_mlp = h_tilde
|
| 1765 |
residual_mlp = h_tilde
|
| 1766 |
|
| 1767 |
# ── MLP block ─────────────────────────────────────────────────────
|
| 1768 |
-
|
| 1769 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1770 |
|
| 1771 |
# ── JTok-M injection (additive alongside MLP residual) ────────────
|
| 1772 |
aux_stats = None
|
|
@@ -1776,12 +2433,18 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
|
|
| 1776 |
z_flat = z_tilde.reshape(-1, z_tilde.shape[-1])
|
| 1777 |
B_flat = B_vals.reshape(-1, B_vals.shape[-2], B_vals.shape[-1])
|
| 1778 |
|
| 1779 |
-
|
|
|
|
| 1780 |
delta_r = delta_r.reshape(orig_shape)
|
| 1781 |
|
| 1782 |
-
|
|
|
|
| 1783 |
else:
|
| 1784 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1785 |
|
| 1786 |
outputs = (hidden_states,)
|
| 1787 |
if output_attentions:
|
|
@@ -1968,6 +2631,30 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
|
|
| 1968 |
else:
|
| 1969 |
self.embed_tokens = value
|
| 1970 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1971 |
def forward(
|
| 1972 |
self,
|
| 1973 |
input_ids: Optional[torch.LongTensor] = None,
|
|
@@ -1977,6 +2664,7 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
|
|
| 1977 |
output_hidden_states: Optional[bool] = None,
|
| 1978 |
output_attentions: Optional[bool] = None,
|
| 1979 |
return_dict: Optional[bool] = None,
|
|
|
|
| 1980 |
**kwargs: Unpack[TransformersKwargs],
|
| 1981 |
) -> Tuple:
|
| 1982 |
output_hidden_states = (
|
|
@@ -1996,12 +2684,18 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
|
|
| 1996 |
z_tilde = None
|
| 1997 |
B_vals = None
|
| 1998 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1999 |
if inputs_embeds is None:
|
| 2000 |
if self.config.use_token_generator:
|
| 2001 |
if self.config.use_jtokm:
|
| 2002 |
# Return internals for reuse by JTok-M surfaces
|
| 2003 |
inputs_embeds, z_tilde, B_vals = self.token_generator(
|
| 2004 |
-
input_ids, return_internals=True
|
| 2005 |
)
|
| 2006 |
# Reshape to [batch, seq, d_seed] and [batch, seq, d_seed, n_knots]
|
| 2007 |
z_tilde = z_tilde.reshape(*input_ids.shape, self.config.generator_d_seed)
|
|
@@ -2011,10 +2705,13 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
|
|
| 2011 |
self.config.generator_num_knots,
|
| 2012 |
)
|
| 2013 |
else:
|
| 2014 |
-
inputs_embeds = self.token_generator(input_ids)
|
| 2015 |
else:
|
| 2016 |
inputs_embeds = self.embed_tokens(input_ids)
|
| 2017 |
|
|
|
|
|
|
|
|
|
|
| 2018 |
if position_ids is None:
|
| 2019 |
position_ids = torch.arange(
|
| 2020 |
0, inputs_embeds.shape[1], device=inputs_embeds.device
|
|
@@ -2061,6 +2758,10 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
|
|
| 2061 |
else 1 # Full AttnRes: every layer is its own "block"
|
| 2062 |
)
|
| 2063 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2064 |
for layer_idx, decoder_layer in enumerate(self.layers[: self.config.num_hidden_layers]):
|
| 2065 |
if output_hidden_states:
|
| 2066 |
all_hidden_states = all_hidden_states + (hidden_states,)
|
|
@@ -2077,6 +2778,13 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
|
|
| 2077 |
attn_res_sources = attn_res_sources + [attn_res_partial]
|
| 2078 |
attn_res_partial = hidden_states # start new block from current output
|
| 2079 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2080 |
layer_outputs = decoder_layer(
|
| 2081 |
hidden_states,
|
| 2082 |
position_embeddings=position_embeddings,
|
|
@@ -2086,6 +2794,7 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
|
|
| 2086 |
B_vals=B_vals,
|
| 2087 |
attn_res_sources=attn_res_sources,
|
| 2088 |
attn_res_partial=attn_res_partial if use_attn_res else None,
|
|
|
|
| 2089 |
output_attentions=output_attentions,
|
| 2090 |
**kwargs,
|
| 2091 |
)
|
|
@@ -2111,6 +2820,14 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
|
|
| 2111 |
if output_hidden_states:
|
| 2112 |
all_hidden_states = all_hidden_states + (hidden_states,)
|
| 2113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2114 |
if not return_dict:
|
| 2115 |
return tuple(
|
| 2116 |
v for v in [hidden_states, None, all_hidden_states, all_attentions]
|
|
@@ -2153,6 +2870,30 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
|
|
| 2153 |
total_loss = CE_loss + L_aux
|
| 2154 |
|
| 2155 |
where L_aux = λ · n_e · (1/L) · Σ_ℓ Σ_i p_i^ℓ · f_i^ℓ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2156 |
"""
|
| 2157 |
|
| 2158 |
_tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
|
|
@@ -2166,8 +2907,70 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
|
|
| 2166 |
if config.use_token_generator:
|
| 2167 |
self._tied_weights_keys = {}
|
| 2168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2169 |
self.post_init()
|
| 2170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2171 |
def get_input_embeddings(self):
|
| 2172 |
return self.model.get_input_embeddings()
|
| 2173 |
|
|
@@ -2217,6 +3020,9 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
|
|
| 2217 |
return_dict: Optional[bool] = None,
|
| 2218 |
**kwargs: Unpack[TransformersKwargs],
|
| 2219 |
) -> CausalLMOutputWithPast:
|
|
|
|
|
|
|
|
|
|
| 2220 |
model_out = self.model(
|
| 2221 |
input_ids=input_ids,
|
| 2222 |
attention_mask=attention_mask,
|
|
@@ -2224,6 +3030,7 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
|
|
| 2224 |
inputs_embeds=inputs_embeds,
|
| 2225 |
output_hidden_states=output_hidden_states,
|
| 2226 |
return_dict=return_dict,
|
|
|
|
| 2227 |
**kwargs,
|
| 2228 |
)
|
| 2229 |
|
|
@@ -2262,6 +3069,11 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
|
|
| 2262 |
)
|
| 2263 |
logits = self.lm_head(hidden_states[:, slice_indices, :])
|
| 2264 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2265 |
return CausalLMOutputWithPast(
|
| 2266 |
loss=loss,
|
| 2267 |
logits=logits,
|
|
@@ -2286,6 +3098,18 @@ __all__ = [
|
|
| 2286 |
"VectorMultiplier",
|
| 2287 |
"LinearWithMultipliers",
|
| 2288 |
"MEAHeadSeeDNorm",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2289 |
]
|
| 2290 |
|
| 2291 |
AutoConfig.register("neollm", NeoLLMConfig)
|
|
|
|
| 20 |
moving-average bias that prevents collapse. Reduces first-token bias,
|
| 21 |
increases attention entropy, and is complementary to Gated Attention
|
| 22 |
(Bae et al. 2026, Table 2: Affine-Scaled + Gated > either alone).
|
| 23 |
+
|
| 24 |
+
Flash/SDPA path (exact, no approximation):
|
| 25 |
+
Expanding [α·softmax(QKᵀ)+β]V distributively yields two terms:
|
| 26 |
+
α · flash_attn(Q,K,V) — Flash computes this directly
|
| 27 |
+
+ β · Σ_{j≤i} V_j — causal prefix-sum of V (V.cumsum)
|
| 28 |
+
The V_cumsum tensor [B,H,S,d_head] is the only extra memory vs a
|
| 29 |
+
standard flash call. Semantically identical to the eager path;
|
| 30 |
+
per-weight softmax tensors (attn_weights_pre/post_affine) are
|
| 31 |
+
unavailable in flash mode and remain None in AnalysisState.
|
| 32 |
+
|
| 33 |
+
Eager path: exact with full weight access, used for interpretability.
|
| 34 |
|
| 35 |
References:
|
| 36 |
FANformer: "FANformer: Improving Large Language Models Through Effective
|
|
|
|
| 52 |
"""
|
| 53 |
|
| 54 |
import math
|
| 55 |
+
from dataclasses import dataclass
|
| 56 |
+
from typing import Callable, List, Optional, Union, Tuple
|
| 57 |
|
| 58 |
import torch
|
| 59 |
import torch.nn.functional as F
|
|
|
|
| 71 |
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
|
| 72 |
from transformers.processing_utils import Unpack
|
| 73 |
from transformers.utils import TransformersKwargs, logging
|
| 74 |
+
from configuration_neollm import NeoLLMConfig
|
| 75 |
|
| 76 |
from transformers import AutoConfig, AutoModel, AutoModelForCausalLM
|
| 77 |
|
| 78 |
logger = logging.get_logger(__name__)
|
| 79 |
|
| 80 |
|
| 81 |
+
# ==================== NATIVE ANALYSIS STATE ====================
|
| 82 |
+
# All dataclasses below define the analysis container hierarchy.
|
| 83 |
+
# This infrastructure is ONLY active when:
|
| 84 |
+
# 1. model.enable_analysis() has been called, AND
|
| 85 |
+
# 2. the model is in eval mode (model.eval() / not model.training)
|
| 86 |
+
#
|
| 87 |
+
# During training, analysis_state is always None — zero overhead,
|
| 88 |
+
# zero interference with gradients, zero change to the training flow.
|
| 89 |
+
#
|
| 90 |
+
# Access after any inference call:
|
| 91 |
+
# state = model.last_analysis # AnalysisState | None
|
| 92 |
+
#
|
| 93 |
+
# Every tensor stored here is detached from the computation graph.
|
| 94 |
+
# Fields are None when the corresponding config flag is inactive,
|
| 95 |
+
# so adding or removing a component only requires updating its own
|
| 96 |
+
# dataclass — all other analysis paths remain untouched.
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
@dataclass
|
| 100 |
+
class FANAnalysis:
|
| 101 |
+
"""
|
| 102 |
+
Decomposed output of a FANLayer call.
|
| 103 |
+
FANLayer'(X) = [cos(Wp·X) ‖ sin(Wp·X) ‖ (Wp̄·X + Bp̄)]
|
| 104 |
+
"""
|
| 105 |
+
cosine_component: Optional[torch.Tensor] = None # [*, p_output_dim]
|
| 106 |
+
sine_component: Optional[torch.Tensor] = None # [*, p_output_dim]
|
| 107 |
+
linear_component: Optional[torch.Tensor] = None # [*, g_output_dim]
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
@dataclass
|
| 111 |
+
class SeeDNormAnalysis:
|
| 112 |
+
"""
|
| 113 |
+
Internals of a SeeDNorm forward pass.
|
| 114 |
+
SeeDNorm(x) = [σ(x·β^T)·α + γ] ⊙ x/RMS(x)
|
| 115 |
+
"""
|
| 116 |
+
rescale_factor: Optional[torch.Tensor] = None # tanh(Σ x·β) — the dynamic gate
|
| 117 |
+
dynamic_scale: Optional[torch.Tensor] = None # rescale_factor·α + γ
|
| 118 |
+
x_normalized: Optional[torch.Tensor] = None # x / RMS(x) before dynamic_scale
|
| 119 |
+
output: Optional[torch.Tensor] = None # final output
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
@dataclass
|
| 123 |
+
class GPASAnalysis:
|
| 124 |
+
"""
|
| 125 |
+
Internals of a GPAS forward pass.
|
| 126 |
+
GPAS(x) = x - silu(α)·x_detached
|
| 127 |
+
"""
|
| 128 |
+
silu_alpha: Optional[torch.Tensor] = None # F.silu(self.alpha) — scalar
|
| 129 |
+
subtracted_component: Optional[torch.Tensor] = None # silu(α) · x.detach()
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
@dataclass
|
| 133 |
+
class PolyNormAnalysis:
|
| 134 |
+
"""
|
| 135 |
+
Internals of a PolyNorm forward pass (used as MLP act_fn).
|
| 136 |
+
Branches: x1 (linear), x2 (quadratic), x3 (cubic), each normalized.
|
| 137 |
+
x2 and x3 are partially orthogonalized against x1 via learned α.
|
| 138 |
+
"""
|
| 139 |
+
x1: Optional[torch.Tensor] = None # RMS-normalized linear branch
|
| 140 |
+
x2_pre_exclusive: Optional[torch.Tensor] = None # quadratic branch before exclusivity
|
| 141 |
+
x3_pre_exclusive: Optional[torch.Tensor] = None # cubic branch before exclusivity
|
| 142 |
+
x2_post_exclusive: Optional[torch.Tensor] = None # quadratic after exclusivity + renorm
|
| 143 |
+
x3_post_exclusive: Optional[torch.Tensor] = None # cubic after exclusivity + renorm
|
| 144 |
+
alpha2: Optional[torch.Tensor] = None # sigmoid(exclusive_logits[0]) for x2
|
| 145 |
+
alpha3: Optional[torch.Tensor] = None # sigmoid(exclusive_logits[1]) for x3
|
| 146 |
+
weights: Optional[torch.Tensor] = None # self.weight [3] — branch mixing
|
| 147 |
+
bias: Optional[torch.Tensor] = None # self.bias scalar
|
| 148 |
+
output: Optional[torch.Tensor] = None # final PolyNorm output
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
@dataclass
|
| 152 |
+
class AttentionAnalysis:
|
| 153 |
+
"""
|
| 154 |
+
Full attention internals for one NeoLLMAttention forward pass.
|
| 155 |
+
Fields present unconditionally capture the always-active path.
|
| 156 |
+
Fields guarded by config flags are None when that flag is off.
|
| 157 |
+
"""
|
| 158 |
+
# ── FANLayer (always active) ──────────────────────────────────────
|
| 159 |
+
fan: Optional[FANAnalysis] = None # FAN components for attention
|
| 160 |
+
|
| 161 |
+
# ── Q/K/V projection (always active) ─────────────────────────────
|
| 162 |
+
q_raw: Optional[torch.Tensor] = None # Q before q_norm [B,S,H,d]
|
| 163 |
+
gate_raw: Optional[torch.Tensor] = None # gate chunk before sigmoid [B,S,H*d]
|
| 164 |
+
gate_sigmoid: Optional[torch.Tensor] = None # sigmoid(gate) — Gated Attention weight
|
| 165 |
+
q_post_norm: Optional[torch.Tensor] = None # Q after SeeDNorm q_norm [B,H,S,d]
|
| 166 |
+
k_post_norm: Optional[torch.Tensor] = None # K after SeeDNorm k_norm [B,H,S,d]
|
| 167 |
+
v_raw: Optional[torch.Tensor] = None # V raw (pre MEA/LUCID) [B,H,S,d]
|
| 168 |
+
|
| 169 |
+
# ── RoPE (always active) ──────────────────────────────────────────
|
| 170 |
+
q_post_rope: Optional[torch.Tensor] = None # Q after RoPE [B,H,S,d]
|
| 171 |
+
k_post_rope: Optional[torch.Tensor] = None # K after RoPE [B,H,S,d]
|
| 172 |
+
|
| 173 |
+
# ── Momentum (conditional on use_momentum_attention) ──────────────
|
| 174 |
+
q_momentum_delta: Optional[torch.Tensor] = None # causal_first_difference(Q)
|
| 175 |
+
k_momentum_delta: Optional[torch.Tensor] = None # causal_first_difference(K)
|
| 176 |
+
q_post_momentum: Optional[torch.Tensor] = None # Q + γ·Δ
|
| 177 |
+
k_post_momentum: Optional[torch.Tensor] = None # K + γ·Δ
|
| 178 |
+
|
| 179 |
+
# ── MEA head mixing (conditional on use_mea_attention) ────────────
|
| 180 |
+
mea_key_mix_matrix: Optional[torch.Tensor] = None # mea_key_mix [H_comp,H_kv]
|
| 181 |
+
mea_value_mix_matrix: Optional[torch.Tensor] = None # mea_value_mix [H_comp,H_kv]
|
| 182 |
+
k_post_mea: Optional[torch.Tensor] = None # K after head mixing
|
| 183 |
+
v_post_mea: Optional[torch.Tensor] = None # V after head mixing
|
| 184 |
+
|
| 185 |
+
# ── LUCID preconditioner (conditional on use_lucid_attention) ─────
|
| 186 |
+
lucid_preconditioner: Optional[torch.Tensor] = None # lower-triangular prec matrix
|
| 187 |
+
v_post_lucid: Optional[torch.Tensor] = None # V after triangular solve
|
| 188 |
+
|
| 189 |
+
# ── Affine-Scaled Attention (conditional on use_affine_scaled_attention) ──
|
| 190 |
+
alpha_per_head: Optional[torch.Tensor] = None # α [B,H,S,1] in [0,1]
|
| 191 |
+
beta_per_head: Optional[torch.Tensor] = None # β [B,H,S,1] moving-avg bias
|
| 192 |
+
alpha_moving_avg: Optional[torch.Tensor] = None # alpha_ma EMA snapshot
|
| 193 |
+
attn_weights_pre_affine: Optional[torch.Tensor] = None # softmax weights before α,β
|
| 194 |
+
attn_weights_post_affine: Optional[torch.Tensor] = None # α·softmax + β
|
| 195 |
+
|
| 196 |
+
# ── Standard attention weights (eager non-affine path) ────────────
|
| 197 |
+
attn_weights: Optional[torch.Tensor] = None # softmax weights (None for flash/sdpa)
|
| 198 |
+
|
| 199 |
+
# ── Post-SDPA (always active) ─────────────────────────────────────
|
| 200 |
+
attn_output_raw: Optional[torch.Tensor] = None # SDPA output [B,S,H,d]
|
| 201 |
+
|
| 202 |
+
# ── MEA output norm (conditional on use_mea_attention) ────────────
|
| 203 |
+
attn_output_post_mea_norm: Optional[torch.Tensor] = None # after MEAHeadSeeDNorm
|
| 204 |
+
|
| 205 |
+
# ── XSA (conditional on use_xsa) ─────────────────────────────────
|
| 206 |
+
xsa_self_position_component: Optional[torch.Tensor] = None # proj subtracted
|
| 207 |
+
attn_output_post_xsa: Optional[torch.Tensor] = None # after XSA removal
|
| 208 |
+
|
| 209 |
+
# ── Directional Routing (conditional on use_directional_routing) ──
|
| 210 |
+
direction_vecs_normalized: Optional[torch.Tensor] = None # unit-norm d [H,K,d]
|
| 211 |
+
dr_router_logits: Optional[torch.Tensor] = None # MLP router output [B,H*K]
|
| 212 |
+
dr_routing_weights: Optional[torch.Tensor] = None # sigmoid(T·logits) [B,H,K]
|
| 213 |
+
dr_projection: Optional[torch.Tensor] = None # (o·d) scalars [B,S,H,K]
|
| 214 |
+
dr_suppression: Optional[torch.Tensor] = None # Σ r·proj·d [B,S,H,d]
|
| 215 |
+
attn_output_post_routing: Optional[torch.Tensor] = None # after DR removal
|
| 216 |
+
|
| 217 |
+
# ── Gate and o_proj (always active) ───────────────────────────────
|
| 218 |
+
attn_output_pre_gate: Optional[torch.Tensor] = None # pre gate multiply [B,S,H,d]
|
| 219 |
+
attn_output_final: Optional[torch.Tensor] = None # after o_proj [B,S,D]
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
@dataclass
|
| 223 |
+
class MLPAnalysis:
|
| 224 |
+
"""
|
| 225 |
+
Internals of a NeoLLMMLP forward pass.
|
| 226 |
+
SwiGLU-like: down_proj(dropout(PolyNorm(gate_proj(fan)) · up_proj(fan)))
|
| 227 |
+
"""
|
| 228 |
+
fan: Optional[FANAnalysis] = None # FAN components for MLP
|
| 229 |
+
gate_proj_output: Optional[torch.Tensor] = None # gate_proj(x_fan) [B,S,I]
|
| 230 |
+
up_proj_output: Optional[torch.Tensor] = None # up_proj(x_fan) [B,S,I]
|
| 231 |
+
polynorm: Optional[PolyNormAnalysis] = None # PolyNorm of gate branch
|
| 232 |
+
act_times_up: Optional[torch.Tensor] = None # PolyNorm(gate)·up [B,S,I]
|
| 233 |
+
output: Optional[torch.Tensor] = None # after down_proj [B,S,D]
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
@dataclass
|
| 237 |
+
class JTokMAnalysis:
|
| 238 |
+
"""
|
| 239 |
+
Internals of a LeviathanJTokM forward pass for one decoder layer.
|
| 240 |
+
"""
|
| 241 |
+
surfaces: Optional[torch.Tensor] = None # all n_e surface outputs [N,n_e,D]
|
| 242 |
+
router_logits: Optional[torch.Tensor] = None # pre-TopK logits [N,n_e]
|
| 243 |
+
topk_indices: Optional[torch.Tensor] = None # selected surface indices [N,K]
|
| 244 |
+
routing_weights: Optional[torch.Tensor] = None # normalized sigmoid weights [N,K]
|
| 245 |
+
mixed_pre_norm: Optional[torch.Tensor] = None # weighted sum before norm [N,D]
|
| 246 |
+
mixed_normalized: Optional[torch.Tensor] = None # direction-normalized mixed [N,D]
|
| 247 |
+
delta_r: Optional[torch.Tensor] = None # final injection (scaled) [N,D]
|
| 248 |
+
p_sum: Optional[torch.Tensor] = None # routing probability sum [n_e]
|
| 249 |
+
f_sum: Optional[torch.Tensor] = None # load fraction sum [n_e]
|
| 250 |
+
lns_scale: Optional[float] = None # 1/√(2ℓ) scaling factor
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
@dataclass
|
| 254 |
+
class AttnResAnalysis:
|
| 255 |
+
"""
|
| 256 |
+
Depth-wise softmax attention weights from AttnRes sublayer calls.
|
| 257 |
+
Only populated when use_attn_res=True.
|
| 258 |
+
"""
|
| 259 |
+
weights_pre_attn: Optional[torch.Tensor] = None # softmax over sources [N+1,B,S]
|
| 260 |
+
weights_pre_mlp: Optional[torch.Tensor] = None # softmax over sources [N+1,B,S]
|
| 261 |
+
sources_count: Optional[int] = None # number of sources including partial
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
@dataclass
|
| 265 |
+
class LayerAnalysis:
|
| 266 |
+
"""
|
| 267 |
+
Complete analysis snapshot for one NeoLLMDecoderLayer forward pass.
|
| 268 |
+
Sub-objects are None when the corresponding config flag is inactive.
|
| 269 |
+
"""
|
| 270 |
+
layer_idx: int = 0
|
| 271 |
+
|
| 272 |
+
# Hidden state boundaries
|
| 273 |
+
hidden_states_input: Optional[torch.Tensor] = None # entering this layer
|
| 274 |
+
hidden_states_output: Optional[torch.Tensor] = None # leaving this layer
|
| 275 |
+
h_tilde: Optional[torch.Tensor] = None # post-attn residual, pre-MLP
|
| 276 |
+
|
| 277 |
+
# Attention sublayer
|
| 278 |
+
seednorm_pre_attn: Optional[SeeDNormAnalysis] = None # input_layernorm
|
| 279 |
+
lns_attn_output: Optional[torch.Tensor] = None # after LNS(1/√ℓ)
|
| 280 |
+
attention: Optional[AttentionAnalysis] = None # full attention analysis
|
| 281 |
+
attn_contribution: Optional[torch.Tensor] = None # attn output before residual
|
| 282 |
+
gpas_attn: Optional[GPASAnalysis] = None # GPAS after attn residual
|
| 283 |
+
|
| 284 |
+
# MLP sublayer
|
| 285 |
+
seednorm_post_attn: Optional[SeeDNormAnalysis] = None # post_attention_layernorm
|
| 286 |
+
lns_mlp_output: Optional[torch.Tensor] = None # after LNS(1/√ℓ)
|
| 287 |
+
mlp: Optional[MLPAnalysis] = None # full MLP analysis
|
| 288 |
+
mlp_contribution: Optional[torch.Tensor] = None # MLP output before residual
|
| 289 |
+
gpas_mlp: Optional[GPASAnalysis] = None # GPAS after MLP residual
|
| 290 |
+
|
| 291 |
+
# Optional components (None when inactive)
|
| 292 |
+
jtokm: Optional[JTokMAnalysis] = None # if use_jtokm
|
| 293 |
+
attn_res: Optional[AttnResAnalysis] = None # if use_attn_res
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
@dataclass
|
| 297 |
+
class GeneratorAnalysis:
|
| 298 |
+
"""
|
| 299 |
+
Internals of a LeviathanGenerator forward pass.
|
| 300 |
+
Only populated when use_token_generator=True.
|
| 301 |
+
"""
|
| 302 |
+
z_raw: Optional[torch.Tensor] = None # [N,d_seed] codebook sum
|
| 303 |
+
z_tilde: Optional[torch.Tensor] = None # [N,d_seed] JTok-M path output
|
| 304 |
+
B_vals: Optional[torch.Tensor] = None # [N,d_seed,n_knots] B-spline basis
|
| 305 |
+
z_all_pre_norm: Optional[torch.Tensor] = None # [N,M,d_seed] per-head pre-sigmoid
|
| 306 |
+
z_all_post_sigmoid: Optional[torch.Tensor] = None # [N,M,d_seed] per-head post-sigmoid
|
| 307 |
+
modes_all: Optional[torch.Tensor] = None # [N,M,krank] KHRONOS tensor product
|
| 308 |
+
embeddings: Optional[torch.Tensor] = None # [N,hidden_size] final output
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
@dataclass
|
| 312 |
+
class AnalysisState:
|
| 313 |
+
"""
|
| 314 |
+
Root analysis container for one NeoLLMForCausalLM forward pass.
|
| 315 |
+
|
| 316 |
+
Populated when model.enable_analysis() is active AND model.training=False.
|
| 317 |
+
All tensors are detached from the computation graph.
|
| 318 |
+
|
| 319 |
+
Structure:
|
| 320 |
+
.input_ids — original input token ids
|
| 321 |
+
.embeddings — token embeddings entering the decoder stack
|
| 322 |
+
.final_hidden_states — post-norm output entering lm_head
|
| 323 |
+
.generator — LeviathanGenerator internals (use_token_generator only)
|
| 324 |
+
.layers[i] — per-layer LayerAnalysis, indexed by layer_idx
|
| 325 |
+
.jtokm_aux_stats — raw per-layer load-balancing tuples (use_jtokm only)
|
| 326 |
+
.attn_res_sources_final — AttnRes source list at end of forward (use_attn_res only)
|
| 327 |
+
.logits — lm_head output (only when labels is None)
|
| 328 |
+
|
| 329 |
+
Access pattern:
|
| 330 |
+
model.eval()
|
| 331 |
+
model.enable_analysis()
|
| 332 |
+
_ = model(input_ids)
|
| 333 |
+
state = model.last_analysis
|
| 334 |
+
alpha = state.layers[3].attention.alpha_per_head
|
| 335 |
+
"""
|
| 336 |
+
input_ids: Optional[torch.Tensor] = None
|
| 337 |
+
embeddings: Optional[torch.Tensor] = None
|
| 338 |
+
final_hidden_states: Optional[torch.Tensor] = None
|
| 339 |
+
|
| 340 |
+
generator: Optional[GeneratorAnalysis] = None
|
| 341 |
+
layers: Optional[List[LayerAnalysis]] = None
|
| 342 |
+
jtokm_aux_stats: Optional[list] = None
|
| 343 |
+
attn_res_sources_final: Optional[list] = None
|
| 344 |
+
logits: Optional[torch.Tensor] = None
|
| 345 |
|
| 346 |
class ScalarMultiplier(nn.Module):
|
| 347 |
"""
|
|
|
|
| 728 |
self,
|
| 729 |
token_ids: torch.Tensor,
|
| 730 |
return_internals: bool = False,
|
| 731 |
+
analysis: Optional[GeneratorAnalysis] = None,
|
| 732 |
) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor, torch.Tensor]]:
|
| 733 |
"""
|
| 734 |
Generate embeddings from discrete token indices.
|
|
|
|
| 753 |
token_ids: (batch, seq_len) or (seq_len,)
|
| 754 |
return_internals: if True, also return z_tilde and B_vals for
|
| 755 |
reuse by JTok-M surfaces in every decoder layer.
|
| 756 |
+
analysis: Optional[GeneratorAnalysis] — when not None and model
|
| 757 |
+
is in eval mode with analysis armed, deposits all
|
| 758 |
+
internal tensors (detached). No-op during training.
|
| 759 |
Returns:
|
| 760 |
embeddings [*token_ids.shape, hidden_size],
|
| 761 |
or (embeddings, z_tilde [N, d_seed], B_vals [N, d_seed, n_knots])
|
|
|
|
| 772 |
for r in range(self.k):
|
| 773 |
z = z + self.codebooks[r][coords_flat[:, r]]
|
| 774 |
|
| 775 |
+
if analysis is not None:
|
| 776 |
+
analysis.z_raw = z.detach()
|
| 777 |
+
|
| 778 |
# ── JTok-M shared path ────────────────────────────────────────────
|
| 779 |
# Produces z_tilde and B_vals consumed by every decoder layer's
|
| 780 |
# JTok-M module. This path is unchanged and uses fixed scalar scale.
|
| 781 |
z_tilde = torch.sigmoid(self.seed_norm(self.seed_proj(z))) # [N, d_seed]
|
| 782 |
B_vals = self._bspline_basis(z_tilde.clamp(0.0, 1.0)) # [N, d_seed, n_knots]
|
| 783 |
|
| 784 |
+
if analysis is not None:
|
| 785 |
+
analysis.z_tilde = z_tilde.detach()
|
| 786 |
+
analysis.B_vals = B_vals.detach()
|
| 787 |
+
|
| 788 |
# ── Per-head generator path (fully vectorized, 6 kernels) ────────
|
| 789 |
# All 8 heads are processed simultaneously. No Python loop.
|
| 790 |
# Maximum intermediate tensor [N, M, d_seed, n_knots] appears once.
|
|
|
|
| 794 |
z_all = F.linear(z.to(target_dtype), self.head_proj_weight)
|
| 795 |
z_all = z_all.view(N, self.num_modes, self.d_seed) # [N, M, d_seed]
|
| 796 |
|
| 797 |
+
if analysis is not None:
|
| 798 |
+
analysis.z_all_pre_norm = z_all.detach()
|
| 799 |
+
|
| 800 |
# Kernel 2: per-head LayerNorm + sigmoid(x/2)
|
| 801 |
# Manual LN over last dim with independent weight/bias per head.
|
| 802 |
# Mathematically identical to 8 separate nn.LayerNorm(d_seed).
|
|
|
|
| 808 |
+ self.head_norm_bias.unsqueeze(0)
|
| 809 |
z_all = torch.sigmoid(z_all / 2.0) # [N, M, d_seed]
|
| 810 |
|
| 811 |
+
if analysis is not None:
|
| 812 |
+
analysis.z_all_post_sigmoid = z_all.detach()
|
| 813 |
+
|
| 814 |
# Kernel 3: vectorized B-spline basis for all heads
|
| 815 |
# head_scale [M, d_seed] is used inside _bspline_basis_all_heads
|
| 816 |
B_all = self._bspline_basis_all_heads(
|
|
|
|
| 820 |
# Kernel 4: vectorized KHRONOS tensor product for all heads
|
| 821 |
modes_all = self._khronos_all_heads(B_all) # [N, M, krank]
|
| 822 |
|
| 823 |
+
if analysis is not None:
|
| 824 |
+
analysis.modes_all = modes_all.detach()
|
| 825 |
+
|
| 826 |
# Kernel 5: project all heads to hidden_size and sum
|
| 827 |
# einsum: token n, head m, krank k → hidden d (summed over m)
|
| 828 |
# head_out_weight [M, krank, hidden_size]
|
|
|
|
| 835 |
# No W_res — confirmed absent in the authors' implementation
|
| 836 |
e = e.reshape(*orig_shape, self.hidden_size)
|
| 837 |
|
| 838 |
+
if analysis is not None:
|
| 839 |
+
analysis.embeddings = e.detach()
|
| 840 |
+
|
| 841 |
if return_internals:
|
| 842 |
return e, z_tilde, B_vals
|
| 843 |
return e
|
|
|
|
| 924 |
B_vals: torch.Tensor,
|
| 925 |
z_tilde: torch.Tensor,
|
| 926 |
target_dtype: torch.dtype,
|
| 927 |
+
analysis: Optional[JTokMAnalysis] = None,
|
| 928 |
) -> torch.Tensor:
|
| 929 |
"""
|
| 930 |
Evaluate all n_e surfaces vectorized over the full token batch.
|
|
|
|
| 936 |
All shapes are static → torch.compile compatible.
|
| 937 |
|
| 938 |
Args:
|
| 939 |
+
B_vals: [N, d_seed, n_knots] float32
|
| 940 |
+
z_tilde: [N, d_seed]
|
| 941 |
target_dtype: model dtype
|
| 942 |
+
analysis: Optional[JTokMAnalysis] — deposits surfaces when not None.
|
| 943 |
Returns:
|
| 944 |
surfaces: [N, n_e, D]
|
| 945 |
"""
|
|
|
|
| 968 |
z = z_tilde.to(target_dtype)
|
| 969 |
out_res = torch.einsum("nd,idc->nic", z, self.W_res.to(target_dtype))
|
| 970 |
|
| 971 |
+
surfaces = out_modes + out_res # [N, n_e, D]
|
| 972 |
+
|
| 973 |
+
if analysis is not None:
|
| 974 |
+
analysis.surfaces = surfaces.detach()
|
| 975 |
+
|
| 976 |
+
return surfaces
|
| 977 |
|
| 978 |
# ── Router ────────────────────────────────────────────────────────────
|
| 979 |
|
|
|
|
| 984 |
self,
|
| 985 |
h_tilde: torch.Tensor,
|
| 986 |
surfaces: torch.Tensor,
|
| 987 |
+
analysis: Optional[JTokMAnalysis] = None,
|
| 988 |
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 989 |
"""
|
| 990 |
Context-dependent routing over h_tilde (hidden state after attention).
|
|
|
|
| 1003 |
Args:
|
| 1004 |
h_tilde: [N, D] — hidden state after attention (before MLP)
|
| 1005 |
surfaces: [N, n_e, D]
|
| 1006 |
+
analysis: Optional[JTokMAnalysis] — deposits routing data when not None.
|
| 1007 |
Returns:
|
| 1008 |
mixed: [N, D]
|
| 1009 |
aux_stats: (p_sum [n_e], f_sum [n_e], N) for loss accumulation
|
|
|
|
| 1026 |
selected = surfaces.gather(dim=1, index=idx_exp) # [N, K, D]
|
| 1027 |
mixed = (w.unsqueeze(-1) * selected).sum(dim=1) # [N, D]
|
| 1028 |
|
| 1029 |
+
if analysis is not None:
|
| 1030 |
+
analysis.router_logits = g.detach()
|
| 1031 |
+
analysis.topk_indices = topk_idx.detach()
|
| 1032 |
+
analysis.routing_weights = w.detach()
|
| 1033 |
+
analysis.mixed_pre_norm = mixed.detach()
|
| 1034 |
+
|
| 1035 |
# Load-balancing statistics for aux loss (Appendix B, Yang et al. 2026)
|
| 1036 |
# p_i = mean routing probability over batch
|
| 1037 |
# f_i = fraction of tokens actually routed to i
|
|
|
|
| 1053 |
h_tilde: torch.Tensor,
|
| 1054 |
z_tilde: torch.Tensor,
|
| 1055 |
B_vals: torch.Tensor,
|
| 1056 |
+
analysis: Optional[JTokMAnalysis] = None,
|
| 1057 |
) -> Tuple[torch.Tensor, Tuple]:
|
| 1058 |
"""
|
| 1059 |
Compute additive JTok-M residual for one decoder layer.
|
| 1060 |
|
| 1061 |
Args:
|
| 1062 |
+
h_tilde: [N, D] hidden state after attention (before MLP)
|
| 1063 |
+
z_tilde: [N, d_seed] latent coordinate from generator
|
| 1064 |
+
B_vals: [N, d_seed, n_k] B-spline basis (computed once, reused)
|
| 1065 |
+
analysis: Optional[JTokMAnalysis] — deposits all JTok-M internals when
|
| 1066 |
+
not None. No-op during training (analysis is always None then).
|
| 1067 |
Returns:
|
| 1068 |
+
delta_r: [N, D] additive residual (already scaled)
|
| 1069 |
aux_stats: tuple for accumulating load-balance loss
|
| 1070 |
"""
|
| 1071 |
target_dtype = h_tilde.dtype
|
| 1072 |
|
| 1073 |
# All n_e surfaces in one vectorized pass
|
| 1074 |
+
surfaces = self._eval_surfaces(B_vals, z_tilde, target_dtype, analysis=analysis)
|
| 1075 |
|
| 1076 |
# Context-dependent routing
|
| 1077 |
+
mixed, aux_stats = self._route_and_mix(h_tilde, surfaces, analysis=analysis)
|
| 1078 |
|
| 1079 |
# Normalise direction, apply scaler, scale with 1/√(2ℓ)
|
| 1080 |
# Norm_ε decouples direction from magnitude (JTok Appendix D.2)
|
| 1081 |
mixed_norm = mixed / (mixed.norm(dim=-1, keepdim=True) + self.norm_eps)
|
| 1082 |
delta_r = self.lns_scale * self.scaler * mixed_norm # [N, D]
|
| 1083 |
|
| 1084 |
+
if analysis is not None:
|
| 1085 |
+
analysis.mixed_normalized = mixed_norm.detach()
|
| 1086 |
+
analysis.delta_r = delta_r.detach()
|
| 1087 |
+
analysis.p_sum = aux_stats[0].detach()
|
| 1088 |
+
analysis.f_sum = aux_stats[1].detach()
|
| 1089 |
+
analysis.lns_scale = self.lns_scale
|
| 1090 |
+
|
| 1091 |
return delta_r, aux_stats
|
| 1092 |
|
| 1093 |
|
|
|
|
| 1147 |
if self.input_linear.bias is not None:
|
| 1148 |
nn.init.zeros_(self.input_linear.bias)
|
| 1149 |
|
| 1150 |
+
def forward(
|
| 1151 |
+
self,
|
| 1152 |
+
x: torch.Tensor,
|
| 1153 |
+
analysis: Optional[FANAnalysis] = None,
|
| 1154 |
+
) -> torch.Tensor:
|
| 1155 |
pg = self.input_linear(x)
|
| 1156 |
p, g = torch.split(pg, [self.p_output_dim, self.g_output_dim], dim=-1)
|
| 1157 |
+
cos_p = torch.cos(p)
|
| 1158 |
+
sin_p = torch.sin(p)
|
| 1159 |
+
if analysis is not None:
|
| 1160 |
+
analysis.cosine_component = cos_p.detach()
|
| 1161 |
+
analysis.sine_component = sin_p.detach()
|
| 1162 |
+
analysis.linear_component = g.detach()
|
| 1163 |
+
return torch.cat([cos_p, sin_p, g], dim=-1)
|
| 1164 |
|
| 1165 |
|
| 1166 |
class LNS(nn.Module):
|
|
|
|
| 1185 |
self.d_model = d_model
|
| 1186 |
self.alpha = nn.Parameter(torch.zeros(1))
|
| 1187 |
|
| 1188 |
+
def forward(
|
| 1189 |
+
self,
|
| 1190 |
+
x: torch.Tensor,
|
| 1191 |
+
analysis: Optional[GPASAnalysis] = None,
|
| 1192 |
+
) -> torch.Tensor:
|
| 1193 |
+
silu_alpha = F.silu(self.alpha)
|
| 1194 |
+
subtracted = silu_alpha * x.detach()
|
| 1195 |
+
if analysis is not None:
|
| 1196 |
+
analysis.silu_alpha = silu_alpha.detach()
|
| 1197 |
+
analysis.subtracted_component = subtracted.detach()
|
| 1198 |
+
return x - subtracted
|
| 1199 |
|
| 1200 |
|
| 1201 |
class SeeDNorm(nn.Module):
|
|
|
|
| 1224 |
def _rms_norm(self, x: torch.Tensor) -> torch.Tensor:
|
| 1225 |
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
| 1226 |
|
| 1227 |
+
def forward(
|
| 1228 |
+
self,
|
| 1229 |
+
x: torch.Tensor,
|
| 1230 |
+
analysis: Optional[SeeDNormAnalysis] = None,
|
| 1231 |
+
) -> torch.Tensor:
|
| 1232 |
x_for_dynamic = F.dropout(x, p=self.dropout_input)
|
| 1233 |
rescale_factor = torch.tanh(
|
| 1234 |
torch.sum(x_for_dynamic * self.beta, dim=-1, keepdim=True)
|
|
|
|
| 1236 |
dynamic_scale = rescale_factor * self.alpha + self.gamma
|
| 1237 |
x_normalized = self._rms_norm(x.float())
|
| 1238 |
x_normalized = F.dropout(x_normalized, p=self.dropout_hidden)
|
| 1239 |
+
output = (x_normalized * dynamic_scale.float()).type_as(x)
|
| 1240 |
+
if analysis is not None:
|
| 1241 |
+
analysis.rescale_factor = rescale_factor.detach()
|
| 1242 |
+
analysis.dynamic_scale = dynamic_scale.detach()
|
| 1243 |
+
analysis.x_normalized = x_normalized.detach()
|
| 1244 |
+
analysis.output = output.detach()
|
| 1245 |
+
return output
|
| 1246 |
|
| 1247 |
def extra_repr(self) -> str:
|
| 1248 |
return (f"dim={self.dim}, eps={self.eps}, "
|
|
|
|
| 1458 |
alpha: torch.Tensor,
|
| 1459 |
beta: torch.Tensor,
|
| 1460 |
dropout: float = 0.0,
|
| 1461 |
+
attn_analysis: Optional[AttentionAnalysis] = None,
|
| 1462 |
**kwargs: Unpack[TransformersKwargs],
|
| 1463 |
):
|
| 1464 |
"""
|
|
|
|
| 1481 |
Reference: Bae et al. (2026), Affine-Scaled Attention, Eq. 6–8.
|
| 1482 |
|
| 1483 |
Args:
|
| 1484 |
+
alpha: [batch, num_heads, seq_q, 1] — input-dependent scale per query
|
| 1485 |
+
beta: [batch, num_heads, seq_q, 1] — input-dependent bias per query
|
| 1486 |
+
attn_analysis: Optional[AttentionAnalysis] — deposits pre/post-affine weights
|
| 1487 |
+
when not None. No-op during training (always None then).
|
| 1488 |
"""
|
| 1489 |
key_states = repeat_kv(key, module.num_key_value_groups)
|
| 1490 |
value_states = repeat_kv(value, module.num_key_value_groups)
|
|
|
|
| 1493 |
if attention_mask is not None:
|
| 1494 |
attn_weights = attn_weights + attention_mask[:, :, :, : key_states.shape[-2]]
|
| 1495 |
|
| 1496 |
+
attn_weights_softmax = nn.functional.softmax(
|
| 1497 |
attn_weights, dim=-1, dtype=torch.float32
|
| 1498 |
).to(query.dtype)
|
| 1499 |
|
| 1500 |
+
if attn_analysis is not None:
|
| 1501 |
+
attn_analysis.attn_weights_pre_affine = attn_weights_softmax.detach()
|
| 1502 |
+
|
| 1503 |
# Affine reweighting: relaxes the unit-sum constraint of softmax.
|
| 1504 |
# α ∈ [0, 1] scales down the softmax distribution (input-adaptively per head).
|
| 1505 |
# β offsets to prevent collapse when α deviates from its running mean.
|
| 1506 |
# Shapes: α, β are [B, H, S_q, 1], attn_weights is [B, H, S_q, S_k] → broadcast.
|
| 1507 |
+
attn_weights_affine = alpha * attn_weights_softmax + beta
|
| 1508 |
|
| 1509 |
+
if attn_analysis is not None:
|
| 1510 |
+
attn_analysis.attn_weights_post_affine = attn_weights_affine.detach()
|
| 1511 |
+
|
| 1512 |
+
attn_weights_affine = nn.functional.dropout(
|
| 1513 |
+
attn_weights_affine, p=dropout, training=module.training
|
| 1514 |
+
)
|
| 1515 |
+
attn_output = torch.matmul(attn_weights_affine, value_states).transpose(1, 2).contiguous()
|
| 1516 |
+
return attn_output, attn_weights_affine
|
| 1517 |
+
|
| 1518 |
+
|
| 1519 |
+
def affine_scaled_flash_attention_forward(
|
| 1520 |
+
module: nn.Module,
|
| 1521 |
+
query: torch.Tensor,
|
| 1522 |
+
key: torch.Tensor,
|
| 1523 |
+
value: torch.Tensor,
|
| 1524 |
+
attention_mask: Optional[torch.Tensor],
|
| 1525 |
+
scaling: float,
|
| 1526 |
+
alpha: torch.Tensor,
|
| 1527 |
+
beta: torch.Tensor,
|
| 1528 |
+
dropout: float = 0.0,
|
| 1529 |
+
attn_analysis: Optional[AttentionAnalysis] = None,
|
| 1530 |
+
**kwargs: Unpack[TransformersKwargs],
|
| 1531 |
+
):
|
| 1532 |
+
"""
|
| 1533 |
+
Affine-Scaled Attention — flash/sdpa path.
|
| 1534 |
+
|
| 1535 |
+
Exact mathematical decomposition of [α·softmax(QKᵀ)+β]V using only the
|
| 1536 |
+
public flash/sdpa interface — no kernel modification required.
|
| 1537 |
+
|
| 1538 |
+
Derivation
|
| 1539 |
+
----------
|
| 1540 |
+
The paper formula expands distributively:
|
| 1541 |
+
|
| 1542 |
+
[α · softmax(QKᵀ/√dk) + β] · V
|
| 1543 |
+
= α · [softmax(QKᵀ/√dk) · V] ← term 1: standard flash output
|
| 1544 |
+
+ β · [Σ_{j≤i} V_j] ← term 2: causal prefix-sum of V
|
| 1545 |
+
|
| 1546 |
+
Term 2 follows because β is a scalar per query (broadcast over all S_k),
|
| 1547 |
+
and softmax weights sum to 1 over the causal window:
|
| 1548 |
+
β_i · Σ_j w_{i,j} · V_j = β_i · Σ_{j≤i} V_j (since Σ w_{i,j} = 1)
|
| 1549 |
+
|
| 1550 |
+
Dropout
|
| 1551 |
+
-------
|
| 1552 |
+
The eager path drops entries of the combined weight matrix (α·softmax + β)
|
| 1553 |
+
before multiplying by V. With the flash interface we cannot access that
|
| 1554 |
+
combined matrix, so we apply dropout=0 to the flash kernel and instead
|
| 1555 |
+
apply nn.functional.dropout to the final combined output tensor. This is
|
| 1556 |
+
output dropout rather than weight dropout — a different (but standard)
|
| 1557 |
+
regularisation that achieves the same intent without the intermediate
|
| 1558 |
+
weight matrix. During inference dropout=0 so the paths are identical.
|
| 1559 |
+
|
| 1560 |
+
Padding mask
|
| 1561 |
+
------------
|
| 1562 |
+
The V_cumsum must not accumulate values from padding positions, since the
|
| 1563 |
+
flash kernel zeros those out internally but a plain cumsum does not.
|
| 1564 |
+
We zero V at padding positions before the cumsum by reading the diagonal
|
| 1565 |
+
of the attention_mask (valid positions have mask value 0, padding has -inf).
|
| 1566 |
+
|
| 1567 |
+
Memory overhead vs standard flash call
|
| 1568 |
+
---------------------------------------
|
| 1569 |
+
One extra tensor of shape [B, H_q, S, d_head] for V_cumsum.
|
| 1570 |
+
At (B=1, H=8, S=2048, d_head=64, bf16): ≈ 4 MB per call, ≈ 48 MB total
|
| 1571 |
+
across 12 layers. Allocated and freed within each forward call.
|
| 1572 |
+
|
| 1573 |
+
Attention-weight analysis fields (attn_weights_pre_affine,
|
| 1574 |
+
attn_weights_post_affine) remain None — those tensors are never
|
| 1575 |
+
materialised by the flash kernel and cannot be recovered.
|
| 1576 |
+
All other AnalysisState fields (alpha, beta, alpha_ma) are deposited
|
| 1577 |
+
by the caller before this function is invoked.
|
| 1578 |
+
|
| 1579 |
+
Args:
|
| 1580 |
+
alpha: [B, H_q, S_q, 1] — input-dependent scale per query, in [0, 1]
|
| 1581 |
+
beta: [B, H_q, S_q, 1] — moving-average bias per query
|
| 1582 |
+
attn_analysis: deposited by caller; this function does not write to it.
|
| 1583 |
+
"""
|
| 1584 |
+
# ── Term 1: standard flash / sdpa output ─────────────────────────────
|
| 1585 |
+
# dropout=0.0: we apply dropout to the combined output below instead.
|
| 1586 |
+
attn_fn = ALL_ATTENTION_FUNCTIONS[module.config._attn_implementation]
|
| 1587 |
+
flash_out, _ = attn_fn(
|
| 1588 |
+
module, query, key, value, attention_mask,
|
| 1589 |
+
dropout=0.0, scaling=scaling, **kwargs,
|
| 1590 |
+
)
|
| 1591 |
+
# flash_out: [B, S, H_q, d_head] — HF wrappers all return this layout
|
| 1592 |
+
|
| 1593 |
+
# ── Term 2: β · causal prefix-sum of V ───────────────────────────────
|
| 1594 |
+
# Expand V from KV heads to query heads for GQA.
|
| 1595 |
+
value_expanded = repeat_kv(value, module.num_key_value_groups) # [B, H_q, S, d_head]
|
| 1596 |
+
|
| 1597 |
+
# Zero out padded positions so they don't accumulate into the prefix sum.
|
| 1598 |
+
# attention_mask is [B, 1, S, S] with 0 at valid positions, -inf at padding.
|
| 1599 |
+
# The diagonal gives the per-position validity: valid → 0, padding → -inf.
|
| 1600 |
+
if attention_mask is not None and attention_mask.ndim == 4:
|
| 1601 |
+
diag = attention_mask.diagonal(dim1=-2, dim2=-1) # [B, 1, S]
|
| 1602 |
+
# valid=True where diag==0 (not -inf), padding=False
|
| 1603 |
+
valid = (diag == 0).to(value_expanded.dtype) # [B, 1, S]
|
| 1604 |
+
valid = valid.unsqueeze(-1) # [B, 1, S, 1]
|
| 1605 |
+
# broadcast over H_q and d_head; zero out V at padding positions
|
| 1606 |
+
value_expanded = value_expanded * valid # [B, H_q, S, d_head]
|
| 1607 |
+
|
| 1608 |
+
# Causal prefix-sum: position i accumulates all valid j ≤ i.
|
| 1609 |
+
v_cumsum = value_expanded.cumsum(dim=2) # [B, H_q, S, d_head]
|
| 1610 |
+
# Transpose to match flash_out layout.
|
| 1611 |
+
v_cumsum_t = v_cumsum.transpose(1, 2).contiguous() # [B, S, H_q, d_head]
|
| 1612 |
+
|
| 1613 |
+
# α, β: [B, H_q, S, 1] → [B, S, H_q, 1] to broadcast over d_head.
|
| 1614 |
+
alpha_t = alpha.permute(0, 2, 1, 3) # [B, S, H_q, 1]
|
| 1615 |
+
beta_t = beta.permute(0, 2, 1, 3) # [B, S, H_q, 1]
|
| 1616 |
+
|
| 1617 |
+
# ── Combine and apply dropout to the full affine output ────���──────────
|
| 1618 |
+
output = alpha_t * flash_out + beta_t * v_cumsum_t # [B, S, H_q, d_head]
|
| 1619 |
+
|
| 1620 |
+
# Apply output dropout on the combined affine result.
|
| 1621 |
+
# This regularises the full [α·flash + β·V_cumsum] output consistently.
|
| 1622 |
+
if dropout > 0.0 and module.training:
|
| 1623 |
+
output = nn.functional.dropout(output, p=dropout, training=True)
|
| 1624 |
+
|
| 1625 |
+
# attn_weights is None — flash never exposes the softmax weight matrix.
|
| 1626 |
+
return output, None
|
| 1627 |
|
| 1628 |
|
| 1629 |
class NeoLLMAttention(nn.Module):
|
|
|
|
| 1808 |
self.direction_vecs = None
|
| 1809 |
self.direction_router = None
|
| 1810 |
|
| 1811 |
+
def _apply_momentum_attention(
|
| 1812 |
+
self,
|
| 1813 |
+
q: torch.Tensor,
|
| 1814 |
+
k: torch.Tensor,
|
| 1815 |
+
attn_analysis: Optional[AttentionAnalysis] = None,
|
| 1816 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 1817 |
if not self.use_momentum_attention or self.momentum_gamma == 0.0:
|
| 1818 |
return q, k
|
| 1819 |
+
dq = causal_first_difference(q)
|
| 1820 |
+
dk = causal_first_difference(k)
|
| 1821 |
+
q_new = q + self.momentum_gamma * dq
|
| 1822 |
+
k_new = k + self.momentum_gamma * dk
|
| 1823 |
+
if attn_analysis is not None:
|
| 1824 |
+
attn_analysis.q_momentum_delta = dq.detach()
|
| 1825 |
+
attn_analysis.k_momentum_delta = dk.detach()
|
| 1826 |
+
attn_analysis.q_post_momentum = q_new.detach()
|
| 1827 |
+
attn_analysis.k_post_momentum = k_new.detach()
|
| 1828 |
+
return q_new, k_new
|
| 1829 |
+
|
| 1830 |
+
def _apply_mea_head_mixing(
|
| 1831 |
+
self,
|
| 1832 |
+
k: torch.Tensor,
|
| 1833 |
+
v: torch.Tensor,
|
| 1834 |
+
attn_analysis: Optional[AttentionAnalysis] = None,
|
| 1835 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 1836 |
if not self.use_mea_attention:
|
| 1837 |
return k, v
|
| 1838 |
+
k_mixed = head_linear_compose(k, self.mea_key_mix).contiguous()
|
| 1839 |
+
v_mixed = head_linear_compose(v, self.mea_value_mix).contiguous()
|
| 1840 |
+
if attn_analysis is not None:
|
| 1841 |
+
attn_analysis.mea_key_mix_matrix = self.mea_key_mix.detach()
|
| 1842 |
+
attn_analysis.mea_value_mix_matrix = self.mea_value_mix.detach()
|
| 1843 |
+
attn_analysis.k_post_mea = k_mixed.detach()
|
| 1844 |
+
attn_analysis.v_post_mea = v_mixed.detach()
|
| 1845 |
+
return k_mixed, v_mixed
|
| 1846 |
+
|
| 1847 |
+
def _apply_lucid_preconditioner(
|
| 1848 |
+
self,
|
| 1849 |
+
k: torch.Tensor,
|
| 1850 |
+
v: torch.Tensor,
|
| 1851 |
+
attention_mask: Optional[torch.Tensor],
|
| 1852 |
+
attn_analysis: Optional[AttentionAnalysis] = None,
|
| 1853 |
+
) -> torch.Tensor:
|
| 1854 |
if not self.use_lucid_attention:
|
| 1855 |
return v.contiguous()
|
| 1856 |
key_rn = rms_key_unit_norm(k, eps=self.lucid_attention_eps)
|
|
|
|
| 1862 |
eye = torch.eye(prec.shape[-1], device=prec.device, dtype=prec.dtype).view(
|
| 1863 |
1, 1, prec.shape[-1], prec.shape[-1]
|
| 1864 |
)
|
| 1865 |
+
prec = prec + eye * (1.0 - prec.diagonal(dim1=-2, dim2=-1).unsqueeze(-1))
|
| 1866 |
+
result = torch.linalg.solve_triangular(
|
| 1867 |
prec, v.float(), upper=False, unitriangular=True
|
| 1868 |
).to(v.dtype).contiguous()
|
| 1869 |
+
if attn_analysis is not None:
|
| 1870 |
+
attn_analysis.lucid_preconditioner = prec.detach()
|
| 1871 |
+
attn_analysis.v_post_lucid = result.detach()
|
| 1872 |
+
return result
|
| 1873 |
|
| 1874 |
def _apply_directional_routing(
|
| 1875 |
self,
|
| 1876 |
attn_out: torch.Tensor,
|
| 1877 |
hidden_states: torch.Tensor,
|
| 1878 |
+
attn_analysis: Optional[AttentionAnalysis] = None,
|
| 1879 |
) -> torch.Tensor:
|
| 1880 |
"""
|
| 1881 |
Directional suppression at position C (post-XSA, pre-reshape).
|
|
|
|
| 1884 |
attn_out: [B, S, H, d_head] — output after XSA and SeeDNorm.
|
| 1885 |
hidden_states: [B, S, hidden_size] — pre-FAN residual stream,
|
| 1886 |
used as router input (same as paper's x_i).
|
| 1887 |
+
attn_analysis: Optional[AttentionAnalysis] — deposits DR internals.
|
| 1888 |
Returns:
|
| 1889 |
[B, S, H, d_head] with selected directional components suppressed.
|
| 1890 |
"""
|
|
|
|
| 1924 |
# Σ_k weighted_{h,k} · d_{h,k}:
|
| 1925 |
# weighted [B, S, H, K] × d [H, K, D] → [B, S, H, D]
|
| 1926 |
suppression = torch.einsum("bshk,hkd->bshd", weighted, d)
|
| 1927 |
+
result = attn_out - suppression
|
| 1928 |
+
|
| 1929 |
+
if attn_analysis is not None:
|
| 1930 |
+
attn_analysis.direction_vecs_normalized = d.detach()
|
| 1931 |
+
attn_analysis.dr_router_logits = logits.detach()
|
| 1932 |
+
attn_analysis.dr_routing_weights = r.squeeze(1).detach()
|
| 1933 |
+
attn_analysis.dr_projection = proj.detach()
|
| 1934 |
+
attn_analysis.dr_suppression = suppression.detach()
|
| 1935 |
+
attn_analysis.attn_output_post_routing = result.detach()
|
| 1936 |
|
| 1937 |
+
return result
|
| 1938 |
|
| 1939 |
def forward(
|
| 1940 |
self,
|
|
|
|
| 1942 |
position_embeddings: tuple[torch.Tensor, torch.Tensor],
|
| 1943 |
attention_mask: Optional[torch.Tensor] = None,
|
| 1944 |
first_layer_fan: Optional[torch.Tensor] = None,
|
| 1945 |
+
attn_analysis: Optional[AttentionAnalysis] = None,
|
| 1946 |
**kwargs: Unpack[FlashAttentionKwargs],
|
| 1947 |
) -> tuple[torch.Tensor, Optional[torch.Tensor], Optional[torch.Tensor]]:
|
| 1948 |
input_shape = hidden_states.shape[:-1]
|
| 1949 |
|
| 1950 |
+
fan_a = attn_analysis.fan if attn_analysis is not None else None
|
| 1951 |
+
h_fan = self.fan_layer(hidden_states, analysis=fan_a)
|
| 1952 |
if first_layer_fan is not None:
|
| 1953 |
h_fan = self.lambda_1 * first_layer_fan + self.lambda_2 * h_fan
|
| 1954 |
current_layer_fan = h_fan.clone()
|
|
|
|
| 1962 |
)
|
| 1963 |
gate = gate.reshape(*input_shape, -1)
|
| 1964 |
|
| 1965 |
+
if attn_analysis is not None:
|
| 1966 |
+
attn_analysis.q_raw = q_raw.detach()
|
| 1967 |
+
attn_analysis.gate_raw = gate.detach()
|
| 1968 |
+
|
| 1969 |
q = self.q_norm(q_raw.view(query_shape)).transpose(1, 2)
|
| 1970 |
k = self.k_norm(self.k_proj(h_fan).view(kv_shape)).transpose(1, 2)
|
| 1971 |
v = self.v_proj(h_fan).view(kv_shape).transpose(1, 2)
|
| 1972 |
|
| 1973 |
+
if attn_analysis is not None:
|
| 1974 |
+
attn_analysis.q_post_norm = q.detach()
|
| 1975 |
+
attn_analysis.k_post_norm = k.detach()
|
| 1976 |
+
attn_analysis.v_raw = v.detach()
|
| 1977 |
+
|
| 1978 |
cos, sin = position_embeddings
|
| 1979 |
q, k = apply_rotary_pos_emb(q, k, cos, sin)
|
| 1980 |
+
|
| 1981 |
+
if attn_analysis is not None:
|
| 1982 |
+
attn_analysis.q_post_rope = q.detach()
|
| 1983 |
+
attn_analysis.k_post_rope = k.detach()
|
| 1984 |
+
|
| 1985 |
+
q, k = self._apply_momentum_attention(q, k, attn_analysis=attn_analysis)
|
| 1986 |
+
k, v = self._apply_mea_head_mixing(k, v, attn_analysis=attn_analysis)
|
| 1987 |
+
v = self._apply_lucid_preconditioner(k, v, attention_mask, attn_analysis=attn_analysis)
|
| 1988 |
|
| 1989 |
# Capture v_ref for XSA after MEA mixing and LUCID preconditioning.
|
| 1990 |
# This is the vector that actually participated in SDPA aggregation.
|
| 1991 |
v_ref = v if self.use_xsa else None
|
| 1992 |
|
| 1993 |
# ── Affine-Scaled Attention ───────────────────────────────────────
|
| 1994 |
+
# Active whenever use_affine_scaled_attention=True, regardless of
|
| 1995 |
+
# attention backend. Two code paths — same math, different execution:
|
| 1996 |
+
# eager : full weight access, attn_weights_pre/post_affine captured.
|
| 1997 |
+
# flash/sdpa: α·flash_out + β·V_cumsum, no weight tensors materialised.
|
| 1998 |
alpha = None
|
| 1999 |
beta = None
|
| 2000 |
+
use_affine = self.use_affine_scaled_attention
|
|
|
|
|
|
|
|
|
|
| 2001 |
if use_affine:
|
| 2002 |
alpha = linear_clipping(self.alpha_proj(hidden_states)) # [B, S, H]
|
| 2003 |
alpha = alpha.permute(0, 2, 1).unsqueeze(-1) # [B, H, S, 1]
|
|
|
|
| 2010 |
self.affine_momentum * self.alpha_ma
|
| 2011 |
+ (1.0 - self.affine_momentum) * batch_mean
|
| 2012 |
)
|
| 2013 |
+
if attn_analysis is not None:
|
| 2014 |
+
attn_analysis.alpha_per_head = alpha.detach()
|
| 2015 |
+
attn_analysis.beta_per_head = beta.detach()
|
| 2016 |
+
attn_analysis.alpha_moving_avg = self.alpha_ma.detach()
|
| 2017 |
|
| 2018 |
if use_affine:
|
| 2019 |
+
if self.config._attn_implementation == "eager":
|
| 2020 |
+
# Eager: materialises softmax weights, full analysis available.
|
| 2021 |
+
attn_out, attn_weights = affine_scaled_eager_attention_forward(
|
| 2022 |
+
self, q, k, v, attention_mask,
|
| 2023 |
+
scaling=self.scaling, alpha=alpha, beta=beta,
|
| 2024 |
+
dropout=0.0 if not self.training else self.attention_dropout,
|
| 2025 |
+
attn_analysis=attn_analysis,
|
| 2026 |
+
**kwargs,
|
| 2027 |
+
)
|
| 2028 |
+
else:
|
| 2029 |
+
# Flash / SDPA: exact formula via V.cumsum, no weight tensors.
|
| 2030 |
+
# attn_weights_pre/post_affine remain None in AnalysisState.
|
| 2031 |
+
attn_out, attn_weights = affine_scaled_flash_attention_forward(
|
| 2032 |
+
self, q, k, v, attention_mask,
|
| 2033 |
+
scaling=self.scaling, alpha=alpha, beta=beta,
|
| 2034 |
+
dropout=0.0 if not self.training else self.attention_dropout,
|
| 2035 |
+
attn_analysis=attn_analysis,
|
| 2036 |
+
**kwargs,
|
| 2037 |
+
)
|
| 2038 |
else:
|
| 2039 |
attn_fn = eager_attention_forward
|
| 2040 |
if self.config._attn_implementation != "eager":
|
|
|
|
| 2044 |
dropout=0.0 if not self.training else self.attention_dropout,
|
| 2045 |
scaling=self.scaling, **kwargs,
|
| 2046 |
)
|
| 2047 |
+
if attn_analysis is not None:
|
| 2048 |
+
attn_analysis.attn_weights = (
|
| 2049 |
+
attn_weights.detach() if attn_weights is not None else None
|
| 2050 |
+
)
|
| 2051 |
+
|
| 2052 |
+
if attn_analysis is not None:
|
| 2053 |
+
attn_analysis.attn_output_raw = attn_out.detach()
|
| 2054 |
|
| 2055 |
attn_out = attn_out.reshape(*input_shape, -1, self.head_dim)
|
| 2056 |
if self.use_mea_attention:
|
| 2057 |
attn_out = self.mea_output_norm(attn_out)
|
| 2058 |
+
if attn_analysis is not None:
|
| 2059 |
+
attn_analysis.attn_output_post_mea_norm = attn_out.detach()
|
| 2060 |
|
| 2061 |
# ── Exclusive Self Attention (position B, pre-routing) ────────────
|
| 2062 |
# Removes auto-position component before directional routing so that
|
|
|
|
| 2067 |
v_ref_t = v_ref_t.to(attn_out.dtype)
|
| 2068 |
proj = (attn_out * v_ref_t).sum(dim=-1, keepdim=True)
|
| 2069 |
norm_sq = (v_ref_t * v_ref_t).sum(dim=-1, keepdim=True).clamp(min=self.xsa_eps)
|
| 2070 |
+
xsa_comp = (proj / norm_sq) * v_ref_t
|
| 2071 |
+
attn_out = attn_out - xsa_comp
|
| 2072 |
+
if attn_analysis is not None:
|
| 2073 |
+
attn_analysis.xsa_self_position_component = xsa_comp.detach()
|
| 2074 |
+
attn_analysis.attn_output_post_xsa = attn_out.detach()
|
| 2075 |
|
| 2076 |
# ── Directional Routing (position C, post-XSA, pre-reshape) ──────
|
| 2077 |
# Suppresses cross-domain interference directions from the head output.
|
|
|
|
| 2079 |
# When use_xsa=False: directions span full head-space (no XSA pre-clean).
|
| 2080 |
# When use_directional_routing=False: this block is skipped entirely.
|
| 2081 |
if self.use_directional_routing:
|
| 2082 |
+
attn_out = self._apply_directional_routing(
|
| 2083 |
+
attn_out, hidden_states, attn_analysis=attn_analysis
|
| 2084 |
+
)
|
| 2085 |
|
| 2086 |
# ── Reshape → o_proj → Gated Attention gate → dropout ────────────
|
| 2087 |
+
attn_out_flat = attn_out.reshape(*input_shape, -1).contiguous()
|
| 2088 |
+
if attn_analysis is not None:
|
| 2089 |
+
attn_analysis.attn_output_pre_gate = attn_out_flat.detach()
|
| 2090 |
+
gate_sig = torch.sigmoid(gate)
|
| 2091 |
+
attn_analysis.gate_sigmoid = gate_sig.detach()
|
| 2092 |
+
attn_out_gated = self.o_proj(attn_out_flat * gate_sig)
|
| 2093 |
+
else:
|
| 2094 |
+
attn_out_gated = self.o_proj(attn_out_flat * torch.sigmoid(gate))
|
| 2095 |
+
|
| 2096 |
+
attn_out_gated = self.dropout(attn_out_gated)
|
| 2097 |
|
| 2098 |
+
if attn_analysis is not None:
|
| 2099 |
+
attn_analysis.attn_output_final = attn_out_gated.detach()
|
| 2100 |
+
|
| 2101 |
+
return attn_out_gated, attn_weights, current_layer_fan
|
| 2102 |
class PolyNorm(nn.Module):
|
| 2103 |
def __init__(
|
| 2104 |
self,
|
|
|
|
| 2137 |
out = branch - alpha.to(branch.dtype) * proj_coeff * ref
|
| 2138 |
return self._norm(out)
|
| 2139 |
|
| 2140 |
+
def forward(
|
| 2141 |
+
self,
|
| 2142 |
+
x: torch.Tensor,
|
| 2143 |
+
analysis: Optional[PolyNormAnalysis] = None,
|
| 2144 |
+
) -> torch.Tensor:
|
| 2145 |
# Caché de potencias: x_sq reutilizado en x1 y x2; x_cu = x·x_sq evita pow(3)
|
| 2146 |
x_sq = x.pow(2)
|
| 2147 |
x_cu = x * x_sq
|
|
|
|
| 2151 |
x2 = x_sq * (x_sq * x_sq).mean(-1, keepdim=True).add(self.eps).rsqrt()
|
| 2152 |
x3 = x_cu * (x_cu * x_cu).mean(-1, keepdim=True).add(self.eps).rsqrt()
|
| 2153 |
|
| 2154 |
+
if analysis is not None:
|
| 2155 |
+
analysis.x1 = x1.detach()
|
| 2156 |
+
analysis.x2_pre_exclusive = x2.detach()
|
| 2157 |
+
analysis.x3_pre_exclusive = x3.detach()
|
| 2158 |
+
|
| 2159 |
# Fuerzas exclusivas aprendibles
|
| 2160 |
alpha2, alpha3 = torch.sigmoid(self.exclusive_logits).unbind()
|
| 2161 |
|
| 2162 |
+
if analysis is not None:
|
| 2163 |
+
analysis.alpha2 = alpha2.detach()
|
| 2164 |
+
analysis.alpha3 = alpha3.detach()
|
| 2165 |
+
analysis.weights = self.weight.detach()
|
| 2166 |
+
analysis.bias = self.bias.detach()
|
| 2167 |
+
|
| 2168 |
# Precalcular ref (x1) en fp32 y su norma al cuadrado — compartido por x2 y x3
|
| 2169 |
x1_f = x1.float()
|
| 2170 |
ref_norm_sq = x1_f.pow(2).sum(-1, keepdim=True).clamp_min(self.proj_eps)
|
|
|
|
| 2173 |
x2 = self._exclusive(x2, x1, alpha2, x1_f, ref_norm_sq)
|
| 2174 |
x3 = self._exclusive(x3, x1, alpha3, x1_f, ref_norm_sq)
|
| 2175 |
|
| 2176 |
+
if analysis is not None:
|
| 2177 |
+
analysis.x2_post_exclusive = x2.detach()
|
| 2178 |
+
analysis.x3_post_exclusive = x3.detach()
|
| 2179 |
+
|
| 2180 |
+
output = (
|
| 2181 |
self.weight[0] * x3
|
| 2182 |
+ self.weight[1] * x2
|
| 2183 |
+ self.weight[2] * x1
|
| 2184 |
+ self.bias
|
| 2185 |
)
|
| 2186 |
|
| 2187 |
+
if analysis is not None:
|
| 2188 |
+
analysis.output = output.detach()
|
| 2189 |
+
|
| 2190 |
+
return output
|
| 2191 |
+
|
| 2192 |
|
| 2193 |
class NeoLLMMLP(nn.Module):
|
| 2194 |
"""MLP with FANformer integration and Learnable Multipliers."""
|
|
|
|
| 2214 |
self.act_fn = PolyNorm(exclusive_init=0.05)
|
| 2215 |
self.dropout = nn.Dropout(config.dropout_rate)
|
| 2216 |
|
| 2217 |
+
def forward(
|
| 2218 |
+
self,
|
| 2219 |
+
x: torch.Tensor,
|
| 2220 |
+
analysis: Optional[MLPAnalysis] = None,
|
| 2221 |
+
) -> torch.Tensor:
|
| 2222 |
+
fan_a = analysis.fan if analysis is not None else None
|
| 2223 |
+
x_fan = self.fan_layer(x, analysis=fan_a)
|
| 2224 |
+
|
| 2225 |
+
gate_out = self.gate_proj(x_fan)
|
| 2226 |
+
up_out = self.up_proj(x_fan)
|
| 2227 |
+
|
| 2228 |
+
if analysis is not None:
|
| 2229 |
+
analysis.gate_proj_output = gate_out.detach()
|
| 2230 |
+
analysis.up_proj_output = up_out.detach()
|
| 2231 |
+
|
| 2232 |
+
poly_a = analysis.polynorm if analysis is not None else None
|
| 2233 |
+
act_out = self.act_fn(gate_out, analysis=poly_a)
|
| 2234 |
+
act_x_up = act_out * up_out
|
| 2235 |
+
|
| 2236 |
+
if analysis is not None:
|
| 2237 |
+
analysis.act_times_up = act_x_up.detach()
|
| 2238 |
+
|
| 2239 |
+
result = self.down_proj(self.dropout(act_x_up))
|
| 2240 |
+
|
| 2241 |
+
if analysis is not None:
|
| 2242 |
+
analysis.output = result.detach()
|
| 2243 |
+
|
| 2244 |
+
return result
|
| 2245 |
|
| 2246 |
|
| 2247 |
class NeoLLMDecoderLayer(GradientCheckpointingLayer):
|
|
|
|
| 2304 |
sources: list,
|
| 2305 |
partial: torch.Tensor,
|
| 2306 |
query: torch.Tensor,
|
| 2307 |
+
analysis_slot: Optional[AttnResAnalysis] = None,
|
| 2308 |
+
analysis_key: Optional[str] = None,
|
| 2309 |
) -> torch.Tensor:
|
| 2310 |
"""
|
| 2311 |
Depth-wise softmax attention over preceding layer outputs.
|
|
|
|
| 2347 |
B_vals: Optional[torch.Tensor] = None,
|
| 2348 |
attn_res_sources: Optional[list] = None,
|
| 2349 |
attn_res_partial: Optional[torch.Tensor] = None,
|
| 2350 |
+
layer_analysis: Optional[LayerAnalysis] = None,
|
| 2351 |
output_attentions: Optional[bool] = False,
|
| 2352 |
**kwargs: Unpack[FlashAttentionKwargs],
|
| 2353 |
) -> Tuple:
|
| 2354 |
+
# ── Snapshot input ────────────────────────────────────────────────
|
| 2355 |
+
if layer_analysis is not None:
|
| 2356 |
+
layer_analysis.hidden_states_input = hidden_states.detach()
|
| 2357 |
+
|
| 2358 |
# ── Attention Residuals: compute pre-attention input ──────────────
|
| 2359 |
# When active, the input to the attention sublayer is no longer the
|
| 2360 |
# raw hidden_states (accumulated residual) but a softmax-weighted
|
|
|
|
| 2362 |
# attn_res_partial carries the intra-block standard residual that
|
| 2363 |
# connects the attention and MLP sublayers within this layer.
|
| 2364 |
# When inactive, flow is identical to the original.
|
| 2365 |
+
ar_analysis = layer_analysis.attn_res if layer_analysis is not None else None
|
| 2366 |
if self.use_attn_res and attn_res_sources is not None and attn_res_partial is not None:
|
| 2367 |
+
h_attn = self._attn_res(
|
| 2368 |
+
attn_res_sources, attn_res_partial, self.attn_res_query_attn,
|
| 2369 |
+
ar_analysis, "attn",
|
| 2370 |
+
)
|
| 2371 |
residual_attn = attn_res_partial
|
| 2372 |
else:
|
| 2373 |
h_attn = hidden_states
|
| 2374 |
residual_attn = hidden_states
|
| 2375 |
|
| 2376 |
# ── Attention block ───────────────────────────────────────────────
|
| 2377 |
+
sn_pre = layer_analysis.seednorm_pre_attn if layer_analysis is not None else None
|
| 2378 |
+
h_normed = self.input_layernorm(h_attn, analysis=sn_pre)
|
| 2379 |
+
h_lns = self.lns_attn(h_normed)
|
| 2380 |
+
if layer_analysis is not None:
|
| 2381 |
+
layer_analysis.lns_attn_output = h_lns.detach()
|
| 2382 |
|
| 2383 |
hidden_states, attn_weights, self.current_layer_fan = self.self_attn(
|
| 2384 |
+
hidden_states=h_lns,
|
| 2385 |
attention_mask=attention_mask,
|
| 2386 |
position_embeddings=position_embeddings,
|
| 2387 |
first_layer_fan=first_layer_fan,
|
| 2388 |
+
attn_analysis=layer_analysis.attention if layer_analysis is not None else None,
|
| 2389 |
**kwargs,
|
| 2390 |
)
|
| 2391 |
+
|
| 2392 |
+
if layer_analysis is not None:
|
| 2393 |
+
layer_analysis.attn_contribution = hidden_states.detach()
|
| 2394 |
+
|
| 2395 |
+
gpas_attn_a = layer_analysis.gpas_attn if layer_analysis is not None else None
|
| 2396 |
+
h_tilde = self.gpas_attn(residual_attn + hidden_states, analysis=gpas_attn_a)
|
| 2397 |
+
|
| 2398 |
+
if layer_analysis is not None:
|
| 2399 |
+
layer_analysis.h_tilde = h_tilde.detach()
|
| 2400 |
|
| 2401 |
# ── Attention Residuals: compute pre-MLP input ────────────────────
|
| 2402 |
# After attention, the partial sum is updated with h_tilde.
|
| 2403 |
# The pre-MLP AttnRes attends over the same sources but with h_tilde
|
| 2404 |
# as the current partial — capturing the within-layer attention output.
|
| 2405 |
if self.use_attn_res and attn_res_sources is not None:
|
| 2406 |
+
h_mlp = self._attn_res(
|
| 2407 |
+
attn_res_sources, h_tilde, self.attn_res_query_mlp,
|
| 2408 |
+
ar_analysis, "mlp",
|
| 2409 |
+
)
|
| 2410 |
residual_mlp = h_tilde
|
| 2411 |
else:
|
| 2412 |
h_mlp = h_tilde
|
| 2413 |
residual_mlp = h_tilde
|
| 2414 |
|
| 2415 |
# ── MLP block ─────────────────────────────────────────────────────
|
| 2416 |
+
sn_post = layer_analysis.seednorm_post_attn if layer_analysis is not None else None
|
| 2417 |
+
h_normed2 = self.post_attention_layernorm(h_mlp, analysis=sn_post)
|
| 2418 |
+
h_lns2 = self.lns_mlp(h_normed2)
|
| 2419 |
+
if layer_analysis is not None:
|
| 2420 |
+
layer_analysis.lns_mlp_output = h_lns2.detach()
|
| 2421 |
+
|
| 2422 |
+
mlp_a = layer_analysis.mlp if layer_analysis is not None else None
|
| 2423 |
+
delta_m = self.mlp(h_lns2, analysis=mlp_a)
|
| 2424 |
+
|
| 2425 |
+
if layer_analysis is not None:
|
| 2426 |
+
layer_analysis.mlp_contribution = delta_m.detach()
|
| 2427 |
|
| 2428 |
# ── JTok-M injection (additive alongside MLP residual) ────────────
|
| 2429 |
aux_stats = None
|
|
|
|
| 2433 |
z_flat = z_tilde.reshape(-1, z_tilde.shape[-1])
|
| 2434 |
B_flat = B_vals.reshape(-1, B_vals.shape[-2], B_vals.shape[-1])
|
| 2435 |
|
| 2436 |
+
jtokm_a = layer_analysis.jtokm if layer_analysis is not None else None
|
| 2437 |
+
delta_r, aux_stats = self.jtokm(h_flat, z_flat, B_flat, analysis=jtokm_a)
|
| 2438 |
delta_r = delta_r.reshape(orig_shape)
|
| 2439 |
|
| 2440 |
+
gpas_mlp_a = layer_analysis.gpas_mlp if layer_analysis is not None else None
|
| 2441 |
+
hidden_states = self.gpas_mlp(residual_mlp + delta_m + delta_r, analysis=gpas_mlp_a)
|
| 2442 |
else:
|
| 2443 |
+
gpas_mlp_a = layer_analysis.gpas_mlp if layer_analysis is not None else None
|
| 2444 |
+
hidden_states = self.gpas_mlp(residual_mlp + delta_m, analysis=gpas_mlp_a)
|
| 2445 |
+
|
| 2446 |
+
if layer_analysis is not None:
|
| 2447 |
+
layer_analysis.hidden_states_output = hidden_states.detach()
|
| 2448 |
|
| 2449 |
outputs = (hidden_states,)
|
| 2450 |
if output_attentions:
|
|
|
|
| 2631 |
else:
|
| 2632 |
self.embed_tokens = value
|
| 2633 |
|
| 2634 |
+
def _build_layer_analysis(self) -> LayerAnalysis:
|
| 2635 |
+
"""
|
| 2636 |
+
Construct a LayerAnalysis with sub-objects pre-allocated for every
|
| 2637 |
+
component that is active in the current config.
|
| 2638 |
+
|
| 2639 |
+
Fields that correspond to disabled flags remain None — the analysis
|
| 2640 |
+
consumer can check for None without needing to inspect config flags.
|
| 2641 |
+
Called once per layer per forward when analysis is active.
|
| 2642 |
+
"""
|
| 2643 |
+
cfg = self.config
|
| 2644 |
+
return LayerAnalysis(
|
| 2645 |
+
seednorm_pre_attn = SeeDNormAnalysis(),
|
| 2646 |
+
seednorm_post_attn = SeeDNormAnalysis(),
|
| 2647 |
+
attention = AttentionAnalysis(fan=FANAnalysis()),
|
| 2648 |
+
mlp = MLPAnalysis(
|
| 2649 |
+
fan = FANAnalysis(),
|
| 2650 |
+
polynorm = PolyNormAnalysis(),
|
| 2651 |
+
),
|
| 2652 |
+
gpas_attn = GPASAnalysis(),
|
| 2653 |
+
gpas_mlp = GPASAnalysis(),
|
| 2654 |
+
jtokm = JTokMAnalysis() if cfg.use_jtokm else None,
|
| 2655 |
+
attn_res = AttnResAnalysis() if getattr(cfg, "use_attn_res", False) else None,
|
| 2656 |
+
)
|
| 2657 |
+
|
| 2658 |
def forward(
|
| 2659 |
self,
|
| 2660 |
input_ids: Optional[torch.LongTensor] = None,
|
|
|
|
| 2664 |
output_hidden_states: Optional[bool] = None,
|
| 2665 |
output_attentions: Optional[bool] = None,
|
| 2666 |
return_dict: Optional[bool] = None,
|
| 2667 |
+
analysis_state: Optional[AnalysisState] = None,
|
| 2668 |
**kwargs: Unpack[TransformersKwargs],
|
| 2669 |
) -> Tuple:
|
| 2670 |
output_hidden_states = (
|
|
|
|
| 2684 |
z_tilde = None
|
| 2685 |
B_vals = None
|
| 2686 |
|
| 2687 |
+
gen_a = (
|
| 2688 |
+
analysis_state.generator
|
| 2689 |
+
if analysis_state is not None and self.config.use_token_generator
|
| 2690 |
+
else None
|
| 2691 |
+
)
|
| 2692 |
+
|
| 2693 |
if inputs_embeds is None:
|
| 2694 |
if self.config.use_token_generator:
|
| 2695 |
if self.config.use_jtokm:
|
| 2696 |
# Return internals for reuse by JTok-M surfaces
|
| 2697 |
inputs_embeds, z_tilde, B_vals = self.token_generator(
|
| 2698 |
+
input_ids, return_internals=True, analysis=gen_a
|
| 2699 |
)
|
| 2700 |
# Reshape to [batch, seq, d_seed] and [batch, seq, d_seed, n_knots]
|
| 2701 |
z_tilde = z_tilde.reshape(*input_ids.shape, self.config.generator_d_seed)
|
|
|
|
| 2705 |
self.config.generator_num_knots,
|
| 2706 |
)
|
| 2707 |
else:
|
| 2708 |
+
inputs_embeds = self.token_generator(input_ids, analysis=gen_a)
|
| 2709 |
else:
|
| 2710 |
inputs_embeds = self.embed_tokens(input_ids)
|
| 2711 |
|
| 2712 |
+
if analysis_state is not None:
|
| 2713 |
+
analysis_state.embeddings = inputs_embeds.detach()
|
| 2714 |
+
|
| 2715 |
if position_ids is None:
|
| 2716 |
position_ids = torch.arange(
|
| 2717 |
0, inputs_embeds.shape[1], device=inputs_embeds.device
|
|
|
|
| 2758 |
else 1 # Full AttnRes: every layer is its own "block"
|
| 2759 |
)
|
| 2760 |
|
| 2761 |
+
# Pre-allocate per-layer analysis list when analysis is active
|
| 2762 |
+
if analysis_state is not None:
|
| 2763 |
+
analysis_state.layers = []
|
| 2764 |
+
|
| 2765 |
for layer_idx, decoder_layer in enumerate(self.layers[: self.config.num_hidden_layers]):
|
| 2766 |
if output_hidden_states:
|
| 2767 |
all_hidden_states = all_hidden_states + (hidden_states,)
|
|
|
|
| 2778 |
attn_res_sources = attn_res_sources + [attn_res_partial]
|
| 2779 |
attn_res_partial = hidden_states # start new block from current output
|
| 2780 |
|
| 2781 |
+
# Build per-layer analysis container (only in eval + analysis mode)
|
| 2782 |
+
layer_analysis = None
|
| 2783 |
+
if analysis_state is not None:
|
| 2784 |
+
layer_analysis = self._build_layer_analysis()
|
| 2785 |
+
layer_analysis.layer_idx = layer_idx
|
| 2786 |
+
analysis_state.layers.append(layer_analysis)
|
| 2787 |
+
|
| 2788 |
layer_outputs = decoder_layer(
|
| 2789 |
hidden_states,
|
| 2790 |
position_embeddings=position_embeddings,
|
|
|
|
| 2794 |
B_vals=B_vals,
|
| 2795 |
attn_res_sources=attn_res_sources,
|
| 2796 |
attn_res_partial=attn_res_partial if use_attn_res else None,
|
| 2797 |
+
layer_analysis=layer_analysis,
|
| 2798 |
output_attentions=output_attentions,
|
| 2799 |
**kwargs,
|
| 2800 |
)
|
|
|
|
| 2820 |
if output_hidden_states:
|
| 2821 |
all_hidden_states = all_hidden_states + (hidden_states,)
|
| 2822 |
|
| 2823 |
+
# ── Finalise analysis snapshot ─────────────────────────────────────
|
| 2824 |
+
if analysis_state is not None:
|
| 2825 |
+
analysis_state.final_hidden_states = hidden_states.detach()
|
| 2826 |
+
analysis_state.jtokm_aux_stats = all_aux_stats if self.config.use_jtokm else None
|
| 2827 |
+
analysis_state.attn_res_sources_final = (
|
| 2828 |
+
attn_res_sources if use_attn_res else None
|
| 2829 |
+
)
|
| 2830 |
+
|
| 2831 |
if not return_dict:
|
| 2832 |
return tuple(
|
| 2833 |
v for v in [hidden_states, None, all_hidden_states, all_attentions]
|
|
|
|
| 2870 |
total_loss = CE_loss + L_aux
|
| 2871 |
|
| 2872 |
where L_aux = λ · n_e · (1/L) · Σ_ℓ Σ_i p_i^ℓ · f_i^ℓ
|
| 2873 |
+
|
| 2874 |
+
── Analysis mode ──────────────────────────────────────────────────────────
|
| 2875 |
+
Analysis is NEVER active during training (model.training=True).
|
| 2876 |
+
It is opt-in at inference time:
|
| 2877 |
+
|
| 2878 |
+
model.eval()
|
| 2879 |
+
model.enable_analysis() # arm the system
|
| 2880 |
+
with torch.no_grad():
|
| 2881 |
+
_ = model(input_ids)
|
| 2882 |
+
state = model.last_analysis # AnalysisState with all internals
|
| 2883 |
+
|
| 2884 |
+
model.disable_analysis() # disarm — zero overhead again
|
| 2885 |
+
# or: model.train() # training always disarms automatically
|
| 2886 |
+
|
| 2887 |
+
last_analysis is replaced at each forward call when analysis is armed.
|
| 2888 |
+
last_analysis is None between enable_analysis() and the first forward,
|
| 2889 |
+
and cleared by disable_analysis().
|
| 2890 |
+
|
| 2891 |
+
Example field access:
|
| 2892 |
+
state.layers[3].attention.alpha_per_head # Affine-Scaled α
|
| 2893 |
+
state.layers[0].mlp.polynorm.x2_post_exclusive
|
| 2894 |
+
state.generator.z_tilde # Leviathan latent
|
| 2895 |
+
state.layers[2].jtokm.routing_weights # JTok-M router
|
| 2896 |
+
state.layers[5].attn_res.weights_pre_attn # AttnRes softmax
|
| 2897 |
"""
|
| 2898 |
|
| 2899 |
_tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
|
|
|
|
| 2907 |
if config.use_token_generator:
|
| 2908 |
self._tied_weights_keys = {}
|
| 2909 |
|
| 2910 |
+
# ── Analysis infrastructure ───────────────────────────────────────
|
| 2911 |
+
# _analysis_armed: set by enable_analysis() / disable_analysis().
|
| 2912 |
+
# last_analysis: populated after each forward when armed + eval.
|
| 2913 |
+
# Neither is an nn.Parameter or buffer — zero effect on training.
|
| 2914 |
+
self._analysis_armed: bool = False
|
| 2915 |
+
self.last_analysis: Optional[AnalysisState] = None
|
| 2916 |
+
|
| 2917 |
self.post_init()
|
| 2918 |
|
| 2919 |
+
# ── Public analysis API ───────────────────────────────────────────────
|
| 2920 |
+
|
| 2921 |
+
def enable_analysis(self) -> None:
|
| 2922 |
+
"""
|
| 2923 |
+
Arm the analysis system.
|
| 2924 |
+
|
| 2925 |
+
Has no effect during training — analysis is always off when
|
| 2926 |
+
model.training=True, regardless of this flag. Safe to call at any
|
| 2927 |
+
point without affecting the training loop.
|
| 2928 |
+
|
| 2929 |
+
Usage::
|
| 2930 |
+
|
| 2931 |
+
model.eval()
|
| 2932 |
+
model.enable_analysis()
|
| 2933 |
+
with torch.no_grad():
|
| 2934 |
+
_ = model(input_ids)
|
| 2935 |
+
state = model.last_analysis
|
| 2936 |
+
"""
|
| 2937 |
+
self._analysis_armed = True
|
| 2938 |
+
|
| 2939 |
+
def disable_analysis(self) -> None:
|
| 2940 |
+
"""
|
| 2941 |
+
Disarm the analysis system and clear last_analysis.
|
| 2942 |
+
|
| 2943 |
+
After this call, forward passes produce zero analysis overhead.
|
| 2944 |
+
"""
|
| 2945 |
+
self._analysis_armed = False
|
| 2946 |
+
self.last_analysis = None
|
| 2947 |
+
|
| 2948 |
+
def _make_analysis_state(
|
| 2949 |
+
self,
|
| 2950 |
+
input_ids: Optional[torch.Tensor],
|
| 2951 |
+
) -> Optional[AnalysisState]:
|
| 2952 |
+
"""
|
| 2953 |
+
Decide whether to build an AnalysisState for this forward pass.
|
| 2954 |
+
|
| 2955 |
+
Returns None (zero cost) when:
|
| 2956 |
+
- analysis is not armed, OR
|
| 2957 |
+
- the model is in training mode (self.training=True).
|
| 2958 |
+
Otherwise returns a fresh AnalysisState with top-level optional
|
| 2959 |
+
fields pre-allocated according to the active config flags.
|
| 2960 |
+
"""
|
| 2961 |
+
if not self._analysis_armed or self.training:
|
| 2962 |
+
return None
|
| 2963 |
+
cfg = self.config
|
| 2964 |
+
return AnalysisState(
|
| 2965 |
+
input_ids = input_ids.detach() if input_ids is not None else None,
|
| 2966 |
+
generator = GeneratorAnalysis() if cfg.use_token_generator else None,
|
| 2967 |
+
layers = None, # filled by NeoLLMModel.forward
|
| 2968 |
+
jtokm_aux_stats = [] if cfg.use_jtokm else None,
|
| 2969 |
+
attn_res_sources_final = [] if getattr(cfg, "use_attn_res", False) else None,
|
| 2970 |
+
)
|
| 2971 |
+
|
| 2972 |
+
# ── Standard model API ────────────────────────────────────────────────
|
| 2973 |
+
|
| 2974 |
def get_input_embeddings(self):
|
| 2975 |
return self.model.get_input_embeddings()
|
| 2976 |
|
|
|
|
| 3020 |
return_dict: Optional[bool] = None,
|
| 3021 |
**kwargs: Unpack[TransformersKwargs],
|
| 3022 |
) -> CausalLMOutputWithPast:
|
| 3023 |
+
# ── Build analysis container (None during training or when disarmed) ──
|
| 3024 |
+
analysis_state = self._make_analysis_state(input_ids)
|
| 3025 |
+
|
| 3026 |
model_out = self.model(
|
| 3027 |
input_ids=input_ids,
|
| 3028 |
attention_mask=attention_mask,
|
|
|
|
| 3030 |
inputs_embeds=inputs_embeds,
|
| 3031 |
output_hidden_states=output_hidden_states,
|
| 3032 |
return_dict=return_dict,
|
| 3033 |
+
analysis_state=analysis_state,
|
| 3034 |
**kwargs,
|
| 3035 |
)
|
| 3036 |
|
|
|
|
| 3069 |
)
|
| 3070 |
logits = self.lm_head(hidden_states[:, slice_indices, :])
|
| 3071 |
|
| 3072 |
+
# ── Finalise and store analysis state ─────────────────────────────
|
| 3073 |
+
if analysis_state is not None:
|
| 3074 |
+
analysis_state.logits = logits.detach() if logits is not None else None
|
| 3075 |
+
self.last_analysis = analysis_state
|
| 3076 |
+
|
| 3077 |
return CausalLMOutputWithPast(
|
| 3078 |
loss=loss,
|
| 3079 |
logits=logits,
|
|
|
|
| 3098 |
"VectorMultiplier",
|
| 3099 |
"LinearWithMultipliers",
|
| 3100 |
"MEAHeadSeeDNorm",
|
| 3101 |
+
# Analysis dataclasses — exported so external tools can type-hint against them
|
| 3102 |
+
"AnalysisState",
|
| 3103 |
+
"LayerAnalysis",
|
| 3104 |
+
"AttentionAnalysis",
|
| 3105 |
+
"MLPAnalysis",
|
| 3106 |
+
"FANAnalysis",
|
| 3107 |
+
"SeeDNormAnalysis",
|
| 3108 |
+
"GPASAnalysis",
|
| 3109 |
+
"PolyNormAnalysis",
|
| 3110 |
+
"JTokMAnalysis",
|
| 3111 |
+
"AttnResAnalysis",
|
| 3112 |
+
"GeneratorAnalysis",
|
| 3113 |
]
|
| 3114 |
|
| 3115 |
AutoConfig.register("neollm", NeoLLMConfig)
|