Update NeoLLM model and configuration sources

#1
by KitsuVp - opened
Files changed (2) hide show
  1. configuration_neollm.py +101 -10
  2. modeling_neollm.py +902 -35
configuration_neollm.py CHANGED
@@ -1,5 +1,6 @@
1
  # ==================== configuration_neollm.py ====================
2
  import math
 
3
 
4
  from transformers.configuration_utils import PretrainedConfig
5
  from transformers.modeling_rope_utils import rope_config_validation
@@ -53,7 +54,8 @@ class NeoLLMConfig(PretrainedConfig):
53
  Cut Cross Entropy through ``linear_cross_entropy(..., shift=1)``.
54
  ``"liger"`` uses Liger Kernel's fused linear cross entropy with an
55
  explicit shift equivalent to ``shift=1``. The two backends are
56
- mutually exclusive at runtime.
 
57
  use_liger_kernel (:obj:`bool`, *optional*, defaults to ``False``):
58
  Convenience flag for ``ntp_loss_backend="liger"``. When enabled,
59
  the model does not call the CCE loss path for NTP.
@@ -61,15 +63,24 @@ class NeoLLMConfig(PretrainedConfig):
61
  CCE implementation used when ``ntp_loss_backend="cce"``. The default
62
  keeps the conservative Kahan/full-classifier-gradient path used for
63
  pretraining from scratch.
64
- use_mile_loss (:obj:`bool`, *optional*, defaults to ``False``):
65
- Enable MiLe Loss (Su et al., 2026, arXiv:2310.19531) in the extended
66
- CCE backend. MiLe reweights token losses with detached,
67
- mean-normalized predictive-entropy weights.
 
 
 
 
 
 
 
 
68
  mile_loss_gamma (:obj:`float`, *optional*, defaults to ``1.0``):
69
  Non-negative exponent applied to MiLe's ``1 + entropy`` weight.
70
- use_mu_loss (:obj:`bool`, *optional*, defaults to ``False``):
71
  Enable the output-embedding centering regularizer from Stollenwerk
72
- et al. (2026, arXiv:2601.02031) in the extended CCE backend.
 
73
  mu_loss_lambda (:obj:`float`, *optional*, defaults to ``1e-4``):
74
  Non-negative coefficient in ``L_mu = lambda * ||mean(C)||^2``.
75
  use_meap (:obj:`bool`, *optional*, defaults to ``False``):
@@ -90,7 +101,19 @@ class NeoLLMConfig(PretrainedConfig):
90
  MEAP implementation, either ``"triton"`` or the reference
91
  ``"torch"`` path.
92
  meap_exclude_last (:obj:`bool`, *optional*, defaults to ``True``):
93
- Exclude the final eligible input because it has no shifted target.
 
 
 
 
 
 
 
 
 
 
 
 
94
  liger_loss_accum_dtype (:obj:`str`, *optional*, defaults to ``"float32"``):
95
  Accumulator dtype passed to Liger FLCE for weight/bias gradient
96
  accumulation. ``"float32"`` is the stability-first setting for BF16
@@ -108,6 +131,15 @@ class NeoLLMConfig(PretrainedConfig):
108
  Whether to add a bias term to Q, K, V, and output projections.
109
  attention_dropout (:obj:`float`, *optional*, defaults to 0.1):
110
  Dropout probability applied to attention weights during training.
 
 
 
 
 
 
 
 
 
111
  use_hola_memory (:obj:`bool`, *optional*, defaults to ``False``):
112
  Enable the FA2-compatible HOLA-derived episodic memory read path for
113
  the IHA seq-expand branch. This is not a second attention branch: it
@@ -190,6 +222,19 @@ class NeoLLMConfig(PretrainedConfig):
190
  Epsilon for the denominator of the XSA projection to prevent
191
  division by zero: ``‖v_ref‖² + xsa_eps``. Ignored when
192
  ``use_xsa=False``.
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  use_stack_memory (:obj:`bool`, *optional*, defaults to ``False``):
194
  Enable the StackMemory / STACKTRANS hidden-state stack between
195
  decoder layers (Zhang et al., NeurIPS 2025). The module keeps the
@@ -675,6 +720,7 @@ class NeoLLMConfig(PretrainedConfig):
675
  ntp_loss_backend="cce",
676
  use_liger_kernel=False,
677
  cce_loss_impl="cce_kahan_full_c",
 
678
  # ── Optional extended-CCE objectives and MEAP input corruption ────
679
  use_mile_loss=True,
680
  mile_loss_gamma=1.0,
@@ -689,12 +735,16 @@ class NeoLLMConfig(PretrainedConfig):
689
  meap_seed=0,
690
  meap_implementation="triton",
691
  meap_exclude_last=True,
 
 
 
692
  liger_loss_accum_dtype="float32",
693
  rope_theta=10000.0,
694
  rope_scaling=None,
695
  partial_rotary_factor=0.25,
696
  attention_bias=False,
697
  attention_dropout=0.1,
 
698
  # ── HOLA-derived episodic memory for FlashAttention-2 ─────────────
699
  use_hola_memory=False,
700
  hola_memory_size=64,
@@ -721,6 +771,11 @@ class NeoLLMConfig(PretrainedConfig):
721
  # ── Attention Residuals (Kimi Team, 2026) ─────────────────────────
722
  use_attn_res=False,
723
  attn_res_num_blocks=4,
 
 
 
 
 
724
  # ── StackMemory / STACKTRANS (Zhang et al., NeurIPS 2025) ───────
725
  use_stack_memory=False,
726
  stack_d_model=24, # H * d_s = 4 * 6
@@ -868,11 +923,12 @@ class NeoLLMConfig(PretrainedConfig):
868
  if use_meap:
869
  reserved_for_model = {
870
  token_id
871
- for token_id in (
872
  kwargs.get("pad_token_id"),
873
  kwargs.get("bos_token_id"),
874
  kwargs.get("eos_token_id"),
875
  )
 
876
  if token_id is not None
877
  }
878
  if meap_mask_token_id in reserved_for_model:
@@ -1055,6 +1111,7 @@ class NeoLLMConfig(PretrainedConfig):
1055
  "use_attn_res": bool(use_attn_res),
1056
  "use_stack_memory": bool(use_stack_memory),
1057
  "use_siamesenorm": bool(use_siamesenorm),
 
1058
  }
1059
  _active_residual_flow = [
1060
  name for name, enabled in _residual_flow_flags.items() if enabled
@@ -1064,16 +1121,34 @@ class NeoLLMConfig(PretrainedConfig):
1064
  and bool(use_siamesenorm)
1065
  and not bool(use_laurel)
1066
  and not bool(use_attn_res)
 
1067
  )
1068
  if len(_active_residual_flow) > 1 and not allow_stack_siamese:
1069
  raise ValueError(
1070
  "Enable at most one residual-flow mechanism among "
1071
  "`use_laurel`, `use_attn_res`, `use_stack_memory`, "
1072
- "and `use_siamesenorm`, except the explicit "
1073
  "`use_stack_memory` + `use_siamesenorm` pair. "
1074
  f"Active flags: {_active_residual_flow}."
1075
  )
1076
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1077
  # ── LAuReL: sub-flag consistency ──────────────────────────────────
1078
  if use_laurel and not use_laurel_rw and not use_laurel_lr:
1079
  raise ValueError(
@@ -1225,6 +1300,13 @@ class NeoLLMConfig(PretrainedConfig):
1225
  self.hidden_size = hidden_size
1226
  self.intermediate_size = intermediate_size
1227
  self.num_hidden_layers = num_hidden_layers
 
 
 
 
 
 
 
1228
  self.num_attention_heads = num_attention_heads
1229
  self.num_key_value_heads = num_key_value_heads
1230
  self.hidden_act = hidden_act
@@ -1235,6 +1317,7 @@ class NeoLLMConfig(PretrainedConfig):
1235
  self.ntp_loss_backend = ntp_loss_backend
1236
  self.use_liger_kernel = bool(use_liger_kernel)
1237
  self.cce_loss_impl = str(cce_loss_impl).strip()
 
1238
  self.use_mile_loss = bool(use_mile_loss)
1239
  self.mile_loss_gamma = float(mile_loss_gamma)
1240
  self.use_mu_loss = bool(use_mu_loss)
@@ -1247,6 +1330,9 @@ class NeoLLMConfig(PretrainedConfig):
1247
  self.meap_seed = int(meap_seed)
1248
  self.meap_implementation = meap_implementation
1249
  self.meap_exclude_last = bool(meap_exclude_last)
 
 
 
1250
  self.liger_loss_accum_dtype = liger_loss_accum_dtype
1251
 
1252
  # ── Positional encoding ───────────────────────────────────────────
@@ -1257,6 +1343,7 @@ class NeoLLMConfig(PretrainedConfig):
1257
  # ── Attention ─────────────────────────────────────────────────────
1258
  self.attention_bias = attention_bias
1259
  self.attention_dropout = attention_dropout
 
1260
  # ── HOLA-derived episodic memory for FlashAttention-2 ─────────────
1261
  self.use_hola_memory = bool(use_hola_memory)
1262
  self.hola_memory_size = int(hola_memory_size)
@@ -1294,6 +1381,10 @@ class NeoLLMConfig(PretrainedConfig):
1294
  # block_size = num_hidden_layers // 4 = 3 layers per block.
1295
  # Memory cost: O(num_blocks × batch × seq × hidden) instead of
1296
  # O(num_layers × batch × seq × hidden).
 
 
 
 
1297
  self.use_attn_res = use_attn_res
1298
  self.attn_res_num_blocks = attn_res_num_blocks
1299
 
 
1
  # ==================== configuration_neollm.py ====================
2
  import math
3
+ from numbers import Integral
4
 
5
  from transformers.configuration_utils import PretrainedConfig
6
  from transformers.modeling_rope_utils import rope_config_validation
 
54
  Cut Cross Entropy through ``linear_cross_entropy(..., shift=1)``.
55
  ``"liger"`` uses Liger Kernel's fused linear cross entropy with an
56
  explicit shift equivalent to ``shift=1``. The two backends are
57
+ mutually exclusive at runtime. Copy-Paste, when enabled, computes
58
+ its mixed objective instead of either backend's NTP loss.
59
  use_liger_kernel (:obj:`bool`, *optional*, defaults to ``False``):
60
  Convenience flag for ``ntp_loss_backend="liger"``. When enabled,
61
  the model does not call the CCE loss path for NTP.
 
63
  CCE implementation used when ``ntp_loss_backend="cce"``. The default
64
  keeps the conservative Kahan/full-classifier-gradient path used for
65
  pretraining from scratch.
66
+ use_mile_loss (:obj:`bool`, *optional*, defaults to ``True``):
67
+ Enable MiLe Loss (Su et al., 2026, arXiv:2310.19531). MiLe
68
+ reweights token losses with detached, mean-normalized predictive-
69
+ entropy weights. Copy-Paste derives those weights from the mixed
70
+ distribution using CCE's sparse statistics on CUDA.
71
+ use_copy_paste (:obj:`bool`, *optional*, defaults to ``False``):
72
+ Add the Copy-Paste LM head (Godey and Artzi, arXiv:2610.19247).
73
+ Its learned copy gate and sink mix vocabulary predictions with a
74
+ causal distribution over observed input tokens. CUDA training uses
75
+ CCE's sparse Copy-Paste path; CPU uses a bounded Torch reference.
76
+ MiLe uses mixture entropy; mu-loss remains a regularizer on the
77
+ ordinary vocabulary matrix. Inference still returns dense logits.
78
  mile_loss_gamma (:obj:`float`, *optional*, defaults to ``1.0``):
79
  Non-negative exponent applied to MiLe's ``1 + entropy`` weight.
80
+ use_mu_loss (:obj:`bool`, *optional*, defaults to ``True``):
81
  Enable the output-embedding centering regularizer from Stollenwerk
82
+ et al. (2026, arXiv:2601.02031). CCE uses its extension; the
83
+ Copy-Paste path computes the regularizer directly in Torch.
84
  mu_loss_lambda (:obj:`float`, *optional*, defaults to ``1e-4``):
85
  Non-negative coefficient in ``L_mu = lambda * ||mean(C)||^2``.
86
  use_meap (:obj:`bool`, *optional*, defaults to ``False``):
 
101
  MEAP implementation, either ``"triton"`` or the reference
102
  ``"torch"`` path.
103
  meap_exclude_last (:obj:`bool`, *optional*, defaults to ``True``):
104
+ Exclude the last valid input, which has no shifted target, before
105
+ structural eligibility filters are applied.
106
+ meap_protect_structural_tokens (:obj:`bool`, *optional*, defaults to ``True``):
107
+ Preserve configured PAD, BOS, EOS and the dedicated MEAP mask ID
108
+ during corruption. EOS may be a single ID or a list of IDs.
109
+ meap_exclude_mask_from_copy (:obj:`bool`, *optional*, defaults to ``True``):
110
+ During MEAP training, exclude artificial mask positions from the
111
+ Copy-Paste source distribution. Clean targets remain unchanged.
112
+ copy_paste_lexical_keys (:obj:`bool`, *optional*, defaults to ``False``):
113
+ Add a low-rank projection of each observed token embedding to its
114
+ patch-context key. This has an effect only when Copy-Paste and
115
+ patch training with K>1 are both active. The rank is derived from
116
+ the hidden size, with no batch- or sequence-specific setting.
117
  liger_loss_accum_dtype (:obj:`str`, *optional*, defaults to ``"float32"``):
118
  Accumulator dtype passed to Liger FLCE for weight/bias gradient
119
  accumulation. ``"float32"`` is the stability-first setting for BF16
 
131
  Whether to add a bias term to Q, K, V, and output projections.
132
  attention_dropout (:obj:`float`, *optional*, defaults to 0.1):
133
  Dropout probability applied to attention weights during training.
134
+ use_vha_attention (:obj:`bool`, *optional*, defaults to ``False``):
135
+ Factorize query projections into virtual heads and apply a low-rank
136
+ residual head mix (Ding et al., arXiv:2610.17959). For Hq query
137
+ heads and Hkv KV heads, the G=Hq/Hkv physical query/gate heads are
138
+ shared across KV groups and expanded to Hq virtual heads; the
139
+ postmix rank is max(1, G//2). The configured KV count is unchanged.
140
+ Hkv=2 gives the paper's halving of physical query heads; other KV
141
+ counts use the same group-specific factorization. Enabling this
142
+ option changes checkpoint parameter shapes.
143
  use_hola_memory (:obj:`bool`, *optional*, defaults to ``False``):
144
  Enable the FA2-compatible HOLA-derived episodic memory read path for
145
  the IHA seq-expand branch. This is not a second attention branch: it
 
222
  Epsilon for the denominator of the XSA projection to prevent
223
  division by zero: ``‖v_ref‖² + xsa_eps``. Ignored when
224
  ``use_xsa=False``.
225
+ use_msar (:obj:`bool`, *optional*, defaults to ``False``):
226
+ Enable Block Multi-Stream Attention Residuals (arXiv:2610.13630).
227
+ It is exclusive with the other residual topologies, GPAS, FAN
228
+ residual and DAGFormer. Single-stream depth summaries are retained;
229
+ causal views and independent depth reads are reconstructed per branch.
230
+ msar_block_branches (:obj:`int` or ``None``, optional):
231
+ Number of attention/MLP branches per block. ``None`` derives a
232
+ size from model depth aiming for roughly eight blocks.
233
+ msar_kernel_sizes (:obj:`tuple`, optional):
234
+ Causal depthwise widths; the paper uses ``(4, 8, 12)``.
235
+ msar_backend (:obj:`str`, optional):
236
+ ``auto`` uses Triton on CUDA and Torch on CPU; ``torch`` selects
237
+ the fully differentiable reference for validation.
238
  use_stack_memory (:obj:`bool`, *optional*, defaults to ``False``):
239
  Enable the StackMemory / STACKTRANS hidden-state stack between
240
  decoder layers (Zhang et al., NeurIPS 2025). The module keeps the
 
720
  ntp_loss_backend="cce",
721
  use_liger_kernel=False,
722
  cce_loss_impl="cce_kahan_full_c",
723
+ use_copy_paste=False,
724
  # ── Optional extended-CCE objectives and MEAP input corruption ────
725
  use_mile_loss=True,
726
  mile_loss_gamma=1.0,
 
735
  meap_seed=0,
736
  meap_implementation="triton",
737
  meap_exclude_last=True,
738
+ meap_protect_structural_tokens=True,
739
+ meap_exclude_mask_from_copy=True,
740
+ copy_paste_lexical_keys=False,
741
  liger_loss_accum_dtype="float32",
742
  rope_theta=10000.0,
743
  rope_scaling=None,
744
  partial_rotary_factor=0.25,
745
  attention_bias=False,
746
  attention_dropout=0.1,
747
+ use_vha_attention=False,
748
  # ── HOLA-derived episodic memory for FlashAttention-2 ─────────────
749
  use_hola_memory=False,
750
  hola_memory_size=64,
 
771
  # ── Attention Residuals (Kimi Team, 2026) ─────────────────────────
772
  use_attn_res=False,
773
  attn_res_num_blocks=4,
774
+ # MSAR: independent residual topology, not combined with SiameseNorm.
775
+ use_msar=False,
776
+ msar_block_branches=None, # auto: approximately eight depth blocks
777
+ msar_kernel_sizes=(4, 8, 12),
778
+ msar_backend="auto", # Triton on CUDA, Torch on CPU; "torch" is a reference
779
  # ── StackMemory / STACKTRANS (Zhang et al., NeurIPS 2025) ───────
780
  use_stack_memory=False,
781
  stack_d_model=24, # H * d_s = 4 * 6
 
923
  if use_meap:
924
  reserved_for_model = {
925
  token_id
926
+ for value in (
927
  kwargs.get("pad_token_id"),
928
  kwargs.get("bos_token_id"),
929
  kwargs.get("eos_token_id"),
930
  )
931
+ for token_id in (value if isinstance(value, (list, tuple)) else (value,))
932
  if token_id is not None
933
  }
934
  if meap_mask_token_id in reserved_for_model:
 
1111
  "use_attn_res": bool(use_attn_res),
1112
  "use_stack_memory": bool(use_stack_memory),
1113
  "use_siamesenorm": bool(use_siamesenorm),
1114
+ "use_msar": bool(use_msar),
1115
  }
1116
  _active_residual_flow = [
1117
  name for name, enabled in _residual_flow_flags.items() if enabled
 
1121
  and bool(use_siamesenorm)
1122
  and not bool(use_laurel)
1123
  and not bool(use_attn_res)
1124
+ and not bool(use_msar)
1125
  )
1126
  if len(_active_residual_flow) > 1 and not allow_stack_siamese:
1127
  raise ValueError(
1128
  "Enable at most one residual-flow mechanism among "
1129
  "`use_laurel`, `use_attn_res`, `use_stack_memory`, "
1130
+ "`use_siamesenorm`, and `use_msar`, except the explicit "
1131
  "`use_stack_memory` + `use_siamesenorm` pair. "
1132
  f"Active flags: {_active_residual_flow}."
1133
  )
1134
 
1135
+ # MSAR owns the residual path: reject other residual contributors too.
1136
+ if use_msar and (use_gpas or use_fan_residual or use_dagformer):
1137
+ raise ValueError(
1138
+ "`use_msar=True` cannot be combined with `use_gpas`, "
1139
+ "`use_fan_residual`, or `use_dagformer`."
1140
+ )
1141
+ if msar_block_branches is None:
1142
+ # Approximately eight blocks, without tuning for batch or sequence.
1143
+ msar_block_branches = 2 * max(1, math.ceil(num_hidden_layers / 8))
1144
+ if isinstance(msar_block_branches, bool) or not isinstance(msar_block_branches, int) or msar_block_branches < 1:
1145
+ raise ValueError("`msar_block_branches` must be a positive integer.")
1146
+ if (not isinstance(msar_kernel_sizes, (tuple, list)) or not msar_kernel_sizes
1147
+ or any(isinstance(k, bool) or not isinstance(k, int) or k < 1 for k in msar_kernel_sizes)):
1148
+ raise ValueError("`msar_kernel_sizes` must contain positive integer widths.")
1149
+ if msar_backend not in ("auto", "torch"):
1150
+ raise ValueError("`msar_backend` must be 'auto' or 'torch'.")
1151
+
1152
  # ── LAuReL: sub-flag consistency ──────────────────────────────────
1153
  if use_laurel and not use_laurel_rw and not use_laurel_lr:
1154
  raise ValueError(
 
1300
  self.hidden_size = hidden_size
1301
  self.intermediate_size = intermediate_size
1302
  self.num_hidden_layers = num_hidden_layers
1303
+ if use_vha_attention and (
1304
+ isinstance(num_attention_heads, bool) or not isinstance(num_attention_heads, Integral)
1305
+ or isinstance(num_key_value_heads, bool) or not isinstance(num_key_value_heads, Integral)
1306
+ or num_attention_heads <= 0 or num_key_value_heads <= 0
1307
+ or num_attention_heads % num_key_value_heads != 0
1308
+ ):
1309
+ raise ValueError("VHA requires positive query/KV head counts, with KV heads dividing query heads.")
1310
  self.num_attention_heads = num_attention_heads
1311
  self.num_key_value_heads = num_key_value_heads
1312
  self.hidden_act = hidden_act
 
1317
  self.ntp_loss_backend = ntp_loss_backend
1318
  self.use_liger_kernel = bool(use_liger_kernel)
1319
  self.cce_loss_impl = str(cce_loss_impl).strip()
1320
+ self.use_copy_paste = bool(use_copy_paste)
1321
  self.use_mile_loss = bool(use_mile_loss)
1322
  self.mile_loss_gamma = float(mile_loss_gamma)
1323
  self.use_mu_loss = bool(use_mu_loss)
 
1330
  self.meap_seed = int(meap_seed)
1331
  self.meap_implementation = meap_implementation
1332
  self.meap_exclude_last = bool(meap_exclude_last)
1333
+ self.meap_protect_structural_tokens = bool(meap_protect_structural_tokens)
1334
+ self.meap_exclude_mask_from_copy = bool(meap_exclude_mask_from_copy)
1335
+ self.copy_paste_lexical_keys = bool(copy_paste_lexical_keys)
1336
  self.liger_loss_accum_dtype = liger_loss_accum_dtype
1337
 
1338
  # ── Positional encoding ───────────────────────────────────────────
 
1343
  # ── Attention ─────────────────────────────────────────────────────
1344
  self.attention_bias = attention_bias
1345
  self.attention_dropout = attention_dropout
1346
+ self.use_vha_attention = bool(use_vha_attention)
1347
  # ── HOLA-derived episodic memory for FlashAttention-2 ─────────────
1348
  self.use_hola_memory = bool(use_hola_memory)
1349
  self.hola_memory_size = int(hola_memory_size)
 
1381
  # block_size = num_hidden_layers // 4 = 3 layers per block.
1382
  # Memory cost: O(num_blocks × batch × seq × hidden) instead of
1383
  # O(num_layers × batch × seq × hidden).
1384
+ self.use_msar = bool(use_msar)
1385
+ self.msar_block_branches = msar_block_branches
1386
+ self.msar_kernel_sizes = tuple(msar_kernel_sizes)
1387
+ self.msar_backend = msar_backend
1388
  self.use_attn_res = use_attn_res
1389
  self.attn_res_num_blocks = attn_res_num_blocks
1390
 
modeling_neollm.py CHANGED
@@ -103,6 +103,15 @@ except ImportError:
103
  linear_cross_entropy = None
104
  _CCE_AVAILABLE = False
105
 
 
 
 
 
 
 
 
 
 
106
  try:
107
  from cut_cross_entropy import meap_mask_inputs # type: ignore[import-not-found]
108
  except ImportError:
@@ -4155,6 +4164,14 @@ class NeoLLMAttention(nn.Module):
4155
  self.use_learnable_multipliers = getattr(
4156
  config, "use_learnable_multipliers", True
4157
  )
 
 
 
 
 
 
 
 
4158
 
4159
  self.fan_layer = FANLayer(
4160
  hidden_size=config.hidden_size,
@@ -4166,7 +4183,7 @@ class NeoLLMAttention(nn.Module):
4166
 
4167
  self.q_proj = LinearWithMultipliers(
4168
  fan_output_dim,
4169
- config.num_attention_heads * self.head_dim * 2,
4170
  bias=config.attention_bias,
4171
  use_row_multiplier=True,
4172
  use_column_multiplier=False,
@@ -4211,6 +4228,18 @@ class NeoLLMAttention(nn.Module):
4211
  self.q_norm = _make_norm(self.head_dim, eps=config.rms_norm_eps)
4212
  self.k_norm = _make_norm(self.head_dim, eps=config.rms_norm_eps)
4213
 
 
 
 
 
 
 
 
 
 
 
 
 
4214
  if self.use_mea_attention:
4215
  self.mea_key_mix = nn.Parameter(
4216
  torch.eye(self.num_mea_component_heads, config.num_key_value_heads)
@@ -5474,7 +5503,12 @@ class NeoLLMAttention(nn.Module):
5474
  kv_shape = (*input_shape, self.num_mea_component_heads, self.head_dim)
5475
 
5476
  q_projected = self.q_proj(h_fan_q).view(
5477
- *input_shape, self.config.num_attention_heads, self.head_dim * 2)
 
 
 
 
 
5478
  q_raw, gate = q_projected.chunk(2, dim=-1)
5479
  gate = gate.reshape(*input_shape, -1)
5480
 
@@ -5486,8 +5520,27 @@ class NeoLLMAttention(nn.Module):
5486
  features = self.lambda_1 * first_layer_fan.unsqueeze(0) + self.lambda_2 * features
5487
  hq = self.config.num_attention_heads
5488
  hk = self.num_mea_component_heads
5489
- q_delta = _dag_project(features, dag_coefficients[..., :hq, :], self.q_proj, hq,
5490
- q_projected if self.dag_reuse_latest else None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5491
  dq, dg = q_delta.chunk(2, dim=-1)
5492
  q_raw = q_raw + dq
5493
  gate = gate + dg.reshape(*input_shape, -1)
@@ -5495,6 +5548,13 @@ class NeoLLMAttention(nn.Module):
5495
  k_raw if self.dag_reuse_latest else None)
5496
  v_raw = v_raw + _dag_project(features, dag_coefficients[..., hq+hk:hq+2*hk, :], self.v_proj, hk,
5497
  v_raw if self.dag_reuse_latest else None)
 
 
 
 
 
 
 
5498
  q = self.q_norm(q_raw.view(query_shape)).transpose(1, 2)
5499
  k = self.k_norm(k_raw).transpose(1, 2)
5500
  v = v_raw.transpose(1, 2)
@@ -5963,7 +6023,18 @@ class NeoLLMAttention(nn.Module):
5963
 
5964
  # ── Reshape → o_proj → Gated Attention gate → dropout ────────────
5965
  attn_out_flat = attn_out.reshape(*input_shape, -1).contiguous()
5966
- attn_out_gated = self.o_proj(attn_out_flat * torch.sigmoid(gate))
 
 
 
 
 
 
 
 
 
 
 
5967
  attn_out_gated = self.dropout(attn_out_gated)
5968
 
5969
  return attn_out_gated, attn_weights, current_layer_fan
@@ -6607,6 +6678,360 @@ def _dag_project(features, coefficients, projection, heads, latest=None):
6607
  return _dag_mix(projected,coefficients.reshape(b*t,heads,size)).reshape(b,t,heads,-1)
6608
 
6609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6610
  class NeoLLMDecoderLayer(GradientCheckpointingLayer):
6611
  """
6612
  Decoder layer with standard residual connections.
@@ -6776,6 +7201,10 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
6776
  # Pseudo-queries are initialized to ZERO so AttnRes starts as uniform
6777
  # average (equivalent to standard residual mean) and training volatility
6778
  # is avoided. This is critical per the paper's ablation.
 
 
 
 
6779
  self.use_attn_res = getattr(config, "use_attn_res", False)
6780
  if self.use_attn_res:
6781
  self.attn_res_query_attn = nn.Parameter(torch.zeros(config.hidden_size))
@@ -7208,6 +7637,11 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7208
  first_layer_fan: Optional[torch.Tensor] = None,
7209
  attn_res_sources: Optional[list] = None,
7210
  attn_res_partial: Optional[torch.Tensor] = None,
 
 
 
 
 
7211
  output_attentions: Optional[bool] = False,
7212
  repo_rope_args: Optional[Tuple[torch.Tensor, float]] = None,
7213
  position_ids: Optional[torch.LongTensor] = None,
@@ -7233,7 +7667,14 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7233
  # attn_res_partial carries the intra-block standard residual that
7234
  # connects the attention and MLP sublayers within this layer.
7235
  # When inactive, flow is identical to the original.
7236
- if (
 
 
 
 
 
 
 
7237
  self.use_attn_res
7238
  and attn_res_sources is not None
7239
  and attn_res_partial is not None
@@ -7283,7 +7724,10 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7283
  )
7284
 
7285
  # ── Residual junction (attention sublayer) ────────────────────────
7286
- if self.use_laurel:
 
 
 
7287
  attn_aug = self._laurel_residual(
7288
  residual_attn,
7289
  hidden_states,
@@ -7301,7 +7745,14 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7301
  # After attention, the partial sum is updated with h_tilde.
7302
  # The pre-MLP AttnRes attends over the same sources but with h_tilde
7303
  # as the current partial — capturing the within-layer attention output.
7304
- if self.use_attn_res and attn_res_sources is not None:
 
 
 
 
 
 
 
7305
  h_mlp = self._attn_res(
7306
  attn_res_sources,
7307
  h_tilde,
@@ -7334,7 +7785,11 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7334
 
7335
  # ── Residual junction (MLP sublayer) ─────────────────────────────
7336
  # LAuReL augments the base MLP residual (residual_mlp + delta_m).
7337
- if self.use_laurel:
 
 
 
 
7338
  mlp_aug = self._laurel_residual(
7339
  residual_mlp,
7340
  delta_m,
@@ -7349,6 +7804,8 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7349
  hidden_states = self.gpas_mlp(mlp_aug) if self.use_gpas else mlp_aug
7350
 
7351
  outputs = (hidden_states,)
 
 
7352
  if jtok_aux_stats is not None:
7353
  outputs += jtok_aux_stats
7354
  if output_attentions:
@@ -8058,6 +8515,11 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
8058
  for layer_idx in range(config.num_hidden_layers)
8059
  ]
8060
  )
 
 
 
 
 
8061
  self.dagformer = (DAGFormerTopology(config, self.layers)
8062
  if bool(getattr(config, "use_dagformer", False)) else None)
8063
  self.use_siamesenorm = bool(getattr(config, "use_siamesenorm", False))
@@ -8191,15 +8653,17 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
8191
  )
8192
  return inputs_embeds, jtok_z_tilde
8193
 
8194
- def prepare_patch_inputs(self, input_ids, attention_mask, *, patch_size, meap_active=False):
 
8195
  """Prepare differentiable core inputs without running decoder layers.
8196
 
8197
  Input IDs must already contain the selected MEAP replacements, while
8198
  clean future targets are maintained separately by the trainer. SBE and
8199
  the dedicated MEAP vector are composed before pooling. The returned
8200
  tuple is the pool_patch_inputs contract, including separate attention
8201
- and geometry validity. The K=1 path shares the ordinary producer;
8202
- no model parameters, optimizer state or coordinate gradients are copied.
 
8203
  """
8204
  from cut_cross_entropy import pool_patch_inputs
8205
 
@@ -8210,10 +8674,11 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
8210
  raise ValueError("Patch preparation requires an explicit boolean raw-token validity mask.")
8211
  embeddings, coordinates = self._compose_input_embeddings(input_ids, attention_mask, meap_active)
8212
  corruption = input_ids.eq(int(self.config.meap_mask_token_id)) if meap_active else None
8213
- return pool_patch_inputs(
8214
  embeddings, attention_mask, patch_size=patch_size,
8215
  coordinates=coordinates, corruption_mask=corruption,
8216
  )
 
8217
 
8218
  def forward(
8219
  self,
@@ -8477,6 +8942,18 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
8477
  # In both modes, attn_res_partial is the current intra-block accumulated
8478
  # hidden state that connects the attn and MLP sublayers and flows between
8479
  # decoder layers within a block.
 
 
 
 
 
 
 
 
 
 
 
 
8480
  use_attn_res = getattr(self.config, "use_attn_res", False)
8481
  attn_res_sources = None
8482
  attn_res_partial = None
@@ -8564,6 +9041,9 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
8564
  first_layer_fan=self.first_layer_fan,
8565
  attn_res_sources=attn_res_sources,
8566
  attn_res_partial=attn_res_partial if use_attn_res else None,
 
 
 
8567
  output_attentions=output_attentions,
8568
  repo_rope_args=repo_rope_args,
8569
  position_ids=position_ids,
@@ -8576,7 +9056,13 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
8576
  **kwargs,
8577
  )
8578
  hidden_states = layer_outputs[0]
8579
- extras_start = 1
 
 
 
 
 
 
8580
 
8581
  if collect_residual:
8582
  packed = residual_dynamics(before_x.detach(), hidden_states.detach(),
@@ -8689,8 +9175,8 @@ def _prepare_lm_labels(labels, device, pad_token_id=None):
8689
 
8690
 
8691
  def _lm_perplexity_sums(labels, ce, mile_delta, *, pad_token_id, patch_targets,
8692
- mile_enabled):
8693
- """Detached sufficient statistics for CCE CE and MiLe pseudo-perplexity.
8694
 
8695
  For N valid supervised targets, return (N*CE, N*MiLe, N).
8696
  MiLe = CE + reweighting_delta, excluding mu and all auxiliary objectives.
@@ -8708,6 +9194,8 @@ def _lm_perplexity_sums(labels, ce, mile_delta, *, pad_token_id, patch_targets,
8708
  valid = targets != -100
8709
  if pad_token_id is not None:
8710
  valid = valid & (targets != pad_token_id)
 
 
8711
  count = valid.sum().to(dtype=torch.float32)
8712
  ce = ce.detach().float()
8713
  mile = ce + mile_delta.detach().float() if mile_enabled else ce
@@ -10506,6 +10994,235 @@ def compute_nextlat_loss(
10506
  return total, mse_loss, kl_loss
10507
 
10508
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10509
  class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10510
  """
10511
  Causal LM with NeoLLM backbone.
@@ -10519,6 +11236,11 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10519
  self.model = NeoLLMModel(config)
10520
  self.vocab_size = config.vocab_size
10521
  self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
 
 
 
 
 
10522
  self.nitp_projector = NITPProjector(config) if config.use_nitp else None
10523
  self.nitp_temporal_transition = (
10524
  NITPTemporalTransition(config)
@@ -10550,10 +11272,13 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10550
  "`ntp_loss_backend` must be 'cce' or 'liger'. "
10551
  f"Got {self.ntp_loss_backend!r}."
10552
  )
10553
- if bool(getattr(config, "use_mile_loss", False)):
10554
- _require_extended_cce_options("mile_enabled", "mile_gamma")
10555
- if bool(getattr(config, "use_mu_loss", False)):
10556
- _require_extended_cce_options("mu_loss_enabled", "mu_loss_lambda")
 
 
 
10557
  # The optional MEAP backend is checked only when training actually
10558
  # applies corruption. Clean training, evaluation, and inference remain
10559
  # usable without cut-cross-entropy even when loading a MEAP checkpoint.
@@ -10561,6 +11286,7 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10561
  self._last_ntp_ce_unweighted = None
10562
  self._last_lm_metric_sums = None
10563
  self._last_topk_metric_sums = None
 
10564
  self._last_mile_reweighting_delta = None
10565
  self._last_mu_loss = None
10566
  self._last_meap_mask_fraction = None
@@ -10679,8 +11405,9 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10679
  def get_dynamics_parameter_groups(self):
10680
  """Existing observers plus residual controllers; never duplicate the backbone.
10681
 
10682
- Control modules share one sampled group per layer/method. Groups are
10683
- sampled around real optimizer updates by train.py, not in forward.
 
10684
  """
10685
  groups = {}
10686
  generator = getattr(self.model, "token_generator", None)
@@ -10688,7 +11415,16 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10688
  for name, parameter in generator.named_parameters():
10689
  if parameter.requires_grad:
10690
  groups[f"leviathan/{name}"] = (parameter,)
 
 
 
 
10691
  for layer in self.model.layers:
 
 
 
 
 
10692
  module = getattr(layer, "jtok", None)
10693
  if module is not None:
10694
  kind = "jtokm" if module.use_mixture else "jtok"
@@ -10729,7 +11465,12 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10729
  self.lm_head = new_embeddings
10730
 
10731
  def _corrupt_meap_inputs(self, input_ids, attention_mask, meap_seed, *, return_mask):
10732
- """Shared raw-token corruption for ordinary and prepared training."""
 
 
 
 
 
10733
  if meap_mask_inputs is None:
10734
  raise ImportError("Active MEAP training requires the extended cut-cross-entropy package.")
10735
  if "return_metrics" not in inspect.signature(meap_mask_inputs).parameters:
@@ -10737,20 +11478,42 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10737
  mask_token_id = getattr(self.config, "meap_mask_token_id", None)
10738
  if mask_token_id is None:
10739
  raise ValueError("`meap_mask_token_id` is required when MEAP is enabled.")
10740
- if meap_seed is None:
10741
- seed = input_ids.new_tensor(int(getattr(self.config, "meap_seed", 0)), dtype=torch.long)
10742
- elif isinstance(meap_seed, torch.Tensor):
10743
  seed = meap_seed.to(device=input_ids.device, dtype=torch.long)
 
 
 
10744
  else:
10745
- seed = input_ids.new_tensor(int(meap_seed), dtype=torch.long)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10746
  result = meap_mask_inputs(
10747
  input_ids, int(mask_token_id), enabled=True,
10748
  mask_ratio=float(getattr(self.config, "meap_mask_ratio", 0.15)),
10749
- eligible_mask=(attention_mask.to(device=input_ids.device, dtype=torch.bool)
10750
- if attention_mask is not None else None),
10751
- seed=seed, exclude_last=bool(getattr(self.config, "meap_exclude_last", True)),
10752
  return_mask=return_mask, return_metrics=True,
10753
- implementation=str(getattr(self.config, "meap_implementation", "triton")),
10754
  )
10755
  if return_mask:
10756
  corrupted, selected, metrics = result
@@ -10792,9 +11555,13 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10792
  input_ids, selected, metrics, seed = self._corrupt_meap_inputs(
10793
  input_ids, attention_mask, meap_seed, return_mask=True,
10794
  )
10795
- e, z, core, geometry, fraction = self.model.prepare_patch_inputs(
 
 
10796
  input_ids, attention_mask, patch_size=patch_size, meap_active=active,
 
10797
  )
 
10798
  if selected is not None:
10799
  grouped_valid = attention_mask.unflatten(-1, (core.size(1), patch_size))
10800
  fraction = ((selected & attention_mask).unflatten(-1, (core.size(1), patch_size))
@@ -10813,6 +11580,13 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10813
  patch_aux_valid_mask=aux_valid,
10814
  prepared_meap_metrics=metrics, prepared_meap_seed=seed,
10815
  )
 
 
 
 
 
 
 
10816
  if z is not None:
10817
  result.update(prepared_jtok_coordinates=z, prepared_jtok_valid_mask=geometry)
10818
  return result
@@ -10860,11 +11634,23 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10860
  prepared_meap_seed: Optional[torch.Tensor] = None,
10861
  raw_patch_size: Optional[int] = None,
10862
  patch_target_slots: int = 4,
 
 
 
10863
  **kwargs: Unpack[TransformersKwargs],
10864
  ) -> CausalLMOutputWithPast:
10865
  self._last_meap_mask_fraction = None
10866
  self._last_meap_masked_tokens = None
10867
  self._last_meap_seed = None
 
 
 
 
 
 
 
 
 
10868
 
10869
  if raw_patch_size is not None:
10870
  # Static K/shape signatures are warmed once. The changing optimizer
@@ -10872,7 +11658,9 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10872
  # so embedding/coordinate VJPs are part of the compiled backward.
10873
  if (patch_training_enabled or inputs_embeds is not None
10874
  or input_ids is None or labels is None or attention_mask is None
10875
- or position_ids is not None or any(value is not None for value in (
 
 
10876
  patch_group_fraction, patch_aux_valid_mask,
10877
  prepared_meap_metrics, prepared_meap_seed,
10878
  kwargs.get("prepared_jtok_coordinates"),
@@ -10884,6 +11672,11 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10884
  patch_size=raw_patch_size, target_slots=patch_target_slots,
10885
  meap_seed=meap_seed,
10886
  )
 
 
 
 
 
10887
  input_ids = None
10888
  inputs_embeds = prepared["inputs_embeds"]
10889
  attention_mask = prepared["attention_mask"]
@@ -10959,6 +11752,46 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
10959
  self._last_meap_masked_tokens = masked_count.detach()
10960
  self._last_meap_seed = effective_meap_seed.detach()
10961
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10962
  tweo_enabled = (
10963
  bool(getattr(self.config, "use_tweo", False))
10964
  and labels is not None
@@ -11086,6 +11919,7 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
11086
  self._last_ntp_ce_unweighted = None
11087
  self._last_lm_metric_sums = None
11088
  self._last_topk_metric_sums = None
 
11089
  self._last_mile_reweighting_delta = None
11090
  self._last_mu_loss = None
11091
  self._last_tweo_loss = None
@@ -11126,7 +11960,33 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
11126
  self._last_stack_gate_effective_slots = None
11127
  self._last_total_loss = None
11128
  if labels is not None:
11129
- if self.ntp_loss_backend == "liger":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11130
  ntp_loss = compute_liger_loss(
11131
  hidden_states,
11132
  labels,
@@ -11202,6 +12062,7 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
11202
  patch_targets=patch_training_enabled,
11203
  mile_enabled=(self.ntp_loss_backend == "cce"
11204
  and bool(getattr(self.config, "use_mile_loss", False))),
 
11205
  )
11206
  loss = ntp_loss
11207
 
@@ -11541,7 +12402,13 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
11541
  if isinstance(logits_to_keep, int)
11542
  else logits_to_keep
11543
  )
11544
- logits = self.lm_head(hidden_states[:, slice_indices, :])
 
 
 
 
 
 
11545
 
11546
  return CausalLMOutputWithPast(
11547
  loss=loss,
 
103
  linear_cross_entropy = None
104
  _CCE_AVAILABLE = False
105
 
106
+ try:
107
+ from cut_cross_entropy import copy_paste_loss as _cce_copy_paste_loss
108
+ except (ImportError, AttributeError):
109
+ _cce_copy_paste_loss = None
110
+ _CCE_COPY_PASTE_HAS_METRICS = (
111
+ _cce_copy_paste_loss is not None
112
+ and "return_copy_metrics" in inspect.signature(_cce_copy_paste_loss).parameters
113
+ )
114
+
115
  try:
116
  from cut_cross_entropy import meap_mask_inputs # type: ignore[import-not-found]
117
  except ImportError:
 
4164
  self.use_learnable_multipliers = getattr(
4165
  config, "use_learnable_multipliers", True
4166
  )
4167
+ self.use_vha_attention = bool(getattr(config, "use_vha_attention", False))
4168
+ # A virtual group shares physical Q and gate projections. The final Q
4169
+ # heads still match the configured GQA geometry expected by every
4170
+ # attention backend and optional head-space operation.
4171
+ self.vha_physical_heads = (
4172
+ config.num_attention_heads // config.num_key_value_heads
4173
+ if self.use_vha_attention else config.num_attention_heads
4174
+ )
4175
 
4176
  self.fan_layer = FANLayer(
4177
  hidden_size=config.hidden_size,
 
4183
 
4184
  self.q_proj = LinearWithMultipliers(
4185
  fan_output_dim,
4186
+ self.vha_physical_heads * self.head_dim * 2,
4187
  bias=config.attention_bias,
4188
  use_row_multiplier=True,
4189
  use_column_multiplier=False,
 
4228
  self.q_norm = _make_norm(self.head_dim, eps=config.rms_norm_eps)
4229
  self.k_norm = _make_norm(self.head_dim, eps=config.rms_norm_eps)
4230
 
4231
+ if self.use_vha_attention:
4232
+ # Premix is group-specific and close to identity; postmix starts as
4233
+ # the identity because its second low-rank factor is exactly zero.
4234
+ rank = max(1, self.vha_physical_heads // 2)
4235
+ identity = torch.eye(self.head_dim).expand(
4236
+ config.num_key_value_heads, -1, -1).clone()
4237
+ self.vha_premix = nn.Parameter(
4238
+ identity + torch.randn_like(identity) * (0.1 * config.initializer_range))
4239
+ self.vha_postmix_a = nn.Parameter(
4240
+ torch.randn(config.num_attention_heads, rank) * config.initializer_range)
4241
+ self.vha_postmix_b = nn.Parameter(torch.zeros(config.num_attention_heads, rank))
4242
+
4243
  if self.use_mea_attention:
4244
  self.mea_key_mix = nn.Parameter(
4245
  torch.eye(self.num_mea_component_heads, config.num_key_value_heads)
 
5503
  kv_shape = (*input_shape, self.num_mea_component_heads, self.head_dim)
5504
 
5505
  q_projected = self.q_proj(h_fan_q).view(
5506
+ *input_shape, self.vha_physical_heads, self.head_dim * 2)
5507
+ if self.use_vha_attention:
5508
+ q_projected = q_projected.unsqueeze(-3).expand(
5509
+ *input_shape, self.config.num_key_value_heads,
5510
+ self.vha_physical_heads, self.head_dim * 2,
5511
+ ).reshape(*input_shape, self.config.num_attention_heads, self.head_dim * 2)
5512
  q_raw, gate = q_projected.chunk(2, dim=-1)
5513
  gate = gate.reshape(*input_shape, -1)
5514
 
 
5520
  features = self.lambda_1 * first_layer_fan.unsqueeze(0) + self.lambda_2 * features
5521
  hq = self.config.num_attention_heads
5522
  hk = self.num_mea_component_heads
5523
+ if self.use_vha_attention:
5524
+ # DAG selects a different depth mixture per virtual head. Apply
5525
+ # those coefficients after expanding each physical projection;
5526
+ # this retains DAG's routing instead of silently sharing it.
5527
+ sources = features.shape[0]
5528
+ projected = self.q_proj(features).reshape(
5529
+ sources, *input_shape, self.vha_physical_heads, self.head_dim * 2)
5530
+ projected = projected.unsqueeze(-3).expand(
5531
+ sources, *input_shape, self.config.num_key_value_heads,
5532
+ self.vha_physical_heads, self.head_dim * 2,
5533
+ ).reshape(sources, *input_shape, hq, self.head_dim * 2)
5534
+ coeff = dag_coefficients[..., :hq, :]
5535
+ earlier_coeff = coeff[..., :-1] if self.dag_reuse_latest else coeff
5536
+ q_delta = torch.einsum(
5537
+ "sbthd,bths->bthd", projected.float(), earlier_coeff.float())
5538
+ if self.dag_reuse_latest:
5539
+ q_delta = q_delta + coeff[..., -1, None].float() * q_projected.float()
5540
+ q_delta = q_delta.to(features.dtype)
5541
+ else:
5542
+ q_delta = _dag_project(features, dag_coefficients[..., :hq, :], self.q_proj, hq,
5543
+ q_projected if self.dag_reuse_latest else None)
5544
  dq, dg = q_delta.chunk(2, dim=-1)
5545
  q_raw = q_raw + dq
5546
  gate = gate + dg.reshape(*input_shape, -1)
 
5548
  k_raw if self.dag_reuse_latest else None)
5549
  v_raw = v_raw + _dag_project(features, dag_coefficients[..., hq+hk:hq+2*hk, :], self.v_proj, hk,
5550
  v_raw if self.dag_reuse_latest else None)
5551
+ if self.use_vha_attention:
5552
+ q_raw = torch.einsum(
5553
+ "btgid,gde->btgie",
5554
+ q_raw.reshape(*input_shape, self.config.num_key_value_heads,
5555
+ self.vha_physical_heads, self.head_dim),
5556
+ self.vha_premix,
5557
+ ).reshape(query_shape)
5558
  q = self.q_norm(q_raw.view(query_shape)).transpose(1, 2)
5559
  k = self.k_norm(k_raw).transpose(1, 2)
5560
  v = v_raw.transpose(1, 2)
 
6023
 
6024
  # ── Reshape → o_proj → Gated Attention gate → dropout ────────────
6025
  attn_out_flat = attn_out.reshape(*input_shape, -1).contiguous()
6026
+ if self.use_vha_attention:
6027
+ # Keep NeoLLM's nonlinear head gate before postmix. Consequently
6028
+ # the linear postmix can be folded into a dense output projection;
6029
+ # folding into a constrained Hadamard parameterization is not assumed.
6030
+ gated_heads = (attn_out_flat * torch.sigmoid(gate)).reshape(
6031
+ *input_shape, self.config.num_attention_heads, self.head_dim)
6032
+ low_rank = torch.einsum("bthd,hr->btrd", gated_heads, self.vha_postmix_a)
6033
+ mixed_heads = gated_heads + torch.einsum(
6034
+ "btrd,hr->bthd", low_rank, self.vha_postmix_b)
6035
+ attn_out_gated = self.o_proj(mixed_heads.reshape(*input_shape, -1))
6036
+ else:
6037
+ attn_out_gated = self.o_proj(attn_out_flat * torch.sigmoid(gate))
6038
  attn_out_gated = self.dropout(attn_out_gated)
6039
 
6040
  return attn_out_gated, attn_weights, current_layer_fan
 
6678
  return _dag_mix(projected,coefficients.reshape(b*t,heads,size)).reshape(b,t,heads,-1)
6679
 
6680
 
6681
+ def _msar_views(sources, banks, valid, position, widths):
6682
+ """Expand sources; positions prevent attention across packed boundaries."""
6683
+ b, t, d = sources[0].shape
6684
+ valid = valid.to(torch.bool)
6685
+ expanded = []
6686
+ for source, bank in zip(sources, banks):
6687
+ x = source.float()
6688
+ streams = [x * valid[..., None]]
6689
+ start = 0
6690
+ for width in widths:
6691
+ view = torch.zeros_like(x)
6692
+ for delta in range(width):
6693
+ if delta >= t:
6694
+ break
6695
+ if delta:
6696
+ past = F.pad(x[:, :t-delta, :], (0, 0, delta, 0))
6697
+ past_valid = F.pad(valid[:, :t-delta], (delta, 0))
6698
+ past_pos = F.pad(position[:, :t-delta], (delta, 0))
6699
+ else:
6700
+ past, past_valid, past_pos = x, valid, position
6701
+ allowed = valid & past_valid & ((position - past_pos) == delta)
6702
+ view = view + past * allowed[..., None] * bank[start + delta]
6703
+ streams.append(view)
6704
+ start += width
6705
+ expanded.append(torch.stack(streams, dim=2))
6706
+ return torch.stack(expanded, dim=2) # B,T,N,M,D
6707
+
6708
+
6709
+ def _msar_reference_read(sources, banks, query, valid, position, widths, eps):
6710
+ """Eqs. 9--12: each source has its own causal expansion bank."""
6711
+ v = _msar_views(sources, banks, valid, position, widths)
6712
+ q = query.float()[None, None, None, :, :]
6713
+ inv = torch.rsqrt(v.square().mean(dim=-1, keepdim=True) + eps)
6714
+ score = (v * inv * q).sum(-1)
6715
+ probs = torch.softmax(score, dim=2)
6716
+ return (probs[..., None] * v).sum(dim=2).to(sources[0].dtype)
6717
+
6718
+
6719
+ if triton is not None:
6720
+ @triton.jit
6721
+ def _msar_read_kernel(SOURCES, BANKS, QUERY, VALID, POSITION, OUT, LSE,
6722
+ T: tl.constexpr, D: tl.constexpr, NS: tl.constexpr,
6723
+ M: tl.constexpr,
6724
+ WIDTHS: tl.constexpr, EPS: tl.constexpr, BD: tl.constexpr):
6725
+ """Online depth softmax; expanded source views stay in registers."""
6726
+ row = tl.program_id(0)
6727
+ stream = tl.program_id(1)
6728
+ batch = row // T
6729
+ token = row % T
6730
+ c = tl.arange(0, BD)
6731
+ mask_c = c < D
6732
+ is_valid = tl.load(VALID + row).to(tl.int1)
6733
+ position = tl.load(POSITION + row)
6734
+ q = tl.load(QUERY + stream * D + c, mask_c, other=0).to(tl.float32)
6735
+ running_max = tl.full((), -float('inf'), tl.float32)
6736
+ denom = tl.full((), 0., tl.float32)
6737
+ accum = tl.full((BD,), 0., tl.float32)
6738
+ for i in tl.static_range(NS):
6739
+ value = tl.full((BD,), 0., tl.float32)
6740
+ if stream == 0:
6741
+ value = tl.load(SOURCES[i] + row * D + c,
6742
+ mask_c & is_valid, other=0).to(tl.float32)
6743
+ offset = 0
6744
+ for r in tl.static_range(len(WIDTHS)):
6745
+ if stream == r + 1:
6746
+ for delta in tl.static_range(WIDTHS[r]):
6747
+ past = row - delta
6748
+ in_seq = token >= delta
6749
+ past_valid = tl.load(VALID + past, in_seq, other=0).to(tl.int1)
6750
+ past_pos = tl.load(POSITION + past, in_seq, other=0)
6751
+ allowed = in_seq & is_valid & past_valid & ((position - past_pos) == delta)
6752
+ x = tl.load(SOURCES[i] + past * D + c,
6753
+ mask_c & allowed, other=0).to(tl.float32)
6754
+ w = tl.load(BANKS[i] + (offset + delta) * D + c,
6755
+ mask_c, other=0).to(tl.float32)
6756
+ value += x * w
6757
+ offset += WIDTHS[r]
6758
+ norm = tl.rsqrt(tl.sum(value * value, 0) / D + EPS)
6759
+ score = tl.sum(value * q, 0) * norm
6760
+ new_max = tl.maximum(running_max, score)
6761
+ old_weight = tl.exp(running_max - new_max)
6762
+ new_weight = tl.exp(score - new_max)
6763
+ accum = accum * old_weight + value * new_weight
6764
+ denom = denom * old_weight + new_weight
6765
+ running_max = new_max
6766
+ tl.store(OUT + row * M * D + stream * D + c, accum / denom, mask_c)
6767
+ tl.store(LSE + row * M + stream, running_max + tl.log(denom))
6768
+
6769
+
6770
+ @triton.jit
6771
+ def _msar_vjp_value(SOURCE, BANK, QUERY, VALID, POSITION, LSE, Z, G, DV, QPART,
6772
+ ROWS: tl.constexpr, T: tl.constexpr, D: tl.constexpr,
6773
+ M: tl.constexpr,
6774
+ WIDTHS: tl.constexpr, EPS: tl.constexpr, R: tl.constexpr,
6775
+ BD: tl.constexpr, GROUPS: tl.constexpr):
6776
+ """Per-source VJP through softmax and RMS; reduce query partials by rows."""
6777
+ group = tl.program_id(0)
6778
+ stream = tl.program_id(1)
6779
+ row = group * R + tl.arange(0, R)
6780
+ c = tl.arange(0, BD)
6781
+ row_ok = row < ROWS
6782
+ tok = row % T
6783
+ valid = tl.load(VALID + row, row_ok, other=0).to(tl.int1)
6784
+ pos = tl.load(POSITION + row, row_ok, other=0)
6785
+ q = tl.load(QUERY + stream*D+c, c < D, other=0).to(tl.float32)
6786
+ v = tl.full((R, BD), 0., tl.float32)
6787
+ if stream == 0:
6788
+ v = tl.load(SOURCE + row[:,None]*D+c[None,:],
6789
+ row_ok[:,None] & valid[:,None] & (c[None,:] < D), other=0).to(tl.float32)
6790
+ offset = 0
6791
+ for r in tl.static_range(len(WIDTHS)):
6792
+ if stream == r+1:
6793
+ for delta in tl.static_range(WIDTHS[r]):
6794
+ past = row - delta
6795
+ in_seq = row_ok & (tok >= delta)
6796
+ pv = tl.load(VALID + past, in_seq, other=0).to(tl.int1)
6797
+ pp = tl.load(POSITION + past, in_seq, other=0)
6798
+ allowed = in_seq & valid & pv & ((pos-pp) == delta)
6799
+ x = tl.load(SOURCE + past[:,None]*D+c[None,:],
6800
+ allowed[:,None] & (c[None,:]<D), other=0).to(tl.float32)
6801
+ w = tl.load(BANK+(offset+delta)*D+c,
6802
+ c<D, other=0).to(tl.float32)
6803
+ v += x*w[None,:]
6804
+ offset += WIDTHS[r]
6805
+ inv = tl.rsqrt(tl.sum(v*v, 1)/D + EPS)
6806
+ qv = tl.sum(v*q[None,:], 1)
6807
+ lse = tl.load(LSE+row*M+stream, row_ok, other=0)
6808
+ p = tl.exp(qv*inv-lse)
6809
+ z = tl.load(Z+row[:,None]*M*D+stream*D+c[None,:],
6810
+ row_ok[:,None] & (c[None,:]<D), other=0).to(tl.float32)
6811
+ g = tl.load(G+row[:,None]*M*D+stream*D+c[None,:],
6812
+ row_ok[:,None] & (c[None,:]<D), other=0).to(tl.float32)
6813
+ ds = p*tl.sum(g*(v-z), 1)
6814
+ dv = p[:,None]*g + ds[:,None]*(q[None,:]*inv[:,None] -
6815
+ v*(qv*inv*inv*inv/D)[:,None])
6816
+ tl.store(DV+stream*ROWS*D+row[:,None]*D+c[None,:], dv,
6817
+ row_ok[:,None] & (c[None,:]<D))
6818
+ qp = tl.sum(ds[:,None]*v*inv[:,None], 0)
6819
+ tl.store(QPART+stream*GROUPS*D+group*D+c, qp, c<D)
6820
+
6821
+
6822
+ @triton.jit
6823
+ def _msar_vjp_source(DV, BANK, VALID, POSITION, DX, ROWS: tl.constexpr,
6824
+ T: tl.constexpr, D: tl.constexpr,
6825
+ WIDTHS: tl.constexpr, BLOCK: tl.constexpr):
6826
+ """Transpose the causal depthwise convolutions without atomics."""
6827
+ j = tl.program_id(0)*BLOCK+tl.arange(0,BLOCK)
6828
+ row = j//D
6829
+ c = j%D
6830
+ ok = row<ROWS
6831
+ token = row%T
6832
+ valid = tl.load(VALID+row, ok, other=0).to(tl.int1)
6833
+ pos = tl.load(POSITION+row, ok, other=0)
6834
+ dx = tl.load(DV+j, ok & valid, other=0)
6835
+ offset = 0
6836
+ for r in tl.static_range(len(WIDTHS)):
6837
+ for delta in tl.static_range(WIDTHS[r]):
6838
+ future = row+delta
6839
+ in_seq = ok & ((token+delta)<T)
6840
+ fv = tl.load(VALID+future, in_seq, other=0).to(tl.int1)
6841
+ fp = tl.load(POSITION+future, in_seq, other=0)
6842
+ allowed = in_seq & valid & fv & ((fp-pos)==delta)
6843
+ v = tl.load(DV+(r+1)*ROWS*D+future*D+c, allowed, other=0)
6844
+ w = tl.load(BANK+(offset+delta)*D+c, ok, other=0).to(tl.float32)
6845
+ dx += v*w
6846
+ offset += WIDTHS[r]
6847
+ tl.store(DX+j, dx, ok)
6848
+
6849
+
6850
+ @triton.jit
6851
+ def _msar_vjp_bank(SOURCE, DV, VALID, POSITION, WPART, ROWS: tl.constexpr,
6852
+ T: tl.constexpr, D: tl.constexpr, WIDTHS: tl.constexpr,
6853
+ R: tl.constexpr, C: tl.constexpr, GROUPS: tl.constexpr):
6854
+ """Reduce source x view-gradient products into convolution bank partials."""
6855
+ group = tl.program_id(0)
6856
+ c = tl.program_id(1)*C + tl.arange(0,C)
6857
+ row = group*R + tl.arange(0,R)
6858
+ ok = row<ROWS
6859
+ tok = row%T
6860
+ valid = tl.load(VALID+row, ok, other=0).to(tl.int1)
6861
+ pos = tl.load(POSITION+row, ok, other=0)
6862
+ offset = 0
6863
+ for r in tl.static_range(len(WIDTHS)):
6864
+ grad = tl.load(DV+(r+1)*ROWS*D+row[:,None]*D+c[None,:],
6865
+ ok[:,None] & (c[None,:]<D), other=0).to(tl.float32)
6866
+ for delta in tl.static_range(WIDTHS[r]):
6867
+ past = row-delta
6868
+ in_seq = ok & (tok>=delta)
6869
+ pv = tl.load(VALID+past, in_seq, other=0).to(tl.int1)
6870
+ pp = tl.load(POSITION+past, in_seq, other=0)
6871
+ allowed = in_seq & valid & pv & ((pos-pp)==delta)
6872
+ x = tl.load(SOURCE+past[:,None]*D+c[None,:],
6873
+ allowed[:,None] & (c[None,:]<D), other=0).to(tl.float32)
6874
+ part = tl.sum(grad*x, 0)
6875
+ tl.store(WPART+(offset+delta)*GROUPS*D+group*D+c, part, c<D)
6876
+ offset += WIDTHS[r]
6877
+
6878
+
6879
+ @torch.library.custom_op("neollm_msar::read", mutates_args=())
6880
+ def _msar_read_op(sources: list[torch.Tensor], banks: list[torch.Tensor],
6881
+ query: torch.Tensor, valid: torch.Tensor, position: torch.Tensor,
6882
+ widths: list[int], eps: float) -> tuple[torch.Tensor, torch.Tensor]:
6883
+ b, t, d = sources[0].shape
6884
+ sources = [x.contiguous() for x in sources]
6885
+ banks = [x.contiguous() for x in banks]
6886
+ query = query.contiguous()
6887
+ valid = valid.contiguous()
6888
+ position = position.contiguous()
6889
+ result = sources[0].new_empty((b, t, len(widths)+1, d))
6890
+ lse = torch.empty((b,t,len(widths)+1),device=result.device,dtype=torch.float32)
6891
+ _msar_read_kernel[(b*t, len(widths)+1)](tuple(sources), tuple(banks), query, valid, position,
6892
+ result, lse, t, d, len(sources), len(widths)+1,
6893
+ tuple(widths), eps, triton.next_power_of_2(d), num_warps=4)
6894
+ return result, lse
6895
+
6896
+
6897
+ @_msar_read_op.register_fake
6898
+ def _msar_read_fake(sources, banks, query, valid, position, widths, eps):
6899
+ b, t, d = sources[0].shape
6900
+ return (sources[0].new_empty((b, t, len(widths)+1, d)),
6901
+ torch.empty((b,t,len(widths)+1),device=sources[0].device,dtype=torch.float32))
6902
+
6903
+
6904
+ @torch.library.custom_op("neollm_msar::backward", mutates_args=())
6905
+ def _msar_backward_op(sources: list[torch.Tensor], banks: list[torch.Tensor],
6906
+ query: torch.Tensor, valid: torch.Tensor, position: torch.Tensor,
6907
+ widths: list[int], eps: float,
6908
+ grad: torch.Tensor, lse: torch.Tensor) -> tuple[list[torch.Tensor], list[torch.Tensor], torch.Tensor]:
6909
+ """Fused VJP with exact FP32 stream recomputation and one-source workspace."""
6910
+ b, t, d = sources[0].shape
6911
+ n, m, ksum = b*t, len(widths)+1, sum(widths)
6912
+ sources = [x.contiguous() for x in sources]
6913
+ banks, query = [x.contiguous() for x in banks], query.contiguous()
6914
+ valid, position, grad = valid.contiguous(), position.contiguous(), grad.contiguous()
6915
+ # Recompute FP32 retrieved streams instead of saving them for every layer.
6916
+ z = torch.empty((b,t,m,d),device=grad.device,dtype=torch.float32)
6917
+ lse_recomputed = torch.empty_like(lse)
6918
+ _msar_read_kernel[(n,m)](tuple(sources), tuple(banks), query, valid, position, z, lse_recomputed,
6919
+ t,d,len(sources),m,tuple(widths),eps,
6920
+ triton.next_power_of_2(d),num_warps=4)
6921
+ # Only one source's view gradients are materialized at a time.
6922
+ dv = torch.empty((m,n,d),device=grad.device,dtype=torch.float32)
6923
+ qrows = max(1, min(8, 8192 // triton.next_power_of_2(d)))
6924
+ qgroups = triton.cdiv(n,qrows)
6925
+ qpart = torch.empty((m,qgroups,d),device=grad.device,dtype=torch.float32)
6926
+ wgroups = triton.cdiv(n,64)
6927
+ wpart = torch.empty((ksum,wgroups,d),device=grad.device,dtype=torch.float32)
6928
+ dq = torch.zeros_like(query, dtype=torch.float32)
6929
+ dws = []
6930
+ dxs = []
6931
+ for source, bank in zip(sources, banks):
6932
+ _msar_vjp_value[(qgroups,m)](source,bank,query,valid,position,lse,z,grad,dv,qpart,
6933
+ n,t,d,m,tuple(widths),eps,qrows,triton.next_power_of_2(d),qgroups,num_warps=4)
6934
+ dx = torch.empty_like(source)
6935
+ _msar_vjp_source[(triton.cdiv(n*d,256),)](dv,bank,valid,position,dx,n,t,d,
6936
+ tuple(widths),256,num_warps=4)
6937
+ _msar_vjp_bank[(wgroups,triton.cdiv(d,32))](source,dv,valid,position,wpart,
6938
+ n,t,d,tuple(widths),64,32,wgroups,num_warps=4)
6939
+ dq.add_(qpart.sum(dim=1))
6940
+ dws.append(wpart.sum(dim=1).to(bank.dtype))
6941
+ dxs.append(dx)
6942
+ return dxs, dws, dq.to(query.dtype).contiguous()
6943
+
6944
+
6945
+ @_msar_backward_op.register_fake
6946
+ def _msar_backward_fake(sources, banks, query, valid, position, widths, eps, grad, lse):
6947
+ return ([torch.empty_like(x) for x in sources], [torch.empty_like(x) for x in banks],
6948
+ torch.empty_like(query))
6949
+
6950
+
6951
+ def _msar_setup(ctx, inputs, output):
6952
+ sources, banks, query, valid, position, widths, eps = inputs
6953
+ ctx.save_for_backward(*sources, *banks, query, valid, position, output[1])
6954
+ ctx.mark_non_differentiable(output[1])
6955
+ ctx.count = len(sources)
6956
+ ctx.widths = widths
6957
+ ctx.eps = eps
6958
+
6959
+
6960
+ def _msar_autograd_backward(ctx, grad, grad_lse):
6961
+ saved = ctx.saved_tensors
6962
+ sources = list(saved[:ctx.count])
6963
+ banks = list(saved[ctx.count:2*ctx.count])
6964
+ query, valid, position, lse = saved[2*ctx.count:]
6965
+ dx, dw, dq = _msar_backward_op(sources, banks, query, valid, position,
6966
+ ctx.widths, ctx.eps, grad.contiguous(), lse)
6967
+ return dx, dw, dq, None, None, None, None
6968
+
6969
+
6970
+ _msar_read_op.register_autograd(_msar_autograd_backward, setup_context=_msar_setup)
6971
+
6972
+
6973
+ def _msar_read(sources, banks, query, valid, position, widths, eps):
6974
+ return _msar_read_op(sources, banks, query, valid, position, widths, eps)[0]
6975
+
6976
+
6977
+ class MSARRead(nn.Module):
6978
+ """One paper-style depth read with independent queries and H-pre mixing.
6979
+
6980
+ Sources are single-stream block summaries; the bank index identifies the
6981
+ embedding, last branch of each completed block, or previous branch for the
6982
+ current partial. RMS normalizations have no learned affine parameters.
6983
+ The paper specifies zero query/convolution initialization and scale 0.01,
6984
+ but not the mixer matrix or bias initialization: here they use the model's
6985
+ normal initializer and zero bias, respectively. The terminal merge uses
6986
+ the last branch input plus its output; no further expansion is performed.
6987
+ """
6988
+
6989
+ def __init__(self, config):
6990
+ super().__init__()
6991
+ self.widths = tuple(config.msar_kernel_sizes)
6992
+ self.eps = float(config.rms_norm_eps)
6993
+ self.backend = config.msar_backend
6994
+ m, d = len(self.widths) + 1, config.hidden_size
6995
+ self.query = nn.Parameter(torch.zeros(m, d))
6996
+ self.mixer_weight = nn.Parameter(torch.empty(m, m * d))
6997
+ nn.init.normal_(self.mixer_weight, std=config.initializer_range)
6998
+ self.mixer_bias = nn.Parameter(torch.zeros(m))
6999
+ self.mixer_scale = nn.Parameter(torch.tensor(0.01))
7000
+
7001
+ def forward(self, sources, partial, banks, branch, block_size, valid, position):
7002
+ source_list = list(sources)
7003
+ bank_ids = [0] + [i * block_size for i in range(1, len(sources))]
7004
+ if partial is not None:
7005
+ source_list.append(partial)
7006
+ bank_ids.append(branch)
7007
+ # Slices remain views of the shared bank parameter. Their pointers
7008
+ # let Triton reuse a specialization across branches with equal source counts.
7009
+ selected_banks = [banks[i] for i in bank_ids]
7010
+ if self.backend == "auto" and source_list[0].is_cuda:
7011
+ if triton is None:
7012
+ raise RuntimeError("MSAR on CUDA requires Triton; set msar_backend='torch' for reference.")
7013
+ streams = _msar_read(source_list, selected_banks, self.query, valid, position,
7014
+ list(self.widths), self.eps)
7015
+ else:
7016
+ streams = _msar_reference_read(source_list, selected_banks, self.query, valid,
7017
+ position, self.widths, self.eps)
7018
+ flat = streams.float().flatten(-2)
7019
+ normed = flat * torch.rsqrt(flat.square().mean(dim=-1, keepdim=True) + self.eps)
7020
+ gates = torch.sigmoid(self.mixer_scale.float() *
7021
+ F.linear(normed, self.mixer_weight.float()) +
7022
+ self.mixer_bias.float())
7023
+ return (gates.unsqueeze(-1) * streams.float()).sum(dim=-2).to(streams.dtype)
7024
+
7025
+
7026
+ def _msar_start_branch(sources, partial, branch, block_size):
7027
+ """Publish a completed single-stream block before a new block starts."""
7028
+ if branch and branch % block_size == 0:
7029
+ if partial is None:
7030
+ raise RuntimeError("MSAR block boundary is missing its partial summary.")
7031
+ return sources + (partial,), None
7032
+ return sources, partial
7033
+
7034
+
7035
  class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7036
  """
7037
  Decoder layer with standard residual connections.
 
7201
  # Pseudo-queries are initialized to ZERO so AttnRes starts as uniform
7202
  # average (equivalent to standard residual mean) and training volatility
7203
  # is avoided. This is critical per the paper's ablation.
7204
+ self.use_msar = bool(getattr(config, "use_msar", False))
7205
+ self.msar_block_branches = config.msar_block_branches
7206
+ self.msar_attn_read = MSARRead(config) if self.use_msar else None
7207
+ self.msar_mlp_read = MSARRead(config) if self.use_msar else None
7208
  self.use_attn_res = getattr(config, "use_attn_res", False)
7209
  if self.use_attn_res:
7210
  self.attn_res_query_attn = nn.Parameter(torch.zeros(config.hidden_size))
 
7637
  first_layer_fan: Optional[torch.Tensor] = None,
7638
  attn_res_sources: Optional[list] = None,
7639
  attn_res_partial: Optional[torch.Tensor] = None,
7640
+ msar_sources=None,
7641
+ msar_partial=None,
7642
+ msar_banks=None,
7643
+ msar_valid=None,
7644
+ msar_position=None,
7645
  output_attentions: Optional[bool] = False,
7646
  repo_rope_args: Optional[Tuple[torch.Tensor, float]] = None,
7647
  position_ids: Optional[torch.LongTensor] = None,
 
7667
  # attn_res_partial carries the intra-block standard residual that
7668
  # connects the attention and MLP sublayers within this layer.
7669
  # When inactive, flow is identical to the original.
7670
+ if self.use_msar:
7671
+ branch = 2 * self.layer_idx
7672
+ msar_sources, msar_partial = _msar_start_branch(
7673
+ msar_sources, msar_partial, branch, self.msar_block_branches)
7674
+ h_attn = self.msar_attn_read(msar_sources, msar_partial, msar_banks,
7675
+ branch, self.msar_block_branches, msar_valid, msar_position)
7676
+ residual_attn = msar_partial
7677
+ elif (
7678
  self.use_attn_res
7679
  and attn_res_sources is not None
7680
  and attn_res_partial is not None
 
7724
  )
7725
 
7726
  # ── Residual junction (attention sublayer) ────────────────────────
7727
+ if self.use_msar:
7728
+ msar_partial = hidden_states if msar_partial is None else msar_partial + hidden_states
7729
+ attn_aug = msar_partial
7730
+ elif self.use_laurel:
7731
  attn_aug = self._laurel_residual(
7732
  residual_attn,
7733
  hidden_states,
 
7745
  # After attention, the partial sum is updated with h_tilde.
7746
  # The pre-MLP AttnRes attends over the same sources but with h_tilde
7747
  # as the current partial — capturing the within-layer attention output.
7748
+ if self.use_msar:
7749
+ branch = 2 * self.layer_idx + 1
7750
+ msar_sources, msar_partial = _msar_start_branch(
7751
+ msar_sources, msar_partial, branch, self.msar_block_branches)
7752
+ h_mlp = self.msar_mlp_read(msar_sources, msar_partial, msar_banks,
7753
+ branch, self.msar_block_branches, msar_valid, msar_position)
7754
+ residual_mlp = msar_partial
7755
+ elif self.use_attn_res and attn_res_sources is not None:
7756
  h_mlp = self._attn_res(
7757
  attn_res_sources,
7758
  h_tilde,
 
7785
 
7786
  # ── Residual junction (MLP sublayer) ─────────────────────────────
7787
  # LAuReL augments the base MLP residual (residual_mlp + delta_m).
7788
+ if self.use_msar:
7789
+ msar_partial = delta_m if msar_partial is None else msar_partial + delta_m
7790
+ # Representative intermediate state for existing layer diagnostics.
7791
+ mlp_aug = h_mlp + delta_m
7792
+ elif self.use_laurel:
7793
  mlp_aug = self._laurel_residual(
7794
  residual_mlp,
7795
  delta_m,
 
7804
  hidden_states = self.gpas_mlp(mlp_aug) if self.use_gpas else mlp_aug
7805
 
7806
  outputs = (hidden_states,)
7807
+ if self.use_msar:
7808
+ outputs += (msar_sources, msar_partial)
7809
  if jtok_aux_stats is not None:
7810
  outputs += jtok_aux_stats
7811
  if output_attentions:
 
8515
  for layer_idx in range(config.num_hidden_layers)
8516
  ]
8517
  )
8518
+ self.msar_banks = (
8519
+ nn.Parameter(torch.zeros(2 * config.num_hidden_layers,
8520
+ sum(config.msar_kernel_sizes), config.hidden_size))
8521
+ if config.use_msar else None
8522
+ )
8523
  self.dagformer = (DAGFormerTopology(config, self.layers)
8524
  if bool(getattr(config, "use_dagformer", False)) else None)
8525
  self.use_siamesenorm = bool(getattr(config, "use_siamesenorm", False))
 
8653
  )
8654
  return inputs_embeds, jtok_z_tilde
8655
 
8656
+ def prepare_patch_inputs(self, input_ids, attention_mask, *, patch_size, meap_active=False,
8657
+ return_token_features=False):
8658
  """Prepare differentiable core inputs without running decoder layers.
8659
 
8660
  Input IDs must already contain the selected MEAP replacements, while
8661
  clean future targets are maintained separately by the trainer. SBE and
8662
  the dedicated MEAP vector are composed before pooling. The returned
8663
  tuple is the pool_patch_inputs contract, including separate attention
8664
+ and geometry validity. When requested, the final item is the observed
8665
+ raw-token representation for lexical copy keys. The K=1 path shares
8666
+ the ordinary producer; no parameters or coordinate gradients are copied.
8667
  """
8668
  from cut_cross_entropy import pool_patch_inputs
8669
 
 
8674
  raise ValueError("Patch preparation requires an explicit boolean raw-token validity mask.")
8675
  embeddings, coordinates = self._compose_input_embeddings(input_ids, attention_mask, meap_active)
8676
  corruption = input_ids.eq(int(self.config.meap_mask_token_id)) if meap_active else None
8677
+ pooled = pool_patch_inputs(
8678
  embeddings, attention_mask, patch_size=patch_size,
8679
  coordinates=coordinates, corruption_mask=corruption,
8680
  )
8681
+ return (*pooled, embeddings) if return_token_features else pooled
8682
 
8683
  def forward(
8684
  self,
 
8942
  # In both modes, attn_res_partial is the current intra-block accumulated
8943
  # hidden state that connects the attn and MLP sublayers and flows between
8944
  # decoder layers within a block.
8945
+ use_msar = bool(getattr(self.config, "use_msar", False))
8946
+ if use_msar:
8947
+ msar_sources, msar_partial = (hidden_states,), None
8948
+ if attention_mask is not None and attention_mask.ndim != 2:
8949
+ raise ValueError("MSAR requires a 2-D token validity mask.")
8950
+ msar_valid = (attention_mask.to(dtype=torch.bool).contiguous()
8951
+ if attention_mask is not None else
8952
+ torch.ones(hidden_states.shape[:2], dtype=torch.bool,
8953
+ device=hidden_states.device))
8954
+ msar_position = position_ids.expand(hidden_states.shape[:2]).contiguous()
8955
+ else:
8956
+ msar_sources = msar_partial = msar_valid = msar_position = None
8957
  use_attn_res = getattr(self.config, "use_attn_res", False)
8958
  attn_res_sources = None
8959
  attn_res_partial = None
 
9041
  first_layer_fan=self.first_layer_fan,
9042
  attn_res_sources=attn_res_sources,
9043
  attn_res_partial=attn_res_partial if use_attn_res else None,
9044
+ msar_sources=msar_sources, msar_partial=msar_partial,
9045
+ msar_banks=self.msar_banks, msar_valid=msar_valid,
9046
+ msar_position=msar_position,
9047
  output_attentions=output_attentions,
9048
  repo_rope_args=repo_rope_args,
9049
  position_ids=position_ids,
 
9056
  **kwargs,
9057
  )
9058
  hidden_states = layer_outputs[0]
9059
+ if use_msar:
9060
+ msar_sources, msar_partial = layer_outputs[1:3]
9061
+ extras_start = 3
9062
+ # The last branch's h_mlp + delta_m is the terminal
9063
+ # residual merge. No new source expansion is applied.
9064
+ else:
9065
+ extras_start = 1
9066
 
9067
  if collect_residual:
9068
  packed = residual_dynamics(before_x.detach(), hidden_states.detach(),
 
9175
 
9176
 
9177
  def _lm_perplexity_sums(labels, ce, mile_delta, *, pad_token_id, patch_targets,
9178
+ mile_enabled, vocab_size=None):
9179
+ """Detached sufficient statistics for NTP CE and MiLe pseudo-perplexity.
9180
 
9181
  For N valid supervised targets, return (N*CE, N*MiLe, N).
9182
  MiLe = CE + reweighting_delta, excluding mu and all auxiliary objectives.
 
9194
  valid = targets != -100
9195
  if pad_token_id is not None:
9196
  valid = valid & (targets != pad_token_id)
9197
+ if vocab_size is not None:
9198
+ valid = valid & (targets >= 0) & (targets < vocab_size)
9199
  count = valid.sum().to(dtype=torch.float32)
9200
  ce = ce.detach().float()
9201
  mile = ce + mile_delta.detach().float() if mile_enabled else ce
 
10994
  return total, mse_loss, kl_loss
10995
 
10996
 
10997
+ _COPY_PASTE_MAX_LOGIT_ELEMENTS = 1 << 24
10998
+
10999
+
11000
+ class CopyPasteHead(nn.Module):
11001
+ """Causal vocabulary/pointer mixture from Godey and Artzi (2026).
11002
+
11003
+ A separate gate vector is equivalent to the paper's extra <copy> output
11004
+ row without adding an ID to the tokenizer. The learned sink returns copy
11005
+ probability to the ordinary LM. Input IDs are those actually observed by
11006
+ the backbone, including any MEAP corruption. For pooled patches, all raw
11007
+ tokens become visible together. By default they share the patch key;
11008
+ optional lexical keys use ``k_i = W_k h_patch + U(V e_i)``, where ``e_i``
11009
+ is the actual post-MEAP input embedding. The automatic rank depends only on
11010
+ hidden size. No clean target is used to construct a copy key.
11011
+
11012
+ CUDA training delegates to CCE's sparse Copy-Paste kernels without a
11013
+ [batch, sequence, vocabulary] tensor. The chunked Torch implementation
11014
+ remains the CPU/reference path; inference still returns dense logits.
11015
+ """
11016
+
11017
+ def __init__(self, hidden_size: int, lexical_keys: bool = False):
11018
+ super().__init__()
11019
+ self.query = nn.Linear(hidden_size, hidden_size, bias=False)
11020
+ self.key = nn.Linear(hidden_size, hidden_size, bias=False)
11021
+ self.copy_gate = nn.Linear(hidden_size, 1, bias=False)
11022
+ self.sink_key = nn.Parameter(torch.zeros(hidden_size))
11023
+ self.scale = hidden_size ** -0.5
11024
+ rank = min(hidden_size, max(4, hidden_size // 16))
11025
+ self.lexical_down = nn.Linear(hidden_size, rank, bias=False) if lexical_keys else None
11026
+ self.lexical_up = nn.Linear(rank, hidden_size, bias=False) if lexical_keys else None
11027
+
11028
+ def _parts(self, hidden, query, keys, source_ids, source_mask, ends, weight, bias):
11029
+ logits = F.linear(hidden, weight, bias).float()
11030
+ lse = torch.logsumexp(logits, dim=-1)
11031
+ gate_delta = self.copy_gate(hidden).squeeze(-1).float() - lse
11032
+ scores = torch.matmul(query.float(), keys.float().transpose(-1, -2)) * self.scale
11033
+ source_position = torch.arange(source_ids.shape[1], device=source_ids.device)
11034
+ allowed = source_mask[:, None, :] & (source_position[None, None, :] <= ends[None, :, None])
11035
+ scores = scores.masked_fill(~allowed, -torch.inf)
11036
+ sink = torch.matmul(query.float(), self.sink_key.float()) * self.scale
11037
+ normalizer = torch.logsumexp(torch.cat((sink.unsqueeze(-1), scores), dim=-1), dim=-1)
11038
+ log_gate = -F.softplus(-gate_delta)
11039
+ log_base_weight = torch.logaddexp(-F.softplus(gate_delta), log_gate + sink - normalizer)
11040
+ return logits, lse, scores, normalizer, log_gate, log_base_weight
11041
+
11042
+ @staticmethod
11043
+ def _distribution(logits, lse, scores, normalizer, log_gate, log_base_weight, source_ids):
11044
+ probs = torch.exp(logits - lse.unsqueeze(-1) + log_base_weight.unsqueeze(-1))
11045
+ copy_probs = torch.exp(scores - normalizer.unsqueeze(-1) + log_gate.unsqueeze(-1))
11046
+ indices = source_ids[:, None, :].expand(-1, scores.shape[1], -1)
11047
+ return probs.scatter_add(-1, indices, copy_probs)
11048
+
11049
+ def _loss_chunk(self, hidden, query, keys, source_ids, source_mask, ends,
11050
+ targets, weight, bias, need_entropy, need_topk):
11051
+ logits, lse, scores, norm, log_gate, log_base = self._parts(
11052
+ hidden, query, keys, source_ids, source_mask, ends, weight, bias)
11053
+ valid = (targets >= 0) & (targets < weight.shape[0])
11054
+ safe_targets = targets.clamp(0, weight.shape[0] - 1)
11055
+ target_logits = logits.gather(-1, safe_targets)
11056
+ matches = ((source_ids[:, None, None, :] == safe_targets[..., None])
11057
+ & torch.isfinite(scores).unsqueeze(-2))
11058
+ # Avoid logsumexp(-inf, ..., -inf), whose backward is undefined. Rows
11059
+ # without a match use a harmless finite reduction and then receive an
11060
+ # exact -inf log-probability, so FP16 needs no overflowing sentinel.
11061
+ has_match = matches.any(-1)
11062
+ matched_scores = scores.unsqueeze(-2).masked_fill(~matches, -torch.inf)
11063
+ matched_scores = torch.where(has_match.unsqueeze(-1), matched_scores,
11064
+ torch.zeros_like(matched_scores))
11065
+ copy_target = torch.where(
11066
+ has_match, torch.logsumexp(matched_scores, dim=-1) - norm.unsqueeze(-1),
11067
+ torch.full_like(target_logits, -torch.inf))
11068
+ log_probability = torch.logaddexp(
11069
+ log_base.unsqueeze(-1) + target_logits - lse.unsqueeze(-1),
11070
+ log_gate.unsqueeze(-1) + copy_target,
11071
+ )
11072
+ nll = torch.where(valid, -log_probability, torch.zeros_like(log_probability))
11073
+ entropy = torch.zeros_like(lse)
11074
+ counts = logits.new_zeros(5)
11075
+ with torch.no_grad():
11076
+ # Sums are weighted by valid target slots, so partial patches and
11077
+ # microbatches can be merged without averaging batch means.
11078
+ c = log_gate.exp()
11079
+ sink_score = torch.matmul(query.float(), self.sink_key.float()) * self.scale
11080
+ s = (sink_score - norm).exp()
11081
+ row_count = valid.sum(-1)
11082
+ responsibility = (log_gate.unsqueeze(-1) + copy_target - log_probability).exp()
11083
+ copy_metrics = torch.stack((
11084
+ (c * row_count).sum(), (s * row_count).sum(),
11085
+ (c * (1.0 - s) * row_count).sum(),
11086
+ torch.where(valid, responsibility, 0.0).sum(),
11087
+ (valid & has_match).sum(), valid.sum(),
11088
+ )).float()
11089
+ if need_entropy or need_topk:
11090
+ with torch.no_grad():
11091
+ probs = self._distribution(
11092
+ logits, lse, scores, norm, log_gate, log_base, source_ids)
11093
+ if need_entropy:
11094
+ entropy = -(probs * probs.clamp_min(torch.finfo(torch.float32).tiny).log()).sum(-1)
11095
+ entropy = entropy.clamp(0.0, math.log(weight.shape[0]))
11096
+ if need_topk:
11097
+ target = safe_targets[..., 0]
11098
+ target_prob = probs.gather(-1, target.unsqueeze(-1)).squeeze(-1)
11099
+ finite = torch.isfinite(target_prob)
11100
+ keep = valid[..., 0] & finite
11101
+ ids = torch.arange(weight.shape[0], device=probs.device)
11102
+ ahead = ((probs > target_prob.unsqueeze(-1)) |
11103
+ ((probs == target_prob.unsqueeze(-1)) & (ids < target.unsqueeze(-1))))
11104
+ rank = ahead.sum(-1)
11105
+ counts = torch.stack(((keep & (rank == 0)).sum(),
11106
+ (keep & (rank < 3)).sum(),
11107
+ keep.new_zeros((), dtype=torch.long),
11108
+ valid[..., 0].sum(),
11109
+ (valid[..., 0] & ~finite).sum())).float()
11110
+ return nll, entropy, counts, copy_metrics
11111
+
11112
+ @getattr(torch.compiler, "nested_compile_region", lambda function: function)
11113
+ def _loss_chunk_reusable(self, *args):
11114
+ """Allow reuse of a compiled tile region when PyTorch supports it."""
11115
+ return self._loss_chunk(*args)
11116
+
11117
+ def loss(self, hidden, source_ids, source_mask, targets, lm_head,
11118
+ *, patch_size, group_fraction, mile_gamma, use_mile, use_mu,
11119
+ mu_lambda, need_topk, source_token_features=None):
11120
+ """Mixed loss with optional lexical keys from observed token features."""
11121
+ rows = targets.shape[1]
11122
+ if rows == 0:
11123
+ zero = hidden.sum() * 0.0
11124
+ mu = (float(mu_lambda) * lm_head.weight.float().mean(0).square().sum()
11125
+ if use_mu else zero)
11126
+ return (zero + mu, zero.detach(), zero.detach(), mu.detach(),
11127
+ zero.new_zeros(5) if need_topk else None, zero.new_zeros(6))
11128
+ keys = self.key(hidden).repeat_interleave(patch_size, dim=1)
11129
+ if source_token_features is not None:
11130
+ if self.lexical_down is None or source_token_features.shape != keys.shape:
11131
+ raise ValueError("Lexical copy features must match the raw source token domain.")
11132
+ keys = keys + self.lexical_up(self.lexical_down(source_token_features))
11133
+ query = self.query(hidden[:, :rows])
11134
+ if hidden.is_cuda:
11135
+ if not _CCE_COPY_PASTE_HAS_METRICS:
11136
+ raise ImportError(
11137
+ "CUDA Copy-Paste training requires a cut-cross-entropy installation "
11138
+ "with Copy-Paste metric support. Update the CCE repository first."
11139
+ )
11140
+ selected = hidden[:, :rows]
11141
+ gate = self.copy_gate(selected).squeeze(-1).float()
11142
+ sink = torch.matmul(query.float(), self.sink_key.float()) * self.scale
11143
+ return _cce_copy_paste_loss(
11144
+ selected, lm_head.weight, lm_head.bias, query, keys, source_ids,
11145
+ source_mask, targets, gate, sink, patch_size=patch_size,
11146
+ group_fraction=group_fraction, mile_gamma=mile_gamma, use_mile=use_mile,
11147
+ use_mu=use_mu, mu_lambda=mu_lambda, need_topk=need_topk,
11148
+ return_copy_metrics=True,
11149
+ )
11150
+ # Bound each temporary FP32 logit tile by ~64 MiB when one row fits.
11151
+ # The value is a memory ceiling, not a model-specific geometry.
11152
+ tile = max(1, min(rows, _COPY_PASTE_MAX_LOGIT_ELEMENTS // max(1, hidden.shape[0] * lm_head.weight.shape[0])))
11153
+ repeated_tiles = tile < rows
11154
+ if repeated_tiles:
11155
+ # invoke_subgraph rejects aliased inputs (labels can be a view of
11156
+ # input_ids); one compact label copy avoids that compiler failure.
11157
+ targets = targets.clone()
11158
+ chunk_function = self._loss_chunk_reusable if repeated_tiles else self._loss_chunk
11159
+ nll_chunks, entropy_chunks, count_chunks, copy_metric_chunks = [], [], [], []
11160
+ for start in range(0, rows, tile):
11161
+ stop = min(start + tile, rows)
11162
+ ends = (torch.arange(start, stop, device=hidden.device) + 1) * patch_size - 1
11163
+ args = (hidden[:, start:stop], query[:, start:stop], keys,
11164
+ source_ids, source_mask, ends, targets[:, start:stop],
11165
+ lm_head.weight, lm_head.bias, use_mile, need_topk)
11166
+ if torch.is_grad_enabled() and self.training:
11167
+ values = _activation_checkpoint(chunk_function, *args, use_reentrant=False)
11168
+ else:
11169
+ values = chunk_function(*args)
11170
+ nll_chunks.append(values[0])
11171
+ entropy_chunks.append(values[1])
11172
+ count_chunks.append(values[2])
11173
+ copy_metric_chunks.append(values[3])
11174
+ nll = torch.cat(nll_chunks, dim=1)
11175
+ valid = (targets >= 0) & (targets < lm_head.weight.shape[0])
11176
+ valid_count = valid.sum().to(torch.float32)
11177
+ count = valid_count.clamp_min(1.0)
11178
+ ce = nll.sum() / count
11179
+ weighted = ce
11180
+ if use_mile:
11181
+ entropy = torch.cat(entropy_chunks, dim=1).detach()
11182
+ row_count = valid.sum(-1).to(torch.float32)
11183
+ active = row_count > 0
11184
+ log_base = torch.where(active, torch.log1p(entropy), 0.0)
11185
+ if group_fraction is None:
11186
+ raw = torch.exp(float(mile_gamma) * (log_base - log_base.amax()))
11187
+ scale = valid_count / (raw * row_count).sum().clamp_min(1e-20)
11188
+ weights = raw * scale
11189
+ else:
11190
+ group = group_fraction.float()
11191
+ other = 1.0 - group
11192
+ group_active = active & (group > 0)
11193
+ other_active = active & (other > 0)
11194
+ group_peak = torch.where(group_active, log_base, -torch.inf).amax()
11195
+ other_peak = torch.where(other_active, log_base, -torch.inf).amax()
11196
+ group_peak = torch.where(torch.isfinite(group_peak), group_peak, 0.0)
11197
+ other_peak = torch.where(torch.isfinite(other_peak), other_peak, 0.0)
11198
+ group_offset = torch.where(group_active, log_base - group_peak, 0.0)
11199
+ other_offset = torch.where(other_active, log_base - other_peak, 0.0)
11200
+ group_raw = torch.where(group_active, torch.exp(float(mile_gamma) * group_offset), 0.0)
11201
+ other_raw = torch.where(other_active, torch.exp(float(mile_gamma) * other_offset), 0.0)
11202
+ group_count = (row_count * group).sum()
11203
+ other_count = (row_count * other).sum()
11204
+ group_scale = group_count / (row_count * group * group_raw).sum().clamp_min(1e-20)
11205
+ other_scale = other_count / (row_count * other * other_raw).sum().clamp_min(1e-20)
11206
+ weights = group * group_raw * group_scale + other * other_raw * other_scale
11207
+ weighted = (nll * weights.unsqueeze(-1)).sum() / count
11208
+ mu = (float(mu_lambda) * lm_head.weight.float().mean(0).square().sum()
11209
+ if use_mu else ce.new_zeros(()))
11210
+ topk = torch.stack(count_chunks).sum(0) if need_topk else None
11211
+ return (weighted + mu, ce.detach(), (weighted - ce).detach(), mu.detach(), topk,
11212
+ torch.stack(copy_metric_chunks).sum(0).detach())
11213
+
11214
+ def logits(self, hidden, source_ids, source_mask, positions, lm_head, *, patch_size=1):
11215
+ """Return normalized log-probabilities for selected query positions."""
11216
+ keys = self.key(hidden).repeat_interleave(patch_size, dim=1)
11217
+ selected = hidden[:, positions]
11218
+ query = self.query(selected)
11219
+ ends = (positions + 1) * patch_size - 1
11220
+ parts = self._parts(selected, query, keys, source_ids, source_mask, ends,
11221
+ lm_head.weight, lm_head.bias)
11222
+ probs = self._distribution(*parts[:3], parts[3], parts[4], parts[5], source_ids)
11223
+ return probs.clamp_min(torch.finfo(torch.float32).tiny).log()
11224
+
11225
+
11226
  class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
11227
  """
11228
  Causal LM with NeoLLM backbone.
 
11236
  self.model = NeoLLMModel(config)
11237
  self.vocab_size = config.vocab_size
11238
  self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
11239
+ self.copy_paste_head = (
11240
+ CopyPasteHead(config.hidden_size,
11241
+ lexical_keys=bool(getattr(config, "copy_paste_lexical_keys", False)))
11242
+ if getattr(config, "use_copy_paste", False) else None
11243
+ )
11244
  self.nitp_projector = NITPProjector(config) if config.use_nitp else None
11245
  self.nitp_temporal_transition = (
11246
  NITPTemporalTransition(config)
 
11272
  "`ntp_loss_backend` must be 'cce' or 'liger'. "
11273
  f"Got {self.ntp_loss_backend!r}."
11274
  )
11275
+ # Copy-Paste computes MiLe and mu-loss in its own Torch path; those
11276
+ # CCE loss extensions are only needed when CCE computes the NTP loss.
11277
+ if self.copy_paste_head is None:
11278
+ if bool(getattr(config, "use_mile_loss", False)):
11279
+ _require_extended_cce_options("mile_enabled", "mile_gamma")
11280
+ if bool(getattr(config, "use_mu_loss", False)):
11281
+ _require_extended_cce_options("mu_loss_enabled", "mu_loss_lambda")
11282
  # The optional MEAP backend is checked only when training actually
11283
  # applies corruption. Clean training, evaluation, and inference remain
11284
  # usable without cut-cross-entropy even when loading a MEAP checkpoint.
 
11286
  self._last_ntp_ce_unweighted = None
11287
  self._last_lm_metric_sums = None
11288
  self._last_topk_metric_sums = None
11289
+ self._last_copy_metric_sums = None
11290
  self._last_mile_reweighting_delta = None
11291
  self._last_mu_loss = None
11292
  self._last_meap_mask_fraction = None
 
11405
  def get_dynamics_parameter_groups(self):
11406
  """Existing observers plus residual controllers; never duplicate the backbone.
11407
 
11408
+ Control modules share one sampled group per layer/method. VHA factors
11409
+ and Copy-Paste projections each have a separate sampled group. Groups
11410
+ are sampled around real optimizer updates by train.py, not in forward.
11411
  """
11412
  groups = {}
11413
  generator = getattr(self.model, "token_generator", None)
 
11415
  for name, parameter in generator.named_parameters():
11416
  if parameter.requires_grad:
11417
  groups[f"leviathan/{name}"] = (parameter,)
11418
+ if self.copy_paste_head is not None:
11419
+ for name, parameter in self.copy_paste_head.named_parameters():
11420
+ if parameter.requires_grad:
11421
+ groups[f"copy_paste/{name}"] = (parameter,)
11422
  for layer in self.model.layers:
11423
+ if layer.self_attn.use_vha_attention:
11424
+ for name in ("vha_premix", "vha_postmix_a", "vha_postmix_b"):
11425
+ parameter = getattr(layer.self_attn, name)
11426
+ if parameter.requires_grad:
11427
+ groups[f"vha/layer_{layer.layer_idx}/{name}"] = (parameter,)
11428
  module = getattr(layer, "jtok", None)
11429
  if module is not None:
11430
  kind = "jtokm" if module.use_mixture else "jtok"
 
11465
  self.lm_head = new_embeddings
11466
 
11467
  def _corrupt_meap_inputs(self, input_ids, attention_mask, meap_seed, *, return_mask):
11468
+ """Corrupt raw IDs while preserving the configured structural tokens.
11469
+
11470
+ The last valid position is removed before structural-token filtering so
11471
+ that a final EOS does not additionally exclude the preceding token.
11472
+ The disabled protection path retains the legacy MEAP behavior.
11473
+ """
11474
  if meap_mask_inputs is None:
11475
  raise ImportError("Active MEAP training requires the extended cut-cross-entropy package.")
11476
  if "return_metrics" not in inspect.signature(meap_mask_inputs).parameters:
 
11478
  mask_token_id = getattr(self.config, "meap_mask_token_id", None)
11479
  if mask_token_id is None:
11480
  raise ValueError("`meap_mask_token_id` is required when MEAP is enabled.")
11481
+ implementation = str(getattr(self.config, "meap_implementation", "triton"))
11482
+ if isinstance(meap_seed, torch.Tensor):
 
11483
  seed = meap_seed.to(device=input_ids.device, dtype=torch.long)
11484
+ if implementation == "torch":
11485
+ raise TypeError("MEAP's torch reference requires a Python integer seed; use triton for tensor seeds.")
11486
+ kernel_seed = seed
11487
  else:
11488
+ seed_value = int(getattr(self.config, "meap_seed", 0)) if meap_seed is None else int(meap_seed)
11489
+ seed = input_ids.new_tensor(seed_value, dtype=torch.long)
11490
+ kernel_seed = seed_value if implementation == "torch" else seed
11491
+
11492
+ eligible = (attention_mask.to(device=input_ids.device, dtype=torch.bool)
11493
+ if attention_mask is not None else None)
11494
+ exclude_last = bool(getattr(self.config, "meap_exclude_last", True))
11495
+ if bool(getattr(self.config, "meap_protect_structural_tokens", True)):
11496
+ if eligible is None:
11497
+ eligible = torch.ones_like(input_ids, dtype=torch.bool)
11498
+ if exclude_last and input_ids.shape[1] > 0:
11499
+ positions = torch.arange(input_ids.shape[1], device=input_ids.device)
11500
+ last_valid = torch.where(eligible, positions, -1).amax(dim=1, keepdim=True)
11501
+ eligible = eligible & positions.ne(last_valid)
11502
+ # The CCE kernel normally removes the last *eligible* token;
11503
+ # doing so after filtering EOS would drop a second position.
11504
+ exclude_last = False
11505
+ for value in (getattr(self.config, "pad_token_id", None),
11506
+ getattr(self.config, "bos_token_id", None),
11507
+ getattr(self.config, "eos_token_id", None), mask_token_id):
11508
+ for token_id in value if isinstance(value, (list, tuple)) else (value,):
11509
+ if token_id is not None:
11510
+ eligible = eligible & input_ids.ne(int(token_id))
11511
  result = meap_mask_inputs(
11512
  input_ids, int(mask_token_id), enabled=True,
11513
  mask_ratio=float(getattr(self.config, "meap_mask_ratio", 0.15)),
11514
+ eligible_mask=eligible, seed=kernel_seed, exclude_last=exclude_last,
 
 
11515
  return_mask=return_mask, return_metrics=True,
11516
+ implementation=implementation,
11517
  )
11518
  if return_mask:
11519
  corrupted, selected, metrics = result
 
11555
  input_ids, selected, metrics, seed = self._corrupt_meap_inputs(
11556
  input_ids, attention_mask, meap_seed, return_mask=True,
11557
  )
11558
+ need_lexical = (self.copy_paste_head is not None
11559
+ and self.copy_paste_head.lexical_down is not None and patch_size > 1)
11560
+ prepared_inputs = self.model.prepare_patch_inputs(
11561
  input_ids, attention_mask, patch_size=patch_size, meap_active=active,
11562
+ return_token_features=need_lexical,
11563
  )
11564
+ e, z, core, geometry, fraction = prepared_inputs[:5]
11565
  if selected is not None:
11566
  grouped_valid = attention_mask.unflatten(-1, (core.size(1), patch_size))
11567
  fraction = ((selected & attention_mask).unflatten(-1, (core.size(1), patch_size))
 
11580
  patch_aux_valid_mask=aux_valid,
11581
  prepared_meap_metrics=metrics, prepared_meap_seed=seed,
11582
  )
11583
+ if self.copy_paste_head is not None:
11584
+ # Preserve only the observed raw IDs and their original mask;
11585
+ # externally prepared token features must follow the same contract:
11586
+ # clean labels/features would leak MEAP-hidden content to the pointer.
11587
+ result.update(copy_input_ids=input_ids, copy_attention_mask=attention_mask)
11588
+ if need_lexical:
11589
+ result["copy_token_features"] = prepared_inputs[5]
11590
  if z is not None:
11591
  result.update(prepared_jtok_coordinates=z, prepared_jtok_valid_mask=geometry)
11592
  return result
 
11634
  prepared_meap_seed: Optional[torch.Tensor] = None,
11635
  raw_patch_size: Optional[int] = None,
11636
  patch_target_slots: int = 4,
11637
+ copy_input_ids: Optional[torch.LongTensor] = None,
11638
+ copy_attention_mask: Optional[torch.Tensor] = None,
11639
+ copy_token_features: Optional[torch.Tensor] = None,
11640
  **kwargs: Unpack[TransformersKwargs],
11641
  ) -> CausalLMOutputWithPast:
11642
  self._last_meap_mask_fraction = None
11643
  self._last_meap_masked_tokens = None
11644
  self._last_meap_seed = None
11645
+ copy_source_ids = copy_input_ids
11646
+ copy_source_mask = copy_attention_mask
11647
+ copy_source_features = copy_token_features
11648
+ copy_patch_size = 1
11649
+ if self.copy_paste_head is None and (copy_input_ids is not None or copy_attention_mask is not None
11650
+ or copy_token_features is not None):
11651
+ raise ValueError("Copy source IDs, mask and token features require use_copy_paste=True.")
11652
+ if copy_token_features is not None and not patch_training_enabled:
11653
+ raise ValueError("copy_token_features is only accepted for prepared patches.")
11654
 
11655
  if raw_patch_size is not None:
11656
  # Static K/shape signatures are warmed once. The changing optimizer
 
11658
  # so embedding/coordinate VJPs are part of the compiled backward.
11659
  if (patch_training_enabled or inputs_embeds is not None
11660
  or input_ids is None or labels is None or attention_mask is None
11661
+ or position_ids is not None or copy_input_ids is not None
11662
+ or copy_attention_mask is not None or copy_token_features is not None
11663
+ or any(value is not None for value in (
11664
  patch_group_fraction, patch_aux_valid_mask,
11665
  prepared_meap_metrics, prepared_meap_seed,
11666
  kwargs.get("prepared_jtok_coordinates"),
 
11672
  patch_size=raw_patch_size, target_slots=patch_target_slots,
11673
  meap_seed=meap_seed,
11674
  )
11675
+ if self.copy_paste_head is not None:
11676
+ copy_source_ids = prepared["copy_input_ids"]
11677
+ copy_source_mask = prepared["copy_attention_mask"]
11678
+ copy_source_features = prepared.get("copy_token_features")
11679
+ copy_patch_size = raw_patch_size
11680
  input_ids = None
11681
  inputs_embeds = prepared["inputs_embeds"]
11682
  attention_mask = prepared["attention_mask"]
 
11752
  self._last_meap_masked_tokens = masked_count.detach()
11753
  self._last_meap_seed = effective_meap_seed.detach()
11754
 
11755
+ if self.copy_paste_head is not None:
11756
+ if input_ids is not None:
11757
+ if copy_input_ids is not None or copy_attention_mask is not None:
11758
+ raise ValueError("Copy-Paste uses the observed input_ids and attention_mask; do not supply separate copy inputs too.")
11759
+ copy_source_ids = input_ids
11760
+ copy_source_mask = attention_mask
11761
+ elif not patch_training_enabled and copy_source_mask is None:
11762
+ copy_source_mask = attention_mask
11763
+ if copy_source_ids is None:
11764
+ raise ValueError("Copy-Paste with inputs_embeds requires the corresponding observed copy_input_ids.")
11765
+ if copy_source_ids.ndim != 2 or copy_source_ids.dtype not in (torch.int32, torch.int64):
11766
+ raise ValueError("Copy-Paste source IDs must be a 2D integer tensor.")
11767
+ if copy_source_mask is None:
11768
+ if patch_training_enabled and raw_patch_size is None:
11769
+ raise ValueError("Prepared patches require their raw copy_attention_mask.")
11770
+ copy_source_mask = torch.ones_like(copy_source_ids, dtype=torch.bool)
11771
+ if copy_source_mask.shape != copy_source_ids.shape or copy_source_mask.device != copy_source_ids.device:
11772
+ raise ValueError("Copy-Paste source mask must match the raw IDs in shape and device.")
11773
+ copy_source_mask = copy_source_mask.to(dtype=torch.bool)
11774
+ copy_source_ids = copy_source_ids.to(dtype=torch.long)
11775
+ if meap_enabled and bool(getattr(self.config, "meap_exclude_mask_from_copy", True)):
11776
+ # Use observed IDs only: never restore the clean target here.
11777
+ copy_source_mask = copy_source_mask & copy_source_ids.ne(int(self.config.meap_mask_token_id))
11778
+ core = inputs_embeds if inputs_embeds is not None else input_ids
11779
+ core_length = core.shape[1]
11780
+ if copy_source_ids.shape[0] != core.shape[0] or copy_source_ids.device != core.device:
11781
+ raise ValueError("Copy-Paste source IDs must match the model batch and device.")
11782
+ if patch_training_enabled and raw_patch_size is None:
11783
+ if core_length == 0 or copy_source_ids.shape[1] % core_length:
11784
+ raise ValueError("Copy-Paste raw source length must be divisible by patch core length.")
11785
+ copy_patch_size = copy_source_ids.shape[1] // core_length
11786
+ if copy_patch_size < 1 or copy_source_ids.shape[1] != core_length * copy_patch_size:
11787
+ raise ValueError("Copy-Paste source length does not match the active token/patch geometry.")
11788
+ if patch_training_enabled and copy_patch_size > 1 and self.copy_paste_head.lexical_down is not None:
11789
+ if (copy_source_features is None
11790
+ or copy_source_features.shape != (*copy_source_ids.shape, self.config.hidden_size)
11791
+ or copy_source_features.device != copy_source_ids.device
11792
+ or not copy_source_features.is_floating_point()):
11793
+ raise ValueError("Lexical Copy-Paste requires observed raw-token embedding features.")
11794
+
11795
  tweo_enabled = (
11796
  bool(getattr(self.config, "use_tweo", False))
11797
  and labels is not None
 
11919
  self._last_ntp_ce_unweighted = None
11920
  self._last_lm_metric_sums = None
11921
  self._last_topk_metric_sums = None
11922
+ self._last_copy_metric_sums = None
11923
  self._last_mile_reweighting_delta = None
11924
  self._last_mu_loss = None
11925
  self._last_tweo_loss = None
 
11960
  self._last_stack_gate_effective_slots = None
11961
  self._last_total_loss = None
11962
  if labels is not None:
11963
+ if self.copy_paste_head is not None:
11964
+ targets = _prepare_lm_labels(labels, hidden_states.device, self.config.pad_token_id)
11965
+ if not patch_training_enabled:
11966
+ targets = targets[:, 1:].unsqueeze(-1)
11967
+ use_mile = bool(getattr(self.config, "use_mile_loss", False))
11968
+ group_fraction = None
11969
+ if use_mile and patch_training_enabled:
11970
+ group_fraction = patch_group_fraction[:, :-1]
11971
+ elif use_mile and meap_selected_mask is not None:
11972
+ group_fraction = meap_selected_mask[:, :-1]
11973
+ collect_topk = (bool(getattr(self.config, "use_topk_metrics", False))
11974
+ and (not patch_training_enabled or raw_patch_size == 1))
11975
+ ntp_loss, ce, delta, mu, topk, copy_metrics = self.copy_paste_head.loss(
11976
+ hidden_states, copy_source_ids, copy_source_mask, targets, self.lm_head,
11977
+ patch_size=copy_patch_size, group_fraction=group_fraction,
11978
+ mile_gamma=float(getattr(self.config, "mile_loss_gamma", 1.0)),
11979
+ use_mile=use_mile, use_mu=bool(getattr(self.config, "use_mu_loss", False)),
11980
+ mu_lambda=float(getattr(self.config, "mu_loss_lambda", 1e-4)),
11981
+ need_topk=collect_topk,
11982
+ source_token_features=copy_source_features,
11983
+ )
11984
+ self._last_ntp_ce_unweighted = ce
11985
+ self._last_mile_reweighting_delta = delta
11986
+ self._last_mu_loss = mu
11987
+ self._last_topk_metric_sums = topk
11988
+ self._last_copy_metric_sums = copy_metrics
11989
+ elif self.ntp_loss_backend == "liger":
11990
  ntp_loss = compute_liger_loss(
11991
  hidden_states,
11992
  labels,
 
12062
  patch_targets=patch_training_enabled,
12063
  mile_enabled=(self.ntp_loss_backend == "cce"
12064
  and bool(getattr(self.config, "use_mile_loss", False))),
12065
+ vocab_size=(self.vocab_size if self.copy_paste_head is not None else None),
12066
  )
12067
  loss = ntp_loss
12068
 
 
12402
  if isinstance(logits_to_keep, int)
12403
  else logits_to_keep
12404
  )
12405
+ if self.copy_paste_head is not None:
12406
+ positions = torch.arange(hidden_states.shape[1], device=hidden_states.device)[slice_indices].reshape(-1)
12407
+ logits = self.copy_paste_head.logits(
12408
+ hidden_states, copy_source_ids, copy_source_mask, positions, self.lm_head,
12409
+ patch_size=copy_patch_size)
12410
+ else:
12411
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
12412
 
12413
  return CausalLMOutputWithPast(
12414
  loss=loss,