KitsuVp commited on
Commit
a3f83b3
·
verified ·
1 Parent(s): 0b58cee

Model save

Browse files
README.md CHANGED
@@ -1,532 +1,78 @@
1
  ---
2
- language: en
3
- license: apache-2.0
4
  tags:
5
- - causal-lm
6
- - research
7
- - bf16
8
- - attention
9
- - normalization
10
- - neollm
11
- datasets:
12
- - HuggingFaceFW/fineweb-edu
13
  ---
14
 
15
- # NeoLLM
16
-
17
- NeoLLM is a **85.09 M parameter** decoder-only language model trained from scratch on
18
- [FineWeb-Edu](https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu) with
19
- **BF16** compute, completing training in approximately
20
- **~0h 47m** on **NVIDIA RTX PRO 6000 Blackwell Server Edition**.
21
- It integrates a collection of recently published attention and normalization techniques
22
- into a single architecture, with the goal of studying how they interact during
23
- pretraining. The model is actively being developed and the current checkpoint represents
24
- an intermediate training state.
25
-
26
- > **Author / contact:** [@Kyokopom](https://x.com/Kyokopom) on X
27
- > **Repository:** [KitsuVp/NeoLLM](https://huggingface.co/KitsuVp/NeoLLM)
28
-
29
- ---
30
-
31
- ## Architecture
32
-
33
- NeoLLM is a decoder-only transformer with the following configuration:
34
-
35
- | Parameter | Value |
36
- |---|---|
37
- | Hidden size | 512 |
38
- | Layers | 12 |
39
- | Attention heads | 8 |
40
- | KV heads (GQA) | 4 |
41
- | Head dim | 64 |
42
- | Intermediate size | 1536 |
43
- | Vocabulary | LiquidAI/LFM2.5-1.2B-Thinking tokenizer (64,402 tokens) |
44
- | Context length | 512 tokens |
45
- | Leviathan | enabled=True; d_seed=128, modes=8, knots=16 |
46
- | JTok / JTok-M | enabled=False / False; Torch continuous surfaces, experts=5, top_k=2 |
47
- | DAGFormer | enabled=False; {'predictor_dim': 256, 'predictor_layers': 2, 'predictor_heads': 4, 'local_dim': 128, 'input_source': 'tokens'} |
48
- | Virtual Head Attention | enabled=False; query/KV heads follow the configured geometry |
49
- | Copy-Paste LM | enabled=True; causal vocabulary/pointer mixture |
50
-
51
- ### Parameter breakdown
52
-
53
- | Parameter bucket | Count |
54
- |---|---|
55
- | **Total parameters** | 85.09M (85,093,304) |
56
- | **Embedding parameters** (tied) | 32.97M (32,973,824) |
57
- | **Non-embedding parameters** | 52.12M (52,119,480) |
58
- | **Effective trainable parameters** | 85.09M (85,093,304) |
59
-
60
- > Weight tying is **enabled**: the input embedding matrix and the language-model head
61
- > share the same parameters, so the effective trainable budget is
62
- > `total − embed = 52.12M`.
63
-
64
- ### Integrated techniques
65
-
66
- NeoLLM combines architecture modules, optional auxiliary objectives, and
67
- training-time optimizer/stability components from the following papers.
68
-
69
- **Embedding and token representation**
70
-
71
- - **Learnable Multipliers** ([arXiv:2601.04890](https://arxiv.org/abs/2601.04890)) — Adds
72
- per-row and per-column learnable scalar parameters to selected matrix layers and, when
73
- enabled, embeddings.
74
- - **Leviathan** ([arXiv:2601.22040](https://arxiv.org/abs/2601.22040)) — Optional
75
- continuous token embedding generator that can replace the discrete input lookup table.
76
- - **Leviathan-JTok / JTok-M** ([arXiv:2602.00800](https://arxiv.org/abs/2602.00800)) —
77
- Optional continuous adaptation of JTok over the shared Leviathan coordinate. JTok
78
- applies one normalized surface as a multiplicative MLP gate; JTok-M routes a
79
- Sigmoid+TopK mixture of continuous surfaces and adds the paper's depth-scaled
80
- residual. Runtime ``jtok_kernel_backend`` selects the PyTorch reference or the
81
- CCE Triton adapter; the JTok surfaces remain outside MXFP8 conversion.
82
- - **KHRONOS** ([arXiv:2505.13315](https://arxiv.org/abs/2505.13315)) — Kernel/basis
83
- reference used by the Leviathan continuous token generator implementation.
84
- - **Spelling Bee Embeddings** ([arXiv:2601.18030](https://arxiv.org/abs/2601.18030)) —
85
- Augments token embeddings with character-level spelling information.
86
- - **Token Embedding Manifold analysis** ([arXiv:2504.01002](https://arxiv.org/abs/2504.01002)) —
87
- Reference motivation for treating token embeddings as structured objects rather than
88
- unconstrained lookup rows.
89
-
90
- **Attention, positions, and output projection**
91
-
92
- - **Virtual Head Attention** ([arXiv:2610.17959](https://arxiv.org/abs/2610.17959)) — Optional
93
- group-specific query premix and low-rank head postmix. NeoLLM shares its gated query
94
- projection across corresponding head slots in KV groups and applies postmix after the gate; inference weights are
95
- not automatically folded into standard GQA projections.
96
- - **Copy-Paste LM** ([arXiv:2610.19247](https://arxiv.org/abs/2610.19247)) — Optional
97
- causal pointer and sink mixed with the vocabulary distribution. CUDA training
98
- uses CCE's sparse Copy-Paste kernels; inference returns dense mixed logits.
99
- - **FAN** ([arXiv:2502.21309](https://arxiv.org/abs/2502.21309)) — Fourier Analysis Networks.
100
- A portion of the projection channels are dedicated to periodic cosine/sine features.
101
- - **MEA** ([arXiv:2601.19611](https://arxiv.org/abs/2601.19611)) — Explicit Multi-head
102
- Attention. Adds small learnable interaction matrices between attention heads for K and V.
103
- - **LUCID** ([arXiv:2602.10410](https://arxiv.org/abs/2602.10410)) — Applies a learned
104
- lower-triangular preconditioner to V before attention, decorrelating value representations
105
- across positions.
106
- - **Affine-Scaled Attention** ([arXiv:2602.23057](https://arxiv.org/abs/2602.23057)) — Adds
107
- two learnable per-head scalars (α and β) to the softmax weights:
108
- `[α·softmax(QKᵀ) + β]·V`.
109
- - **XSA** ([arXiv:2603.09078](https://arxiv.org/abs/2603.09078)) — Exclusive Self Attention.
110
- After computing attention, removes the component of the output aligned with the token's
111
- own value vector.
112
- - **Directional Routing** ([arXiv:2603.14923](https://arxiv.org/abs/2603.14923)) — Each head
113
- learns K=4 directions in the output space; a learned router suppresses the attention output
114
- along each direction per input.
115
- - **Gated Attention** ([arXiv:2505.06708](https://arxiv.org/abs/2505.06708)) — A sigmoid gate
116
- is applied to the attention output before the output projection, introducing non-linearity
117
- and preventing attention sinks.
118
- - **Momentum Attention** ([arXiv:2411.03884](https://arxiv.org/abs/2411.03884)) — Modifies Q
119
- and K by subtracting a fraction of the previous position's Q and K values (causal
120
- first-difference).
121
- - **Interleaved Head Attention / IHA** ([arXiv:2602.21371](https://arxiv.org/abs/2602.21371)) —
122
- Builds pseudo-heads from learned cross-head mixtures to create multiple attention patterns
123
- per original head.
124
- - **REPO** ([arXiv:2512.14391](https://arxiv.org/abs/2512.14391)) — Context re-positioning
125
- module that learns contextual position coordinates above a configurable start layer.
126
- - **GRAPE** ([arXiv:2512.07805](https://arxiv.org/abs/2512.07805)) — Group representational
127
- position encoding used by the REPO-GRAPE positional path.
128
- - **GOAT priors** ([arXiv:2601.15380](https://arxiv.org/abs/2601.15380)) — Optional
129
- factorized attention log-prior channels inspired by trainable attention priors.
130
- - **Hadamard output projection** ([arXiv:2603.08343](https://arxiv.org/abs/2603.08343)) —
131
- Replaces dense attention output projection with a structured Hadamard transform plus
132
- lightweight scaling.
133
-
134
- **Normalization, residual flow, and MLP**
135
-
136
- - **SeeDNorm** ([arXiv:2510.22777](https://arxiv.org/abs/2510.22777)) — Applied to Q and K
137
- projections. Dynamically rescales normalization from the input's own statistics.
138
- - **LayerNorm Scaling / LNS** ([arXiv:2502.05795](https://arxiv.org/abs/2502.05795)) — Each
139
- layer's output is scaled by 1/√ℓ where ℓ is the layer index.
140
- - **GPAS** ([arXiv:2506.22049](https://arxiv.org/abs/2506.22049)) — Gradient-Preserving
141
- Activation Scaling for residual junctions.
142
- - **PolyNorm** ([arXiv:2602.04902](https://arxiv.org/abs/2602.04902)) — Replaces the standard
143
- MLP activation with normalized linear, quadratic, and cubic branches.
144
- - **SimpleGPT** ([arXiv:2602.01212](https://arxiv.org/abs/2602.01212)) — Second-order
145
- geometry-inspired normalization strategy applied inside MLP projections.
146
- - **StackMemory / STACKTRANS** ([NeurIPS 2025](https://openreview.net/forum?id=2bbDg587uh)) —
147
- Optional differentiable hidden-state stack between decoder layers.
148
- - **Attention Residuals / AttnRes** ([arXiv:2603.15031](https://arxiv.org/abs/2603.15031)) —
149
- Optional learned depth-wise aggregation over previous layer outputs or block summaries.
150
- - **LAUREL** ([arXiv:2411.07501](https://arxiv.org/abs/2411.07501)) — Optional learned
151
- augmented residual layer with residual-weight and low-rank variants.
152
-
153
- **Training objectives and training-time regularizers**
154
-
155
- - **Cut Cross Entropy** ([Apple repository](https://github.com/apple/ml-cross-entropy)) —
156
- Memory-efficient next-token loss that avoids materializing the full token-by-vocabulary
157
- logits tensor. NeoLLM remains compatible with the upstream package when the extensions
158
- below are disabled.
159
- - **MiLe Loss** ([arXiv:2310.19531](https://arxiv.org/abs/2310.19531)) — Optional detached,
160
- mean-normalized predictive-entropy weighting of token losses, implemented inside the
161
- extended CCE path.
162
- - **Output Embedding Centering / mu-loss**
163
- ([arXiv:2601.02031](https://arxiv.org/abs/2601.02031)) — Optional
164
- `lambda * ||mean(output_embeddings)||^2` regularizer for output-logit stability.
165
- - **MEAP** ([arXiv:2502.07490](https://arxiv.org/abs/2502.07490)) — Optional training-only
166
- input corruption that masks a fixed fraction of eligible tokens while preserving clean
167
- next-token labels, causal attention, and the inference path.
168
- - **TWEO** ([arXiv:2511.23225](https://arxiv.org/abs/2511.23225)) — Optional
169
- Transformers Without Extreme Outliers activation regularizer for FP8/low-bit-friendly
170
- training.
171
- - **NITP** ([arXiv:2605.24956](https://arxiv.org/abs/2605.24956)) — Optional Next Implicit
172
- Token Prediction auxiliary objective using shallow-layer implicit token targets and a
173
- cosine loss.
174
- - **NextLat** ([arXiv:2511.05963](https://arxiv.org/abs/2511.05963)) — Optional next-latent
175
- prediction objective using latent dynamics, Smooth L1 supervision, and frozen-head KL.
176
-
177
- ### Optional extended-CCE configuration
178
-
179
- | Feature | Enabled | Value |
180
- |---|---:|---:|
181
- | MiLe Loss | True | gamma=1.0 |
182
- | mu-loss | True | lambda=0.0001 |
183
- | MEAP | True | ratio=0.15 |
184
-
185
- Copy-Paste uses CCE sparse mixture statistics for CUDA training and computes mu-loss in Torch. MEAP and patch training also use the extended cut-cross-entropy package.
186
- [`Kitsunp/ml-cross-entropy`](https://github.com/Kitsunp/ml-cross-entropy) supplies the extended kernels.
187
- Copy-Paste exposes three compact
188
- scalars: unweighted NTP cross entropy, the MiLe reweighting delta, and the mu-loss penalty.
189
- Their sum reconstructs `ntp_loss` exactly. MEAP reports its eligible and selected counts from
190
- the masking kernel; the trainer logs the selected count and exact fraction. Copy-Paste diagnostics use sparse source-token candidates and CCE vocabulary reductions.
191
-
192
- **Optimizer and training stability**
193
-
194
- - **Adam-family optimizer mode** — Active variant: **AdEMAMix**.
195
- **AdEMAMix** ([arXiv:2409.03137](https://arxiv.org/abs/2409.03137)) contributes the optional
196
- beta3 slow-memory branch when `ADEMAMIX=True`. **DeltaMomentum**
197
- ([arXiv:2608.19491](https://arxiv.org/abs/2608.19491)) optionally replaces only the fast beta1
198
- first-moment update on hidden Linear weights when `DELTA=True`; beta2 remains Adam's
199
- ordinary squared-gradient EMA in every mode.
200
- - **BF16 stochastic rounding** ([arXiv:2502.20566](https://arxiv.org/abs/2502.20566)) —
201
- Optional master-free BF16 model update with stochastic rather than nearest rounding; the same
202
- write-back path is shared by AdamW, DeltaAdamW, AdEMAMix, and DeltaAdEMAMix.
203
- - **Cautious Weight Decay** ([arXiv:2510.12402](https://arxiv.org/abs/2510.12402)) —
204
- Sign-selective weight decay variant used by the custom optimizer logic.
205
- - **Correction of Decoupled Weight Decay** ([arXiv:2512.08217](https://arxiv.org/abs/2512.08217)) —
206
- Adapts decoupled weight decay during learning-rate decay.
207
- - **AdamHD** ([arXiv:2511.14721](https://arxiv.org/abs/2511.14721)) —
208
- Decoupled Huber decay regularization reference used by the optimizer.
209
- - **Gradient conditioning** — CAST active; history-adaptive spectral/norm policies by optimizer group; 15625 committed updates.
210
- References: [CAST](https://arxiv.org/abs/2610.19761),
211
- [GradientStabilizer](https://arxiv.org/abs/2502.17055).
212
- ---
213
-
214
- ## Training
215
-
216
- | Setting | Value |
217
- |---|---|
218
- | Dataset | FineWeb-Edu (sample-10BT) |
219
- | Tokens seen | ~0.51B (15,625 steps × batch 64 × length 512) |
220
- | Precision | BF16 model state (bfloat16: 85,091,128 elements, float32: 2,176 elements) + AdEMAMix FP32 master active; stochastic rounding disabled |
221
- | Optimizer | AdEMAMix |
222
- | Gradient transform | CAST active; history-adaptive spectral/norm policies by optimizer group; 15625 committed updates |
223
- | Learning rate | 1e-03 with 4,000-step linear warmup |
224
- | Weight decay | coefficients [0.005, 0.01, 0.1]; WD 5/5 groups; CWD 2; Huber 0; Correction 0 |
225
- | Training time | ~0h 47m |
226
- | Hardware | NVIDIA RTX PRO 6000 Blackwell Server Edition |
227
-
228
 
229
- ### Training curve — total objective
230
-
231
- Total includes MiLe when enabled, plus all active auxiliary objectives and regularizers. Without MiLe means model total minus its MiLe delta on the same forward; this is not a separate training run without MiLe.
232
-
233
- | Step | Train total | Val total | Train without MiLe | Val without MiLe |
234
- |---|---|---|---|---|
235
- | 1 | 12.2845 | — | 12.2845 | — |
236
- | 250 | 7.51328 | — | 7.50593 | — |
237
- | 500 | 7.37632 | — | 7.36168 | — |
238
- | 750 | 7.33534 | — | 7.31743 | — |
239
- | 1,000 | 7.2956 | — | 7.27509 | — |
240
- | 1,250 | 7.25757 | — | 7.2354 | — |
241
- | 1,500 | 7.22776 | — | 7.20332 | — |
242
- | 1,750 | 7.18971 | — | 7.16376 | — |
243
- | 2,000 | 7.16679 | — | 7.13879 | — |
244
- | 2,250 | 7.15075 | — | 7.12142 | — |
245
- | 2,500 | 7.1182 | — | 7.08786 | — |
246
- | 2,750 | 7.09542 | — | 7.064 | — |
247
- | 3,000 | 7.07595 | — | 7.0437 | — |
248
- | 3,250 | 7.05828 | — | 7.02471 | — |
249
- | 3,500 | 7.04374 | — | 7.00896 | — |
250
- | 3,750 | 7.03011 | — | 6.99452 | — |
251
- | 4,000 | 7.01765 | — | 6.98176 | — |
252
- | 4,250 | 7.00604 | — | 6.96907 | — |
253
- | 4,500 | 6.99175 | — | 6.95386 | — |
254
- | 4,750 | 6.97323 | — | 6.93439 | — |
255
- | 5,000 | 6.95435 | 7.03772 | 6.91428 | 6.97998 |
256
- | 5,250 | 6.94098 | — | 6.90037 | — |
257
- | 5,500 | 6.9301 | — | 6.88864 | — |
258
- | 5,750 | 6.91726 | — | 6.87426 | — |
259
- | 6,000 | 6.905 | — | 6.86125 | — |
260
- | 6,250 | 6.89508 | — | 6.85136 | — |
261
- | 6,500 | 6.8811 | — | 6.8378 | — |
262
- | 6,750 | 6.87753 | — | 6.8327 | — |
263
- | 7,000 | 6.86382 | — | 6.81881 | — |
264
- | 7,250 | 6.85904 | — | 6.81314 | — |
265
- | 7,500 | 6.85565 | — | 6.8089 | — |
266
- | 7,750 | 6.84594 | — | 6.79925 | — |
267
- | 8,000 | 6.8399 | — | 6.79186 | — |
268
- | 8,250 | 6.8271 | — | 6.77829 | — |
269
- | 8,500 | 6.82443 | — | 6.77634 | — |
270
- | 8,750 | 6.8251 | — | 6.77586 | — |
271
- | 9,000 | 6.81961 | — | 6.77061 | — |
272
- | 9,250 | 6.80826 | — | 6.75788 | — |
273
- | 9,500 | 6.80517 | — | 6.75465 | — |
274
- | 9,750 | 6.805 | — | 6.7549 | — |
275
- | 10,000 | 6.79291 | 6.90652 | 6.74131 | 6.83695 |
276
- | 10,250 | 6.79435 | — | 6.74302 | — |
277
- | 10,500 | 6.57804 | — | 6.46818 | — |
278
- | 10,750 | 5.64887 | — | 5.30678 | — |
279
- | 11,000 | 5.50961 | — | 5.1322 | — |
280
- | 11,250 | 5.44243 | — | 5.04573 | — |
281
- | 11,500 | 5.39135 | — | 4.97991 | — |
282
- | 11,750 | 5.3537 | — | 4.93408 | — |
283
- | 12,000 | 5.32865 | — | 4.90204 | — |
284
- | 12,250 | 5.29837 | — | 4.86656 | — |
285
- | 12,500 | 5.26806 | — | 4.83252 | — |
286
- | 12,750 | 5.25518 | — | 4.81263 | — |
287
- | 13,000 | 5.22838 | — | 4.78307 | — |
288
- | 13,250 | 5.2163 | — | 4.7663 | — |
289
- | 13,500 | 5.1947 | — | 4.74032 | — |
290
- | 13,750 | 5.19089 | — | 4.73445 | — |
291
- | 14,000 | 5.16889 | — | 4.71043 | — |
292
- | 14,250 | 5.1559 | — | 4.69624 | — |
293
- | 14,500 | 5.14548 | — | 4.68216 | — |
294
- | 14,750 | 5.13203 | — | 4.66433 | — |
295
- | 15,000 | 5.13145 | 4.7568 | 4.66355 | 4.18981 |
296
- | 15,250 | 5.11204 | — | 4.64183 | — |
297
- | 15,500 | 5.10473 | — | 4.63283 | — |
298
- | 15,625 | 5.09438 | 4.7291 | 4.62103 | 4.15709 |
299
-
300
- ### Cross-entropy with and without MiLe
301
-
302
- CE without MiLe is unweighted CE; CE with MiLe adds the reweighting delta. Both exclude mu and auxiliary terms. `ntp_loss` includes mu when enabled.
303
-
304
- | Step | Train CE without MiLe | Val CE without MiLe | Train CE with MiLe | Val CE with MiLe |
305
- |---|---|---|---|---|
306
- | 1 | 11.2377 | — | 11.2377 | — |
307
- | 250 | 7.33083 | — | 7.33818 | — |
308
- | 500 | 7.01992 | — | 7.03457 | — |
309
- | 750 | 6.9117 | — | 6.92962 | — |
310
- | 1,000 | 6.84122 | — | 6.86173 | — |
311
- | 1,250 | 6.78967 | — | 6.81184 | — |
312
- | 1,500 | 6.75051 | — | 6.77496 | — |
313
- | 1,750 | 6.71397 | — | 6.73992 | — |
314
- | 2,000 | 6.68534 | — | 6.71334 | — |
315
- | 2,250 | 6.66963 | — | 6.69896 | — |
316
- | 2,500 | 6.64192 | — | 6.67227 | — |
317
- | 2,750 | 6.621 | — | 6.65242 | — |
318
- | 3,000 | 6.60442 | — | 6.63668 | — |
319
- | 3,250 | 6.59009 | — | 6.62366 | — |
320
- | 3,500 | 6.5758 | — | 6.61058 | — |
321
- | 3,750 | 6.55762 | — | 6.59321 | — |
322
- | 4,000 | 6.54081 | — | 6.5767 | — |
323
- | 4,250 | 6.52894 | — | 6.56591 | — |
324
- | 4,500 | 6.51391 | — | 6.5518 | — |
325
- | 4,750 | 6.49645 | — | 6.53529 | — |
326
- | 5,000 | 6.4769 | 6.35905 | 6.51696 | 6.41679 |
327
- | 5,250 | 6.46543 | — | 6.50604 | — |
328
- | 5,500 | 6.45592 | — | 6.49738 | — |
329
- | 5,750 | 6.441 | — | 6.484 | — |
330
- | 6,000 | 6.43025 | — | 6.474 | — |
331
- | 6,250 | 6.42147 | — | 6.4652 | — |
332
- | 6,500 | 6.40953 | — | 6.45284 | — |
333
- | 6,750 | 6.40453 | — | 6.44936 | — |
334
- | 7,000 | 6.39054 | — | 6.43555 | — |
335
- | 7,250 | 6.38482 | — | 6.43072 | — |
336
- | 7,500 | 6.3804 | — | 6.42715 | — |
337
- | 7,750 | 6.36999 | — | 6.41668 | — |
338
- | 8,000 | 6.36302 | — | 6.41106 | — |
339
- | 8,250 | 6.35259 | — | 6.4014 | — |
340
- | 8,500 | 6.35024 | — | 6.39833 | — |
341
- | 8,750 | 6.34786 | — | 6.3971 | — |
342
- | 9,000 | 6.34222 | — | 6.39122 | — |
343
- | 9,250 | 6.32834 | — | 6.37873 | — |
344
- | 9,500 | 6.32368 | — | 6.37419 | — |
345
- | 9,750 | 6.32497 | — | 6.37507 | — |
346
- | 10,000 | 6.31065 | 6.20464 | 6.36225 | 6.27421 |
347
- | 10,250 | 6.31157 | — | 6.3629 | — |
348
- | 10,500 | 6.01485 | — | 6.12471 | — |
349
- | 10,750 | 4.89594 | — | 5.23804 | — |
350
- | 11,000 | 4.72479 | — | 5.1022 | — |
351
- | 11,250 | 4.63385 | — | 5.03055 | — |
352
- | 11,500 | 4.56533 | — | 4.97677 | — |
353
- | 11,750 | 4.5158 | — | 4.93542 | — |
354
- | 12,000 | 4.48125 | — | 4.90787 | — |
355
- | 12,250 | 4.44365 | — | 4.87546 | — |
356
- | 12,500 | 4.40877 | — | 4.84431 | — |
357
- | 12,750 | 4.38846 | — | 4.83101 | — |
358
- | 13,000 | 4.35794 | — | 4.80324 | — |
359
- | 13,250 | 4.33989 | — | 4.78989 | — |
360
- | 13,500 | 4.31309 | — | 4.76746 | — |
361
- | 13,750 | 4.30621 | — | 4.76265 | — |
362
- | 14,000 | 4.28204 | — | 4.7405 | — |
363
- | 14,250 | 4.26676 | — | 4.72641 | — |
364
- | 14,500 | 4.2524 | — | 4.71572 | — |
365
- | 14,750 | 4.23404 | — | 4.70174 | — |
366
- | 15,000 | 4.23243 | 3.78875 | 4.70032 | 4.35574 |
367
- | 15,250 | 4.21065 | — | 4.68087 | — |
368
- | 15,500 | 4.20151 | — | 4.67341 | — |
369
- | 15,625 | 4.18959 | 3.75747 | 4.66293 | 4.32948 |
370
-
371
- ### Observed loss components — weighted contributions
372
-
373
- Only contributions actually added to the objective are shown; raw values and subcomponents remain in training metrics. `regularization_loss` is the subtotal of mu, weighted TWeo, JTok-M and StackMemory entropy, excluding NITP/NextLat; do not add it again to those components. JTok-M auxiliary loss is training-only.
374
-
375
- | Step | Phase | mu | TWeo | NITP | Reg. subtotal |
376
- |---|---|---|---|---|---|
377
- | 1 | train | 3.16275e-10 | 8.85299e-07 | 1.0468 | 8.85615e-07 |
378
- | 250 | train | 4.01168e-06 | 0.00101028 | 0.174095 | 0.00101429 |
379
- | 500 | train | 8.93938e-06 | 0.000425533 | 0.341321 | 0.000434472 |
380
- | 750 | train | 1.49341e-05 | 0.000574217 | 0.405132 | 0.000589151 |
381
- | 1,000 | train | 2.21405e-05 | 0.000717058 | 0.433133 | 0.000739198 |
382
- | 1,250 | train | 3.00225e-05 | 0.000947325 | 0.444758 | 0.000977348 |
383
- | 1,500 | train | 3.7833e-05 | 0.00116823 | 0.451597 | 0.00120607 |
384
- | 1,750 | train | 4.49507e-05 | 0.00147651 | 0.448272 | 0.00152146 |
385
- | 2,000 | train | 5.20222e-05 | 0.00178034 | 0.451619 | 0.00183236 |
386
- | 2,250 | train | 5.97428e-05 | 0.00206258 | 0.449671 | 0.00212232 |
387
- | 2,500 | train | 6.78635e-05 | 0.00220622 | 0.443663 | 0.00227409 |
388
- | 2,750 | train | 7.61504e-05 | 0.00231168 | 0.440618 | 0.00238783 |
389
- | 3,000 | train | 8.58517e-05 | 0.00233333 | 0.436857 | 0.00241919 |
390
- | 3,250 | train | 9.77149e-05 | 0.00236563 | 0.432168 | 0.00246334 |
391
- | 3,500 | train | 0.000110827 | 0.00231476 | 0.430735 | 0.00242559 |
392
- | 3,750 | train | 0.000126875 | 0.00226564 | 0.434512 | 0.00239251 |
393
- | 4,000 | train | 0.000145417 | 0.00211525 | 0.438694 | 0.00226067 |
394
- | 4,250 | train | 0.000167628 | 0.00196029 | 0.437996 | 0.00212792 |
395
- | 4,500 | train | 0.000194782 | 0.00182893 | 0.437925 | 0.00202371 |
396
- | 4,750 | train | 0.000227027 | 0.00170113 | 0.436016 | 0.00192816 |
397
- | 5,000 | train | 0.000263738 | 0.00154546 | 0.435576 | 0.00180919 |
398
- | 5,000 | validation | 0.000283719 | 0.0060813 | 0.614562 | 0.00636502 |
399
- | 5,250 | train | 0.000303911 | 0.00140913 | 0.433229 | 0.00171304 |
400
- | 5,500 | train | 0.000345929 | 0.00130875 | 0.431072 | 0.00165468 |
401
- | 5,750 | train | 0.000391956 | 0.00119768 | 0.431669 | 0.00158963 |
402
- | 6,000 | train | 0.000435046 | 0.00113317 | 0.429428 | 0.00156821 |
403
- | 6,250 | train | 0.000476276 | 0.00107261 | 0.428334 | 0.00154889 |
404
- | 6,500 | train | 0.000520947 | 0.000988251 | 0.426753 | 0.0015092 |
405
- | 6,750 | train | 0.000561467 | 0.000925999 | 0.426688 | 0.00148747 |
406
- | 7,000 | train | 0.000600642 | 0.000889937 | 0.426782 | 0.00149058 |
407
- | 7,250 | train | 0.000640212 | 0.000860261 | 0.426819 | 0.00150047 |
408
- | 7,500 | train | 0.000679096 | 0.000852769 | 0.426972 | 0.00153187 |
409
- | 7,750 | train | 0.000715778 | 0.000802992 | 0.427743 | 0.00151877 |
410
- | 8,000 | train | 0.000753992 | 0.00078903 | 0.4273 | 0.00154302 |
411
- | 8,250 | train | 0.000795478 | 0.000771698 | 0.424126 | 0.00156718 |
412
- | 8,500 | train | 0.000837118 | 0.000758088 | 0.424502 | 0.00159521 |
413
- | 8,750 | train | 0.000873988 | 0.00075022 | 0.426376 | 0.00162421 |
414
- | 9,000 | train | 0.000910428 | 0.000754263 | 0.426734 | 0.00166469 |
415
- | 9,250 | train | 0.000944036 | 0.000730808 | 0.427862 | 0.00167484 |
416
- | 9,500 | train | 0.000975691 | 0.00071411 | 0.429285 | 0.0016898 |
417
- | 9,750 | train | 0.00100823 | 0.000682743 | 0.428235 | 0.00169098 |
418
- | 10,000 | train | 0.00104292 | 0.000664123 | 0.428943 | 0.00170704 |
419
- | 10,000 | validation | 0.00106212 | 0.0022162 | 0.629043 | 0.00327832 |
420
- | 10,250 | train | 0.00108166 | 0.000681578 | 0.429688 | 0.00176324 |
421
- | 10,500 | train | 0.0011197 | 0.00105885 | 0.451152 | 0.00217854 |
422
- | 10,750 | train | 0.00114721 | 0.000800759 | 0.408884 | 0.00194796 |
423
- | 11,000 | train | 0.00114271 | 0.000609513 | 0.405662 | 0.00175222 |
424
- | 11,250 | train | 0.00112443 | 0.000589441 | 0.410163 | 0.00171387 |
425
- | 11,500 | train | 0.00110764 | 0.000626262 | 0.412851 | 0.0017339 |
426
- | 11,750 | train | 0.0011031 | 0.0007034 | 0.416477 | 0.0018065 |
427
- | 12,000 | train | 0.00110705 | 0.000701879 | 0.418976 | 0.00180893 |
428
- | 12,250 | train | 0.00111366 | 0.000731281 | 0.42106 | 0.00184494 |
429
- | 12,500 | train | 0.00112138 | 0.000758367 | 0.421869 | 0.00187974 |
430
- | 12,750 | train | 0.00113102 | 0.000750959 | 0.422287 | 0.00188198 |
431
- | 13,000 | train | 0.00114004 | 0.000732814 | 0.423269 | 0.00187286 |
432
- | 13,250 | train | 0.0011468 | 0.000694571 | 0.424568 | 0.00184138 |
433
- | 13,500 | train | 0.00115065 | 0.000657363 | 0.425431 | 0.00180801 |
434
- | 13,750 | train | 0.00115104 | 0.000615922 | 0.426476 | 0.00176697 |
435
- | 14,000 | train | 0.00115097 | 0.000595539 | 0.42664 | 0.00174651 |
436
- | 14,250 | train | 0.00115072 | 0.000557399 | 0.427775 | 0.00170812 |
437
- | 14,500 | train | 0.00114966 | 0.000522873 | 0.428093 | 0.00167254 |
438
- | 14,750 | train | 0.00115023 | 0.000500229 | 0.428635 | 0.00165046 |
439
- | 15,000 | train | 0.00115158 | 0.000471158 | 0.429507 | 0.00162274 |
440
- | 15,000 | validation | 0.00115209 | 0.000515729 | 0.399388 | 0.00166782 |
441
- | 15,250 | train | 0.00115244 | 0.000452634 | 0.429571 | 0.00160508 |
442
- | 15,500 | train | 0.00115244 | 0.000431864 | 0.429741 | 0.00158431 |
443
- | 15,625 | train | 0.00115257 | 0.000415416 | 0.429879 | 0.00156799 |
444
- | 15,625 | validation | 0.00115263 | 0.000468992 | 0.397996 | 0.00162163 |
445
-
446
- Tables use exact logged steps; missing values are — without carrying values forward. Repeated evaluations at one step use the latest logged value. Components average batch scalars weighted by examples; CE/PPL token metrics retain their valid-target weighting. Weight decay/CWD act in the optimizer, outside these forward losses.
447
-
448
- ---
449
-
450
- ## Limitations
451
-
452
- - **Token budget** — ~1.5 B tokens seen; below estimated optimum. Knowledge-intensive tasks
453
- will improve with more training.
454
- - **Gradient spike at step 40k** — Reorganized the attention pattern in layer 9 that
455
- previously captured long-range token correlations. A checkpoint from ~step 38k is expected
456
- to have better aggregate benchmark scores.
457
- - **PolyNorm exclusivity** — The quadratic branch has become partially redundant with the
458
- linear branch. Will be corrected in the next training run.
459
- - **Base model only** — Not instruction-tuned or aligned; purely a next-token-prediction
460
- base model.
461
-
462
- ---
463
-
464
- ## References
465
-
466
- All papers whose techniques are integrated into NeoLLM's architecture,
467
- training objective, or training stack:
468
-
469
- | Area | Technique | Paper title | Reference |
470
- |---|---|---|---|
471
- | Embeddings | Learnable Multipliers | Freeing the Scale of Language Model Matrix Layers | [arXiv:2601.04890](https://arxiv.org/abs/2601.04890) |
472
- | Embeddings | Leviathan | A Separable Architecture for Continuous Token Representation in Language Models | [arXiv:2601.22040](https://arxiv.org/abs/2601.22040) |
473
- | Embeddings / residual modulation | JTok / JTok-M | JTok: On Token Embedding as another Axis of Scaling Law via Joint Token Self-modulation | [arXiv:2602.00800](https://arxiv.org/abs/2602.00800) |
474
- | Embeddings | KHRONOS | KHRONOS: a Kernel-Based Neural Architecture for Rapid, Resource-Efficient Scientific Computation | [arXiv:2505.13315](https://arxiv.org/abs/2505.13315) |
475
- | Embeddings | Spelling Bee | Spelling Bee Embeddings for Language Modeling | [arXiv:2601.18030](https://arxiv.org/abs/2601.18030) |
476
- | Embeddings | Token embedding analysis | Token Embeddings Violate the Manifold Hypothesis | [arXiv:2504.01002](https://arxiv.org/abs/2504.01002) |
477
- | Attention / positions | FAN | Fourier Analysis Networks | [arXiv:2502.21309](https://arxiv.org/abs/2502.21309) |
478
- | Attention / positions | MEA | Explicit Multi-head Attention for Inter-head Interaction in Large Language Models | [arXiv:2601.19611](https://arxiv.org/abs/2601.19611) |
479
- | Attention / positions | LUCID | Attention with Preconditioned Representations | [arXiv:2602.10410](https://arxiv.org/abs/2602.10410) |
480
- | Attention / positions | Affine-Scaled Attention | Affine-Scaled Attention: Towards Flexible and Stable Transformer Attention | [arXiv:2602.23057](https://arxiv.org/abs/2602.23057) |
481
- | Attention / positions | XSA | Exclusive Self Attention | [arXiv:2603.09078](https://arxiv.org/abs/2603.09078) |
482
- | Attention / positions | Directional Routing | Directional Routing in Transformers | [arXiv:2603.14923](https://arxiv.org/abs/2603.14923) |
483
- | Attention / positions | Gated Attention | Gated Attention for Large Language Models: Non-linearity, Sparsity, and Attention-Sink-Free | [arXiv:2505.06708](https://arxiv.org/abs/2505.06708) |
484
- | Attention / positions | Momentum Attention | Momentum Attention | [arXiv:2411.03884](https://arxiv.org/abs/2411.03884) |
485
- | Attention / positions | IHA | Interleaved Head Attention | [arXiv:2602.21371](https://arxiv.org/abs/2602.21371) |
486
- | Attention / positions | REPO | Language Models with Context Re-Positioning | [arXiv:2512.14391](https://arxiv.org/abs/2512.14391) |
487
- | Attention / positions | GRAPE | Group Representational Position Encoding | [arXiv:2512.07805](https://arxiv.org/abs/2512.07805) |
488
- | Attention / positions | GOAT priors | You Need Better Attention Priors | [arXiv:2601.15380](https://arxiv.org/abs/2601.15380) |
489
- | Attention / positions | Hadamard o_proj | Rethinking Attention Output Projection: Structured Hadamard Transforms for Efficient Transformers | [arXiv:2603.08343](https://arxiv.org/abs/2603.08343) |
490
- | Residual / normalization | SeeDNorm | Self-Rescaled Dynamic Normalization | [arXiv:2510.22777](https://arxiv.org/abs/2510.22777) |
491
- | Residual / normalization | LNS | The Curse of Depth in LLMs | [arXiv:2502.05795](https://arxiv.org/abs/2502.05795) |
492
- | Residual / normalization | GPAS | Gradient-Preserving Activation Scaling | [arXiv:2506.22049](https://arxiv.org/abs/2506.22049) |
493
- | Residual / normalization | PolyNorm | PolyNorm / PolyCom | [arXiv:2602.04902](https://arxiv.org/abs/2602.04902) |
494
- | Residual / normalization | SimpleGPT | SimpleGPT | [arXiv:2602.01212](https://arxiv.org/abs/2602.01212) |
495
- | Residual / normalization | StackMemory / STACKTRANS | Recursive Transformer: Boosting Reasoning Ability with State Stack | [NeurIPS 2025](https://openreview.net/forum?id=2bbDg587uh) |
496
- | Residual / normalization | Attention Residuals | Attention Residuals | [arXiv:2603.15031](https://arxiv.org/abs/2603.15031) |
497
- | Residual / normalization | LAUREL | LAUREL: Learned Augmented Residual Layer | [arXiv:2411.07501](https://arxiv.org/abs/2411.07501) |
498
- | Objectives | TWEO | Transformers Without Extreme Outliers Enables FP8 Training And Quantization For Dummies | [arXiv:2511.23225](https://arxiv.org/abs/2511.23225) |
499
- | Objectives | NITP | Next Implicit Token Prediction for LLM Pre-training | [arXiv:2605.24956](https://arxiv.org/abs/2605.24956) |
500
- | Objectives | NextLat | Next-Latent Prediction Transformers Learn Compact World Models | [arXiv:2511.05963](https://arxiv.org/abs/2511.05963) |
501
- | Optimizer / training | AdEMAMix | The AdEMAMix Optimizer: Better, Faster, Older | [arXiv:2409.03137](https://arxiv.org/abs/2409.03137) |
502
- | Optimizer / training | DeltaMomentum | DeltaMomentum: A Key-Value based Anisotropic Momentum Update via Delta Rule | [arXiv:2608.19491](https://arxiv.org/abs/2608.19491) |
503
- | Optimizer / training | BF16 + SR | Stochastic Rounding for LLM Training: Theory and Practice | [arXiv:2502.20566](https://arxiv.org/abs/2502.20566) |
504
- | Optimizer / training | CWD | Cautious Weight Decay | [arXiv:2510.12402](https://arxiv.org/abs/2510.12402) |
505
- | Optimizer / training | WD correction | Correction of Decoupled Weight Decay | [arXiv:2512.08217](https://arxiv.org/abs/2512.08217) |
506
- | Optimizer / training | AdamHD | AdamHD: Decoupled Huber Decay Regularization for Language Model Pre-Training | [arXiv:2511.14721](https://arxiv.org/abs/2511.14721) |
507
- | Optimizer / training | GradientStabilizer | GradientStabilizer | [arXiv:2502.17055](https://arxiv.org/abs/2502.17055) |
508
-
509
- ---
510
-
511
- ## Citation
512
-
513
- ```bibtex
514
- @misc{neollm2026,
515
- title = {NeoLLM: A Research Language Model Integrating Recent Attention and Normalization Techniques},
516
- author = {KitsuVp},
517
- year = {2026},
518
- url = {https://huggingface.co/KitsuVp/NeoLLM}
519
- }
520
- ```
521
-
522
- ---
523
-
524
- ## Author
525
-
526
- [@Kyokopom](https://x.com/Kyokopom) on X
527
-
528
- ---
529
-
530
- ## License
531
 
532
- Apache 2.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: transformers
 
3
  tags:
4
+ - generated_from_trainer
5
+ model-index:
6
+ - name: NeoLLM
7
+ results: []
 
 
 
 
8
  ---
9
 
10
+ <!-- This model card has been generated automatically according to the information the Trainer had access to. You
11
+ should probably proofread and complete it, then remove this comment. -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ # NeoLLM
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ This model is a fine-tuned version of [](https://huggingface.co/) on an unknown dataset.
16
+ It achieves the following results on the evaluation set:
17
+ - Loss: 4.9291
18
+ - Ce: 3.9075
19
+ - Valid Targets: 2083162.0
20
+ - Ppl: 49.7745
21
+ - Mile Loss: 4.5021
22
+ - Mile Ppl: 90.2041
23
+ - Top1 Accuracy: 0.3108
24
+ - Top3 Accuracy: 0.4650
25
+ - Topk Valid Targets: 2083162.0
26
+ - Ntp Loss: 4.5034
27
+ - Ntp Ce Unweighted: 3.9080
28
+ - Mile Reweighting Delta: 0.5945
29
+ - Mu Loss: 0.0010
30
+ - Tweo Loss: 0.0668
31
+ - Nitp Loss: 0.4250
32
+ - Total Model Loss: 4.9291
33
+ - Tweo Weighted Loss: 0.0007
34
+ - Nitp Weighted Loss: 0.4250
35
+ - Auxiliary Loss: 0.4257
36
+ - Regularization Loss: 0.0016
37
+
38
+ ## Model description
39
+
40
+ More information needed
41
+
42
+ ## Intended uses & limitations
43
+
44
+ More information needed
45
+
46
+ ## Training and evaluation data
47
+
48
+ More information needed
49
+
50
+ ## Training procedure
51
+
52
+ ### Training hyperparameters
53
+
54
+ The following hyperparameters were used during training:
55
+ - learning_rate: 0.001
56
+ - train_batch_size: 64
57
+ - eval_batch_size: 64
58
+ - seed: 42
59
+ - optimizer: Use OptimizerNames.ADAMW_TORCH_FUSED with betas=(0.9,0.999) and epsilon=1e-08 and optimizer_args=No additional optimizer arguments
60
+ - lr_scheduler_type: linear
61
+ - num_epochs: 1
62
+
63
+ ### Training results
64
+
65
+ | Training Loss | Epoch | Step | Validation Loss | Ce | Valid Targets | Ppl | Mile Loss | Mile Ppl | Top1 Accuracy | Top3 Accuracy | Topk Valid Targets | Ntp Loss | Ntp Ce Unweighted | Mile Reweighting Delta | Mu Loss | Tweo Loss | Nitp Loss | Total Model Loss | Tweo Weighted Loss | Nitp Weighted Loss | Auxiliary Loss | Regularization Loss |
66
+ |:-------------:|:-----:|:-----:|:---------------:|:------:|:-------------:|:--------:|:---------:|:--------:|:-------------:|:-------------:|:------------------:|:--------:|:-----------------:|:----------------------:|:-------:|:---------:|:---------:|:----------------:|:------------------:|:------------------:|:--------------:|:-------------------:|
67
+ | 7.2623 | 0.32 | 5000 | 7.4209 | 6.6778 | 2083162.0 | 794.6030 | 6.7310 | 837.9544 | 0.0784 | 0.1546 | 2083162.0 | 6.7312 | 6.6778 | 0.0531 | 0.0003 | 0.8364 | 0.6813 | 7.4209 | 0.0084 | 0.6813 | 0.6897 | 0.0087 |
68
+ | 6.9846 | 0.64 | 10000 | 7.1389 | 6.3917 | 2083162.0 | 596.8784 | 6.4607 | 639.4872 | 0.0952 | 0.1738 | 2083162.0 | 6.4614 | 6.3917 | 0.0690 | 0.0008 | 0.1872 | 0.6756 | 7.1389 | 0.0019 | 0.6756 | 0.6775 | 0.0027 |
69
+ | 5.3718 | 0.96 | 15000 | 4.9829 | 3.9751 | 2083162.0 | 53.2555 | 4.5515 | 94.7726 | 0.3041 | 0.4569 | 2083162.0 | 4.5528 | 3.9756 | 0.5763 | 0.0009 | 0.0772 | 0.4294 | 4.9829 | 0.0008 | 0.4294 | 0.4301 | 0.0017 |
70
+ | 5.3163 | 1.0 | 15625 | 4.9291 | 3.9075 | 2083162.0 | 49.7745 | 4.5021 | 90.2041 | 0.3108 | 0.4650 | 2083162.0 | 4.5034 | 3.9080 | 0.5945 | 0.0010 | 0.0668 | 0.4250 | 4.9291 | 0.0007 | 0.4250 | 0.4257 | 0.0016 |
71
+
72
+
73
+ ### Framework versions
74
+
75
+ - Transformers 5.22.1
76
+ - Pytorch 2.14.1+cu130
77
+ - Datasets 4.8.5
78
+ - Tokenizers 0.23.3
config.json CHANGED
@@ -13,6 +13,7 @@
13
  },
14
  "bos_token_id": 1,
15
  "cce_loss_impl": "cce_kahan_full_c",
 
16
  "dagformer": {
17
  "input_source": "tokens",
18
  "local_dim": 128,
@@ -56,19 +57,29 @@
56
  "jtok_num_modes": 4,
57
  "jtok_top_k": 2,
58
  "laurel_lr_rank": 32,
 
 
 
59
  "liger_loss_accum_dtype": "float32",
60
  "lucid_attention_eps": 1e-06,
61
  "max_position_embeddings": 512,
62
  "mea_component_key_value_heads": 4,
63
  "mea_groupnorm_eps": 1e-06,
64
  "meap_exclude_last": true,
 
65
  "meap_implementation": "triton",
66
  "meap_mask_ratio": 0.15,
67
  "meap_mask_token_id": 16,
 
68
  "meap_seed": 42,
69
  "mile_loss_gamma": 1.0,
70
  "model_type": "neollm",
71
  "momentum_gamma": 0.1,
 
 
 
 
 
72
  "mu_loss_lambda": 0.0001,
73
  "nextlat_dynamics_proj_factor": 1.0,
74
  "nextlat_horizon": 1,
@@ -113,7 +124,7 @@
113
  "stack_memory_cache_size": 2048,
114
  "stack_slots": 16,
115
  "tie_word_embeddings": false,
116
- "transformers_version": "5.21.1",
117
  "tweo_eps": 1e-06,
118
  "tweo_loss_weight": 0.01,
119
  "tweo_power": 4.0,
@@ -121,7 +132,7 @@
121
  "use_affine_scaled_attention": false,
122
  "use_attn_res": false,
123
  "use_cache": false,
124
- "use_copy_paste": true,
125
  "use_dagformer": false,
126
  "use_directional_routing": false,
127
  "use_embedding_input_norm": true,
@@ -136,6 +147,7 @@
136
  "use_laurel": false,
137
  "use_laurel_lr": false,
138
  "use_laurel_rw": false,
 
139
  "use_learnable_multipliers": true,
140
  "use_liger_kernel": false,
141
  "use_lns": true,
@@ -144,6 +156,7 @@
144
  "use_meap": true,
145
  "use_mile_loss": true,
146
  "use_momentum_attention": true,
 
147
  "use_mu_loss": true,
148
  "use_nextlat": false,
149
  "use_nitp": true,
 
13
  },
14
  "bos_token_id": 1,
15
  "cce_loss_impl": "cce_kahan_full_c",
16
+ "copy_paste_lexical_keys": false,
17
  "dagformer": {
18
  "input_source": "tokens",
19
  "local_dim": 128,
 
57
  "jtok_num_modes": 4,
58
  "jtok_top_k": 2,
59
  "laurel_lr_rank": 32,
60
+ "ldm_groups": 8,
61
+ "ldm_hidden_dim": 32,
62
+ "ldm_key_dim": 8,
63
  "liger_loss_accum_dtype": "float32",
64
  "lucid_attention_eps": 1e-06,
65
  "max_position_embeddings": 512,
66
  "mea_component_key_value_heads": 4,
67
  "mea_groupnorm_eps": 1e-06,
68
  "meap_exclude_last": true,
69
+ "meap_exclude_mask_from_copy": false,
70
  "meap_implementation": "triton",
71
  "meap_mask_ratio": 0.15,
72
  "meap_mask_token_id": 16,
73
+ "meap_protect_structural_tokens": false,
74
  "meap_seed": 42,
75
  "mile_loss_gamma": 1.0,
76
  "model_type": "neollm",
77
  "momentum_gamma": 0.1,
78
+ "msar_backend": "auto",
79
+ "msar_block_branches": 4,
80
+ "msar_kernel_sizes": [
81
+ 4
82
+ ],
83
  "mu_loss_lambda": 0.0001,
84
  "nextlat_dynamics_proj_factor": 1.0,
85
  "nextlat_horizon": 1,
 
124
  "stack_memory_cache_size": 2048,
125
  "stack_slots": 16,
126
  "tie_word_embeddings": false,
127
+ "transformers_version": "5.22.1",
128
  "tweo_eps": 1e-06,
129
  "tweo_loss_weight": 0.01,
130
  "tweo_power": 4.0,
 
132
  "use_affine_scaled_attention": false,
133
  "use_attn_res": false,
134
  "use_cache": false,
135
+ "use_copy_paste": false,
136
  "use_dagformer": false,
137
  "use_directional_routing": false,
138
  "use_embedding_input_norm": true,
 
147
  "use_laurel": false,
148
  "use_laurel_lr": false,
149
  "use_laurel_rw": false,
150
+ "use_ldm": false,
151
  "use_learnable_multipliers": true,
152
  "use_liger_kernel": false,
153
  "use_lns": true,
 
156
  "use_meap": true,
157
  "use_mile_loss": true,
158
  "use_momentum_attention": true,
159
+ "use_msar": false,
160
  "use_mu_loss": true,
161
  "use_nextlat": false,
162
  "use_nitp": true,
configuration_neollm.py CHANGED
@@ -224,12 +224,26 @@ class NeoLLMConfig(PretrainedConfig):
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):
@@ -771,8 +785,9 @@ class NeoLLMConfig(PretrainedConfig):
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
@@ -1104,14 +1119,15 @@ class NeoLLMConfig(PretrainedConfig):
1104
 
1105
  # ── Residual-flow mechanism mutex ─────────────────────────────────
1106
  # AttnRes and LAuReL compete directly with residual topology.
1107
- # StackMemory may be paired with SiameseNorm because it acts on the
1108
- # shared block input, while SiameseNorm owns the X/Y stream updates.
1109
  _residual_flow_flags = {
1110
  "use_laurel": bool(use_laurel),
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
@@ -1122,13 +1138,25 @@ class NeoLLMConfig(PretrainedConfig):
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
 
@@ -1138,9 +1166,15 @@ class NeoLLMConfig(PretrainedConfig):
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
@@ -1382,6 +1416,16 @@ class NeoLLMConfig(PretrainedConfig):
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
 
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 may be combined with SiameseNorm; the hybrid adds an MSAR read
228
+ of the shared unscaled branch history to the normalized X/Y sum,
229
+ before its existing optional shared input norm. No extra
230
+ normalization or user parameter is introduced by the combination.
231
+ This is an architectural adaptation, not a configuration tested in
232
+ the MSAR paper. Other residual topologies, GPAS, FAN residual and
233
+ DAGFormer remain exclusive. Single-stream summaries are retained;
234
  causal views and independent depth reads are reconstructed per branch.
235
+ use_ldm (:obj:`bool`, *optional*, defaults to ``False``):
236
+ Enable Linear Depth Memory (RLS per token and feature group). Group
237
+ count follows the attention head geometry automatically, and the
238
+ key and controller dimensions follow the paper's small-width recipe.
239
+ SiameseNorm can be combined with LDM through a shared history of
240
+ unscaled branch outputs; this hybrid is an architectural adaptation.
241
+ Other alternative residual flows remain mutually exclusive.
242
  msar_block_branches (:obj:`int` or ``None``, optional):
243
  Number of attention/MLP branches per block. ``None`` derives a
244
+ size from the total number of attention/MLP branches, aiming for
245
+ roughly eight contiguous blocks; block boundaries may fall between
246
+ attention and MLP. An explicit value is preserved as supplied.
247
  msar_kernel_sizes (:obj:`tuple`, optional):
248
  Causal depthwise widths; the paper uses ``(4, 8, 12)``.
249
  msar_backend (:obj:`str`, optional):
 
785
  # ── Attention Residuals (Kimi Team, 2026) ─────────────────────────
786
  use_attn_res=False,
787
  attn_res_num_blocks=4,
788
+ # MSAR may also be paired with the existing SiameseNorm residual flow.
789
  use_msar=False,
790
+ use_ldm=False,
791
  msar_block_branches=None, # auto: approximately eight depth blocks
792
  msar_kernel_sizes=(4, 8, 12),
793
  msar_backend="auto", # Triton on CUDA, Torch on CPU; "torch" is a reference
 
1119
 
1120
  # ── Residual-flow mechanism mutex ─────────────────────────────────
1121
  # AttnRes and LAuReL compete directly with residual topology.
1122
+ # StackMemory and MSAR may separately be paired with SiameseNorm. The
1123
+ # hybrid keeps the X/Y updates and reads MSAR's shared branch history.
1124
  _residual_flow_flags = {
1125
  "use_laurel": bool(use_laurel),
1126
  "use_attn_res": bool(use_attn_res),
1127
  "use_stack_memory": bool(use_stack_memory),
1128
  "use_siamesenorm": bool(use_siamesenorm),
1129
  "use_msar": bool(use_msar),
1130
+ "use_ldm": bool(use_ldm),
1131
  }
1132
  _active_residual_flow = [
1133
  name for name, enabled in _residual_flow_flags.items() if enabled
 
1138
  and not bool(use_laurel)
1139
  and not bool(use_attn_res)
1140
  and not bool(use_msar)
1141
+ and not bool(use_ldm)
1142
  )
1143
+ allow_msar_siamese = (
1144
+ bool(use_msar) and bool(use_siamesenorm)
1145
+ and not bool(use_stack_memory) and not bool(use_laurel)
1146
+ and not bool(use_attn_res)
1147
+ and not bool(use_ldm)
1148
+ )
1149
+ allow_ldm_siamese = (
1150
+ bool(use_ldm) and bool(use_siamesenorm)
1151
+ and not any((use_stack_memory, use_laurel, use_attn_res, use_msar))
1152
+ )
1153
+ if len(_active_residual_flow) > 1 and not (allow_stack_siamese or allow_msar_siamese or allow_ldm_siamese):
1154
  raise ValueError(
1155
  "Enable at most one residual-flow mechanism among "
1156
  "`use_laurel`, `use_attn_res`, `use_stack_memory`, "
1157
+ "`use_siamesenorm`, `use_msar`, and `use_ldm`, except the explicit "
1158
+ "`use_stack_memory` + `use_siamesenorm` and "
1159
+ "`use_msar` + `use_siamesenorm` and `use_ldm` + `use_siamesenorm` pairs. "
1160
  f"Active flags: {_active_residual_flow}."
1161
  )
1162
 
 
1166
  "`use_msar=True` cannot be combined with `use_gpas`, "
1167
  "`use_fan_residual`, or `use_dagformer`."
1168
  )
1169
+ if use_ldm and (use_gpas or use_fan_residual or use_dagformer):
1170
+ raise ValueError(
1171
+ "`use_ldm=True` cannot be combined with `use_gpas`, "
1172
+ "`use_fan_residual`, or `use_dagformer`."
1173
+ )
1174
  if msar_block_branches is None:
1175
+ # Branch granularity counts attention and MLP independently. Do not
1176
+ # round to whole decoder layers: e.g. 24 branches / 8 = 3.
1177
+ msar_block_branches = max(1, math.ceil(2 * num_hidden_layers / 8))
1178
  if isinstance(msar_block_branches, bool) or not isinstance(msar_block_branches, int) or msar_block_branches < 1:
1179
  raise ValueError("`msar_block_branches` must be a positive integer.")
1180
  if (not isinstance(msar_kernel_sizes, (tuple, list)) or not msar_kernel_sizes
 
1416
  # Memory cost: O(num_blocks × batch × seq × hidden) instead of
1417
  # O(num_layers × batch × seq × hidden).
1418
  self.use_msar = bool(use_msar)
1419
+ self.use_ldm = bool(use_ldm)
1420
+ # Follow the paper's value width = attention head width, even when
1421
+ # num_attention_heads * head_dim differs from hidden_size. For an
1422
+ # incompatible width choose the closest divisor without padding values.
1423
+ divisors = (g for g in range(1, hidden_size + 1) if hidden_size % g == 0)
1424
+ self.ldm_groups = min(divisors,
1425
+ key=lambda g: (abs(hidden_size // g - head_dim),
1426
+ abs(g - num_attention_heads)))
1427
+ self.ldm_key_dim = min(8, hidden_size // self.ldm_groups)
1428
+ self.ldm_hidden_dim = min(32, hidden_size)
1429
  self.msar_block_branches = msar_block_branches
1430
  self.msar_kernel_sizes = tuple(msar_kernel_sizes)
1431
  self.msar_backend = msar_backend
generation_config.json CHANGED
@@ -7,5 +7,5 @@
7
  "output_attentions": false,
8
  "output_hidden_states": false,
9
  "pad_token_id": 0,
10
- "transformers_version": "5.21.1"
11
  }
 
7
  "output_attentions": false,
8
  "output_hidden_states": false,
9
  "pad_token_id": 0,
10
+ "transformers_version": "5.22.1"
11
  }
model.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:0dc8e2c81e7e67462b6b6c74089c5f3a25f19051ccaf3f0cedafdc5eb8db79d3
3
- size 170235776
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:30daee49b68ae04dcd13eaf707ee60e1ae2daaf2acb5ff9cf4f0f93975e6fff7
3
+ size 169184776
modeling_neollm.py CHANGED
@@ -76,6 +76,8 @@ References:
76
  arXiv:2601.15380.
77
  StackMemory / STACKTRANS: Zhang et al. (2025). "Recursive Transformer:
78
  Boosting Reasoning Ability with State Stack." NeurIPS 2025.
 
 
79
  HOLA: Cui (2026). "A Hippocampus for Linear Attention: An Exact Memory
80
  for What the Recurrent State Forgets." arXiv:2607.02303.
81
  TWEO: Liang et al. (2026). "Transformers Without Extreme Outliers Enables
@@ -6718,11 +6720,12 @@ def _msar_reference_read(sources, banks, query, valid, position, widths, eps):
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
@@ -6734,7 +6737,11 @@ if triton is not None:
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:
@@ -6743,7 +6750,7 @@ if triton is not None:
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)
@@ -6760,91 +6767,141 @@ if triton is not None:
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
@@ -6861,21 +6918,78 @@ if triton is not None:
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,
@@ -6888,9 +7002,10 @@ def _msar_read_op(sources: list[torch.Tensor], banks: list[torch.Tensor],
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
 
@@ -6906,38 +7021,66 @@ 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
 
@@ -7032,6 +7175,335 @@ def _msar_start_branch(sources, partial, branch, block_size):
7032
  return sources, partial
7033
 
7034
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7035
  class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7036
  """
7037
  Decoder layer with standard residual connections.
@@ -7205,6 +7677,11 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
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))
@@ -7500,19 +7977,43 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7500
  dag_sources_y=None,
7501
  dag_plan=None,
7502
  dynamics_valid_mask: Optional[torch.Tensor] = None,
 
 
 
 
 
 
 
7503
  **kwargs: Unpack[FlashAttentionKwargs],
7504
  ) -> Tuple:
7505
- """Update coupled X/Y streams with optional global/local DAG corrections.
7506
-
7507
- Raw X/Y histories retain separate residual reads and shared target-layer
7508
- attention reads. Existing stream scaling, GPAS and memory policies remain
7509
- in their original order. A missing dag_plan selects the legacy path.
 
 
 
 
 
 
 
 
 
7510
  """
7511
  # SiameseNorm keeps two coupled streams with shared Attention/MLP
7512
  # parameters. All Siamese normalization modules are RMSNorm by
7513
  # construction; no dynamic-normalization branch remains.
7514
  # ── Attention shared block ────────────────────────────────────────
7515
  norm_backend = getattr(self, "siamesenorm_kernel_backend", "reference")
 
 
 
 
 
 
 
 
7516
  if norm_backend == "reference":
7517
  x_attn_norm = self.siamese_attn_x_norm(x_states)
7518
  y_attn_norm = self.siamese_attn_y_norm(y_states)
@@ -7520,17 +8021,23 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7520
  dtype=x_attn_norm.dtype, device=x_attn_norm.device
7521
  )
7522
  h_attn = x_scale * x_attn_norm + y_attn_norm
7523
- if self.siamese_attn_input_norm is not None:
7524
- h_attn = self.siamese_attn_input_norm(h_attn)
7525
  else:
7526
  from cut_cross_entropy.siamesenorm import siamesenorm
7527
  h_attn = siamesenorm(
7528
  x_states, y_states,
7529
  self.siamese_attn_x_norm.weight, self.siamese_attn_y_norm.weight,
7530
- self.siamese_attn_input_norm.weight if self.siamese_attn_input_norm is not None else None,
 
7531
  self.siamese_attn_x_scale.to(dtype=x_states.dtype, device=x_states.device),
7532
  eps=self.siamese_attn_x_norm.eps, backend=norm_backend,
7533
  )
 
 
 
 
 
 
 
7534
 
7535
  stack_metrics = None
7536
  if self.use_stack_memory:
@@ -7566,36 +8073,56 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7566
  y_states = y_states + dag_ry
7567
  x_after_attn = x_states + stream_scale * attn_out
7568
  y_after_attn = y_states + attn_out
 
 
 
 
 
 
7569
 
7570
  if self.use_gpas:
7571
  x_after_attn = self.gpas_attn(x_after_attn)
7572
 
7573
  # ── MLP shared block ──────────────────────────────────────────────
 
 
 
 
 
 
 
 
7574
  if norm_backend == "reference":
7575
  x_mlp_norm = self.siamese_mlp_x_norm(x_after_attn)
7576
  y_mlp_norm = self.siamese_mlp_y_norm(y_after_attn)
7577
  h_mlp = x_mlp_norm + y_mlp_norm
7578
- if self.siamese_mlp_input_norm is not None:
7579
- h_mlp = self.siamese_mlp_input_norm(h_mlp)
7580
  else:
7581
  h_mlp = siamesenorm(
7582
  x_after_attn, y_after_attn,
7583
  self.siamese_mlp_x_norm.weight, self.siamese_mlp_y_norm.weight,
7584
- self.siamese_mlp_input_norm.weight if self.siamese_mlp_input_norm is not None else None,
 
7585
  eps=self.siamese_mlp_x_norm.eps, backend=norm_backend,
7586
  post_scale=(
7587
  self.lns_mlp.scale.to(device=x_after_attn.device, dtype=x_after_attn.dtype)
7588
- if self.use_lns else None
7589
  ),
7590
  out_dtype=(
7591
  torch.get_autocast_dtype("cuda")
7592
- if torch.is_autocast_enabled("cuda") else None
7593
  ),
7594
  )
 
 
 
 
 
 
 
7595
 
7596
  # Only this consumer starts directly with an autocast Linear. Attention
7597
  # also exposes native states to positional, router and memory consumers.
7598
- h_lns2 = self.lns_mlp(h_mlp) if self.use_lns and norm_backend == "reference" else h_mlp
7599
  delta_m = self.mlp(h_lns2)
7600
 
7601
  jtok_aux_stats = None
@@ -7616,11 +8143,21 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7616
 
7617
  x_next = x_after_attn + stream_scale * shared_update
7618
  y_next = y_after_attn + shared_update
 
 
 
 
 
 
7619
 
7620
  if self.use_gpas:
7621
  x_next = self.gpas_mlp(x_next)
7622
 
7623
  outputs = (x_next, y_next)
 
 
 
 
7624
  if self.use_stack_memory:
7625
  outputs += (stack_memory, stack_memory_mask, stack_metrics)
7626
  if jtok_aux_stats is not None:
@@ -7642,6 +8179,8 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
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,
@@ -7690,6 +8229,10 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7690
  h_attn = hidden_states
7691
  residual_attn = hidden_states
7692
 
 
 
 
 
7693
  # ── Attention block ───────────────────────────────────────────────
7694
 
7695
  # Optional embedding input normalisation:
@@ -7740,6 +8283,9 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7740
  attn_aug = residual_attn + hidden_states
7741
 
7742
  h_tilde = self.gpas_attn(attn_aug) if self.use_gpas else attn_aug
 
 
 
7743
 
7744
  # ── Attention Residuals: compute pre-MLP input ────────────────────
7745
  # After attention, the partial sum is updated with h_tilde.
@@ -7764,6 +8310,10 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7764
  h_mlp = h_tilde
7765
  residual_mlp = h_tilde
7766
 
 
 
 
 
7767
  # ── MLP block ─────────────────────────────────────────────────────
7768
  h_normed2 = _apply_norm(self.post_attention_layernorm, h_mlp)
7769
  h_lns2 = self.lns_mlp(h_normed2) if self.use_lns else h_normed2
@@ -7802,8 +8352,13 @@ class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7802
  mlp_aug = residual_mlp + delta_m
7803
 
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:
@@ -8147,6 +8702,9 @@ class NeoLLMPreTrainedModel(PreTrainedModel):
8147
  _supports_flash_attn_2 = True
8148
  _supports_sdpa = True
8149
  _is_stateful = True
 
 
 
8150
 
8151
  @classmethod
8152
  def from_pretrained(cls, *args, **kwargs):
@@ -8250,9 +8808,9 @@ class NeoLLMPreTrainedModel(PreTrainedModel):
8250
  return model
8251
 
8252
  def _init_weights(self, module):
8253
- """Apply HF initialization, then enforce exact-zero marked DAG heads."""
8254
  super()._init_weights(module)
8255
- if getattr(module, "_dag_zero_init", False):
8256
  nn.init.zeros_(module.weight)
8257
  nn.init.zeros_(module.bias)
8258
  return
@@ -8520,6 +9078,8 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
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))
@@ -8862,6 +9422,26 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
8862
  hidden_states = inputs_embeds
8863
  use_siamesenorm = bool(getattr(self.config, "use_siamesenorm", False))
8864
  siamese_y_states = inputs_embeds if use_siamesenorm else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8865
  # The same validated dense/right-padding training contract applies to
8866
  # the causal planner: valid queries cannot see future padded keys.
8867
  dag_plans = (self.dagformer(input_ids, inputs_embeds, position_ids,
@@ -9021,18 +9601,28 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
9021
  dag_sources=dag_sources,
9022
  dag_plan=dag_plans[layer_idx] if dag_plans is not None else None,
9023
  dynamics_valid_mask=attention_mask,
 
 
 
 
9024
  **kwargs,
9025
  )
9026
  hidden_states = layer_outputs[0]
9027
  siamese_y_states = layer_outputs[1]
9028
- if use_stack_memory:
9029
- stack_memory = layer_outputs[2]
9030
- stack_memory_mask = layer_outputs[3]
9031
- stack_layer_metrics = layer_outputs[4]
9032
- all_stack_metrics = all_stack_metrics + (stack_layer_metrics,)
9033
- extras_start = 5
9034
  else:
9035
  extras_start = 2
 
 
 
 
 
 
9036
  else:
9037
  layer_outputs = decoder_layer(
9038
  hidden_states,
@@ -9044,6 +9634,7 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
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,
@@ -9061,6 +9652,9 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
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
 
@@ -9106,7 +9700,11 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
9106
 
9107
  if use_siamesenorm:
9108
  norm_backend = getattr(self, "siamesenorm_kernel_backend", "reference")
9109
- if norm_backend == "reference":
 
 
 
 
9110
  hidden_states = self.siamese_final_norm(
9111
  self.siamese_x_final_norm(hidden_states)
9112
  + self.siamese_y_final_norm(siamese_y_states)
@@ -9120,6 +9718,9 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
9120
  eps=self.siamese_x_final_norm.eps, backend=norm_backend,
9121
  )
9122
  else:
 
 
 
9123
  hidden_states = self.norm(hidden_states)
9124
 
9125
  if output_hidden_states:
@@ -11431,6 +12032,17 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
11431
  for name, parameter in module.named_parameters():
11432
  if parameter.requires_grad:
11433
  groups[f"{kind}/layer_{module.layer_idx}/{name}"] = (parameter,)
 
 
 
 
 
 
 
 
 
 
 
11434
  families = {
11435
  "dag": ("dagformer_local",),
11436
  "attnres": ("attn_res_query_attn", "attn_res_query_mlp", "attn_res_norm"),
@@ -11453,6 +12065,8 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
11453
  groups[f"fan/layer_{layer.layer_idx}"] = (layer.self_attn.lambda_1, layer.self_attn.lambda_2)
11454
  if self.model.dagformer is not None:
11455
  groups["dag/global_topology"] = tuple(p for p in self.model.dagformer.parameters() if p.requires_grad)
 
 
11456
  return groups
11457
 
11458
  def set_input_embeddings(self, value):
 
76
  arXiv:2601.15380.
77
  StackMemory / STACKTRANS: Zhang et al. (2025). "Recursive Transformer:
78
  Boosting Reasoning Ability with State Stack." NeurIPS 2025.
79
+ Linear Depth Memory: González-Martínez et al. (2026). "Depth Memory."
80
+ arXiv:2610.18809; flexible Triton backend adapted under the MIT license.
81
  HOLA: Cui (2026). "A Hippocampus for Linear Attention: An Exact Memory
82
  for What the Recurrent State Forgets." arXiv:2607.02303.
83
  TWEO: Liang et al. (2026). "Transformers Without Extreme Outliers Enables
 
6720
 
6721
  if triton is not None:
6722
  @triton.jit
6723
+ def _msar_read_kernel(SOURCES, BANKS, QUERY, VALID, POSITION, OUT, LSE, GRAD,
6724
  T: tl.constexpr, D: tl.constexpr, NS: tl.constexpr,
6725
  M: tl.constexpr,
6726
+ WIDTHS: tl.constexpr, EPS: tl.constexpr, BD: tl.constexpr,
6727
+ DOT_ONLY: tl.constexpr):
6728
+ """Read depth streams, or recompute only the backward scalar <g,z>."""
6729
  row = tl.program_id(0)
6730
  stream = tl.program_id(1)
6731
  batch = row // T
 
6737
  q = tl.load(QUERY + stream * D + c, mask_c, other=0).to(tl.float32)
6738
  running_max = tl.full((), -float('inf'), tl.float32)
6739
  denom = tl.full((), 0., tl.float32)
6740
+ if DOT_ONLY:
6741
+ g = tl.load(GRAD + row * M * D + stream * D + c, mask_c, other=0).to(tl.float32)
6742
+ dot = tl.full((), 0., tl.float32)
6743
+ else:
6744
+ accum = tl.full((BD,), 0., tl.float32)
6745
  for i in tl.static_range(NS):
6746
  value = tl.full((BD,), 0., tl.float32)
6747
  if stream == 0:
 
6750
  offset = 0
6751
  for r in tl.static_range(len(WIDTHS)):
6752
  if stream == r + 1:
6753
+ for delta in tl.static_range(min(WIDTHS[r], T)):
6754
  past = row - delta
6755
  in_seq = token >= delta
6756
  past_valid = tl.load(VALID + past, in_seq, other=0).to(tl.int1)
 
6767
  new_max = tl.maximum(running_max, score)
6768
  old_weight = tl.exp(running_max - new_max)
6769
  new_weight = tl.exp(score - new_max)
6770
+ if DOT_ONLY:
6771
+ dot = dot * old_weight + tl.sum(g * value, 0) * new_weight
6772
+ else:
6773
+ accum = accum * old_weight + value * new_weight
6774
  denom = denom * old_weight + new_weight
6775
  running_max = new_max
6776
+ if DOT_ONLY:
6777
+ tl.store(OUT + row * M + stream, dot / denom)
6778
+ else:
6779
+ tl.store(OUT + row * M * D + stream * D + c, accum / denom, mask_c)
6780
+ tl.store(LSE + row * M + stream, running_max + tl.log(denom))
6781
 
6782
 
6783
  @triton.jit
6784
+ def _msar_vjp_value(SOURCE, BANK, QUERY, VALID, POSITION, LSE, GDOT, G, DV, QPART,
6785
  ROWS: tl.constexpr, T: tl.constexpr, D: tl.constexpr,
6786
  M: tl.constexpr,
6787
  WIDTHS: tl.constexpr, EPS: tl.constexpr, R: tl.constexpr,
6788
+ BD: tl.constexpr, GROUPS: tl.constexpr, SINGLE_SOURCE: tl.constexpr):
6789
+ """Materialize convolution-view VJPs and their query partials."""
6790
  group = tl.program_id(0)
6791
+ # Identity is handled by _msar_vjp_source: avoid its full DV buffer.
6792
+ stream = tl.program_id(1) + 1
6793
  row = group * R + tl.arange(0, R)
6794
  c = tl.arange(0, BD)
6795
  row_ok = row < ROWS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6796
  lse = tl.load(LSE+row*M+stream, row_ok, other=0)
 
 
 
6797
  g = tl.load(G+row[:,None]*M*D+stream*D+c[None,:],
6798
  row_ok[:,None] & (c[None,:]<D), other=0).to(tl.float32)
6799
+ if SINGLE_SOURCE:
6800
+ # A finite one-entry softmax is constant: dv=g and dq=0. The
6801
+ # saved LSE and upstream gradient detect nonfinite inputs without
6802
+ # reconstructing any convolution or forming score derivatives.
6803
+ bad_g = tl.sum(((tl.abs(g) >= float('inf')) | (g != g)).to(tl.int32), 1) > 0
6804
+ finite = (tl.abs(lse) < float('inf')) & ~bad_g
6805
+ dv = tl.where(finite[:,None], g, float('nan'))
6806
+ bad_rows = tl.sum((~finite & row_ok).to(tl.int32), 0) > 0
6807
+ # The query VJP is identically zero for a finite one-source
6808
+ # softmax. All D components have the same zero/NaN status, so
6809
+ # store one scalar per row group rather than D identical values.
6810
+ qp = tl.where(bad_rows, float('nan'), 0.)
6811
+ else:
6812
+ tok = row % T
6813
+ valid = tl.load(VALID + row, row_ok, other=0).to(tl.int1)
6814
+ pos = tl.load(POSITION + row, row_ok, other=0)
6815
+ q = tl.load(QUERY + stream*D+c, c < D, other=0).to(tl.float32)
6816
+ v = tl.full((R, BD), 0., tl.float32)
6817
+ offset = 0
6818
+ for r in tl.static_range(len(WIDTHS)):
6819
+ if stream == r+1:
6820
+ for delta in tl.static_range(min(WIDTHS[r], T)):
6821
+ past = row - delta
6822
+ in_seq = row_ok & (tok >= delta)
6823
+ pv = tl.load(VALID + past, in_seq, other=0).to(tl.int1)
6824
+ pp = tl.load(POSITION + past, in_seq, other=0)
6825
+ allowed = in_seq & valid & pv & ((pos-pp) == delta)
6826
+ x = tl.load(SOURCE + past[:,None]*D+c[None,:],
6827
+ allowed[:,None] & (c[None,:]<D), other=0).to(tl.float32)
6828
+ w = tl.load(BANK+(offset+delta)*D+c,
6829
+ c<D, other=0).to(tl.float32)
6830
+ v += x*w[None,:]
6831
+ offset += WIDTHS[r]
6832
+ inv = tl.rsqrt(tl.sum(v*v, 1)/D + EPS)
6833
+ qv = tl.sum(v*q[None,:], 1)
6834
+ p = tl.exp(qv*inv-lse)
6835
+ gv = tl.sum(g*v, 1)
6836
+ gdot = tl.load(GDOT+row*M+stream, row_ok, other=0)
6837
+ ds = p*(gv-gdot)
6838
+ dv = p[:,None]*g + ds[:,None]*(q[None,:]*inv[:,None] -
6839
+ v*(qv*inv*inv*inv/D)[:,None])
6840
+ qp = tl.sum(ds[:,None]*v*inv[:,None], 0)
6841
+ tl.store(DV+(stream-1)*ROWS*D+row[:,None]*D+c[None,:], dv,
6842
  row_ok[:,None] & (c[None,:]<D))
6843
+ if SINGLE_SOURCE:
6844
+ tl.store(QPART+stream*GROUPS+group, qp)
6845
+ else:
6846
+ tl.store(QPART+stream*GROUPS*D+group*D+c, qp, c<D)
6847
 
6848
 
6849
  @triton.jit
6850
+ def _msar_vjp_source(SOURCE, BANK, QUERY, VALID, POSITION, LSE, GDOT, G, DV, QPART, DX,
6851
+ ROWS: tl.constexpr, T: tl.constexpr, D: tl.constexpr, M: tl.constexpr,
6852
+ WIDTHS: tl.constexpr, EPS: tl.constexpr, R: tl.constexpr,
6853
+ BD: tl.constexpr, GROUPS: tl.constexpr, SINGLE_SOURCE: tl.constexpr):
6854
+ """Fuse the identity-view VJP into the convolution input transpose.
6855
+
6856
+ The identity query partial is still reduced in FP32. For a finite
6857
+ one-source softmax its query VJP is exactly zero; nonfinite rows keep
6858
+ the same NaN propagation as the materialized implementation.
6859
+ """
6860
+ group = tl.program_id(0)
6861
+ row = group * R + tl.arange(0, R)
6862
+ c = tl.arange(0, BD)
6863
+ ok = row < ROWS
6864
+ token = row % T
6865
+ valid = tl.load(VALID + row, ok, other=0).to(tl.int1)
6866
+ pos = tl.load(POSITION + row, ok, other=0)
6867
+ lse = tl.load(LSE + row * M, ok, other=0)
6868
+ g = tl.load(G + row[:,None] * M * D + c[None,:],
6869
+ ok[:,None] & (c[None,:] < D), other=0).to(tl.float32)
6870
+ if SINGLE_SOURCE:
6871
+ bad_g = tl.sum(((tl.abs(g) >= float('inf')) | (g != g)).to(tl.int32), 1) > 0
6872
+ finite = (tl.abs(lse) < float('inf')) & ~bad_g
6873
+ identity_vjp = tl.where(finite[:,None], g, float('nan'))
6874
+ bad_rows = tl.sum((~finite & ok).to(tl.int32), 0) > 0
6875
+ tl.store(QPART + group, tl.where(bad_rows, float('nan'), 0.))
6876
+ else:
6877
+ q = tl.load(QUERY + c, c < D, other=0).to(tl.float32)
6878
+ v = tl.load(SOURCE + row[:,None] * D + c[None,:],
6879
+ ok[:,None] & valid[:,None] & (c[None,:] < D), other=0).to(tl.float32)
6880
+ inv = tl.rsqrt(tl.sum(v*v, 1) / D + EPS)
6881
+ qv = tl.sum(v*q[None,:], 1)
6882
+ p = tl.exp(qv*inv-lse)
6883
+ gv = tl.sum(g*v, 1)
6884
+ gdot = tl.load(GDOT + row*M, ok, other=0)
6885
+ ds = p*(gv-gdot)
6886
+ identity_vjp = p[:,None]*g + ds[:,None]*(q[None,:]*inv[:,None] -
6887
+ v*(qv*inv*inv*inv/D)[:,None])
6888
+ qp = tl.sum(ds[:,None]*v*inv[:,None], 0)
6889
+ tl.store(QPART + group*D + c, qp, c < D)
6890
+ dx = tl.where(valid[:,None], identity_vjp, 0.)
6891
  offset = 0
6892
  for r in tl.static_range(len(WIDTHS)):
6893
+ for delta in tl.static_range(min(WIDTHS[r], T)):
6894
  future = row+delta
6895
  in_seq = ok & ((token+delta)<T)
6896
  fv = tl.load(VALID+future, in_seq, other=0).to(tl.int1)
6897
  fp = tl.load(POSITION+future, in_seq, other=0)
6898
  allowed = in_seq & valid & fv & ((fp-pos)==delta)
6899
+ dv = tl.load(DV+r*ROWS*D+future[:,None]*D+c[None,:],
6900
+ allowed[:,None] & (c[None,:]<D), other=0)
6901
+ w = tl.load(BANK+(offset+delta)*D+c, c<D, other=0).to(tl.float32)
6902
+ dx += dv*w[None,:]
6903
  offset += WIDTHS[r]
6904
+ tl.store(DX+row[:,None]*D+c[None,:], dx, ok[:,None] & (c[None,:]<D))
6905
 
6906
 
6907
  @triton.jit
 
6918
  pos = tl.load(POSITION+row, ok, other=0)
6919
  offset = 0
6920
  for r in tl.static_range(len(WIDTHS)):
6921
+ grad = tl.load(DV+r*ROWS*D+row[:,None]*D+c[None,:],
6922
  ok[:,None] & (c[None,:]<D), other=0).to(tl.float32)
6923
  for delta in tl.static_range(WIDTHS[r]):
6924
+ if delta < T:
6925
+ past = row-delta
6926
+ in_seq = ok & (tok>=delta)
6927
+ pv = tl.load(VALID+past, in_seq, other=0).to(tl.int1)
6928
+ pp = tl.load(POSITION+past, in_seq, other=0)
6929
+ allowed = in_seq & valid & pv & ((pos-pp)==delta)
6930
+ x = tl.load(SOURCE+past[:,None]*D+c[None,:],
6931
+ allowed[:,None] & (c[None,:]<D), other=0).to(tl.float32)
6932
+ part = tl.sum(grad*x, 0)
6933
+ else:
6934
+ # These convolution taps cannot be reached by any token.
6935
+ # The partial buffer is uninitialized, so write exact zero.
6936
+ part = tl.full((C,), 0., tl.float32)
6937
  tl.store(WPART+(offset+delta)*GROUPS*D+group*D+c, part, c<D)
6938
  offset += WIDTHS[r]
6939
 
6940
 
6941
+ @triton.jit
6942
+ def _msar_reduce_query_bank(QPART, WPART, DQ, DW, D: tl.constexpr,
6943
+ M: tl.constexpr, KSUM: tl.constexpr,
6944
+ QGROUPS: tl.constexpr, WGROUPS: tl.constexpr,
6945
+ C: tl.constexpr, REDUCE_ROWS: tl.constexpr,
6946
+ FIRST_TILE: tl.constexpr, SINGLE_SOURCE: tl.constexpr):
6947
+ """Accumulate query and bank partials in FP32 in one launch.
6948
+
6949
+ A source tile contributes to ``DQ`` across all sources and tiles;
6950
+ ``DW`` belongs to one source and is initialized by its first tile.
6951
+ The single-source query partial is one zero/NaN scalar per row group.
6952
+ Chunked reductions accumulate in FP32, but their order need not be
6953
+ bit-identical to the framework's separate reductions.
6954
+ """
6955
+ pid = tl.program_id(0)
6956
+ column_blocks = tl.cdiv(D, C)
6957
+ if pid < M * column_blocks:
6958
+ q_stream = pid // column_blocks
6959
+ q_col = (pid % column_blocks)*C + tl.arange(0, C)
6960
+ q_offsets = tl.arange(0, REDUCE_ROWS)
6961
+ if SINGLE_SOURCE:
6962
+ q_sum = tl.full((), 0., tl.float32)
6963
+ for q_start in range(tl.cdiv(QGROUPS, REDUCE_ROWS)):
6964
+ q_row = q_start*REDUCE_ROWS + q_offsets
6965
+ q_value = tl.load(QPART + q_stream*QGROUPS + q_row,
6966
+ q_row < QGROUPS, other=0)
6967
+ q_sum += tl.sum(q_value, 0)
6968
+ else:
6969
+ q_sum = tl.full((C,), 0., tl.float32)
6970
+ for q_start in range(tl.cdiv(QGROUPS, REDUCE_ROWS)):
6971
+ q_row = q_start*REDUCE_ROWS + q_offsets
6972
+ q_value = tl.load(QPART + q_stream*QGROUPS*D + q_row[:,None]*D + q_col[None,:],
6973
+ (q_row[:,None] < QGROUPS) & (q_col[None,:] < D), other=0)
6974
+ q_sum += tl.sum(q_value, 0)
6975
+ previous_q = tl.load(DQ + q_stream*D + q_col, q_col < D, other=0)
6976
+ tl.store(DQ + q_stream*D + q_col, previous_q + q_sum, q_col < D)
6977
+ else:
6978
+ bank_pid = pid - M * column_blocks
6979
+ bank_tap = bank_pid // column_blocks
6980
+ bank_col = (bank_pid % column_blocks)*C + tl.arange(0, C)
6981
+ bank_offsets = tl.arange(0, REDUCE_ROWS)
6982
+ bank_sum = tl.full((C,), 0., tl.float32)
6983
+ for bank_start in range(tl.cdiv(WGROUPS, REDUCE_ROWS)):
6984
+ bank_row = bank_start*REDUCE_ROWS + bank_offsets
6985
+ bank_value = tl.load(WPART + bank_tap*WGROUPS*D + bank_row[:,None]*D + bank_col[None,:],
6986
+ (bank_row[:,None] < WGROUPS) & (bank_col[None,:] < D), other=0)
6987
+ bank_sum += tl.sum(bank_value, 0)
6988
+ if not FIRST_TILE:
6989
+ bank_sum += tl.load(DW + bank_tap*D + bank_col, bank_col < D, other=0)
6990
+ tl.store(DW + bank_tap*D + bank_col, bank_sum, bank_col < D)
6991
+
6992
+
6993
  @torch.library.custom_op("neollm_msar::read", mutates_args=())
6994
  def _msar_read_op(sources: list[torch.Tensor], banks: list[torch.Tensor],
6995
  query: torch.Tensor, valid: torch.Tensor, position: torch.Tensor,
 
7002
  position = position.contiguous()
7003
  result = sources[0].new_empty((b, t, len(widths)+1, d))
7004
  lse = torch.empty((b,t,len(widths)+1),device=result.device,dtype=torch.float32)
7005
+ if b*t:
7006
+ _msar_read_kernel[(b*t, len(widths)+1)](tuple(sources), tuple(banks), query, valid, position,
7007
+ result, lse, None, t, d, len(sources), len(widths)+1,
7008
+ tuple(widths), eps, triton.next_power_of_2(d), False, num_warps=4)
7009
  return result, lse
7010
 
7011
 
 
7021
  query: torch.Tensor, valid: torch.Tensor, position: torch.Tensor,
7022
  widths: list[int], eps: float,
7023
  grad: torch.Tensor, lse: torch.Tensor) -> tuple[list[torch.Tensor], list[torch.Tensor], torch.Tensor]:
7024
+ """Fused VJP with FP32 scalar recomputation and one-source workspace."""
7025
  b, t, d = sources[0].shape
7026
  n, m, ksum = b*t, len(widths)+1, sum(widths)
7027
+ if not n:
7028
+ return ([torch.empty_like(source) for source in sources],
7029
+ [torch.zeros_like(bank) for bank in banks], torch.zeros_like(query))
7030
  sources = [x.contiguous() for x in sources]
7031
  banks, query = [x.contiguous() for x in banks], query.contiguous()
7032
  valid, position, grad = valid.contiguous(), position.contiguous(), grad.contiguous()
7033
+ single = len(sources) == 1
7034
+ # ds_i = p_i * (<g,v_i> - <g,z>); recompute the scalar <g,z>
7035
+ # directly rather than materializing all FP32 stream vectors z. A single
7036
+ # finite source has ds=0, so that specialization skips recomputation.
7037
+ gdot = lse
7038
+ if not single:
7039
+ gdot = torch.empty((b,t,m),device=grad.device,dtype=torch.float32)
7040
+ _msar_read_kernel[(n,m)](tuple(sources), tuple(banks), query, valid, position, gdot, lse, grad,
7041
+ t,d,len(sources),m,tuple(widths),eps,
7042
+ triton.next_power_of_2(d),True,num_warps=4)
7043
+ # Process complete sequences together so convolution never crosses a tile.
7044
+ # Bound temporary storage automatically by both geometry and device memory;
7045
+ # at least one sequence fits even when a single sequence exceeds the budget.
7046
+ scratch_limit = min(128 * 1024**2, torch.cuda.get_device_properties(grad.device).total_memory // 64)
7047
+ bytes_per_sequence = max(1, t * m * d * 4)
7048
+ batch_tile = max(1, min(b, scratch_limit // bytes_per_sequence))
7049
+ max_rows = batch_tile * t
7050
+ # One source at a time, and only its convolution views: the identity VJP
7051
+ # is computed in the input-transpose kernel instead of stored in FP32.
7052
+ dv = torch.empty(((m-1)*max_rows*d,),device=grad.device,dtype=torch.float32)
7053
  qrows = max(1, min(8, 8192 // triton.next_power_of_2(d)))
7054
+ max_qgroups = triton.cdiv(max_rows,qrows)
7055
+ # A single source has the same zero/NaN query partial in every dimension;
7056
+ # the general multi-source path still needs one partial per dimension.
7057
+ qpart_width = 1 if single else d
7058
+ qpart = torch.empty((m*max_qgroups*qpart_width,),device=grad.device,dtype=torch.float32)
7059
+ max_wgroups = triton.cdiv(max_rows,64)
7060
+ wpart = torch.empty((ksum*max_wgroups*d,),device=grad.device,dtype=torch.float32)
7061
  dq = torch.zeros_like(query, dtype=torch.float32)
7062
  dws = []
7063
  dxs = []
7064
  for source, bank in zip(sources, banks):
 
 
7065
  dx = torch.empty_like(source)
7066
+ dw = torch.empty_like(bank, dtype=torch.float32)
7067
+ for start in range(0,b,batch_tile):
7068
+ end = min(start+batch_tile,b)
7069
+ rows = (end-start)*t
7070
+ qgroups = triton.cdiv(rows,qrows)
7071
+ wgroups = triton.cdiv(rows,64)
7072
+ _msar_vjp_value[(qgroups,m-1)](source[start:end],bank,query,valid[start:end],
7073
+ position[start:end],lse[start:end],gdot[start:end],grad[start:end],dv,qpart,
7074
+ rows,t,d,m,tuple(widths),eps,qrows,triton.next_power_of_2(d),qgroups,single,num_warps=4)
7075
+ _msar_vjp_source[(qgroups,)](source[start:end],bank,query,valid[start:end],
7076
+ position[start:end],lse[start:end],gdot[start:end],grad[start:end],dv,qpart,dx[start:end],
7077
+ rows,t,d,m,tuple(widths),eps,qrows,triton.next_power_of_2(d),qgroups,single,num_warps=4)
7078
+ _msar_vjp_bank[(wgroups,triton.cdiv(d,32))](source[start:end],dv,valid[start:end],
7079
+ position[start:end],wpart,rows,t,d,tuple(widths),64,32,wgroups,num_warps=4)
7080
+ _msar_reduce_query_bank[((m+ksum)*triton.cdiv(d,32),)](
7081
+ qpart,wpart,dq,dw,d,m,ksum,qgroups,wgroups,32,128,
7082
+ start == 0,single,num_warps=4)
7083
+ dws.append(dw.to(bank.dtype))
7084
  dxs.append(dx)
7085
  return dxs, dws, dq.to(query.dtype).contiguous()
7086
 
 
7175
  return sources, partial
7176
 
7177
 
7178
+ # Linear Depth Memory (RLS), adapted from the authors' flexible backend,
7179
+ # github.com/davidgonmar/depth-memory, commit 9a6865feeb43a153a4501cc9a6457e047ffef3c3.
7180
+ # The live memory is unwound in backward, so BF16 restoration is approximate.
7181
+ # The symmetric covariance tape stores only its upper triangle as in paper Appendix F.
7182
+ # MIT License
7183
+ # Copyright (c) 2026 David Gonzalez Martinez
7184
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7185
+ # of this software and associated documentation files (the "Software"), to deal
7186
+ # in the Software without restriction, including without limitation the rights
7187
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7188
+ # copies of the Software, and to permit persons to whom the Software is
7189
+ # furnished to do so, subject to the following conditions:
7190
+ # The above copyright notice and this permission notice shall be included in all
7191
+ # copies or substantial portions of the Software.
7192
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
7193
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7194
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7195
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
7196
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
7197
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
7198
+ # SOFTWARE.
7199
+
7200
+
7201
+ if triton is not None:
7202
+ @triton.jit
7203
+ def _ldm_forward(M, P, COV, KEY, VALUE, QUERY, LAM, OUT, ERROR, GAIN, DEN,
7204
+ N: tl.constexpr, K: tl.constexpr, D: tl.constexpr,
7205
+ BK: tl.constexpr, BD: tl.constexpr, BN: tl.constexpr):
7206
+ """Write/read RLS and save the symmetric covariance as a triangle."""
7207
+ row = tl.program_id(0)*BN + tl.arange(0, BN)
7208
+ kk = tl.arange(0, BK)
7209
+ dd = tl.arange(0, BD)
7210
+ row_ok = row < N
7211
+ km = row_ok[:, None] & (kk[None, :] < K)
7212
+ vm = row_ok[:, None] & (dd[None, :] < D)
7213
+ key = tl.load(KEY + row[:, None]*K + kk[None, :], km, 0).to(tl.float32)
7214
+ value = tl.load(VALUE + row[:, None]*D + dd[None, :], vm, 0).to(tl.float32)
7215
+ lam = tl.load(LAM + row, row_ok, 1.).to(tl.float32)
7216
+ u = tl.zeros((BN, BK), tl.float32)
7217
+ den = lam
7218
+ for i in tl.static_range(K):
7219
+ p_off = row[:, None]*K*K + i*K + kk[None, :]
7220
+ p_row = tl.load(P + p_off, km, 0).to(tl.float32)
7221
+ # The live covariance starts at I and the RLS update preserves exact
7222
+ # symmetry. Store its upper triangle only; backward mirrors indices.
7223
+ packed = row[:, None] * (K*(K+1)//2) + i*(2*K-i+1)//2 + kk[None, :] - i
7224
+ tl.store(COV + packed, p_row, km & (kk[None, :] >= i))
7225
+ ui = tl.sum(p_row*key, 1)
7226
+ ki = tl.load(KEY + row*K + i, row_ok, 0).to(tl.float32)
7227
+ den += ki*ui
7228
+ u = tl.where(kk[None, :] == i, ui[:, None], u)
7229
+ # The tape is produced by the full CTA; synchronize before reading it.
7230
+ tl.debug_barrier()
7231
+ for i in tl.static_range(K):
7232
+ p_off = row[:, None]*K*K + i*K + kk[None, :]
7233
+ packed = row[:, None] * (K*(K+1)//2) + i*(2*K-i+1)//2 + kk[None, :] - i
7234
+ upper = km & (kk[None, :] >= i)
7235
+ p_row = tl.load(COV + packed, upper, 0).to(tl.float32)
7236
+ ui = tl.sum(tl.where(kk[None, :] == i, u, 0.), 1)
7237
+ p_new = (p_row - ui[:, None]*u/den[:, None])/lam[:, None]
7238
+ tl.store(P + p_off, p_new, upper)
7239
+ transpose_off = row[:, None]*K*K + kk[None, :]*K + i
7240
+ tl.store(P + transpose_off, p_new, km & (kk[None, :] > i))
7241
+ error = value
7242
+ for i in tl.static_range(K):
7243
+ m_off = row[:, None]*K*D + i*D + dd[None, :]
7244
+ mr = tl.load(M + m_off, vm, 0).to(tl.float32)
7245
+ ki = tl.load(KEY + row*K + i, row_ok, 0).to(tl.float32)
7246
+ error -= ki[:, None]*mr
7247
+ output = tl.zeros((BN, BD), tl.float32)
7248
+ for i in tl.static_range(K):
7249
+ m_off = row[:, None]*K*D + i*D + dd[None, :]
7250
+ old = tl.load(M + m_off, vm, 0).to(tl.float32)
7251
+ ui = tl.sum(tl.where(kk[None, :] == i, u, 0.), 1)
7252
+ gain = ui/den
7253
+ after = (old + gain[:, None]*error).to(M.dtype.element_ty)
7254
+ tl.store(M + m_off, after, vm)
7255
+ qi = tl.load(QUERY + row*K + i, row_ok, 0).to(tl.float32)
7256
+ output += after.to(tl.float32)*qi[:, None]
7257
+ tl.store(GAIN + row*K + i, gain, row_ok)
7258
+ tl.store(OUT + row[:, None]*D + dd[None, :], output.to(OUT.dtype.element_ty), vm)
7259
+ tl.store(ERROR + row[:, None]*D + dd[None, :], error.to(ERROR.dtype.element_ty), vm)
7260
+ tl.store(DEN + row, den, row_ok)
7261
+
7262
+
7263
+ @triton.jit
7264
+ def _ldm_backward(M, P, COV, GM_IN, GP_IN, GM_OUT, GP_OUT, KEY, QUERY, LAM, ERROR, GAIN, DEN, DY,
7265
+ DK, DV, DQ, DL, N: tl.constexpr, K: tl.constexpr, D: tl.constexpr,
7266
+ BK: tl.constexpr, BD: tl.constexpr, BN: tl.constexpr, FIRST: tl.constexpr,
7267
+ HAS_GM: tl.constexpr, HAS_GP: tl.constexpr):
7268
+ """Reverse RLS without mutating or pre-copying incoming cotangents."""
7269
+ row = tl.program_id(0)*BN + tl.arange(0, BN)
7270
+ ii = tl.arange(0, BK)
7271
+ jj = tl.arange(0, BD)
7272
+ row_ok = row < N
7273
+ km = row_ok[:, None] & (ii[None, :] < K)
7274
+ vm = row_ok[:, None] & (jj[None, :] < D)
7275
+ mm = row_ok[:, None, None] & (ii[None, :, None] < K) & (jj[None, None, :] < D)
7276
+ pm = row_ok[:, None, None] & (ii[None, :, None] < K) & (ii[None, None, :] < K)
7277
+ mo = row[:, None, None]*K*D + ii[None, :, None]*D + jj[None, None, :]
7278
+ po = row[:, None, None]*K*K + ii[None, :, None]*K + ii[None, None, :]
7279
+ ko = row[:, None]*K + ii[None, :]
7280
+ vo = row[:, None]*D + jj[None, :]
7281
+ after = tl.load(M + mo, mm, 0).to(tl.float32)
7282
+ p_after = tl.load(P + po, pm, 0).to(tl.float32)
7283
+ lo = tl.minimum(ii[:, None], ii[None, :])
7284
+ hi = tl.maximum(ii[:, None], ii[None, :])
7285
+ packed = row[:, None, None]*(K*(K+1)//2) + lo[None, :, :]*(2*K-lo[None, :, :]+1)//2 + hi[None, :, :]-lo[None, :, :]
7286
+ p_before = tl.load(COV + packed, pm, 0).to(tl.float32)
7287
+ key = tl.load(KEY + ko, km, 0).to(tl.float32)
7288
+ query = tl.load(QUERY + ko, km, 0).to(tl.float32)
7289
+ error = tl.load(ERROR + vo, vm, 0).to(tl.float32)
7290
+ gain = tl.load(GAIN + ko, km, 0).to(tl.float32)
7291
+ lam = tl.load(LAM + row, row_ok, 1.).to(tl.float32)
7292
+ den = tl.load(DEN + row, row_ok, 1.).to(tl.float32)
7293
+ dy = tl.load(DY + vo, vm, 0).to(tl.float32)
7294
+ before = (after - gain[:, :, None]*error[:, None, :]).to(M.dtype.element_ty).to(tl.float32)
7295
+ if FIRST:
7296
+ before = tl.zeros((BN, BK, BD), tl.float32)
7297
+ after = (gain[:, :, None]*error[:, None, :]).to(M.dtype.element_ty).to(tl.float32)
7298
+ read_grad = (query[:, :, None]*dy[:, None, :]).to(M.dtype.element_ty).to(tl.float32)
7299
+ if HAS_GM:
7300
+ gm_in = tl.load(GM_IN + mo, mm, 0).to(tl.float32)
7301
+ else:
7302
+ gm_in = tl.zeros((BN, BK, BD), tl.float32)
7303
+ h = (gm_in + read_grad).to(M.dtype.element_ty).to(tl.float32)
7304
+ dq = tl.sum(after*dy[:, None, :], 2)
7305
+ dv = tl.sum(h*gain[:, :, None], 1)
7306
+ dg = tl.sum(h*error[:, None, :], 2)
7307
+ if HAS_GP:
7308
+ jp = tl.load(GP_IN + po, pm, 0).to(tl.float32)
7309
+ else:
7310
+ jp = tl.zeros((BN, BK, BK), tl.float32)
7311
+ u = gain*den[:, None]
7312
+ ju = tl.sum(jp*u[:, None, :], 2)
7313
+ jtu = tl.sum(jp*u[:, :, None], 1)
7314
+ du = dg/den[:, None] - (ju+jtu)/(lam*den)[:, None]
7315
+ dden = -tl.sum(dg*gain, 1)/den + tl.sum(u*ju, 1)/(lam*den*den)
7316
+ du += dden[:, None]*key
7317
+ dk = -tl.sum(before*dv[:, None, :], 2) + dden[:, None]*u + tl.sum(p_before*du[:, :, None], 1)
7318
+ gp = jp/lam[:, None, None] + du[:, :, None]*key[:, None, :]
7319
+ dl = dden - tl.sum(tl.sum(jp*p_after, 2), 1)/lam
7320
+ gm = h - key[:, :, None]*dv[:, None, :]
7321
+ tl.store(GM_OUT + mo, gm.to(GM_OUT.dtype.element_ty), mm)
7322
+ tl.store(GP_OUT + po, gp, pm)
7323
+ tl.store(DK + ko, dk.to(DK.dtype.element_ty), km)
7324
+ tl.store(DQ + ko, dq.to(DQ.dtype.element_ty), km)
7325
+ tl.store(DV + vo, dv.to(DV.dtype.element_ty), vm)
7326
+ tl.store(DL + row, dl, row_ok)
7327
+ tl.store(M + mo, before.to(M.dtype.element_ty), mm)
7328
+ tl.store(P + po, p_before, pm)
7329
+
7330
+
7331
+
7332
+ def _ldm_kernel_shape(memory):
7333
+ k, d = memory.shape[-2:]
7334
+ return memory.numel()//(k*d), k, d, max(2, triton.next_power_of_2(k)), max(2, triton.next_power_of_2(d))
7335
+
7336
+
7337
+ def _ldm_row_tile(bk, bd, budget, maximum):
7338
+ rows = max(1, min(maximum, budget//(bk*bd)))
7339
+ return 1 << (rows.bit_length()-1)
7340
+
7341
+
7342
+ @torch.library.custom_op("neollm_ldm::forward", mutates_args=("memory", "covariance"))
7343
+ def _ldm_forward_op(memory: torch.Tensor, covariance: torch.Tensor, key: torch.Tensor, value: torch.Tensor,
7344
+ query: torch.Tensor, forgetting: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
7345
+ out, error = torch.empty_like(value), torch.empty_like(value)
7346
+ gain = torch.empty_like(key, dtype=torch.float32)
7347
+ den = torch.empty_like(forgetting, dtype=torch.float32)
7348
+ k = memory.shape[-2]
7349
+ before_cov = torch.empty((*covariance.shape[:-2], k*(k+1)//2), dtype=covariance.dtype, device=covariance.device)
7350
+ n, k, d, bk, bd = _ldm_kernel_shape(memory)
7351
+ if n:
7352
+ bn = _ldm_row_tile(bk, bd, 8192, 16)
7353
+ _ldm_forward[(triton.cdiv(n, bn),)](memory, covariance, before_cov, key, value, query, forgetting,
7354
+ out, error, gain, den, n, k, d, bk, bd, bn, num_warps=4)
7355
+ return out, error, gain, den, before_cov
7356
+
7357
+
7358
+ @_ldm_forward_op.register_fake
7359
+ def _ldm_forward_fake(memory, covariance, key, value, query, forgetting):
7360
+ k = memory.shape[-2]
7361
+ return (torch.empty_like(value), torch.empty_like(value), torch.empty_like(key, dtype=torch.float32),
7362
+ torch.empty_like(forgetting, dtype=torch.float32),
7363
+ torch.empty((*covariance.shape[:-2], k*(k+1)//2), dtype=covariance.dtype, device=covariance.device))
7364
+
7365
+
7366
+ @torch.library.custom_op("neollm_ldm::backward", mutates_args=("memory", "covariance"))
7367
+ def _ldm_backward_op(memory: torch.Tensor, covariance: torch.Tensor, before_cov: torch.Tensor,
7368
+ grad_memory: torch.Tensor | None, grad_covariance: torch.Tensor | None, key: torch.Tensor,
7369
+ query: torch.Tensor, forgetting: torch.Tensor, error: torch.Tensor,
7370
+ gain: torch.Tensor, den: torch.Tensor, grad_out: torch.Tensor,
7371
+ first: bool) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
7372
+ dk, dv, dq, dl = (torch.empty_like(item) for item in (key, error, query, forgetting))
7373
+ gm, gp = torch.empty_like(memory), torch.empty_like(covariance)
7374
+ gm_in = grad_memory.contiguous() if grad_memory is not None else memory
7375
+ gp_in = grad_covariance.contiguous() if grad_covariance is not None else covariance
7376
+ n, k, d, bk, bd = _ldm_kernel_shape(memory)
7377
+ if n:
7378
+ bn = _ldm_row_tile(bk, bd, 2048, 8)
7379
+ _ldm_backward[(triton.cdiv(n, bn),)](memory, covariance, before_cov, gm_in, gp_in, gm, gp,
7380
+ key, query, forgetting, error, gain, den, grad_out, dk, dv, dq, dl,
7381
+ n, k, d, bk, bd, bn, first, grad_memory is not None, grad_covariance is not None, num_warps=4)
7382
+ return gm, gp, dk, dv, dq, dl
7383
+
7384
+
7385
+ @_ldm_backward_op.register_fake
7386
+ def _ldm_backward_fake(memory, covariance, before_cov, grad_memory, grad_covariance, key, query,
7387
+ forgetting, error, gain, den, grad_out, first):
7388
+ return tuple(torch.empty_like(item) for item in (memory, covariance, key, error, query, forgetting))
7389
+
7390
+
7391
+ class _LDMReversibleStep(torch.autograd.Function):
7392
+ """First-order reverse-mode update with an explicitly owned mutable state."""
7393
+
7394
+ @staticmethod
7395
+ def forward(ctx, memory, covariance, key, value, query, forgetting, first):
7396
+ out, error, gain, den, before_cov = _ldm_forward_op(
7397
+ memory, covariance, key.contiguous(), value.contiguous(), query.contiguous(), forgetting.contiguous())
7398
+ ctx.mark_dirty(memory, covariance)
7399
+ ctx.set_materialize_grads(False)
7400
+ ctx.memory, ctx.covariance = memory.detach(), covariance.detach()
7401
+ ctx.first, ctx.used = first, False
7402
+ ctx.save_for_backward(before_cov, key, query, forgetting, error, gain, den)
7403
+ return memory, covariance, out
7404
+
7405
+ @staticmethod
7406
+ def backward(ctx, grad_memory, grad_covariance, grad_out):
7407
+ if ctx.used:
7408
+ raise RuntimeError("LDM reversible state supports one backward per forward")
7409
+ ctx.used = True
7410
+ before_cov, key, query, forgetting, error, gain, den = ctx.saved_tensors
7411
+ dy = torch.zeros_like(error) if grad_out is None else grad_out.contiguous()
7412
+ gm, gp, dk, dv, dq, dl = _ldm_backward_op(ctx.memory, ctx.covariance, before_cov,
7413
+ grad_memory, grad_covariance, key.contiguous(),
7414
+ query.contiguous(), forgetting.contiguous(),
7415
+ error, gain, den, dy, ctx.first)
7416
+ return gm, gp, dk, dv, dq, dl, None
7417
+
7418
+
7419
+ def _ldm_step(memory, covariance, key, value, query, forgetting, first):
7420
+ return _LDMReversibleStep.apply(memory, covariance, key, value, query, forgetting, first)
7421
+
7422
+
7423
+ def _ldm_reference_step(memory, covariance, key, value, query, forgetting):
7424
+ """Non-mutating RLS reference, also used where Triton is unavailable."""
7425
+ k, v, q = key.float(), value.float(), query.float()
7426
+ u = torch.einsum("...ij,...j->...i", covariance, k)
7427
+ den = forgetting + (k * u).sum(-1)
7428
+ gain = u / den.unsqueeze(-1)
7429
+ error = v - torch.einsum("...kd,...k->...d", memory.float(), k)
7430
+ updated = (memory.float() + gain.unsqueeze(-1) * error.unsqueeze(-2)).to(memory.dtype)
7431
+ covariance_new = (covariance - u.unsqueeze(-1) * u.unsqueeze(-2)
7432
+ / den[..., None, None]) / forgetting[..., None, None]
7433
+ read = torch.einsum("...kd,...k->...d", updated.float(), q).to(value.dtype)
7434
+ return updated, covariance_new, read
7435
+
7436
+
7437
+ class LDMAddress(nn.Module):
7438
+ """Small input-dependent address and identity-initialized group gate."""
7439
+
7440
+ def __init__(self, config):
7441
+ super().__init__()
7442
+ self.groups = config.ldm_groups
7443
+ self.key_dim = config.ldm_key_dim
7444
+ self.context_norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
7445
+ self.w1 = nn.Linear(config.hidden_size, config.ldm_hidden_dim, bias=True)
7446
+ self.w2 = nn.Linear(config.ldm_hidden_dim, self.groups * self.key_dim, bias=True)
7447
+ self.gate = nn.Linear(config.ldm_hidden_dim, self.groups, bias=True)
7448
+ # NeoLLM's HF post_init visits child Linear modules independently.
7449
+ self.gate._ldm_gate_zero_init = True
7450
+ nn.init.zeros_(self.gate.weight)
7451
+ nn.init.zeros_(self.gate.bias)
7452
+
7453
+ def forward(self, source):
7454
+ normalized = F.rms_norm(source.float(), self.context_norm.normalized_shape,
7455
+ self.context_norm.weight.float(), self.context_norm.eps)
7456
+ features = F.silu(self.w1(normalized.to(source.dtype)))
7457
+ address = self.w2(features).float().reshape(*source.shape[:-1], self.groups, self.key_dim)
7458
+ address = F.normalize(address, p=2, dim=-1, eps=1e-6)
7459
+ gate = 2.0 * torch.sigmoid(self.gate(features).float())
7460
+ return address, gate
7461
+
7462
+
7463
+ class LDMWrite(nn.Module):
7464
+ """Generate a weighted write from a residual context and branch output."""
7465
+
7466
+ def __init__(self, config):
7467
+ super().__init__()
7468
+ self.address = LDMAddress(config)
7469
+ self.groups = config.ldm_groups
7470
+ self.value_dim = config.hidden_size // self.groups
7471
+ self.forgetting_logit = nn.Parameter(torch.full((self.groups,), math.log(.99 / .01)))
7472
+
7473
+ def forward(self, context, value, valid, dtype):
7474
+ key, weight = self.address(context)
7475
+ # Mask after sqrt: sqrt(0) in an autograd path can produce NaN gradients.
7476
+ # Match the authors' numerical floors for an extreme negative gate
7477
+ # or forgetting logit, avoiding sqrt(0) and division by zero.
7478
+ scale = weight.clamp_min(1e-6).sqrt() * valid.unsqueeze(-1).to(weight.dtype)
7479
+ key = (key * scale.unsqueeze(-1)).to(dtype).contiguous()
7480
+ value = (value.float().reshape(*value.shape[:-1], self.groups, self.value_dim)
7481
+ * scale.unsqueeze(-1)).to(dtype).contiguous()
7482
+ forgetting = torch.sigmoid(self.forgetting_logit.float()).clamp_min(1e-6).expand_as(weight)
7483
+ forgetting = torch.where(valid.unsqueeze(-1), forgetting, torch.ones_like(forgetting)).contiguous()
7484
+ return key, value, forgetting
7485
+
7486
+
7487
+ class LDMRead(nn.Module):
7488
+ """Read the latest pending write with a normalized gated query."""
7489
+
7490
+ def __init__(self, config):
7491
+ super().__init__()
7492
+ self.address = LDMAddress(config)
7493
+ self.read_gain = nn.Parameter(torch.ones(config.ldm_groups))
7494
+
7495
+ def forward(self, context, state, valid):
7496
+ memory, covariance, key, value, forgetting, first = state
7497
+ query, gate = self.address(context)
7498
+ query = (query * (gate * self.read_gain.float()).unsqueeze(-1)
7499
+ * valid[..., None, None].to(query.dtype)).to(memory.dtype).contiguous()
7500
+ if memory.is_cuda and triton is not None:
7501
+ memory, covariance, read = _ldm_step(memory, covariance, key, value, query, forgetting, first)
7502
+ else:
7503
+ memory, covariance, read = _ldm_reference_step(memory, covariance, key, value, query, forgetting)
7504
+ return memory, covariance, read.reshape_as(context).to(context.dtype)
7505
+
7506
+
7507
  class NeoLLMDecoderLayer(GradientCheckpointingLayer):
7508
  """
7509
  Decoder layer with standard residual connections.
 
7677
  self.msar_block_branches = config.msar_block_branches
7678
  self.msar_attn_read = MSARRead(config) if self.use_msar else None
7679
  self.msar_mlp_read = MSARRead(config) if self.use_msar else None
7680
+ self.use_ldm = bool(getattr(config, "use_ldm", False))
7681
+ self.ldm_attn_read = LDMRead(config) if self.use_ldm else None
7682
+ self.ldm_attn_write = LDMWrite(config) if self.use_ldm else None
7683
+ self.ldm_mlp_read = LDMRead(config) if self.use_ldm else None
7684
+ self.ldm_mlp_write = LDMWrite(config) if self.use_ldm else None
7685
  self.use_attn_res = getattr(config, "use_attn_res", False)
7686
  if self.use_attn_res:
7687
  self.attn_res_query_attn = nn.Parameter(torch.zeros(config.hidden_size))
 
7977
  dag_sources_y=None,
7978
  dag_plan=None,
7979
  dynamics_valid_mask: Optional[torch.Tensor] = None,
7980
+ msar_sources=None,
7981
+ msar_partial=None,
7982
+ msar_banks=None,
7983
+ msar_valid=None,
7984
+ msar_position=None,
7985
+ ldm_state=None,
7986
+ ldm_valid=None,
7987
  **kwargs: Unpack[FlashAttentionKwargs],
7988
  ) -> Tuple:
7989
+ """Update X/Y streams with the selected optional depth mechanism.
7990
+
7991
+ In the hybrid, MSAR stores one history of unscaled shared branch outputs.
7992
+ Its causal depth read is added after separate X/Y normalization, before
7993
+ their optional shared input norm. This uses the existing SiameseNorm
7994
+ input norm and does not introduce an additional normalization. The X/Y
7995
+ update equations and final merge are unchanged, while branch values can
7996
+ change.
7997
+ This hybrid is an adaptation, not the single-stream paper architecture.
7998
+
7999
+ LDM likewise stores one shared history of unscaled branch outputs. It
8000
+ adds its read to the separate X/Y normalization result before the
8001
+ existing shared input norm; write keys use the mean of the updated X/Y
8002
+ streams. Its kernel is independent of the SiameseNorm kernel.
8003
  """
8004
  # SiameseNorm keeps two coupled streams with shared Attention/MLP
8005
  # parameters. All Siamese normalization modules are RMSNorm by
8006
  # construction; no dynamic-normalization branch remains.
8007
  # ── Attention shared block ────────────────────────────────────────
8008
  norm_backend = getattr(self, "siamesenorm_kernel_backend", "reference")
8009
+ if self.use_msar:
8010
+ branch = 2 * self.layer_idx
8011
+ msar_sources, msar_partial = _msar_start_branch(
8012
+ msar_sources, msar_partial, branch, self.msar_block_branches)
8013
+ msar_attn = self.msar_attn_read(msar_sources, msar_partial, msar_banks,
8014
+ branch, self.msar_block_branches, msar_valid, msar_position)
8015
+ else:
8016
+ msar_attn = None
8017
  if norm_backend == "reference":
8018
  x_attn_norm = self.siamese_attn_x_norm(x_states)
8019
  y_attn_norm = self.siamese_attn_y_norm(y_states)
 
8021
  dtype=x_attn_norm.dtype, device=x_attn_norm.device
8022
  )
8023
  h_attn = x_scale * x_attn_norm + y_attn_norm
 
 
8024
  else:
8025
  from cut_cross_entropy.siamesenorm import siamesenorm
8026
  h_attn = siamesenorm(
8027
  x_states, y_states,
8028
  self.siamese_attn_x_norm.weight, self.siamese_attn_y_norm.weight,
8029
+ self.siamese_attn_input_norm.weight
8030
+ if not (self.use_msar or self.use_ldm) and self.siamese_attn_input_norm is not None else None,
8031
  self.siamese_attn_x_scale.to(dtype=x_states.dtype, device=x_states.device),
8032
  eps=self.siamese_attn_x_norm.eps, backend=norm_backend,
8033
  )
8034
+ if self.use_msar:
8035
+ h_attn = h_attn + msar_attn
8036
+ if self.use_ldm:
8037
+ ldm_memory, ldm_covariance, ldm_attn = self.ldm_attn_read(h_attn, ldm_state, ldm_valid)
8038
+ h_attn = h_attn + ldm_attn
8039
+ if self.siamese_attn_input_norm is not None and (norm_backend == "reference" or self.use_msar or self.use_ldm):
8040
+ h_attn = self.siamese_attn_input_norm(h_attn)
8041
 
8042
  stack_metrics = None
8043
  if self.use_stack_memory:
 
8073
  y_states = y_states + dag_ry
8074
  x_after_attn = x_states + stream_scale * attn_out
8075
  y_after_attn = y_states + attn_out
8076
+ if self.use_ldm:
8077
+ ldm_state = (ldm_memory, ldm_covariance,
8078
+ *self.ldm_attn_write((x_after_attn + y_after_attn) * .5,
8079
+ attn_out, ldm_valid, ldm_memory.dtype), False)
8080
+ if self.use_msar:
8081
+ msar_partial = attn_out if msar_partial is None else msar_partial + attn_out
8082
 
8083
  if self.use_gpas:
8084
  x_after_attn = self.gpas_attn(x_after_attn)
8085
 
8086
  # ── MLP shared block ──────────────────────────────────────────────
8087
+ if self.use_msar:
8088
+ branch = 2 * self.layer_idx + 1
8089
+ msar_sources, msar_partial = _msar_start_branch(
8090
+ msar_sources, msar_partial, branch, self.msar_block_branches)
8091
+ msar_mlp = self.msar_mlp_read(msar_sources, msar_partial, msar_banks,
8092
+ branch, self.msar_block_branches, msar_valid, msar_position)
8093
+ else:
8094
+ msar_mlp = None
8095
  if norm_backend == "reference":
8096
  x_mlp_norm = self.siamese_mlp_x_norm(x_after_attn)
8097
  y_mlp_norm = self.siamese_mlp_y_norm(y_after_attn)
8098
  h_mlp = x_mlp_norm + y_mlp_norm
 
 
8099
  else:
8100
  h_mlp = siamesenorm(
8101
  x_after_attn, y_after_attn,
8102
  self.siamese_mlp_x_norm.weight, self.siamese_mlp_y_norm.weight,
8103
+ self.siamese_mlp_input_norm.weight
8104
+ if not (self.use_msar or self.use_ldm) and self.siamese_mlp_input_norm is not None else None,
8105
  eps=self.siamese_mlp_x_norm.eps, backend=norm_backend,
8106
  post_scale=(
8107
  self.lns_mlp.scale.to(device=x_after_attn.device, dtype=x_after_attn.dtype)
8108
+ if self.use_lns and not (self.use_msar or self.use_ldm) else None
8109
  ),
8110
  out_dtype=(
8111
  torch.get_autocast_dtype("cuda")
8112
+ if not (self.use_msar or self.use_ldm) and torch.is_autocast_enabled("cuda") else None
8113
  ),
8114
  )
8115
+ if self.use_msar:
8116
+ h_mlp = h_mlp + msar_mlp
8117
+ if self.use_ldm:
8118
+ ldm_memory, ldm_covariance, ldm_mlp = self.ldm_mlp_read(h_mlp, ldm_state, ldm_valid)
8119
+ h_mlp = h_mlp + ldm_mlp
8120
+ if self.siamese_mlp_input_norm is not None and (norm_backend == "reference" or self.use_msar or self.use_ldm):
8121
+ h_mlp = self.siamese_mlp_input_norm(h_mlp)
8122
 
8123
  # Only this consumer starts directly with an autocast Linear. Attention
8124
  # also exposes native states to positional, router and memory consumers.
8125
+ h_lns2 = self.lns_mlp(h_mlp) if self.use_lns and (norm_backend == "reference" or self.use_msar or self.use_ldm) else h_mlp
8126
  delta_m = self.mlp(h_lns2)
8127
 
8128
  jtok_aux_stats = None
 
8143
 
8144
  x_next = x_after_attn + stream_scale * shared_update
8145
  y_next = y_after_attn + shared_update
8146
+ if self.use_ldm:
8147
+ ldm_state = (ldm_memory, ldm_covariance,
8148
+ *self.ldm_mlp_write((x_next + y_next) * .5,
8149
+ shared_update, ldm_valid, ldm_memory.dtype), False)
8150
+ if self.use_msar:
8151
+ msar_partial = shared_update if msar_partial is None else msar_partial + shared_update
8152
 
8153
  if self.use_gpas:
8154
  x_next = self.gpas_mlp(x_next)
8155
 
8156
  outputs = (x_next, y_next)
8157
+ if self.use_ldm:
8158
+ outputs += (ldm_state,)
8159
+ if self.use_msar:
8160
+ outputs += (msar_sources, msar_partial)
8161
  if self.use_stack_memory:
8162
  outputs += (stack_memory, stack_memory_mask, stack_metrics)
8163
  if jtok_aux_stats is not None:
 
8179
  msar_banks=None,
8180
  msar_valid=None,
8181
  msar_position=None,
8182
+ ldm_state=None,
8183
+ ldm_valid=None,
8184
  output_attentions: Optional[bool] = False,
8185
  repo_rope_args: Optional[Tuple[torch.Tensor, float]] = None,
8186
  position_ids: Optional[torch.LongTensor] = None,
 
8229
  h_attn = hidden_states
8230
  residual_attn = hidden_states
8231
 
8232
+ if self.use_ldm:
8233
+ ldm_memory, ldm_covariance, ldm_attn = self.ldm_attn_read(h_attn, ldm_state, ldm_valid)
8234
+ h_attn = h_attn + ldm_attn
8235
+
8236
  # ── Attention block ───────────────────────────────────────────────
8237
 
8238
  # Optional embedding input normalisation:
 
8283
  attn_aug = residual_attn + hidden_states
8284
 
8285
  h_tilde = self.gpas_attn(attn_aug) if self.use_gpas else attn_aug
8286
+ if self.use_ldm:
8287
+ ldm_state = (ldm_memory, ldm_covariance,
8288
+ *self.ldm_attn_write(h_tilde, hidden_states, ldm_valid, ldm_memory.dtype), False)
8289
 
8290
  # ── Attention Residuals: compute pre-MLP input ────────────────────
8291
  # After attention, the partial sum is updated with h_tilde.
 
8310
  h_mlp = h_tilde
8311
  residual_mlp = h_tilde
8312
 
8313
+ if self.use_ldm:
8314
+ ldm_memory, ldm_covariance, ldm_mlp = self.ldm_mlp_read(h_mlp, ldm_state, ldm_valid)
8315
+ h_mlp = h_mlp + ldm_mlp
8316
+
8317
  # ── MLP block ─────────────────────────────────────────────────────
8318
  h_normed2 = _apply_norm(self.post_attention_layernorm, h_mlp)
8319
  h_lns2 = self.lns_mlp(h_normed2) if self.use_lns else h_normed2
 
8352
  mlp_aug = residual_mlp + delta_m
8353
 
8354
  hidden_states = self.gpas_mlp(mlp_aug) if self.use_gpas else mlp_aug
8355
+ if self.use_ldm:
8356
+ ldm_state = (ldm_memory, ldm_covariance,
8357
+ *self.ldm_mlp_write(hidden_states, delta_m, ldm_valid, ldm_memory.dtype), False)
8358
 
8359
  outputs = (hidden_states,)
8360
+ if self.use_ldm:
8361
+ outputs += (ldm_state,)
8362
  if self.use_msar:
8363
  outputs += (msar_sources, msar_partial)
8364
  if jtok_aux_stats is not None:
 
8702
  _supports_flash_attn_2 = True
8703
  _supports_sdpa = True
8704
  _is_stateful = True
8705
+ # Preserve the paper's FP32 LDM controls while loading a BF16 checkpoint.
8706
+ # Transformers applies this before copying tensors, avoiding lossy casts.
8707
+ _keep_in_fp32_modules_strict = {"ldm_*.read_gain", "ldm_*.forgetting_logit"}
8708
 
8709
  @classmethod
8710
  def from_pretrained(cls, *args, **kwargs):
 
8808
  return model
8809
 
8810
  def _init_weights(self, module):
8811
+ """Apply HF initialization, then preserve identity-centered gates."""
8812
  super()._init_weights(module)
8813
+ if getattr(module, "_dag_zero_init", False) or getattr(module, "_ldm_gate_zero_init", False):
8814
  nn.init.zeros_(module.weight)
8815
  nn.init.zeros_(module.bias)
8816
  return
 
9078
  sum(config.msar_kernel_sizes), config.hidden_size))
9079
  if config.use_msar else None
9080
  )
9081
+ self.ldm_embedding_write = LDMWrite(config) if config.use_ldm else None
9082
+ self.ldm_final_read = LDMRead(config) if config.use_ldm else None
9083
  self.dagformer = (DAGFormerTopology(config, self.layers)
9084
  if bool(getattr(config, "use_dagformer", False)) else None)
9085
  self.use_siamesenorm = bool(getattr(config, "use_siamesenorm", False))
 
9422
  hidden_states = inputs_embeds
9423
  use_siamesenorm = bool(getattr(self.config, "use_siamesenorm", False))
9424
  siamese_y_states = inputs_embeds if use_siamesenorm else None
9425
+ use_ldm = bool(getattr(self.config, "use_ldm", False))
9426
+ if use_ldm:
9427
+ if self.gradient_checkpointing and self.training:
9428
+ raise ValueError("LDM reversible state is incompatible with whole-layer gradient checkpointing.")
9429
+ if attention_mask is not None and (attention_mask.ndim != 2 or attention_mask.shape != hidden_states.shape[:2]):
9430
+ raise ValueError("LDM requires a token-domain 2-D attention mask.")
9431
+ ldm_valid = (attention_mask.to(dtype=torch.bool).contiguous() if attention_mask is not None
9432
+ else torch.ones(hidden_states.shape[:2], dtype=torch.bool, device=hidden_states.device))
9433
+ state_dtype = (torch.get_autocast_dtype("cuda") if hidden_states.is_cuda and
9434
+ torch.is_autocast_enabled("cuda") else hidden_states.dtype)
9435
+ groups, key_dim = self.config.ldm_groups, self.config.ldm_key_dim
9436
+ value_dim = self.config.hidden_size // groups
9437
+ ldm_memory = torch.zeros((*hidden_states.shape[:2], groups, key_dim, value_dim),
9438
+ device=hidden_states.device, dtype=state_dtype)
9439
+ ldm_covariance = torch.eye(key_dim, device=hidden_states.device, dtype=torch.float32).expand(
9440
+ *hidden_states.shape[:2], groups, key_dim, key_dim).clone()
9441
+ ldm_state = (ldm_memory, ldm_covariance,
9442
+ *self.ldm_embedding_write(hidden_states, hidden_states, ldm_valid, state_dtype), True)
9443
+ else:
9444
+ ldm_state = ldm_valid = None
9445
  # The same validated dense/right-padding training contract applies to
9446
  # the causal planner: valid queries cannot see future padded keys.
9447
  dag_plans = (self.dagformer(input_ids, inputs_embeds, position_ids,
 
9601
  dag_sources=dag_sources,
9602
  dag_plan=dag_plans[layer_idx] if dag_plans is not None else None,
9603
  dynamics_valid_mask=attention_mask,
9604
+ msar_sources=msar_sources, msar_partial=msar_partial,
9605
+ msar_banks=self.msar_banks, msar_valid=msar_valid,
9606
+ msar_position=msar_position,
9607
+ ldm_state=ldm_state, ldm_valid=ldm_valid,
9608
  **kwargs,
9609
  )
9610
  hidden_states = layer_outputs[0]
9611
  siamese_y_states = layer_outputs[1]
9612
+ if use_msar:
9613
+ msar_sources, msar_partial = layer_outputs[2:4]
9614
+ extras_start = 4
9615
+ elif use_ldm:
9616
+ ldm_state = layer_outputs[2]
9617
+ extras_start = 3
9618
  else:
9619
  extras_start = 2
9620
+ if use_stack_memory:
9621
+ stack_memory = layer_outputs[extras_start]
9622
+ stack_memory_mask = layer_outputs[extras_start + 1]
9623
+ stack_layer_metrics = layer_outputs[extras_start + 2]
9624
+ all_stack_metrics = all_stack_metrics + (stack_layer_metrics,)
9625
+ extras_start += 3
9626
  else:
9627
  layer_outputs = decoder_layer(
9628
  hidden_states,
 
9634
  msar_sources=msar_sources, msar_partial=msar_partial,
9635
  msar_banks=self.msar_banks, msar_valid=msar_valid,
9636
  msar_position=msar_position,
9637
+ ldm_state=ldm_state, ldm_valid=ldm_valid,
9638
  output_attentions=output_attentions,
9639
  repo_rope_args=repo_rope_args,
9640
  position_ids=position_ids,
 
9652
  extras_start = 3
9653
  # The last branch's h_mlp + delta_m is the terminal
9654
  # residual merge. No new source expansion is applied.
9655
+ elif use_ldm:
9656
+ ldm_state = layer_outputs[1]
9657
+ extras_start = 2
9658
  else:
9659
  extras_start = 1
9660
 
 
9700
 
9701
  if use_siamesenorm:
9702
  norm_backend = getattr(self, "siamesenorm_kernel_backend", "reference")
9703
+ if use_ldm:
9704
+ merged = self.siamese_x_final_norm(hidden_states) + self.siamese_y_final_norm(siamese_y_states)
9705
+ _, _, ldm_final = self.ldm_final_read(merged, ldm_state, ldm_valid)
9706
+ hidden_states = self.siamese_final_norm(merged + ldm_final)
9707
+ elif norm_backend == "reference":
9708
  hidden_states = self.siamese_final_norm(
9709
  self.siamese_x_final_norm(hidden_states)
9710
  + self.siamese_y_final_norm(siamese_y_states)
 
9718
  eps=self.siamese_x_final_norm.eps, backend=norm_backend,
9719
  )
9720
  else:
9721
+ if use_ldm:
9722
+ _, _, ldm_final = self.ldm_final_read(hidden_states, ldm_state, ldm_valid)
9723
+ hidden_states = hidden_states + ldm_final
9724
  hidden_states = self.norm(hidden_states)
9725
 
9726
  if output_hidden_states:
 
12032
  for name, parameter in module.named_parameters():
12033
  if parameter.requires_grad:
12034
  groups[f"{kind}/layer_{module.layer_idx}/{name}"] = (parameter,)
12035
+ for slot in ("attn", "mlp"):
12036
+ read = getattr(layer, f"msar_{slot}_read", None)
12037
+ if read is not None:
12038
+ prefix = f"msar/layer_{layer.layer_idx}/{slot}"
12039
+ if read.query.requires_grad:
12040
+ groups[f"{prefix}/query"] = (read.query,)
12041
+ mixer = tuple(parameter for parameter in
12042
+ (read.mixer_weight, read.mixer_bias, read.mixer_scale)
12043
+ if parameter.requires_grad)
12044
+ if mixer:
12045
+ groups[f"{prefix}/mixer"] = mixer
12046
  families = {
12047
  "dag": ("dagformer_local",),
12048
  "attnres": ("attn_res_query_attn", "attn_res_query_mlp", "attn_res_norm"),
 
12065
  groups[f"fan/layer_{layer.layer_idx}"] = (layer.self_attn.lambda_1, layer.self_attn.lambda_2)
12066
  if self.model.dagformer is not None:
12067
  groups["dag/global_topology"] = tuple(p for p in self.model.dagformer.parameters() if p.requires_grad)
12068
+ if self.model.msar_banks is not None and self.model.msar_banks.requires_grad:
12069
+ groups["msar/banks"] = (self.model.msar_banks,)
12070
  return groups
12071
 
12072
  def set_input_embeddings(self, value):
optimizer_precision.json CHANGED
@@ -8,9 +8,9 @@
8
  "delta_enabled": false,
9
  "delta_eta": 0.37,
10
  "fp32_master_active": true,
11
- "fp32_master_elements": 85093304,
12
  "fp32_master_groups": 6,
13
- "fp32_master_tensors": 378,
14
  "fp32_passthrough_elements": 0,
15
  "mode": "fp32_master",
16
  "mxfp8_active": false,
@@ -19,12 +19,12 @@
19
  "mxfp8_recipe": null,
20
  "native_precision_elements": 0,
21
  "optimizer_variant": "AdEMAMix",
22
- "ordinary_linear_layers": 127,
23
  "parameter_elements_by_dtype": {
24
- "bfloat16": 85091128,
25
  "float32": 2176
26
  },
27
- "parameter_storage_bytes": 170190960,
28
  "runtime_precision_mode": "BF16_FP32_MASTER",
29
  "stochastic_round_seed": 0
30
  }
 
8
  "delta_enabled": false,
9
  "delta_eta": 0.37,
10
  "fp32_master_active": true,
11
+ "fp32_master_elements": 84567992,
12
  "fp32_master_groups": 6,
13
+ "fp32_master_tensors": 374,
14
  "fp32_passthrough_elements": 0,
15
  "mode": "fp32_master",
16
  "mxfp8_active": false,
 
19
  "mxfp8_recipe": null,
20
  "native_precision_elements": 0,
21
  "optimizer_variant": "AdEMAMix",
22
+ "ordinary_linear_layers": 124,
23
  "parameter_elements_by_dtype": {
24
+ "bfloat16": 84565816,
25
  "float32": 2176
26
  },
27
+ "parameter_storage_bytes": 169140336,
28
  "runtime_precision_mode": "BF16_FP32_MASTER",
29
  "stochastic_round_seed": 0
30
  }
training_args.bin CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:bc8f43459b8eea473f97704f0e1395ab4f28e9644cd98156e2a840b62cd44b23
3
  size 5329
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f25ab9f8e336f20fbb462d246d9a32edde7efc0113368fb5f2ca06f3cfe35459
3
  size 5329