Update NeoLLM model and configuration sources
#1
by KitsuVp - opened
- configuration_neollm.py +101 -10
- 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 ``
|
| 65 |
-
Enable MiLe Loss (Su et al., 2026, arXiv:2310.19531)
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 ``
|
| 71 |
Enable the output-embedding centering regularizer from Stollenwerk
|
| 72 |
-
et al. (2026, arXiv:2601.02031)
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 `
|
| 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 |
-
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 5490 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
|
|
|
|
|
|
|
|
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 8202 |
-
|
|
|
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
| 10554 |
-
|
| 10555 |
-
if
|
| 10556 |
-
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 10683 |
-
|
|
|
|
| 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 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 10741 |
-
|
| 10742 |
-
elif isinstance(meap_seed, torch.Tensor):
|
| 10743 |
seed = meap_seed.to(device=input_ids.device, dtype=torch.long)
|
|
|
|
|
|
|
|
|
|
| 10744 |
else:
|
| 10745 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 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=
|
| 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 |
-
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|