Quazim0t0 commited on
Commit
d37dc17
·
verified ·
1 Parent(s): 0cf8e52

Jet-Long dynamic bifocal RoPE: 4K->10K zero-shot context extension (arXiv:2607.07740)

Browse files
Files changed (10) hide show
  1. .gitattributes +1 -0
  2. Escarda-LLMs.png +3 -0
  3. README.md +313 -0
  4. config.json +70 -0
  5. config.py +164 -0
  6. model.safetensors +3 -0
  7. model_v2.py +1042 -0
  8. spike_tokenizer.py +117 -0
  9. tokenizer.json +0 -0
  10. tokenizer_config.json +204 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ Escarda-LLMs.png filter=lfs diff=lfs merge=lfs -text
Escarda-LLMs.png ADDED

Git LFS Details

  • SHA256: e5c77ff3cc40624c40822258fb15bc28931e3bf34aa739314803c0da717e7bf1
  • Pointer size: 132 Bytes
  • Size of remote file: 3.04 MB
README.md ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ tags:
6
+ - text-generation
7
+ - small-models
8
+ - base-model
9
+ - mla
10
+ - jepa
11
+ - experimental
12
+ pipeline_tag: text-generation
13
+ library_name: transformers
14
+ datasets:
15
+ - HuggingFaceFW/fineweb-edu
16
+ ---
17
+
18
+ <img src="Escarda-LLMs.png" width="50%">
19
+
20
+
21
+ # Escarda-86M-Base
22
+
23
+ ## 🔭 Jet-Long context extension (native 4K → 10K)
24
+
25
+ This is the **Jet-Long** edition of [`Escarda-86M-Base`](https://huggingface.co/Quazim0t0/Escarda-86M-Base).
26
+ It extends the usable context from the native **4,096**-token training window to
27
+ **10,240** tokens with **no fine-tuning** and **no change to short-context behaviour**,
28
+ by adding *dynamic bifocal RoPE* from **Jet-Long** ([arXiv:2607.07740](https://arxiv.org/abs/2607.07740), NVIDIA).
29
+
30
+ ### What was applied
31
+
32
+ Jet-Long pairs a **local window** (`w0 = 2048`, classic RoPE) with a **remote window**
33
+ whose position map aliases far-apart tokens back onto the pretrained rotation grid:
34
+
35
+ ```
36
+ f(x) = floor(x / G), G = max(1, ceil(L / 4096))
37
+ ```
38
+
39
+ `G` adapts to the **current** sequence length `L`, so:
40
+
41
+ - **`L ≤ 4096` → `G = 1` → f is the identity → the model is bit-for-bit the base model.**
42
+ (Verified: max |Δlogit| between Jet-Long on/off within the window is `0.000e+00`.)
43
+ - **`L > 4096`** → the remote window keeps every rotation in-distribution, so the model
44
+ extrapolates instead of collapsing.
45
+
46
+ Implementation notes specific to this SpikeWhaleLM build:
47
+ - Only the **decoupled RoPE partition** (16 of 64 head dims) is aliased; the NoPE partition
48
+ is untouched. Softmax attention (`use_derf=False`) — the standard Jet-Long merge applies.
49
+ - The remote view is realized by an **on-the-fly correction rotation** on the already-RoPE'd
50
+ KV cache (RoPE composes additively), so **the cache is never rewritten** and decode is cheap.
51
+ - Enabled via config: `use_jetlong=true`, `jetlong_w0=2048`, `jetlong_w_pretrained=4096`,
52
+ `max_position_embeddings=10240`. Set `use_jetlong=false` to recover the exact base model.
53
+ - The inclusion–exclusion / CuTe throughput kernel from the paper is **not** included (it targets
54
+ 100K+ contexts on H100); at 86M params the bifocal attention is computed directly.
55
+
56
+ ### Measured (PG-19-style perplexity on held-out text, lower is better)
57
+
58
+ | Context length | Base model | This Jet-Long model |
59
+ |---|---|---|
60
+ | ≤ 4,096 (in-window) | *(identical — Jet-Long is a no-op)* | *(identical)* |
61
+ | **10,240** | **59.68** | **16.04** |
62
+
63
+ Beyond the training window the base model's perplexity blows up while Jet-Long stays flat —
64
+ and long-context generation stays grammatical where the base model degrades into word-salad.
65
+
66
+ ### Usage
67
+
68
+ Jet-Long is **on by default** in this repo. Pass explicit `position_ids` so RoPE gets true
69
+ absolute positions during cached decode:
70
+
71
+ ```python
72
+ import torch
73
+ from transformers import AutoModelForCausalLM
74
+ m = AutoModelForCausalLM.from_pretrained("Quazim0t0/Escarda-86M-Base-JL", trust_remote_code=True)
75
+ ids = ... # up to ~10,240 tokens
76
+ pos = torch.arange(ids.shape[1]).unsqueeze(0)
77
+ out = m(input_ids=ids, position_ids=pos, use_cache=True) # prefill, then decode step-by-step
78
+ ```
79
+
80
+ *Method: Tang, Wang, Gu, Han, Cai — “Jet-Long: Efficient Long-Context Extension with Dynamic
81
+ Bifocal RoPE”, arXiv:2607.07740. Applied here zero-shot to SpikeWhaleLM; no weights were retrained.*
82
+
83
+
84
+ **Escarda-86M-Base** is a ~86M-parameter, from-scratch decoder-only language model — the
85
+ **base** sibling of [Quazim0t0/Escarda-86M](https://huggingface.co/Quazim0t0/Escarda-86M)
86
+ (the chat-tuned model). It shares the same `SpikeWhaleLM` architecture (Multi-head Latent
87
+ Attention, an n-gram "engram" memory, hash-lookup layers, hyper-connections, an HRM
88
+ refinement step, and JEPA / multi-token-prediction training objectives) and the same
89
+ custom ChatML-aware tokenizer.
90
+
91
+ This checkpoint is a JEPA-distilled base. It is best used as a **starting point for
92
+ continued pretraining / fine-tuning** rather than as a chat assistant.
93
+
94
+ > **Related models:** SFT / chat model → [Quazim0t0/Escarda-86M](https://huggingface.co/Quazim0t0/Escarda-86M)
95
+ > · live demo → [Escarda-86M-Chat Space](https://huggingface.co/spaces/Quazim0t0/Escarda-86M-Chat)
96
+
97
+ Trained using **Modal's credits** during the **Small Models, Big Adventures Hackathon**.
98
+
99
+ ---
100
+
101
+ ## Model summary
102
+
103
+ | | |
104
+ |---|---|
105
+ | **Parameters** | ~85.7M (`tie_word_embeddings=True`) |
106
+ | **Type** | Decoder-only LM (`SpikeWhaleLM`, `model_type: spike_whale`) |
107
+ | **Hidden size / layers** | 640 / 16 |
108
+ | **Attention** | 10 heads (`head_dim=64`), 1 KV head (MQA), MLA low-rank Q/O, decoupled RoPE(16)+NoPE(48), QK-norm |
109
+ | **Context length** | 4096 tokens |
110
+ | **Vocab** | 16,512 (custom `length-max` tokenizer) |
111
+ | **License** | Apache-2.0 |
112
+
113
+ For the full architecture description see the
114
+ [chat model's card](https://huggingface.co/Quazim0t0/Escarda-86M#architecture).
115
+
116
+ ---
117
+
118
+ <!-- ARCH_TOK_START -->
119
+ ## Architecture
120
+
121
+ These models are built on **SpikeWhaleLM**, a custom ~86M-parameter decoder-only transformer
122
+ (16 layers, hidden size 640, 4096-token context, 16,512 vocab, tied input/output embeddings).
123
+ It combines several non-standard components:
124
+
125
+ - **Multi-head Latent Attention (MLA + XSA)** — queries and the output projection are
126
+ LoRA-compressed (rank 128); each head splits into a decoupled RoPE part (dim 16) and a
127
+ position-agnostic NoPE part (dim 48); 10 query heads share a **single KV head**
128
+ (multi-query attention), with QK-norm for stable logits.
129
+ - **Engram n-gram memory** — a gated associative memory that hashes local n-grams (up to
130
+ trigrams) into a learned 4,096-entry table and mixes the result back into the residual stream.
131
+ - **Hash-lookup layers (×2)** — multi-head content-addressable features alongside the token
132
+ embeddings.
133
+ - **Hyper-Connections** — learned, width-expanded residual connections mixed via
134
+ Sinkhorn-normalized routing, in place of the plain residual add.
135
+ - **HRM refinement** — a Hierarchical Reasoning Model block that performs an extra latent
136
+ "think a bit more" refinement pass over the hidden states before the output head.
137
+ - **Multi-Token Prediction (MTP)** — a DeepSeek-V3-style auxiliary training head predicting
138
+ more than one next token (no inference cost).
139
+ - Feed-forward is **dense** (the block is MoE-capable, but MoE is disabled in this release).
140
+
141
+ > **JEPA vs HRM.** The **Escarda** models are trained with **both HRM refinement and a JEPA (Joint-Embedding Predictive Architecture) auxiliary objective** (`use_hrm_refine=True`, `use_jepa=True`) — the JEPA term predicts future latent states during training to shape the model's representations. The sibling **Byrne** models drop JEPA and use **HRM refinement only**.
142
+
143
+ <a href="https://hfviewer.com/Quazim0t0/Escarda-86M-Base?utm_source=huggingface&amp;utm_medium=embedded_model_card&amp;utm_campaign=Quazim0t0__Escarda-86M-Base_card&amp;utm_content=embedded_card_open_viewer&amp;from=embedded-model-card" target="_blank" rel="noopener">
144
+ <img
145
+ src="https://hfviewer.com/api/card.svg?source=Quazim0t0%2FEscarda-86M-Base&amp;granularity=auto&amp;v=20260516-title-pills-card"
146
+ alt="Architecture graph for Quazim0t0/Escarda-86M-Base. Open in hfviewer"
147
+ width="100%"
148
+ />
149
+ </a>
150
+
151
+ ## Tokenizer
152
+
153
+ These models use **`SpikeTokenizer`**, a custom **byte-level "length-max" (greedy
154
+ longest-match)** tokenizer with a **16,512-token vocabulary** — not a standard BPE/HF
155
+ tokenizer. Text is UTF-8 encoded, each byte mapped to a latin-1 character, then greedily
156
+ matched against the vocab using the longest key that fits at each position. It is
157
+ **ChatML-aware**, with atomic special tokens for framing and reasoning/tool markers
158
+ (`<|im_start|>`, `<|im_end|>`, `<think>`/`</think>`, `<begin_solution>`/`<end_solution>`,
159
+ tool-call markers) plus `<bos>`/`<eos>`/`<pad>`/`<unk>`. It ships as a `PreTrainedTokenizer`
160
+ subclass (`spike_tokenizer.py`) and loads via
161
+ `AutoTokenizer.from_pretrained(..., trust_remote_code=True)`.
162
+ <!-- ARCH_TOK_END -->
163
+
164
+ ## Evaluation
165
+
166
+ splits. byte_ppl is `exp(sum_NLL_nats / total_UTF8_bytes)` on WikiText-2 test (tokenizer-
167
+ independent). BLiMP is fraction of minimal pairs with `logprob(good) > logprob(bad)`
168
+ (12 paradigms × 150). Stderr is binomial `sqrt(p(1-p)/n)`.
169
+
170
+ ### Language modeling
171
+
172
+ | Metric | Value |
173
+ |---|---|
174
+ | WikiText-2 byte_ppl ↓ | **2.2228** |
175
+ | BLiMP acc ↑ | 0.7144 |
176
+
177
+ ### Multiple-choice suite
178
+
179
+ | Task | acc | ± | acc_norm | ± |
180
+ |---|---|---|---|---|
181
+ | arc_easy | 0.3801 | 0.0100 | 0.3615 | 0.0099 |
182
+ | arc_challenge | 0.1886 | 0.0114 | 0.2235 | 0.0122 |
183
+ | hellaswag | 0.2759 | 0.0045 | 0.2832 | 0.0045 |
184
+ | winogrande | 0.5162 | 0.0140 | — | — |
185
+ | piqa | 0.5843 | 0.0115 | 0.5631 | 0.0116 |
186
+ | openbookqa | 0.1300 | 0.0150 | 0.2500 | 0.0194 |
187
+ | boolq | 0.5138 | 0.0087 | — | — |
188
+
189
+ ### ArithMark-2.0 ([AxiomicLabs](https://huggingface.co/datasets/AxiomicLabs/ArithMark-2.0))
190
+
191
+ | Metric | Value |
192
+ |---|---|
193
+ | acc | 0.2536 ± 0.0087 |
194
+ | acc_norm | 0.2348 ± 0.0085 |
195
+
196
+ n = 2,500 · chance = 0.25.
197
+
198
+ > **Note:** as a distilled base, this checkpoint has the **lowest byte-perplexity** of the
199
+ > Escarda family but trades off downstream task accuracy — a good reminder that perplexity
200
+ > alone is not a reliable capability ranking. For the strongest chat behaviour use
201
+ > [Escarda-86M](https://huggingface.co/Quazim0t0/Escarda-86M); use **this** model when you
202
+ > want a low-loss base to continue pretraining or fine-tune.
203
+
204
+ ---
205
+
206
+ ## Training & token budget
207
+
208
+ - **Tokens:** ~20B (from-scratch pretraining of the SpikeWhale base, ~28k steps); this
209
+ checkpoint is a JEPA-distilled snapshot of that base.
210
+ - **Token/param ratio:** ~233 tokens/param (20B / 85.7M) — roughly **11–12× the Chinchilla
211
+ ~20-tokens/param compute-optimal heuristic**, i.e. a deliberately **over-trained small
212
+ model** (the inference-efficient trade-off).
213
+
214
+ Fitting the Chinchilla data term to this model's own pretraining loss curve gives:
215
+
216
+ `L(D) ≈ 2.611 + 77,715 · D^(−0.537)` (nats/token, R² = 0.92)
217
+
218
+ From that fit:
219
+ - **Compute-optimal tokens for this 86M size ≈ 4.3B** → the 20B run is **~4.6× past
220
+ compute-optimal**.
221
+ - **Diminishing-returns knee ≈ 22.5B** tokens (where +1B tokens buys < 0.005 nats) — the
222
+ 20B stopping point lands **right at the knee**, a well-judged budget.
223
+ - The model is **parameter-bound, not data-bound** at 20B: the capacity term (~0.82 nats)
224
+ exceeds the data term (~0.54), so extra tokens help little. Doubling to 40B is projected
225
+ to lower loss only ~0.07 nats (~7% perplexity) with negligible downstream gain — the lever
226
+ for better quality is **more parameters, not more tokens**. (This is also why, as a
227
+ distilled base, it reaches the lowest perplexity of the family without the best downstream
228
+ scores — it is already at its data-term floor.)
229
+
230
+ *Caveats: single-size fit (folds irreducible loss + capacity floor into one constant); the
231
+ cosine-LR decay inflates the fitted exponent, so treat β as an upper bound; token counts are
232
+ anchored to the ~20B figure and scale linearly if that differs.*
233
+
234
+ ---
235
+
236
+ ## Usage
237
+
238
+ Custom architecture — load with `trust_remote_code=True` (the modeling code ships in this
239
+ repo via `auto_map`):
240
+
241
+ ```python
242
+ from transformers import AutoModelForCausalLM
243
+ model = AutoModelForCausalLM.from_pretrained(
244
+ "Quazim0t0/Escarda-86M-Base", trust_remote_code=True)
245
+ ```
246
+
247
+ The tokenizer is the custom `SpikeTokenizer` (`tokenizer.json`, `algorithm: length-max`);
248
+ load it with the `spike_tokenizer.py` helper from the project rather than `AutoTokenizer`.
249
+
250
+ ## Acknowledgements
251
+
252
+ Built with **Modal** credits during the **Small Models, Big Adventures Hackathon**, and
253
+ released to the community as a base to build on.
254
+
255
+ <!-- CITE_START -->
256
+ ## Citation
257
+
258
+ If you use this model, please cite:
259
+
260
+ ```bibtex
261
+ @misc{escarda86mbase,
262
+ title = {Escarda-86M-Base: A ~86M-parameter SpikeWhaleLM},
263
+ author = {Dean Byrne (Quazim0t0)},
264
+ year = {2026},
265
+ howpublished = {HuggingFace, \url{https://huggingface.co/Quazim0t0/Escarda-86M-Base}},
266
+ note = {Quazim0t0/Escarda-86M-Base}
267
+ }
268
+ ```
269
+ <!-- CITE_END -->
270
+
271
+
272
+ ## Escarda vs Byrne — vision family comparison
273
+
274
+ The **Byrne** family uses HRM refinement. **Escarda** = Byrne **+ JEPA** (Joint-Embedding
275
+ Predictive head) added *alongside* HRM in both the vision encoder and the LM trunk —
276
+ auxiliary only, **zero inference cost**.
277
+
278
+ **Vision encoder (DINOv2 teacher-alignment, n=1024 held-out):**
279
+
280
+ | | Byrne-VE | Escarda-VE |
281
+ |---|---|---|
282
+ | Params | 39.34M | 39.60M (+JEPA head) |
283
+ | CLS cosine | **0.776** | 0.771 |
284
+ | PATCH cosine | **0.600** | 0.584 |
285
+ | JEPA self-consistency | — | 0.040 |
286
+
287
+ **Docling (same held-out doc images, atomic DocTags):** both emit well-formed DocTags;
288
+ Byrne-Docling is marginally more complete on the hardest samples (closes `</formula>`,
289
+ includes the `<code>` wrapper), consistent with its slightly higher teacher-alignment.
290
+ Escarda-Docling is structurally on par and adds the JEPA representation-learning trait.
291
+
292
+ **Pros/cons.** *Byrne (HRM):* higher teacher-alignment, all capacity on distillation
293
+ fidelity; no self-supervised objective. *Escarda (HRM+JEPA):* self-supervised
294
+ neighbour-prediction (richer spatial structure) at zero inference cost, trading ~1–3%
295
+ teacher-alignment. Same size class.
296
+
297
+ Family repos: [Byrne-VE](https://huggingface.co/Quazim0t0/Byrne-VE) ·
298
+ [Escarda-VE](https://huggingface.co/Quazim0t0/Escarda-VE) ·
299
+ [Byrne-Docling-131M](https://huggingface.co/Quazim0t0/Byrne-Docling-131M) ·
300
+ [Escarda-Docling-126M](https://huggingface.co/Quazim0t0/Escarda-Docling-126M)
301
+
302
+ ## Update: engram repair (behavior-preserving)
303
+
304
+ The n-gram Engram memory in the original weights was degenerate: with the frozen
305
+ LSH compressor at init scale, every token hashed to bucket 0, so only one table
306
+ row ever received gradient. This revision rescales the (frozen) compressor and
307
+ broadcasts the learned bucket-0 vector across all table rows.
308
+
309
+ **Outputs are bit-identical to the previous revision** (verified: max logit
310
+ difference 0.0 across a prompt battery). The only change: the Engram's hash now
311
+ spreads across the full table and every bucket is independently trainable — so
312
+ if you distill or SFT on top of this base, the n-gram memory will actually learn
313
+ instead of staying a constant bias.
config.json ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "SpikeWhaleLM"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "config.SpikeWhaleConfig",
7
+ "AutoModel": "model_v2.SpikeWhaleLM",
8
+ "AutoModelForCausalLM": "model_v2.SpikeWhaleLM"
9
+ },
10
+ "attention_dropout": 0.0,
11
+ "bos_token_id": 2,
12
+ "dtype": "float32",
13
+ "engram_compress_dim": 32,
14
+ "engram_gate_init_bias": -1.0,
15
+ "engram_max_ngram": 3,
16
+ "engram_num_heads": 2,
17
+ "engram_table_size": 4096,
18
+ "eos_token_id": 3,
19
+ "hc_eps": 1e-06,
20
+ "hc_mult": 2,
21
+ "hc_sinkhorn_iters": 20,
22
+ "head_dim": 64,
23
+ "hidden_dropout": 0.0,
24
+ "hidden_size": 640,
25
+ "hrm_refine_dim": 128,
26
+ "hrm_refine_steps": 1,
27
+ "initializer_range": 0.02,
28
+ "jepa_horizon": 1,
29
+ "jepa_loss_weight": 0.1,
30
+ "jepa_pred_dim": 256,
31
+ "max_position_embeddings": 10240,
32
+ "model_type": "spike_whale",
33
+ "moe_aux_loss_coef": 0.01,
34
+ "moe_intermediate_size": 2000,
35
+ "moe_layers": [],
36
+ "mtp_loss_weight": 0.3,
37
+ "n_routed_experts": 6,
38
+ "n_shared_experts": 1,
39
+ "nope_head_dim": 48,
40
+ "norm_topk_prob": true,
41
+ "num_attention_heads": 10,
42
+ "num_experts_per_tok": 2,
43
+ "num_hash_layers": 2,
44
+ "num_hidden_layers": 16,
45
+ "num_key_value_heads": 1,
46
+ "num_nextn_predict_layers": 1,
47
+ "o_lora_rank": 128,
48
+ "q_lora_rank": 128,
49
+ "qk_rope_head_dim": 16,
50
+ "rms_norm_eps": 1e-06,
51
+ "rope_theta": 10000.0,
52
+ "routed_scaling_factor": 1.0,
53
+ "scoring_func": "sqrtsoftplus",
54
+ "tie_word_embeddings": true,
55
+ "transformers_version": "5.8.0",
56
+ "use_derf": false,
57
+ "use_engram": true,
58
+ "use_hrm_refine": true,
59
+ "use_hyper_connections": true,
60
+ "use_jepa": true,
61
+ "use_moe": false,
62
+ "use_qk_norm": true,
63
+ "use_value_embed": false,
64
+ "use_xsa": true,
65
+ "vocab_size": 16512,
66
+ "zloss_coef": 0.0001,
67
+ "use_jetlong": true,
68
+ "jetlong_w_pretrained": 4096,
69
+ "jetlong_w0": 2048
70
+ }
config.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ config.py -- SpikeWhale: combined config from SpikeTransformer (My Project) + NanoWhale (DeepSeek-V4).
3
+
4
+ Features carried from My Project (not in NanoWhale):
5
+ - DERF attention: erf(alpha*score+bias)*gamma replaces softmax
6
+ - XSA (Exclusive Self-Attention): orthogonality correction removes self-echo from attn output
7
+ - Engram N-gram module: hash-table N-gram lookup with DERF gate injected into embeddings
8
+ - Three-tier optimizer: embed/table params trained at lower LR
9
+
10
+ Features carried from NanoWhale (not in My Project):
11
+ - MLA (Multi-Head Latent Attention): low-rank Q projection + direct K,V (MQA)
12
+ - Partial RoPE: rotary embeddings on only qk_rope_head_dim dims of Q and K
13
+ - Low-rank grouped output projection (o_lora_rank)
14
+ - Hyper-Connections: hc_mult residual streams with learned routing between layers
15
+ - Shared expert in MoE (always-active expert alongside routed experts)
16
+ - sqrtsoftplus expert scoring (vs softmax in My Project)
17
+ - Hash-based routing for first num_hash_layers layers
18
+ - norm_topk_prob + routed_scaling_factor
19
+ - Multi-Token Prediction (MTP): extra heads predict k steps ahead
20
+ - torch.compile, FineWeb-Edu streaming, Trackio, YAML configs in train.py
21
+ """
22
+
23
+ from transformers import PretrainedConfig
24
+
25
+
26
+ class SpikeWhaleConfig(PretrainedConfig):
27
+ model_type = "spike_whale"
28
+
29
+ def __init__(
30
+ self,
31
+ # Standard
32
+ vocab_size: int = 16512, # SpikeTokenizer: 16384 base + 128 padded special slots
33
+ hidden_size: int = 2048,
34
+ num_hidden_layers: int = 11,
35
+ max_position_embeddings: int = 4096,
36
+ rms_norm_eps: float = 1e-6,
37
+ initializer_range: float = 0.02,
38
+ tie_word_embeddings: bool = False,
39
+ hidden_dropout: float = 0.0,
40
+ bos_token_id: int = 0,
41
+ eos_token_id: int = 1,
42
+ # MLA Attention (NanoWhale)
43
+ num_attention_heads: int = 8,
44
+ num_key_value_heads: int = 1, # 1 = MQA; >1 = GQA
45
+ q_lora_rank: int = 160, # low-rank Q: hidden -> q_lora_rank -> num_heads*head_dim
46
+ head_dim: int = 96, # total per-head dim = nope_head_dim + qk_rope_head_dim
47
+ qk_rope_head_dim: int = 32, # RoPE applied only to these dims
48
+ o_lora_rank: int = 80, # low-rank output: num_heads*head_dim -> o_lora_rank -> hidden
49
+ attention_dropout: float = 0.0,
50
+ rope_theta: float = 10000.0,
51
+ # DERF + XSA (My Project)
52
+ use_derf: bool = True,
53
+ use_xsa: bool = True,
54
+ # MoE (combined)
55
+ use_moe: bool = True,
56
+ moe_intermediate_size: int = 640,
57
+ n_routed_experts: int = 4,
58
+ n_shared_experts: int = 1, # NanoWhale: always-active shared expert
59
+ num_experts_per_tok: int = 2,
60
+ norm_topk_prob: bool = True, # NanoWhale: normalize top-k routing weights
61
+ scoring_func: str = "sqrtsoftplus", # NanoWhale: sqrt(softplus(x)) vs softmax
62
+ routed_scaling_factor: float = 1.0, # NanoWhale: scale routed expert weights
63
+ num_hash_layers: int = 2, # NanoWhale: first N layers use hash routing
64
+ moe_aux_loss_coef: float = 0.01,
65
+ moe_layers: list = None,
66
+ # Hyper-Connections (NanoWhale)
67
+ use_hyper_connections: bool = True,
68
+ hc_mult: int = 4, # number of parallel residual streams
69
+ hc_sinkhorn_iters: int = 20,
70
+ hc_eps: float = 1e-6,
71
+ # Multi-Token Prediction (NanoWhale)
72
+ num_nextn_predict_layers: int = 1, # extra MTP heads (0 = disabled)
73
+ # Engram N-gram module (My Project)
74
+ use_engram: bool = True,
75
+ engram_compress_dim: int = 64,
76
+ engram_num_heads: int = 4,
77
+ engram_table_size: int = 8192,
78
+ engram_max_ngram: int = 3,
79
+ engram_gate_init_bias: float = -4.0,
80
+ # HRM-inspired iterative refinement (EXPERIMENTAL; off by default).
81
+ # Adds one small block that refines the final hidden state over N inner
82
+ # steps before the output norm. This is the "iterative refinement" part
83
+ # that the ARC-Prize ablation found carried most of HRM's benefit -- NOT
84
+ # the full two-timescale H/L hierarchy. Honestly labeled HRM-inspired.
85
+ use_hrm_refine: bool = False,
86
+ hrm_refine_steps: int = 3, # inner refinement iterations
87
+ hrm_refine_dim: int = 256, # bottleneck width of the refine MLP
88
+ # --- v2 additions ---
89
+ use_qk_norm: bool = True, # per-head RMSNorm on Q,K before RoPE
90
+ zloss_coef: float = 1e-4, # log^2(Z) penalty on lm_head logits (0=off)
91
+ mtp_loss_weight: float = 0.3, # down-weight for MTP CE loss
92
+ use_value_embed: bool = False, # per-layer value-embedding residual (zero-init)
93
+ # --- JEPA secondary prediction head (jepa_v2) ---
94
+ # Predicts the trunk's FUTURE hidden state in representation space
95
+ # (stop-gradient target), complementing MTP which predicts future
96
+ # TOKENS. Same bottleneck-MLP shape as the HRM refinement block.
97
+ use_jepa: bool = True,
98
+ jepa_horizon: int = 1, # predict hidden state k=1..horizon ahead
99
+ jepa_pred_dim: int = 256, # bottleneck width of the predictor MLP
100
+ jepa_loss_weight: float = 0.1, # weight of the (1 - cosine) JEPA loss
101
+ **kwargs,
102
+ ):
103
+ super().__init__(
104
+ bos_token_id=bos_token_id,
105
+ eos_token_id=eos_token_id,
106
+ tie_word_embeddings=tie_word_embeddings,
107
+ **kwargs,
108
+ )
109
+ self.vocab_size = vocab_size
110
+ self.hidden_size = hidden_size
111
+ self.num_hidden_layers = num_hidden_layers
112
+ self.max_position_embeddings = max_position_embeddings
113
+ self.rms_norm_eps = rms_norm_eps
114
+ self.initializer_range = initializer_range
115
+ self.hidden_dropout = hidden_dropout
116
+
117
+ self.num_attention_heads = num_attention_heads
118
+ self.num_key_value_heads = num_key_value_heads
119
+ self.q_lora_rank = q_lora_rank
120
+ self.head_dim = head_dim
121
+ self.qk_rope_head_dim = qk_rope_head_dim
122
+ self.nope_head_dim = head_dim - qk_rope_head_dim
123
+ self.o_lora_rank = o_lora_rank
124
+ self.attention_dropout = attention_dropout
125
+ self.rope_theta = rope_theta
126
+ self.use_derf = use_derf
127
+ self.use_xsa = use_xsa
128
+
129
+ self.use_moe = use_moe
130
+ self.moe_intermediate_size = moe_intermediate_size
131
+ self.n_routed_experts = n_routed_experts
132
+ self.n_shared_experts = n_shared_experts
133
+ self.num_experts_per_tok = num_experts_per_tok
134
+ self.norm_topk_prob = norm_topk_prob
135
+ self.scoring_func = scoring_func
136
+ self.routed_scaling_factor = routed_scaling_factor
137
+ self.num_hash_layers = num_hash_layers
138
+ self.moe_aux_loss_coef = moe_aux_loss_coef
139
+ self.moe_layers = moe_layers if moe_layers is not None else list(range(num_hidden_layers))
140
+
141
+ self.use_hyper_connections = use_hyper_connections
142
+ self.hc_mult = hc_mult
143
+ self.hc_sinkhorn_iters = hc_sinkhorn_iters
144
+ self.hc_eps = hc_eps
145
+
146
+ self.num_nextn_predict_layers = num_nextn_predict_layers
147
+
148
+ self.use_engram = use_engram
149
+ self.engram_compress_dim = engram_compress_dim
150
+ self.engram_num_heads = engram_num_heads
151
+ self.engram_table_size = engram_table_size
152
+ self.engram_max_ngram = engram_max_ngram
153
+ self.engram_gate_init_bias = engram_gate_init_bias
154
+ self.use_hrm_refine = use_hrm_refine
155
+ self.hrm_refine_steps = hrm_refine_steps
156
+ self.hrm_refine_dim = hrm_refine_dim
157
+ self.use_qk_norm = use_qk_norm
158
+ self.zloss_coef = zloss_coef
159
+ self.mtp_loss_weight = mtp_loss_weight
160
+ self.use_value_embed = use_value_embed
161
+ self.use_jepa = use_jepa
162
+ self.jepa_horizon = jepa_horizon
163
+ self.jepa_pred_dim = jepa_pred_dim
164
+ self.jepa_loss_weight = jepa_loss_weight
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c9d88f130b882daa2a64ca9cb070e5bc9de9128e801eec76b7846bee9f97cfb6
3
+ size 389129376
model_v2.py ADDED
@@ -0,0 +1,1042 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ model_v2.py -- SpikeWhaleLM v2: optimized base architecture.
3
+
4
+ Changes vs model.py (v1):
5
+
6
+ PERFORMANCE
7
+ - SparseMoEFFN: sort-based expert dispatch (one contiguous slice per expert,
8
+ index_add_ scatter-back) replaces per-expert boolean masking. Far fewer
9
+ kernel launches, torch.compile-friendly (no data-dependent boolean
10
+ indexing in the hot path).
11
+ - Shared experts fused into ONE ExpertFFN with n_shared * intermediate width
12
+ (mathematically equivalent to the averaged sum, 1 matmul set instead of N).
13
+
14
+ QUALITY / STABILITY
15
+ - QK-Norm: per-head RMSNorm on Q and K before RoPE (Gemma2/OLMo2-style).
16
+ Stabilizes attention logits, tolerates higher LR. (cfg.use_qk_norm, default ON)
17
+ - z-loss on lm_head logits: zloss_coef * mean(log^2 Z). Prevents logit drift.
18
+ (cfg.zloss_coef, default 1e-4; set 0 to disable)
19
+ - MTP heads REDESIGNED: instead of K independent full H x V matrices (which at
20
+ 50M params dwarfed the model), each MTP head is now a small zero-init H x H
21
+ projection feeding the SHARED lm_head. Param cost per head: H^2 instead of
22
+ H*V. MTP loss is down-weighted by cfg.mtp_loss_weight (default 0.3).
23
+ - HC output: learned softmax mix over streams (HCOutputMix) instead of mean().
24
+ - Value-embedding residual (nanoGPT-speedrun style): per-layer learned gate
25
+ (zero-init => exact no-op at init) adds a projection of the token embedding
26
+ into each block's input. (cfg.use_value_embed, default OFF = opt-in)
27
+
28
+ All new config keys are read with getattr(cfg, key, default) so your existing
29
+ config.py works unmodified. NOTE: QK-Norm and HCOutputMix add parameters, so
30
+ v1 checkpoints need load_state_dict(strict=False) (new params keep init;
31
+ QK-Norm at init is NOT identity -- prefer training v2 from scratch, or set
32
+ use_qk_norm=False to stay v1-loadable).
33
+
34
+ XSA is kept byte-identical to v1 but read the note in MLADerfXSAAttention:
35
+ with num_kv_heads == 1 it removes the SAME rank-1 value subspace from every
36
+ head. A/B it at 50M before keeping it in the final base.
37
+ """
38
+
39
+ import math
40
+ import torch
41
+ import torch.nn as nn
42
+ import torch.nn.functional as F
43
+ from typing import Optional, Tuple, List
44
+ from transformers import PreTrainedModel
45
+ from transformers.modeling_outputs import CausalLMOutputWithPast
46
+ from torch.utils.checkpoint import checkpoint as gradient_checkpoint
47
+
48
+ # Force the LOCAL jepa_v2/config.py (with the JEPA fields) even when this file
49
+ # is imported from the project root, where the root config.py would win.
50
+ import os as _os, sys as _sys
51
+ _sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__)))
52
+ try:
53
+ # Dotted import so HuggingFace's trust_remote_code loader fetches config.py
54
+ # as a relative dependency; falls back to flat import for local script use.
55
+ from .config import SpikeWhaleConfig
56
+ except ImportError:
57
+ from config import SpikeWhaleConfig
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # Primitives
62
+ # ---------------------------------------------------------------------------
63
+
64
+ class RMSNorm(nn.Module):
65
+ def __init__(self, dim: int, eps: float = 1e-6):
66
+ super().__init__()
67
+ self.eps = eps
68
+ self.weight = nn.Parameter(torch.ones(dim))
69
+
70
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
71
+ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.weight
72
+
73
+
74
+ class RotaryEmbedding(nn.Module):
75
+ """RoPE for the rope partition of Q and K (qk_rope_head_dim dims only)."""
76
+
77
+ def __init__(self, dim: int, max_positions: int = 4096, theta: float = 10000.0):
78
+ super().__init__()
79
+ inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2).float() / dim))
80
+ self.register_buffer("inv_freq", inv_freq)
81
+ t = torch.arange(max_positions).float()
82
+ freqs = torch.outer(t, inv_freq)
83
+ self.register_buffer("cos_cache", freqs.cos(), persistent=False)
84
+ self.register_buffer("sin_cache", freqs.sin(), persistent=False)
85
+
86
+ def forward(self, x: torch.Tensor, position_ids: torch.Tensor) -> torch.Tensor:
87
+ cos = self.cos_cache[position_ids].unsqueeze(1) # [B, 1, S, rope_dim//2]
88
+ sin = self.sin_cache[position_ids].unsqueeze(1)
89
+ d = cos.shape[-1]
90
+ x1, x2 = x[..., :d], x[..., d:]
91
+ return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
92
+
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # Engram: N-gram hash lookup + DERF gate (unchanged from v1)
96
+ # ---------------------------------------------------------------------------
97
+
98
+ class TokenCompressor(nn.Module):
99
+ def __init__(self, embed_dim: int, compress_dim: int):
100
+ super().__init__()
101
+ self.proj = nn.Linear(embed_dim, compress_dim, bias=False)
102
+ nn.init.normal_(self.proj.weight, std=0.02)
103
+ # Frozen LSH-style projection: gradient never reaches it through the
104
+ # .long() hash cast, so a fixed random projection is correct (see v1).
105
+ self.proj.weight.requires_grad_(False)
106
+
107
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
108
+ return self.proj(x)
109
+
110
+
111
+ class MultiHeadHashLookup(nn.Module):
112
+ def __init__(self, num_heads: int, table_size: int,
113
+ compress_dim: int, out_dim: int, max_ngram: int = 3):
114
+ super().__init__()
115
+ self.num_heads = num_heads
116
+ self.table_size = table_size
117
+ self.max_ngram = max_ngram
118
+ self.out_dim = out_dim
119
+
120
+ self.tables = nn.ModuleList([
121
+ nn.Embedding(table_size, out_dim) for _ in range(num_heads)
122
+ ])
123
+ for t in self.tables:
124
+ nn.init.normal_(t.weight, std=0.01)
125
+
126
+ for n in range(1, max_ngram + 1):
127
+ for k in range(n):
128
+ proj = torch.randn(num_heads, compress_dim)
129
+ proj = proj / (proj.norm(dim=1, keepdim=True) + 1e-8)
130
+ self.register_buffer(f"hash_proj_n{n}_p{k}", proj)
131
+
132
+ def forward(self, compressed: torch.Tensor) -> torch.Tensor:
133
+ B, S, _ = compressed.shape
134
+ device = compressed.device
135
+ out = torch.zeros(B, S, self.out_dim, device=device, dtype=compressed.dtype)
136
+ norm = torch.zeros(S, device=device)
137
+
138
+ for n in range(1, self.max_ngram + 1):
139
+ if S < n:
140
+ continue
141
+ valid_len = S - n + 1
142
+ start = n - 1
143
+
144
+ h = torch.zeros(B, valid_len, self.num_heads, device=device)
145
+ for k in range(n):
146
+ proj = getattr(self, f"hash_proj_n{n}_p{k}")
147
+ h = h + torch.matmul(compressed[:, k:k + valid_len, :].float(), proj.t())
148
+
149
+ idx = h.abs().long() % self.table_size
150
+
151
+ for head_idx, table in enumerate(self.tables):
152
+ out[:, start:, :] = out[:, start:, :] + table(idx[:, :, head_idx])
153
+
154
+ norm[start:] += self.num_heads
155
+
156
+ return (out / norm.view(1, -1, 1).clamp(min=1)).to(compressed.dtype)
157
+
158
+
159
+ class DERFContextGate(nn.Module):
160
+ def __init__(self, obs_size: int, init_bias: float = -4.0):
161
+ super().__init__()
162
+ self.proj = nn.Linear(obs_size * 2, obs_size)
163
+ self.alpha = nn.Parameter(torch.ones(obs_size))
164
+ self.bias = nn.Parameter(torch.full((obs_size,), init_bias))
165
+ self.gamma = nn.Parameter(torch.ones(obs_size))
166
+
167
+ def forward(self, retrieved: torch.Tensor, x: torch.Tensor) -> torch.Tensor:
168
+ logits = self.proj(torch.cat([retrieved, x], dim=-1))
169
+ gate = self.gamma * ((torch.erf(self.alpha * logits + self.bias) + 1.0) / 2.0)
170
+ return retrieved * gate
171
+
172
+
173
+ class EngramModule(nn.Module):
174
+ def __init__(self, cfg: SpikeWhaleConfig):
175
+ super().__init__()
176
+ self.compressor = TokenCompressor(cfg.hidden_size, cfg.engram_compress_dim)
177
+ self.lookup = MultiHeadHashLookup(
178
+ cfg.engram_num_heads, cfg.engram_table_size,
179
+ cfg.engram_compress_dim, cfg.hidden_size, cfg.engram_max_ngram,
180
+ )
181
+ self.gate = DERFContextGate(cfg.hidden_size, cfg.engram_gate_init_bias)
182
+
183
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
184
+ compressed = self.compressor(x.detach())
185
+ retrieved = self.lookup(compressed)
186
+ return self.gate(retrieved, x)
187
+
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # Hyper-Connections
191
+ # ---------------------------------------------------------------------------
192
+
193
+ class HyperConnectionLayer(nn.Module):
194
+ """Simplified HC: softmax pre-mix / post-distribute over hc_mult streams.
195
+ Asymmetric init (v1 bugfix) so streams diverge and gradients flow."""
196
+ def __init__(self, hidden_size: int, hc_mult: int,
197
+ sinkhorn_iters: int = 20, eps: float = 1e-6):
198
+ super().__init__()
199
+ self.hc_mult = hc_mult
200
+ self.pre_weight = nn.Parameter(
201
+ torch.linspace(0.5, -0.5, hc_mult) / max(hc_mult, 1)
202
+ )
203
+ self.post_weight = nn.Parameter(
204
+ torch.linspace(-0.5, 0.5, hc_mult) / max(hc_mult, 1)
205
+ )
206
+
207
+ def pre_op(self, copies: torch.Tensor) -> torch.Tensor:
208
+ w = F.softmax(self.pre_weight, dim=0)
209
+ return (copies * w.view(1, -1, 1, 1)).sum(dim=1)
210
+
211
+ def post_op(self, copies: torch.Tensor, delta: torch.Tensor) -> torch.Tensor:
212
+ w = F.softmax(self.post_weight, dim=0)
213
+ return copies + delta.unsqueeze(1) * w.view(1, -1, 1, 1)
214
+
215
+
216
+ class HCOutputMix(nn.Module):
217
+ """
218
+ NEW (v2): learned combination of the hc_mult streams at the model output,
219
+ replacing the v1 mean(dim=1). Mean forces the streams toward redundancy at
220
+ exactly the point where you want them specialized. Initialized uniform so
221
+ it starts identical to mean() -- a strict generalization, zero risk.
222
+ """
223
+ def __init__(self, hc_mult: int):
224
+ super().__init__()
225
+ self.weight = nn.Parameter(torch.zeros(hc_mult)) # softmax(0)=uniform=mean
226
+
227
+ def forward(self, copies: torch.Tensor) -> torch.Tensor:
228
+ w = F.softmax(self.weight, dim=0)
229
+ return (copies * w.view(1, -1, 1, 1)).sum(dim=1)
230
+
231
+
232
+ # ---------------------------------------------------------------------------
233
+ # MLA + (DERF) + XSA Attention, now with QK-Norm
234
+ # ---------------------------------------------------------------------------
235
+
236
+ class MLADerfXSAAttention(nn.Module):
237
+ """
238
+ v2 additions:
239
+ - QK-Norm (cfg.use_qk_norm, default True): per-head RMSNorm applied to Q
240
+ and K BEFORE the rope/nope split. Bounds attention logits, the standard
241
+ modern stability fix; composes cleanly with SDPA and partial RoPE.
242
+
243
+ XSA NOTE (unchanged mechanics, important caveat): with num_kv_heads == 1
244
+ (MQA) every query head shares the same value vector, so the self-projection
245
+ subtraction removes the SAME rank-1 value subspace from all heads -- much
246
+ more aggressive than per-head XSA. Ablate use_xsa on/off at 50M before
247
+ locking the base config.
248
+ """
249
+
250
+ def __init__(self, cfg: SpikeWhaleConfig):
251
+ super().__init__()
252
+ self.num_heads = cfg.num_attention_heads
253
+ self.num_kv_heads = cfg.num_key_value_heads
254
+ self.head_dim = cfg.head_dim
255
+ self.qk_rope_head_dim = cfg.qk_rope_head_dim
256
+ self.nope_head_dim = cfg.nope_head_dim
257
+ self.hidden_size = cfg.hidden_size
258
+ self.use_derf = cfg.use_derf
259
+ # Jet-Long dynamic bifocal RoPE (arXiv:2607.07740), inference-time only.
260
+ self.use_jetlong = getattr(cfg, 'use_jetlong', False)
261
+ self.jetlong_w_pretrained = int(getattr(cfg, 'jetlong_w_pretrained', 4096))
262
+ self.jetlong_w0 = int(getattr(cfg, 'jetlong_w0', 2048))
263
+ self.use_xsa = cfg.use_xsa
264
+ self.dropout_p = cfg.attention_dropout
265
+ self.kv_groups = self.num_heads // self.num_kv_heads
266
+ self.use_qk_norm = getattr(cfg, "use_qk_norm", True)
267
+
268
+ self.q_a_proj = nn.Linear(cfg.hidden_size, cfg.q_lora_rank, bias=False)
269
+ self.q_a_norm = RMSNorm(cfg.q_lora_rank, cfg.rms_norm_eps)
270
+ self.q_b_proj = nn.Linear(cfg.q_lora_rank, self.num_heads * self.head_dim, bias=False)
271
+
272
+ self.k_proj = nn.Linear(cfg.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
273
+ self.v_proj = nn.Linear(cfg.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
274
+
275
+ self.o_a_proj = nn.Linear(self.num_heads * self.head_dim, cfg.o_lora_rank, bias=False)
276
+ self.o_b_proj = nn.Linear(cfg.o_lora_rank, cfg.hidden_size, bias=False)
277
+
278
+ # QK-Norm: one RMSNorm over head_dim, shared across heads (Gemma-2 style).
279
+ if self.use_qk_norm:
280
+ self.q_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps)
281
+ self.k_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps)
282
+
283
+ self.rope = RotaryEmbedding(
284
+ self.qk_rope_head_dim,
285
+ max_positions=cfg.max_position_embeddings,
286
+ theta=cfg.rope_theta,
287
+ )
288
+
289
+ if self.use_derf:
290
+ self.derf_alpha = nn.Parameter(torch.ones(self.num_heads))
291
+ self.derf_bias = nn.Parameter(torch.zeros(self.num_heads))
292
+ self.derf_gamma = nn.Parameter(torch.ones(self.num_heads))
293
+
294
+ for m in (self.q_a_proj, self.q_b_proj, self.k_proj,
295
+ self.v_proj, self.o_a_proj, self.o_b_proj):
296
+ nn.init.normal_(m.weight, std=cfg.initializer_range)
297
+
298
+ def forward(
299
+ self,
300
+ x: torch.Tensor,
301
+ position_ids: torch.Tensor,
302
+ attention_mask: Optional[torch.Tensor] = None,
303
+ past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
304
+ use_cache: bool = False,
305
+ ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
306
+ B, S, _ = x.shape
307
+
308
+ q = self.q_a_norm(self.q_a_proj(x))
309
+ q = self.q_b_proj(q).view(B, S, self.num_heads, self.head_dim).transpose(1, 2)
310
+
311
+ k = self.k_proj(x).view(B, S, self.num_kv_heads, self.head_dim).transpose(1, 2)
312
+ v = self.v_proj(x).view(B, S, self.num_kv_heads, self.head_dim).transpose(1, 2)
313
+
314
+ # QK-Norm before RoPE (v2). Cache stores the NORMALIZED k so prefill and
315
+ # incremental decode agree.
316
+ if self.use_qk_norm:
317
+ q = self.q_norm(q)
318
+ k = self.k_norm(k)
319
+
320
+ q_nope = q[..., :self.nope_head_dim]
321
+ q_rope = q[..., self.nope_head_dim:]
322
+ k_nope = k[..., :self.nope_head_dim]
323
+ k_rope = k[..., self.nope_head_dim:]
324
+
325
+ q_rope = self.rope(q_rope, position_ids)
326
+ k_rope = self.rope(k_rope, position_ids)
327
+
328
+ q = torch.cat([q_nope, q_rope], dim=-1)
329
+ k = torch.cat([k_nope, k_rope], dim=-1)
330
+
331
+ if past_key_value is not None:
332
+ k = torch.cat([past_key_value[0], k], dim=2)
333
+ v = torch.cat([past_key_value[1], v], dim=2)
334
+ present = (k, v) if use_cache else None
335
+ N = k.shape[2]
336
+
337
+ if self.kv_groups > 1:
338
+ k = k.unsqueeze(2).expand(-1, -1, self.kv_groups, -1, -1).reshape(
339
+ B, self.num_heads, N, self.head_dim)
340
+ v = v.unsqueeze(2).expand(-1, -1, self.kv_groups, -1, -1).reshape(
341
+ B, self.num_heads, N, self.head_dim)
342
+
343
+ # --- Jet-Long dynamic bifocal RoPE (arXiv:2607.07740) ---
344
+ # No-op within the native window (G==1); position-aliased remote
345
+ # window beyond it. Correction rotation composes on the already-RoPE'd
346
+ # cache, so the KV cache (and TriAttention compression) is untouched.
347
+ y = None
348
+ if getattr(self, 'use_jetlong', False) and not self.use_derf:
349
+ _G = max(1, math.ceil(N / self.jetlong_w_pretrained))
350
+ if _G > 1:
351
+ y = self._jetlong_attend(q, k, v, position_ids, S, N, _G)
352
+ if y is not None:
353
+ pass
354
+ elif self.use_derf:
355
+ scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
356
+
357
+ if attention_mask is None and past_key_value is None:
358
+ is_masked = torch.triu(
359
+ torch.ones(S, N, dtype=torch.bool, device=scores.device),
360
+ diagonal=N - S + 1,
361
+ ).unsqueeze(0).unsqueeze(0)
362
+ else:
363
+ is_masked = (attention_mask < -1.0) if attention_mask is not None \
364
+ else torch.zeros_like(scores, dtype=torch.bool)
365
+
366
+ safe_scores = scores.masked_fill(is_masked, -10000.0)
367
+
368
+ a = self.derf_alpha.view(1, -1, 1, 1)
369
+ b = self.derf_bias.view(1, -1, 1, 1)
370
+ g = self.derf_gamma.view(1, -1, 1, 1)
371
+
372
+ attn_weights = g * torch.erf(a * safe_scores + b)
373
+ attn_weights = (attn_weights + g) / 2.0
374
+ attn_weights = attn_weights.masked_fill(is_masked, 0.0)
375
+ attn_weights = attn_weights / (attn_weights.sum(dim=-1, keepdim=True) + 1e-8)
376
+
377
+ if self.dropout_p > 0 and self.training:
378
+ attn_weights = F.dropout(attn_weights, p=self.dropout_p)
379
+
380
+ y = torch.matmul(attn_weights, v)
381
+ else:
382
+ q = q.contiguous()
383
+ k = k.contiguous()
384
+ v = v.contiguous()
385
+ drop = self.dropout_p if self.training else 0.0
386
+ if past_key_value is None and attention_mask is None:
387
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True, dropout_p=drop)
388
+ else:
389
+ if attention_mask is not None:
390
+ is_masked = (attention_mask < -1.0)
391
+ else:
392
+ is_masked = torch.triu(
393
+ torch.ones(S, N, dtype=torch.bool, device=q.device),
394
+ diagonal=N - S + 1,
395
+ ).unsqueeze(0).unsqueeze(0)
396
+ y = F.scaled_dot_product_attention(
397
+ q, k, v, attn_mask=~is_masked, dropout_p=drop)
398
+
399
+ if self.use_xsa:
400
+ past_len = N - S
401
+ v_self = v[:, :, past_len:past_len + S, :]
402
+ vn = v_self / (v_self.norm(dim=-1, keepdim=True) + 1e-8)
403
+ projection = (y * vn).sum(dim=-1, keepdim=True) * vn
404
+ y = y - projection
405
+
406
+ y = y.transpose(1, 2).contiguous().view(B, S, self.num_heads * self.head_dim)
407
+ y = self.o_b_proj(self.o_a_proj(y))
408
+ return y, present
409
+
410
+ def _jetlong_attend(self, q, k, v, position_ids, S, N, G):
411
+ """Bifocal softmax attention (Jet-Long, arXiv:2607.07740).
412
+
413
+ q, k arrive already RoPE-rotated at their true absolute positions. We add
414
+ a *correction rotation* delta = floor(p/G) - p to realize the remote,
415
+ position-aliased view (RoPE composes additively, so this works directly on
416
+ the rotated -- and possibly TriAttention-compressed -- cache without
417
+ rewriting it), then merge the local and remote windows by query-key
418
+ distance. Handles prefill (S==N) and decode (S==1, N==cache+1) uniformly.
419
+ Only reached when use_jetlong and G>1, so behaviour within the native
420
+ window is byte-identical to the base model."""
421
+ import math as _m
422
+ B = q.shape[0]
423
+ nd = self.nope_head_dim
424
+ dev = q.device
425
+ if position_ids is not None:
426
+ q_pos = position_ids.reshape(B, S).to(dev).long()
427
+ else:
428
+ q_pos = torch.arange(N - S, N, device=dev).view(1, S).expand(B, S)
429
+ _kp = getattr(self, '_jl_key_pos', None)
430
+ if _kp is not None and _kp.numel() == N:
431
+ k_pos = _kp.to(dev).long().view(1, N).expand(B, N)
432
+ else:
433
+ k_pos = torch.arange(N, device=dev).view(1, N).expand(B, N)
434
+ dq = torch.div(q_pos, G, rounding_mode='floor') - q_pos # [B,S]
435
+ dk = torch.div(k_pos, G, rounding_mode='floor') - k_pos # [B,N]
436
+ inv_freq = self.rope.inv_freq.to(dev).float() # [rd/2]
437
+
438
+ def rot_by(x, delta):
439
+ d = x.shape[-1] // 2
440
+ ang = delta.float().unsqueeze(1).unsqueeze(-1) * inv_freq # [B,1,T,d]
441
+ cos, sin = ang.cos(), ang.sin()
442
+ x1, x2 = x[..., :d], x[..., d:]
443
+ return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
444
+
445
+ q_nope, q_rope = q[..., :nd], q[..., nd:]
446
+ k_nope, k_rope = k[..., :nd], k[..., nd:]
447
+ q_rope_rem = rot_by(q_rope, dq)
448
+ k_rope_rem = rot_by(k_rope, dk)
449
+
450
+ scale = 1.0 / _m.sqrt(self.head_dim)
451
+ s_nope = torch.matmul(q_nope, k_nope.transpose(-2, -1))
452
+ s_loc = (s_nope + torch.matmul(q_rope, k_rope.transpose(-2, -1))) * scale
453
+ s_rem = (s_nope + torch.matmul(q_rope_rem, k_rope_rem.transpose(-2, -1))) * scale
454
+
455
+ dist = q_pos.view(B, 1, S, 1) - k_pos.view(B, 1, 1, N) # i - j
456
+ local_mask = (dist <= self.jetlong_w0) & (dist >= 0)
457
+ causal = dist >= 0
458
+ scores = torch.where(local_mask, s_loc, s_rem)
459
+ scores = scores.masked_fill(~causal, float('-inf'))
460
+ attn = torch.softmax(scores.float(), dim=-1).to(v.dtype)
461
+ return torch.matmul(attn, v)
462
+
463
+
464
+ # ---------------------------------------------------------------------------
465
+ # MoE FFN -- v2: sort-based dispatch + fused shared expert
466
+ # ---------------------------------------------------------------------------
467
+
468
+ class ExpertFFN(nn.Module):
469
+ """Single SwiGLU expert."""
470
+ def __init__(self, hidden_size: int, intermediate_size: int):
471
+ super().__init__()
472
+ self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
473
+ self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
474
+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
475
+
476
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
477
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
478
+
479
+
480
+ def sqrtsoftplus(x: torch.Tensor) -> torch.Tensor:
481
+ return torch.sqrt(F.softplus(x) + 1e-8)
482
+
483
+
484
+ class SparseMoEFFN(nn.Module):
485
+ """
486
+ v2 changes:
487
+ - FUSED shared expert: one ExpertFFN with width n_shared * intermediate,
488
+ scaled by 1/n_shared on output -- equivalent to v1's averaged Python
489
+ loop, one fused matmul set. (state-dict key changes: shared_expert.*)
490
+ - SORT-BASED dispatch for routed experts: flatten (token, slot) pairs,
491
+ argsort by expert id, run each expert on ONE contiguous slice, weighted
492
+ index_add_ back. No boolean masks, no nonzero(), no per-expert scatter.
493
+ Routing logic (hash routing, sqrtsoftplus, aux loss) is unchanged.
494
+ """
495
+ def __init__(self, cfg: SpikeWhaleConfig, layer_idx: int = 0):
496
+ super().__init__()
497
+ self.n_routed_experts = cfg.n_routed_experts
498
+ self.n_shared_experts = cfg.n_shared_experts
499
+ self.num_experts_per_tok = cfg.num_experts_per_tok
500
+ self.norm_topk_prob = cfg.norm_topk_prob
501
+ self.scoring_func = cfg.scoring_func
502
+ self.routed_scaling_factor = cfg.routed_scaling_factor
503
+ self.use_hash_routing = layer_idx < cfg.num_hash_layers
504
+ self.aux_loss_coef = cfg.moe_aux_loss_coef
505
+
506
+ self.router = nn.Linear(cfg.hidden_size, cfg.n_routed_experts, bias=False)
507
+ self.experts = nn.ModuleList([
508
+ ExpertFFN(cfg.hidden_size, cfg.moe_intermediate_size)
509
+ for _ in range(cfg.n_routed_experts)
510
+ ])
511
+ # Fused shared expert (v2)
512
+ self.shared_expert = (
513
+ ExpertFFN(cfg.hidden_size,
514
+ cfg.moe_intermediate_size * cfg.n_shared_experts)
515
+ if cfg.n_shared_experts > 0 else None
516
+ )
517
+
518
+ self._last_aux_loss: Optional[torch.Tensor] = None
519
+
520
+ def forward(self, x: torch.Tensor,
521
+ position_ids: Optional[torch.Tensor] = None) -> torch.Tensor:
522
+ B, S, H = x.shape
523
+ x_flat = x.view(B * S, H)
524
+ T = B * S
525
+ K = self.num_experts_per_tok
526
+
527
+ # Shared expert: always active, single fused pass.
528
+ if self.shared_expert is not None:
529
+ shared_out = self.shared_expert(x_flat)
530
+ if self.n_shared_experts > 1:
531
+ shared_out = shared_out / self.n_shared_experts
532
+ else:
533
+ shared_out = None
534
+
535
+ # ---- Routing (unchanged logic) ----
536
+ if self.use_hash_routing:
537
+ if position_ids is not None:
538
+ base = (position_ids.reshape(T, 1) % self.n_routed_experts).long()
539
+ else:
540
+ base = (torch.arange(T, device=x.device) % self.n_routed_experts).unsqueeze(1)
541
+ offsets = torch.arange(K, device=x.device)
542
+ top_k_indices = (base + offsets.unsqueeze(0)) % self.n_routed_experts # [T, K]
543
+ top_k_weights = torch.full((T, K), 1.0 / K, device=x.device, dtype=x_flat.dtype)
544
+ self._last_aux_loss = None
545
+ else:
546
+ router_logits = self.router(x_flat)
547
+ if self.scoring_func == "sqrtsoftplus":
548
+ routing_scores = sqrtsoftplus(router_logits)
549
+ else:
550
+ routing_scores = F.softmax(router_logits, dim=-1)
551
+
552
+ top_k_scores, top_k_indices = torch.topk(routing_scores, K, dim=-1)
553
+ if self.norm_topk_prob:
554
+ top_k_weights = top_k_scores / (top_k_scores.sum(dim=-1, keepdim=True) + 1e-8)
555
+ else:
556
+ top_k_weights = top_k_scores
557
+ top_k_weights = top_k_weights * self.routed_scaling_factor
558
+
559
+ softmax_probs = F.softmax(router_logits, dim=-1)
560
+ expert_mask = torch.zeros_like(softmax_probs)
561
+ expert_mask.scatter_(1, top_k_indices, 1.0)
562
+ f_e = expert_mask.mean(0)
563
+ p_e = softmax_probs.mean(0)
564
+ self._last_aux_loss = self.n_routed_experts * (f_e * p_e).sum() * self.aux_loss_coef
565
+
566
+ # ---- Sort-based dispatch (v2) ----
567
+ # Flatten the (token, slot) assignment: T*K rows total.
568
+ flat_expert = top_k_indices.reshape(-1) # [T*K]
569
+ flat_weight = top_k_weights.reshape(-1, 1) # [T*K, 1]
570
+ flat_token = torch.arange(T, device=x.device).repeat_interleave(K) # [T*K]
571
+
572
+ order = torch.argsort(flat_expert, stable=True) # group by expert
573
+ sorted_expert = flat_expert[order]
574
+ sorted_token = flat_token[order]
575
+ sorted_weight = flat_weight[order]
576
+
577
+ counts = torch.bincount(sorted_expert, minlength=self.n_routed_experts)
578
+ # boundaries per expert in the sorted order (CPU sync once per forward;
579
+ # unavoidable without grouped-GEMM, still vastly cheaper than v1's
580
+ # per-expert nonzero/masking)
581
+ counts_list = counts.tolist()
582
+
583
+ gathered = x_flat[sorted_token] # [T*K, H]
584
+ out_flat = torch.zeros_like(x_flat)
585
+
586
+ start = 0
587
+ for expert_idx, cnt in enumerate(counts_list):
588
+ if cnt == 0:
589
+ continue
590
+ end = start + cnt
591
+ seg = gathered[start:end]
592
+ seg_out = self.experts[expert_idx](seg) * sorted_weight[start:end]
593
+ out_flat.index_add_(0, sorted_token[start:end], seg_out.to(out_flat.dtype))
594
+ start = end
595
+
596
+ if shared_out is not None:
597
+ out_flat = out_flat + shared_out
598
+ return out_flat.view(B, S, H)
599
+
600
+ def get_aux_loss(self) -> Optional[torch.Tensor]:
601
+ return self._last_aux_loss
602
+
603
+
604
+ class DenseFFN(nn.Module):
605
+ def __init__(self, cfg: SpikeWhaleConfig):
606
+ super().__init__()
607
+ self.gate_proj = nn.Linear(cfg.hidden_size, cfg.moe_intermediate_size, bias=False)
608
+ self.up_proj = nn.Linear(cfg.hidden_size, cfg.moe_intermediate_size, bias=False)
609
+ self.down_proj = nn.Linear(cfg.moe_intermediate_size, cfg.hidden_size, bias=False)
610
+
611
+ def forward(self, x: torch.Tensor,
612
+ position_ids: Optional[torch.Tensor] = None) -> torch.Tensor:
613
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
614
+
615
+ def get_aux_loss(self) -> Optional[torch.Tensor]:
616
+ return None
617
+
618
+
619
+ # ---------------------------------------------------------------------------
620
+ # Transformer block
621
+ # ---------------------------------------------------------------------------
622
+
623
+ class TransformerBlock(nn.Module):
624
+ def __init__(self, cfg: SpikeWhaleConfig, layer_idx: int):
625
+ super().__init__()
626
+ self.use_hc = cfg.use_hyper_connections
627
+ self.hidden_dropout = cfg.hidden_dropout
628
+ self.use_value_embed = getattr(cfg, "use_value_embed", False)
629
+
630
+ self.attn_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
631
+ self.attn = MLADerfXSAAttention(cfg)
632
+ self.ffn_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
633
+
634
+ if cfg.use_moe and layer_idx in cfg.moe_layers:
635
+ self.ffn = SparseMoEFFN(cfg, layer_idx)
636
+ self.is_moe = True
637
+ else:
638
+ self.ffn = DenseFFN(cfg)
639
+ self.is_moe = False
640
+
641
+ if self.use_hc:
642
+ self.hc_attn = HyperConnectionLayer(cfg.hidden_size, cfg.hc_mult,
643
+ cfg.hc_sinkhorn_iters, cfg.hc_eps)
644
+ self.hc_ffn = HyperConnectionLayer(cfg.hidden_size, cfg.hc_mult,
645
+ cfg.hc_sinkhorn_iters, cfg.hc_eps)
646
+
647
+ # NEW (v2, opt-in): value-embedding residual. Zero-init gate -> exact
648
+ # no-op at init; learns to mix raw token-embedding signal into each
649
+ # block's input (nanoGPT-speedrun "value embedding"/U-net skip family;
650
+ # consistent wins at the 50-500M scale).
651
+ if self.use_value_embed:
652
+ self.ve_gate = nn.Parameter(torch.zeros(1))
653
+
654
+ def forward(
655
+ self,
656
+ x: torch.Tensor, # [B, hc_mult, S, H] if HC else [B, S, H]
657
+ position_ids: torch.Tensor,
658
+ attention_mask: Optional[torch.Tensor] = None,
659
+ past_key_value: Optional[Tuple] = None,
660
+ use_cache: bool = False,
661
+ token_embed: Optional[torch.Tensor] = None, # [B, S, H] (value-embed)
662
+ ) -> Tuple[torch.Tensor, Optional[Tuple], Optional[torch.Tensor]]:
663
+
664
+ # --- Attention sub-layer ---
665
+ if self.use_hc:
666
+ h = self.hc_attn.pre_op(x)
667
+ else:
668
+ h = x
669
+
670
+ if self.use_value_embed and token_embed is not None:
671
+ h = h + torch.tanh(self.ve_gate) * token_embed
672
+
673
+ attn_out, present = self.attn(
674
+ self.attn_norm(h), position_ids, attention_mask, past_key_value, use_cache
675
+ )
676
+ attn_out = F.dropout(attn_out, p=self.hidden_dropout, training=self.training)
677
+
678
+ if self.use_hc:
679
+ x = self.hc_attn.post_op(x, attn_out)
680
+ h = self.hc_ffn.pre_op(x)
681
+ else:
682
+ h = h + attn_out
683
+
684
+ # --- FFN sub-layer ---
685
+ ffn_out = self.ffn(self.ffn_norm(h), position_ids)
686
+ ffn_out = F.dropout(ffn_out, p=self.hidden_dropout, training=self.training)
687
+
688
+ if self.use_hc:
689
+ x = self.hc_ffn.post_op(x, ffn_out)
690
+ else:
691
+ x = h + ffn_out
692
+
693
+ return x, present, self.ffn.get_aux_loss()
694
+
695
+
696
+ # ---------------------------------------------------------------------------
697
+ # HRM refinement
698
+ # ---------------------------------------------------------------------------
699
+
700
+ class HRMRefinementBlock(nn.Module):
701
+ def __init__(self, hidden_size: int, refine_dim: int, steps: int, eps: float = 1e-6):
702
+ super().__init__()
703
+ self.steps = steps
704
+ self.norm = RMSNorm(hidden_size, eps)
705
+ self.down = nn.Linear(hidden_size * 2, refine_dim, bias=False)
706
+ self.up = nn.Linear(refine_dim, hidden_size, bias=False)
707
+ self.gate = nn.Parameter(torch.zeros(steps))
708
+ nn.init.normal_(self.down.weight, std=0.02)
709
+ # FIX vs the original: up was ALSO zero-init, so update == 0 (kills the
710
+ # gate's gradient) and tanh(gate) == 0 (kills up's gradient) -- a saddle
711
+ # both gradients can never leave; the block stayed a no-op forever.
712
+ # The zero gate alone already makes init an exact no-op; up must be
713
+ # nonzero so the gate receives gradient.
714
+ nn.init.normal_(self.up.weight, std=0.02)
715
+
716
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
717
+ anchor = x
718
+ h = x
719
+ for t in range(self.steps):
720
+ inp = torch.cat([self.norm(h), anchor], dim=-1)
721
+ update = self.up(F.silu(self.down(inp)))
722
+ h = h + torch.tanh(self.gate[t]) * update
723
+ return h
724
+
725
+
726
+ # ---------------------------------------------------------------------------
727
+ # JEPA secondary prediction head (jepa_v2)
728
+ # ---------------------------------------------------------------------------
729
+
730
+ class JEPAPredictorBlock(nn.Module):
731
+ """
732
+ JEPA-inspired representation-space prediction (I-JEPA / LLM-JEPA family):
733
+ from the trunk's hidden state at position t, predict the trunk's OWN hidden
734
+ state at position t+k. The target is stop-gradient (detached), the standard
735
+ JEPA asymmetry that prevents the trivial collapse where the trunk just
736
+ makes all hidden states identical.
737
+
738
+ This complements the MTP heads: MTP predicts future TOKENS through the
739
+ lm_head (output space); JEPA predicts the future REPRESENTATION directly
740
+ (embedding space), pressuring the trunk to encode where its own state is
741
+ going -- abstract next-step structure rather than surface vocabulary.
742
+
743
+ Deliberately shaped like HRMRefinementBlock: RMSNorm -> down (bottleneck)
744
+ -> SiLU -> up -> per-offset tanh-gated residual. gate starts at zero so the
745
+ predictor is exactly identity at init (zero-risk insertion); up is NORMAL
746
+ init (not zero) so the gate actually receives gradient -- see the
747
+ double-zero saddle note on HRMRefinementBlock above.
748
+ """
749
+ def __init__(self, hidden_size: int, pred_dim: int, horizon: int, eps: float = 1e-6):
750
+ super().__init__()
751
+ self.horizon = horizon
752
+ self.norm = RMSNorm(hidden_size, eps)
753
+ self.down = nn.Linear(hidden_size, pred_dim, bias=False)
754
+ self.up = nn.Linear(pred_dim, hidden_size, bias=False)
755
+ self.gate = nn.Parameter(torch.zeros(horizon))
756
+ nn.init.normal_(self.down.weight, std=0.02)
757
+ nn.init.normal_(self.up.weight, std=0.02)
758
+
759
+ def forward(self, h: torch.Tensor, k: int) -> torch.Tensor:
760
+ """Predict the hidden state k steps ahead of each position in h."""
761
+ update = self.up(F.silu(self.down(self.norm(h))))
762
+ return h + torch.tanh(self.gate[k - 1]) * update
763
+
764
+
765
+ # ---------------------------------------------------------------------------
766
+ # Full model
767
+ # ---------------------------------------------------------------------------
768
+
769
+ class SpikeWhaleModel(nn.Module):
770
+ """Decoder stack without LM head."""
771
+
772
+ def __init__(self, cfg: SpikeWhaleConfig):
773
+ super().__init__()
774
+ self.cfg = cfg
775
+ self.embed_tokens = nn.Embedding(cfg.vocab_size, cfg.hidden_size)
776
+ nn.init.normal_(self.embed_tokens.weight, std=cfg.initializer_range)
777
+
778
+ self.engram = EngramModule(cfg) if cfg.use_engram else None
779
+ self.layers = nn.ModuleList([
780
+ TransformerBlock(cfg, layer_idx=i)
781
+ for i in range(cfg.num_hidden_layers)
782
+ ])
783
+ self.norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
784
+ self.hc_out_mix = (
785
+ HCOutputMix(cfg.hc_mult) if cfg.use_hyper_connections else None
786
+ )
787
+ self.hrm_refine = (
788
+ HRMRefinementBlock(cfg.hidden_size, cfg.hrm_refine_dim, cfg.hrm_refine_steps,
789
+ cfg.rms_norm_eps)
790
+ if getattr(cfg, "use_hrm_refine", False) else None
791
+ )
792
+ self.use_value_embed = getattr(cfg, "use_value_embed", False)
793
+ self.gradient_checkpointing = False
794
+
795
+ def forward(
796
+ self,
797
+ input_ids: torch.Tensor,
798
+ attention_mask: Optional[torch.Tensor] = None,
799
+ position_ids: Optional[torch.Tensor] = None,
800
+ past_key_values: Optional[List[Tuple]] = None,
801
+ use_cache: bool = False,
802
+ engram_context_ids: Optional[torch.Tensor] = None,
803
+ ) -> Tuple[torch.Tensor, Optional[List[Tuple]], torch.Tensor]:
804
+ B, S = input_ids.shape
805
+ device = input_ids.device
806
+
807
+ if position_ids is None:
808
+ past_len = past_key_values[0][0].shape[2] if past_key_values else 0
809
+ position_ids = torch.arange(
810
+ past_len, past_len + S, device=device
811
+ ).unsqueeze(0).expand(B, -1)
812
+
813
+ x = self.embed_tokens(input_ids)
814
+ token_embed = x if self.use_value_embed else None
815
+
816
+ if self.engram is not None:
817
+ if engram_context_ids is not None and engram_context_ids.numel() > 0:
818
+ # Cached decode: the n-gram hashes need the (max_ngram - 1)
819
+ # tokens BEFORE this window, which a KV cache does not carry.
820
+ # Prepend their embeddings, run the engram over the joined
821
+ # window, and keep only this window's positions.
822
+ ctx = self.embed_tokens(engram_context_ids)
823
+ n_ctx = ctx.shape[1]
824
+ x = x + self.engram(torch.cat([ctx, x], dim=1))[:, n_ctx:, :]
825
+ else:
826
+ x = x + self.engram(x)
827
+
828
+ if self.cfg.use_hyper_connections:
829
+ x = x.unsqueeze(1).expand(-1, self.cfg.hc_mult, -1, -1).clone()
830
+
831
+ present_key_values = [] if use_cache else None
832
+ total_aux_loss = torch.tensor(0.0, device=device)
833
+
834
+ # Gradient checkpointing is incompatible with use_cache (the cache from
835
+ # the discarded forward would be silently wrong on recompute).
836
+ assert not (self.gradient_checkpointing and self.training and use_cache), \
837
+ "use_cache=True is not supported with gradient checkpointing"
838
+
839
+ for layer_idx, layer in enumerate(self.layers):
840
+ pkv = past_key_values[layer_idx] if past_key_values else None
841
+
842
+ if self.gradient_checkpointing and self.training:
843
+ x, present, aux_loss = gradient_checkpoint(
844
+ layer, x, position_ids, attention_mask, None, False, token_embed,
845
+ use_reentrant=False,
846
+ )
847
+ else:
848
+ x, present, aux_loss = layer(
849
+ x, position_ids, attention_mask, pkv, use_cache, token_embed)
850
+
851
+ if use_cache:
852
+ present_key_values.append(present)
853
+ if aux_loss is not None:
854
+ total_aux_loss = total_aux_loss + aux_loss
855
+
856
+ if self.cfg.use_hyper_connections:
857
+ x = self.hc_out_mix(x) # v2: learned mix (init == mean)
858
+
859
+ if self.hrm_refine is not None:
860
+ x = self.hrm_refine(x)
861
+
862
+ x = self.norm(x)
863
+ return x, present_key_values, total_aux_loss
864
+
865
+
866
+ class MTPHead(nn.Module):
867
+ """
868
+ v2 MTP head: small zero-init H x H projection feeding the SHARED lm_head.
869
+ Cost per head: H^2 params (e.g. 1M at H=1024) instead of H*V (e.g. 50M+).
870
+ Zero-init means at step 0 the head predicts exactly what lm_head predicts
871
+ for the residual path = 0, i.e. uniform-ish gradient pressure; the residual
872
+ form (x + proj(x)) keeps it anchored to the trunk representation.
873
+ """
874
+ def __init__(self, hidden_size: int):
875
+ super().__init__()
876
+ self.proj = nn.Linear(hidden_size, hidden_size, bias=False)
877
+ nn.init.zeros_(self.proj.weight)
878
+
879
+ def forward(self, hidden: torch.Tensor) -> torch.Tensor:
880
+ return hidden + self.proj(hidden)
881
+
882
+
883
+ class SpikeWhaleLM(PreTrainedModel):
884
+ """
885
+ v2 loss = CE + zloss_coef * z-loss
886
+ + mtp_loss_weight * mean(MTP CE)
887
+ + jepa_loss_weight * mean(JEPA 1-cosine) (jepa_v2)
888
+ + MoE aux loss
889
+ """
890
+ config_class = SpikeWhaleConfig
891
+ base_model_prefix = "model"
892
+ supports_gradient_checkpointing = True
893
+ _no_split_modules = ["TransformerBlock"]
894
+
895
+ def __init__(self, cfg: SpikeWhaleConfig):
896
+ super().__init__(cfg)
897
+ self.model = SpikeWhaleModel(cfg)
898
+ self.lm_head = nn.Linear(cfg.hidden_size, cfg.vocab_size, bias=False)
899
+ nn.init.normal_(self.lm_head.weight, std=cfg.initializer_range)
900
+
901
+ self.zloss_coef = getattr(cfg, "zloss_coef", 1e-4)
902
+ self.mtp_loss_weight = getattr(cfg, "mtp_loss_weight", 0.3)
903
+
904
+ # v2 MTP: H x H residual projections sharing lm_head (see MTPHead).
905
+ self.mtp_heads = nn.ModuleList([
906
+ MTPHead(cfg.hidden_size)
907
+ for _ in range(cfg.num_nextn_predict_layers)
908
+ ]) if cfg.num_nextn_predict_layers > 0 else None
909
+
910
+ # JEPA secondary prediction head (representation-space, stop-grad target).
911
+ self.jepa_loss_weight = getattr(cfg, "jepa_loss_weight", 0.1)
912
+ self.jepa_horizon = getattr(cfg, "jepa_horizon", 1)
913
+ self.jepa = (
914
+ JEPAPredictorBlock(cfg.hidden_size,
915
+ getattr(cfg, "jepa_pred_dim", 256),
916
+ self.jepa_horizon, cfg.rms_norm_eps)
917
+ if getattr(cfg, "use_jepa", False) else None
918
+ )
919
+
920
+ self.post_init()
921
+
922
+ def get_input_embeddings(self):
923
+ return self.model.embed_tokens
924
+
925
+ def set_input_embeddings(self, value):
926
+ self.model.embed_tokens = value
927
+
928
+ def get_output_embeddings(self):
929
+ return self.lm_head
930
+
931
+ def set_output_embeddings(self, new_embeddings):
932
+ self.lm_head = new_embeddings
933
+
934
+ def tie_weights(self, **kwargs):
935
+ if self.config.tie_word_embeddings:
936
+ self.lm_head.weight = self.model.embed_tokens.weight
937
+
938
+ def save_pretrained(self, *args, **kwargs):
939
+ tied = (
940
+ self.config.tie_word_embeddings
941
+ and self.lm_head.weight.data_ptr() == self.model.embed_tokens.weight.data_ptr()
942
+ )
943
+ if tied:
944
+ self.lm_head.weight = nn.Parameter(self.model.embed_tokens.weight.detach().clone())
945
+ try:
946
+ super().save_pretrained(*args, **kwargs)
947
+ finally:
948
+ if tied:
949
+ self.lm_head.weight = self.model.embed_tokens.weight
950
+
951
+ def _set_gradient_checkpointing(self, module, value=False):
952
+ if isinstance(module, SpikeWhaleModel):
953
+ module.gradient_checkpointing = value
954
+
955
+ def forward(
956
+ self,
957
+ input_ids: Optional[torch.Tensor] = None,
958
+ attention_mask: Optional[torch.Tensor] = None,
959
+ position_ids: Optional[torch.Tensor] = None,
960
+ past_key_values: Optional[List[Tuple]] = None,
961
+ labels: Optional[torch.Tensor] = None,
962
+ use_cache: bool = False,
963
+ engram_context_ids: Optional[torch.Tensor] = None,
964
+ **kwargs,
965
+ ) -> CausalLMOutputWithPast:
966
+ hidden, present_kvs, aux_loss = self.model(
967
+ input_ids=input_ids,
968
+ attention_mask=attention_mask,
969
+ position_ids=position_ids,
970
+ past_key_values=past_key_values,
971
+ use_cache=use_cache,
972
+ engram_context_ids=engram_context_ids,
973
+ )
974
+
975
+ logits = self.lm_head(hidden)
976
+ loss = None
977
+
978
+ if labels is not None:
979
+ shift_logits = logits[..., :-1, :].contiguous()
980
+ shift_labels = labels[..., 1:].contiguous()
981
+ flat_logits = shift_logits.view(-1, shift_logits.size(-1))
982
+ flat_labels = shift_labels.view(-1)
983
+ loss = F.cross_entropy(flat_logits, flat_labels, ignore_index=-100)
984
+
985
+ # z-loss (v2): penalize log^2 of the partition function on valid
986
+ # positions. Keeps logits from drifting; pairs well with Muon.
987
+ if self.zloss_coef > 0:
988
+ valid = flat_labels != -100
989
+ if valid.any():
990
+ log_z = torch.logsumexp(flat_logits[valid].float(), dim=-1)
991
+ loss = loss + self.zloss_coef * (log_z ** 2).mean()
992
+
993
+ # MTP (v2): residual H x H head -> shared lm_head, down-weighted.
994
+ if self.mtp_heads is not None and self.mtp_loss_weight > 0:
995
+ mtp_total = torch.tensor(0.0, device=loss.device)
996
+ n_active = 0
997
+ for k, head in enumerate(self.mtp_heads, start=1):
998
+ offset = k + 1
999
+ if hidden.size(1) > offset:
1000
+ mtp_hidden = head(hidden[..., :-offset, :])
1001
+ mtp_logits = self.lm_head(mtp_hidden)
1002
+ mtp_labels = labels[..., offset:].contiguous()
1003
+ mtp_total = mtp_total + F.cross_entropy(
1004
+ mtp_logits.reshape(-1, mtp_logits.size(-1)),
1005
+ mtp_labels.reshape(-1),
1006
+ ignore_index=-100,
1007
+ )
1008
+ n_active += 1
1009
+ if n_active > 0:
1010
+ loss = loss + self.mtp_loss_weight * mtp_total / n_active
1011
+
1012
+ # JEPA (jepa_v2): predict the trunk's hidden state k steps ahead in
1013
+ # representation space. Target is DETACHED (JEPA stop-gradient) and
1014
+ # the loss is (1 - cosine), computed only on positions whose target
1015
+ # carries a real label (skips padding / masked prompt tokens).
1016
+ if self.jepa is not None and self.jepa_loss_weight > 0:
1017
+ jepa_total = torch.tensor(0.0, device=loss.device)
1018
+ n_jepa = 0
1019
+ for k in range(1, self.jepa_horizon + 1):
1020
+ if hidden.size(1) <= k:
1021
+ break
1022
+ pred = self.jepa(hidden[..., :-k, :], k)
1023
+ target = hidden[..., k:, :].detach()
1024
+ valid = labels[..., k:] != -100
1025
+ if not valid.any():
1026
+ continue
1027
+ cos = F.cosine_similarity(pred.float(), target.float(), dim=-1)
1028
+ jepa_total = jepa_total + (1.0 - cos)[valid].mean()
1029
+ n_jepa += 1
1030
+ if n_jepa > 0:
1031
+ loss = loss + self.jepa_loss_weight * jepa_total / n_jepa
1032
+
1033
+ loss = loss + aux_loss
1034
+
1035
+ return CausalLMOutputWithPast(
1036
+ loss=loss,
1037
+ logits=logits,
1038
+ past_key_values=present_kvs,
1039
+ )
1040
+
1041
+ def count_parameters(self) -> int:
1042
+ return sum(p.numel() for p in self.parameters())
spike_tokenizer.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ spike_tokenizer.py -- HuggingFace-compatible wrapper for the custom
3
+ byte-level "length-max" (greedy longest-match) tokenizer in tokenizer.json.
4
+
5
+ The raw tokenizer.json is NOT a HuggingFace `tokenizers` file; it is a plain
6
+ dict {vocab, vocab_size, max_token_len, algorithm:"length-max"}. This wrapper
7
+ makes it loadable by AutoTokenizer.from_pretrained / save_pretrained and
8
+ exposes encode/decode + the bos/eos/pad/unk ids the training scripts expect.
9
+
10
+ Encoding scheme (verified): byte-level. Text is UTF-8 encoded, each byte mapped
11
+ to its latin-1 character, then greedily matched against the vocab using the
12
+ longest key that matches at each position (max key length = max_token_len).
13
+ """
14
+ import json, os
15
+ from typing import List, Optional
16
+ from transformers import PreTrainedTokenizer
17
+
18
+
19
+ class SpikeTokenizer(PreTrainedTokenizer):
20
+ vocab_files_names = {"vocab_file": "tokenizer.json"}
21
+ model_input_names = ["input_ids"]
22
+
23
+ def __init__(self, vocab_file=None, **kwargs):
24
+ with open(vocab_file, "r", encoding="utf-8") as f:
25
+ data = json.load(f)
26
+ self._vocab = data["vocab"] # str -> id
27
+ self._ids_to_tokens = {i: t for t, i in self._vocab.items()}
28
+ self.max_token_len = int(data.get("max_token_len", 24))
29
+ # length-bucketed keys for fast greedy match (longest length first)
30
+ self._lengths = sorted({len(k) for k in self._vocab}, reverse=True)
31
+
32
+ # Appended special tokens (im_start / <think> / <begin_solution> / ...).
33
+ # They already live in self._vocab at their real ids; we hand them to the
34
+ # HF base class as `additional_special_tokens` so its AddedToken trie:
35
+ # (1) splits them out ATOMICALLY before our byte-level greedy match
36
+ # (verified: each maps back to its existing vocab id, no phantom id), and
37
+ # (2) drops them on decode(skip_special_tokens=True).
38
+ # The set is stored in tokenizer.json under "special_tokens" so it
39
+ # survives save_pretrained/from_pretrained round-trips.
40
+ self._extra_specials = [
41
+ t for t in data.get("special_tokens", []) if t in self._vocab
42
+ ]
43
+ if self._extra_specials:
44
+ existing = list(kwargs.get("additional_special_tokens", []) or [])
45
+ merged = existing + [t for t in self._extra_specials if t not in existing]
46
+ kwargs["additional_special_tokens"] = merged
47
+
48
+ kwargs.setdefault("bos_token", "<bos>")
49
+ kwargs.setdefault("eos_token", "<eos>")
50
+ kwargs.setdefault("unk_token", "<unk>")
51
+ kwargs.setdefault("pad_token", "<pad>")
52
+ super().__init__(**kwargs)
53
+
54
+ @property
55
+ def vocab_size(self) -> int:
56
+ return len(self._vocab)
57
+
58
+ def get_vocab(self):
59
+ return dict(self._vocab)
60
+
61
+ # --- core byte-level greedy tokenization ---
62
+ def _tokenize(self, text: str) -> List[str]:
63
+ s = text.encode("utf-8").decode("latin-1") # one char per byte
64
+ out, i, n = [], 0, len(s)
65
+ while i < n:
66
+ matched = None
67
+ hi = min(self.max_token_len, n - i)
68
+ for L in range(hi, 0, -1):
69
+ sub = s[i:i + L]
70
+ if sub in self._vocab:
71
+ matched = sub
72
+ break
73
+ if matched is None: # single byte always exists in vocab
74
+ matched = s[i]
75
+ out.append(matched)
76
+ i += len(matched)
77
+ return out
78
+
79
+ def _convert_token_to_id(self, token: str) -> int:
80
+ return self._vocab.get(token, self._vocab["<unk>"])
81
+
82
+ def _convert_id_to_token(self, index: int) -> str:
83
+ return self._ids_to_tokens.get(index, "<unk>")
84
+
85
+ def convert_tokens_to_string(self, tokens: List[str]) -> str:
86
+ # transformers 5.x hands the FULL token list here (special tokens
87
+ # included; skip_special_tokens is already applied upstream via
88
+ # convert_ids_to_tokens). So we can't just byte-decode everything: a
89
+ # special token like "<|im_start|>" is a literal marker, not latin-1
90
+ # bytes. Decode runs of ordinary byte-tokens together (needed so
91
+ # multi-byte UTF-8 sequences reassemble) and emit any special token
92
+ # inline as its literal string.
93
+ specials = {"<pad>", "<unk>", "<bos>", "<eos>", *self._extra_specials}
94
+ out, buf = [], []
95
+ for tok in tokens:
96
+ if tok in specials:
97
+ if buf:
98
+ out.append("".join(buf).encode("latin-1").decode("utf-8", errors="replace"))
99
+ buf = []
100
+ out.append(tok)
101
+ else:
102
+ buf.append(tok)
103
+ if buf:
104
+ out.append("".join(buf).encode("latin-1").decode("utf-8", errors="replace"))
105
+ return "".join(out)
106
+
107
+ def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None):
108
+ os.makedirs(save_directory, exist_ok=True)
109
+ fn = (filename_prefix + "-" if filename_prefix else "") + "tokenizer.json"
110
+ path = os.path.join(save_directory, fn)
111
+ with open(path, "w", encoding="utf-8") as f:
112
+ json.dump({"vocab": self._vocab, "vocab_size": self.vocab_size,
113
+ "max_token_len": self.max_token_len,
114
+ "algorithm": "length-max",
115
+ "special_tokens": list(self._extra_specials)},
116
+ f, ensure_ascii=False)
117
+ return (path,)
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "<pad>",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "1": {
12
+ "content": "<unk>",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "2": {
20
+ "content": "<bos>",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "3": {
28
+ "content": "<eos>",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ },
35
+ "16384": {
36
+ "content": "<|im_start|>",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false,
41
+ "special": true
42
+ },
43
+ "16385": {
44
+ "content": "<|im_end|>",
45
+ "lstrip": false,
46
+ "normalized": false,
47
+ "rstrip": false,
48
+ "single_word": false,
49
+ "special": true
50
+ },
51
+ "16386": {
52
+ "content": "<think>",
53
+ "lstrip": false,
54
+ "normalized": false,
55
+ "rstrip": false,
56
+ "single_word": false,
57
+ "special": true
58
+ },
59
+ "16387": {
60
+ "content": "</think>",
61
+ "lstrip": false,
62
+ "normalized": false,
63
+ "rstrip": false,
64
+ "single_word": false,
65
+ "special": true
66
+ },
67
+ "16388": {
68
+ "content": "<begin_solution>",
69
+ "lstrip": false,
70
+ "normalized": false,
71
+ "rstrip": false,
72
+ "single_word": false,
73
+ "special": true
74
+ },
75
+ "16389": {
76
+ "content": "<end_solution>",
77
+ "lstrip": false,
78
+ "normalized": false,
79
+ "rstrip": false,
80
+ "single_word": false,
81
+ "special": true
82
+ },
83
+ "16390": {
84
+ "content": "<tool_call>",
85
+ "lstrip": false,
86
+ "normalized": false,
87
+ "rstrip": false,
88
+ "single_word": false,
89
+ "special": true
90
+ },
91
+ "16391": {
92
+ "content": "</tool_call>",
93
+ "lstrip": false,
94
+ "normalized": false,
95
+ "rstrip": false,
96
+ "single_word": false,
97
+ "special": true
98
+ },
99
+ "16392": {
100
+ "content": "<tool_response>",
101
+ "lstrip": false,
102
+ "normalized": false,
103
+ "rstrip": false,
104
+ "single_word": false,
105
+ "special": true
106
+ },
107
+ "16393": {
108
+ "content": "</tool_response>",
109
+ "lstrip": false,
110
+ "normalized": false,
111
+ "rstrip": false,
112
+ "single_word": false,
113
+ "special": true
114
+ },
115
+ "16394": {
116
+ "content": "<|system|>",
117
+ "lstrip": false,
118
+ "normalized": false,
119
+ "rstrip": false,
120
+ "single_word": false,
121
+ "special": true
122
+ },
123
+ "16395": {
124
+ "content": "<|user|>",
125
+ "lstrip": false,
126
+ "normalized": false,
127
+ "rstrip": false,
128
+ "single_word": false,
129
+ "special": true
130
+ },
131
+ "16396": {
132
+ "content": "<|assistant|>",
133
+ "lstrip": false,
134
+ "normalized": false,
135
+ "rstrip": false,
136
+ "single_word": false,
137
+ "special": true
138
+ },
139
+ "16397": {
140
+ "content": "<|fim_prefix|>",
141
+ "lstrip": false,
142
+ "normalized": false,
143
+ "rstrip": false,
144
+ "single_word": false,
145
+ "special": true
146
+ },
147
+ "16398": {
148
+ "content": "<|fim_middle|>",
149
+ "lstrip": false,
150
+ "normalized": false,
151
+ "rstrip": false,
152
+ "single_word": false,
153
+ "special": true
154
+ },
155
+ "16399": {
156
+ "content": "<|fim_suffix|>",
157
+ "lstrip": false,
158
+ "normalized": false,
159
+ "rstrip": false,
160
+ "single_word": false,
161
+ "special": true
162
+ },
163
+ "16400": {
164
+ "content": "<|endoftext|>",
165
+ "lstrip": false,
166
+ "normalized": false,
167
+ "rstrip": false,
168
+ "single_word": false,
169
+ "special": true
170
+ }
171
+ },
172
+ "backend": "custom",
173
+ "bos_token": "<bos>",
174
+ "eos_token": "<eos>",
175
+ "extra_special_tokens": [
176
+ "</think>",
177
+ "</tool_call>",
178
+ "</tool_response>",
179
+ "<begin_solution>",
180
+ "<end_solution>",
181
+ "<think>",
182
+ "<tool_call>",
183
+ "<tool_response>",
184
+ "<|assistant|>",
185
+ "<|endoftext|>",
186
+ "<|fim_middle|>",
187
+ "<|fim_prefix|>",
188
+ "<|fim_suffix|>",
189
+ "<|im_end|>",
190
+ "<|im_start|>",
191
+ "<|system|>",
192
+ "<|user|>"
193
+ ],
194
+ "model_max_length": 1000000000000000019884624838656,
195
+ "pad_token": "<pad>",
196
+ "tokenizer_class": "SpikeTokenizer",
197
+ "unk_token": "<unk>",
198
+ "auto_map": {
199
+ "AutoTokenizer": [
200
+ "spike_tokenizer.SpikeTokenizer",
201
+ null
202
+ ]
203
+ }
204
+ }