Quazim0t0 commited on
Commit
64e35b9
·
verified ·
1 Parent(s): 2404cf5

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. Byrne-LLMs.png +3 -0
  3. README.md +182 -0
  4. config.json +66 -0
  5. config.py +152 -0
  6. model.safetensors +3 -0
  7. model_v2.py +962 -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
+ Byrne-LLMs.png filter=lfs diff=lfs merge=lfs -text
Byrne-LLMs.png ADDED

Git LFS Details

  • SHA256: 27f3fafff50337a2da124297ce5fae92cabf2f6ea0f4506954a503b12aa84992
  • Pointer size: 132 Bytes
  • Size of remote file: 2.97 MB
README.md ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language: [en]
4
+ tags: [text-generation, small-models, mla, jepa, experimental, long-context, jet-long, rope]
5
+ pipeline_tag: text-generation
6
+ library_name: transformers
7
+ ---
8
+
9
+ <img src="Byrne-LLMs.png" width="50%">
10
+
11
+
12
+ # Byrne-86M-Base
13
+
14
+ ## 🔭 Jet-Long context extension (native 4K → 10K)
15
+
16
+ This is the **Jet-Long** edition of [`Byrne-86M-Base`](https://huggingface.co/Quazim0t0/Byrne-86M-Base).
17
+ It extends the usable context from the native **4,096**-token training window to
18
+ **10,240** tokens with **no fine-tuning** and **no change to short-context behaviour**,
19
+ by adding *dynamic bifocal RoPE* from **Jet-Long** ([arXiv:2607.07740](https://arxiv.org/abs/2607.07740), NVIDIA).
20
+
21
+ ### What was applied
22
+
23
+ Jet-Long pairs a **local window** (`w0 = 2048`, classic RoPE) with a **remote window**
24
+ whose position map aliases far-apart tokens back onto the pretrained rotation grid:
25
+
26
+ ```
27
+ f(x) = floor(x / G), G = max(1, ceil(L / 4096))
28
+ ```
29
+
30
+ `G` adapts to the **current** sequence length `L`, so:
31
+
32
+ - **`L ≤ 4096` → `G = 1` → f is the identity → the model is bit-for-bit the base model.**
33
+ (Verified: max |Δlogit| between Jet-Long on/off within the window is `0.000e+00`.)
34
+ - **`L > 4096`** → the remote window keeps every rotation in-distribution, so the model
35
+ extrapolates instead of collapsing.
36
+
37
+ Implementation notes specific to this SpikeWhaleLM build:
38
+ - Only the **decoupled RoPE partition** (16 of 64 head dims) is aliased; the NoPE partition
39
+ is untouched. Softmax attention (`use_derf=False`) — the standard Jet-Long merge applies.
40
+ - The remote view is realized by an **on-the-fly correction rotation** on the already-RoPE'd
41
+ KV cache (RoPE composes additively), so **the cache is never rewritten** and decode is cheap.
42
+ - Enabled via config: `use_jetlong=true`, `jetlong_w0=2048`, `jetlong_w_pretrained=4096`,
43
+ `max_position_embeddings=10240`. Set `use_jetlong=false` to recover the exact base model.
44
+ - The inclusion–exclusion / CuTe throughput kernel from the paper is **not** included (it targets
45
+ 100K+ contexts on H100); at 86M params the bifocal attention is computed directly.
46
+
47
+ ### Measured (PG-19-style perplexity on held-out text, lower is better)
48
+
49
+ | Context length | Base model | This Jet-Long model |
50
+ |---|---|---|
51
+ | ≤ 4,096 (in-window) | *(identical — Jet-Long is a no-op)* | *(identical)* |
52
+ | **10,240** | **39.34** | **8.16** |
53
+
54
+ Beyond the training window the base model's perplexity blows up while Jet-Long stays flat —
55
+ and long-context generation stays grammatical where the base model degrades into word-salad.
56
+
57
+ ### Usage
58
+
59
+ Jet-Long is **on by default** in this repo. Pass explicit `position_ids` so RoPE gets true
60
+ absolute positions during cached decode:
61
+
62
+ ```python
63
+ import torch
64
+ from transformers import AutoModelForCausalLM
65
+ m = AutoModelForCausalLM.from_pretrained("Quazim0t0/Byrne-86M-Base-JL", trust_remote_code=True)
66
+ ids = ... # up to ~10,240 tokens
67
+ pos = torch.arange(ids.shape[1]).unsqueeze(0)
68
+ out = m(input_ids=ids, position_ids=pos, use_cache=True) # prefill, then decode step-by-step
69
+ ```
70
+
71
+ *Method: Tang, Wang, Gu, Han, Cai — “Jet-Long: Efficient Long-Context Extension with Dynamic
72
+ Bifocal RoPE”, arXiv:2607.07740. Applied here zero-shot to SpikeWhaleLM; no weights were retrained.*
73
+
74
+
75
+ The **base** model of the Byrne family (distilled step-4000 checkpoint) — a strong general base for continued pretraining / fine-tuning. A ~86M-parameter, from-scratch `SpikeWhaleLM` decoder (Multi-head Latent Attention,
76
+ n-gram engram memory, hash-lookup layers, hyper-connections, HRM refinement, MTP) with a
77
+ custom ChatML-aware tokenizer. Trained with **Modal** credits during the **Small Models,
78
+ Big Adventures Hackathon**.
79
+
80
+ > **Related:** main model → [Byrne-86M](https://huggingface.co/Quazim0t0/Byrne-86M)
81
+
82
+ ## Usage
83
+
84
+ ```python
85
+ from transformers import AutoModelForCausalLM, AutoTokenizer
86
+ tok = AutoTokenizer.from_pretrained("Quazim0t0/Byrne-86M-Base", trust_remote_code=True)
87
+ model = AutoModelForCausalLM.from_pretrained("Quazim0t0/Byrne-86M-Base", trust_remote_code=True)
88
+ ```
89
+
90
+ <!-- ARCH_TOK_START -->
91
+ ## Architecture
92
+
93
+ These models are built on **SpikeWhaleLM**, a custom ~86M-parameter decoder-only transformer
94
+ (16 layers, hidden size 640, 4096-token context, 16,512 vocab, tied input/output embeddings).
95
+ It combines several non-standard components:
96
+
97
+ - **Multi-head Latent Attention (MLA + XSA)** — queries and the output projection are
98
+ LoRA-compressed (rank 128); each head splits into a decoupled RoPE part (dim 16) and a
99
+ position-agnostic NoPE part (dim 48); 10 query heads share a **single KV head**
100
+ (multi-query attention), with QK-norm for stable logits.
101
+ - **Engram n-gram memory** — a gated associative memory that hashes local n-grams (up to
102
+ trigrams) into a learned 4,096-entry table and mixes the result back into the residual stream.
103
+ - **Hash-lookup layers (×2)** — multi-head content-addressable features alongside the token
104
+ embeddings.
105
+ - **Hyper-Connections** — learned, width-expanded residual connections mixed via
106
+ Sinkhorn-normalized routing, in place of the plain residual add.
107
+ - **HRM refinement** — a Hierarchical Reasoning Model block that performs an extra latent
108
+ "think a bit more" refinement pass over the hidden states before the output head.
109
+ - **Multi-Token Prediction (MTP)** — a DeepSeek-V3-style auxiliary training head predicting
110
+ more than one next token (no inference cost).
111
+ - Feed-forward is **dense** (the block is MoE-capable, but MoE is disabled in this release).
112
+
113
+ > **JEPA vs HRM.** The **Byrne** models are **Non-JEPA**: they are trained with **HRM refinement only** (`use_hrm_refine=True`, `use_jepa=False`). The sibling **Escarda** models add a **JEPA** (Joint-Embedding Predictive) auxiliary objective on top of HRM refinement.
114
+
115
+ <a href="https://hfviewer.com/Quazim0t0/Byrne-86M-Base?utm_source=huggingface&amp;utm_medium=embedded_model_card&amp;utm_campaign=Quazim0t0__Byrne-86M-Base_card&amp;utm_content=embedded_card_open_viewer&amp;from=embedded-model-card" target="_blank" rel="noopener">
116
+ <img
117
+ src="https://hfviewer.com/api/card.svg?source=Quazim0t0%2FByrne-86M-Base&amp;granularity=auto&amp;v=20260516-title-pills-card"
118
+ alt="Architecture graph for Quazim0t0/Byrne-86M-Base. Open in hfviewer"
119
+ width="100%"
120
+ />
121
+ </a>
122
+
123
+ ## Tokenizer
124
+
125
+ These models use **`SpikeTokenizer`**, a custom **byte-level "length-max" (greedy
126
+ longest-match)** tokenizer with a **16,512-token vocabulary** — not a standard BPE/HF
127
+ tokenizer. Text is UTF-8 encoded, each byte mapped to a latin-1 character, then greedily
128
+ matched against the vocab using the longest key that fits at each position. It is
129
+ **ChatML-aware**, with atomic special tokens for framing and reasoning/tool markers
130
+ (`<|im_start|>`, `<|im_end|>`, `<think>`/`</think>`, `<begin_solution>`/`<end_solution>`,
131
+ tool-call markers) plus `<bos>`/`<eos>`/`<pad>`/`<unk>`. It ships as a `PreTrainedTokenizer`
132
+ subclass (`spike_tokenizer.py`) and loads via
133
+ `AutoTokenizer.from_pretrained(..., trust_remote_code=True)`.
134
+ <!-- ARCH_TOK_END -->
135
+
136
+ ## Evaluation
137
+
138
+ log-likelihood, `acc_norm` = byte-length-normalized).
139
+
140
+ | Task | acc | acc_norm |
141
+ |---|---|---|
142
+ | arc_easy | 0.4205 | 0.3931 |
143
+ | arc_challenge | 0.1877 | 0.2389 |
144
+ | hellaswag | 0.2792 | 0.2927 |
145
+ | winogrande | 0.5193 | — |
146
+ | piqa | 0.5941 | 0.5860 |
147
+ | openbookqa | 0.1420 | 0.2820 |
148
+ | boolq | 0.6171 | — |
149
+
150
+ **ArithMark-2.0** ([AxiomicLabs](https://huggingface.co/datasets/AxiomicLabs/ArithMark-2.0))
151
+ — official metric is raw **`acc`**: **0.2732**.
152
+
153
+ **Language modeling:** WikiText-2 byte_ppl (↓) **2.3753** · BLiMP (↑) **0.7356**.
154
+
155
+ <!-- CITE_START -->
156
+ ## Citation
157
+
158
+ If you use this model, please cite:
159
+
160
+ ```bibtex
161
+ @misc{byrne86mbase,
162
+ title = {Byrne-86M-Base: A ~86M-parameter SpikeWhaleLM},
163
+ author = {Dean Byrne (Quazim0t0)},
164
+ year = {2026},
165
+ howpublished = {HuggingFace, \url{https://huggingface.co/Quazim0t0/Byrne-86M-Base}},
166
+ note = {Quazim0t0/Byrne-86M-Base}
167
+ }
168
+ ```
169
+ <!-- CITE_END -->
170
+
171
+ ## Update: engram repair (behavior-preserving)
172
+
173
+ The n-gram Engram memory in the original weights was degenerate: with the frozen
174
+ LSH compressor at init scale, every token hashed to bucket 0, so only one table
175
+ row ever received gradient. This revision rescales the (frozen) compressor and
176
+ broadcasts the learned bucket-0 vector across all table rows.
177
+
178
+ **Outputs are bit-identical to the previous revision** (verified: max logit
179
+ difference 0.0 across a prompt battery). The only change: the Engram's hash now
180
+ spreads across the full table and every bucket is independently trainable — so
181
+ if you distill or SFT on top of this base, the n-gram memory will actually learn
182
+ instead of staying a constant bias.
config.json ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "SpikeWhaleLM"
4
+ ],
5
+ "attention_dropout": 0.0,
6
+ "bos_token_id": 2,
7
+ "dtype": "float32",
8
+ "engram_compress_dim": 32,
9
+ "engram_gate_init_bias": -1.0,
10
+ "engram_max_ngram": 3,
11
+ "engram_num_heads": 2,
12
+ "engram_table_size": 4096,
13
+ "eos_token_id": 3,
14
+ "hc_eps": 1e-06,
15
+ "hc_mult": 2,
16
+ "hc_sinkhorn_iters": 20,
17
+ "head_dim": 64,
18
+ "hidden_dropout": 0.0,
19
+ "hidden_size": 640,
20
+ "hrm_refine_dim": 128,
21
+ "hrm_refine_steps": 1,
22
+ "initializer_range": 0.02,
23
+ "max_position_embeddings": 10240,
24
+ "model_type": "spike_whale",
25
+ "moe_aux_loss_coef": 0.01,
26
+ "moe_intermediate_size": 2000,
27
+ "moe_layers": [],
28
+ "mtp_loss_weight": 0.3,
29
+ "n_routed_experts": 6,
30
+ "n_shared_experts": 1,
31
+ "nope_head_dim": 48,
32
+ "norm_topk_prob": true,
33
+ "num_attention_heads": 10,
34
+ "num_experts_per_tok": 2,
35
+ "num_hash_layers": 2,
36
+ "num_hidden_layers": 16,
37
+ "num_key_value_heads": 1,
38
+ "num_nextn_predict_layers": 1,
39
+ "o_lora_rank": 128,
40
+ "q_lora_rank": 128,
41
+ "qk_rope_head_dim": 16,
42
+ "rms_norm_eps": 1e-06,
43
+ "rope_theta": 10000.0,
44
+ "routed_scaling_factor": 1.0,
45
+ "scoring_func": "sqrtsoftplus",
46
+ "tie_word_embeddings": true,
47
+ "transformers_version": "5.8.0",
48
+ "use_derf": false,
49
+ "use_engram": true,
50
+ "use_hrm_refine": true,
51
+ "use_hyper_connections": true,
52
+ "use_moe": false,
53
+ "use_qk_norm": true,
54
+ "use_value_embed": false,
55
+ "use_xsa": true,
56
+ "vocab_size": 16512,
57
+ "zloss_coef": 0.0001,
58
+ "auto_map": {
59
+ "AutoConfig": "config.SpikeWhaleConfig",
60
+ "AutoModel": "model_v2.SpikeWhaleLM",
61
+ "AutoModelForCausalLM": "model_v2.SpikeWhaleLM"
62
+ },
63
+ "use_jetlong": true,
64
+ "jetlong_w_pretrained": 4096,
65
+ "jetlong_w0": 2048
66
+ }
config.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ **kwargs,
94
+ ):
95
+ super().__init__(
96
+ bos_token_id=bos_token_id,
97
+ eos_token_id=eos_token_id,
98
+ tie_word_embeddings=tie_word_embeddings,
99
+ **kwargs,
100
+ )
101
+ self.vocab_size = vocab_size
102
+ self.hidden_size = hidden_size
103
+ self.num_hidden_layers = num_hidden_layers
104
+ self.max_position_embeddings = max_position_embeddings
105
+ self.rms_norm_eps = rms_norm_eps
106
+ self.initializer_range = initializer_range
107
+ self.hidden_dropout = hidden_dropout
108
+
109
+ self.num_attention_heads = num_attention_heads
110
+ self.num_key_value_heads = num_key_value_heads
111
+ self.q_lora_rank = q_lora_rank
112
+ self.head_dim = head_dim
113
+ self.qk_rope_head_dim = qk_rope_head_dim
114
+ self.nope_head_dim = head_dim - qk_rope_head_dim
115
+ self.o_lora_rank = o_lora_rank
116
+ self.attention_dropout = attention_dropout
117
+ self.rope_theta = rope_theta
118
+ self.use_derf = use_derf
119
+ self.use_xsa = use_xsa
120
+
121
+ self.use_moe = use_moe
122
+ self.moe_intermediate_size = moe_intermediate_size
123
+ self.n_routed_experts = n_routed_experts
124
+ self.n_shared_experts = n_shared_experts
125
+ self.num_experts_per_tok = num_experts_per_tok
126
+ self.norm_topk_prob = norm_topk_prob
127
+ self.scoring_func = scoring_func
128
+ self.routed_scaling_factor = routed_scaling_factor
129
+ self.num_hash_layers = num_hash_layers
130
+ self.moe_aux_loss_coef = moe_aux_loss_coef
131
+ self.moe_layers = moe_layers if moe_layers is not None else list(range(num_hidden_layers))
132
+
133
+ self.use_hyper_connections = use_hyper_connections
134
+ self.hc_mult = hc_mult
135
+ self.hc_sinkhorn_iters = hc_sinkhorn_iters
136
+ self.hc_eps = hc_eps
137
+
138
+ self.num_nextn_predict_layers = num_nextn_predict_layers
139
+
140
+ self.use_engram = use_engram
141
+ self.engram_compress_dim = engram_compress_dim
142
+ self.engram_num_heads = engram_num_heads
143
+ self.engram_table_size = engram_table_size
144
+ self.engram_max_ngram = engram_max_ngram
145
+ self.engram_gate_init_bias = engram_gate_init_bias
146
+ self.use_hrm_refine = use_hrm_refine
147
+ self.hrm_refine_steps = hrm_refine_steps
148
+ self.hrm_refine_dim = hrm_refine_dim
149
+ self.use_qk_norm = use_qk_norm
150
+ self.zloss_coef = zloss_coef
151
+ self.mtp_loss_weight = mtp_loss_weight
152
+ self.use_value_embed = use_value_embed
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce10eed160fd9adbcd1a8682dd962f533faed86cde45e6941399e7616704b2ca
3
+ size 387815772
model_v2.py ADDED
@@ -0,0 +1,962 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ try:
49
+ # Dotted import so HuggingFace's trust_remote_code loader fetches config.py
50
+ # as a relative dependency; falls back to flat import for local script use.
51
+ from .config import SpikeWhaleConfig
52
+ except ImportError:
53
+ from config import SpikeWhaleConfig
54
+
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Primitives
58
+ # ---------------------------------------------------------------------------
59
+
60
+ class RMSNorm(nn.Module):
61
+ def __init__(self, dim: int, eps: float = 1e-6):
62
+ super().__init__()
63
+ self.eps = eps
64
+ self.weight = nn.Parameter(torch.ones(dim))
65
+
66
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
67
+ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.weight
68
+
69
+
70
+ class RotaryEmbedding(nn.Module):
71
+ """RoPE for the rope partition of Q and K (qk_rope_head_dim dims only)."""
72
+
73
+ def __init__(self, dim: int, max_positions: int = 4096, theta: float = 10000.0):
74
+ super().__init__()
75
+ inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2).float() / dim))
76
+ self.register_buffer("inv_freq", inv_freq)
77
+ t = torch.arange(max_positions).float()
78
+ freqs = torch.outer(t, inv_freq)
79
+ self.register_buffer("cos_cache", freqs.cos(), persistent=False)
80
+ self.register_buffer("sin_cache", freqs.sin(), persistent=False)
81
+
82
+ def forward(self, x: torch.Tensor, position_ids: torch.Tensor) -> torch.Tensor:
83
+ cos = self.cos_cache[position_ids].unsqueeze(1) # [B, 1, S, rope_dim//2]
84
+ sin = self.sin_cache[position_ids].unsqueeze(1)
85
+ d = cos.shape[-1]
86
+ x1, x2 = x[..., :d], x[..., d:]
87
+ return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
88
+
89
+
90
+ # ---------------------------------------------------------------------------
91
+ # Engram: N-gram hash lookup + DERF gate (unchanged from v1)
92
+ # ---------------------------------------------------------------------------
93
+
94
+ class TokenCompressor(nn.Module):
95
+ def __init__(self, embed_dim: int, compress_dim: int):
96
+ super().__init__()
97
+ self.proj = nn.Linear(embed_dim, compress_dim, bias=False)
98
+ nn.init.normal_(self.proj.weight, std=0.02)
99
+ # Frozen LSH-style projection: gradient never reaches it through the
100
+ # .long() hash cast, so a fixed random projection is correct (see v1).
101
+ self.proj.weight.requires_grad_(False)
102
+
103
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
104
+ return self.proj(x)
105
+
106
+
107
+ class MultiHeadHashLookup(nn.Module):
108
+ def __init__(self, num_heads: int, table_size: int,
109
+ compress_dim: int, out_dim: int, max_ngram: int = 3):
110
+ super().__init__()
111
+ self.num_heads = num_heads
112
+ self.table_size = table_size
113
+ self.max_ngram = max_ngram
114
+ self.out_dim = out_dim
115
+
116
+ self.tables = nn.ModuleList([
117
+ nn.Embedding(table_size, out_dim) for _ in range(num_heads)
118
+ ])
119
+ for t in self.tables:
120
+ nn.init.normal_(t.weight, std=0.01)
121
+
122
+ for n in range(1, max_ngram + 1):
123
+ for k in range(n):
124
+ proj = torch.randn(num_heads, compress_dim)
125
+ proj = proj / (proj.norm(dim=1, keepdim=True) + 1e-8)
126
+ self.register_buffer(f"hash_proj_n{n}_p{k}", proj)
127
+
128
+ def forward(self, compressed: torch.Tensor) -> torch.Tensor:
129
+ B, S, _ = compressed.shape
130
+ device = compressed.device
131
+ out = torch.zeros(B, S, self.out_dim, device=device, dtype=compressed.dtype)
132
+ norm = torch.zeros(S, device=device)
133
+
134
+ for n in range(1, self.max_ngram + 1):
135
+ if S < n:
136
+ continue
137
+ valid_len = S - n + 1
138
+ start = n - 1
139
+
140
+ h = torch.zeros(B, valid_len, self.num_heads, device=device)
141
+ for k in range(n):
142
+ proj = getattr(self, f"hash_proj_n{n}_p{k}")
143
+ h = h + torch.matmul(compressed[:, k:k + valid_len, :].float(), proj.t())
144
+
145
+ idx = h.abs().long() % self.table_size
146
+
147
+ for head_idx, table in enumerate(self.tables):
148
+ out[:, start:, :] = out[:, start:, :] + table(idx[:, :, head_idx])
149
+
150
+ norm[start:] += self.num_heads
151
+
152
+ return (out / norm.view(1, -1, 1).clamp(min=1)).to(compressed.dtype)
153
+
154
+
155
+ class DERFContextGate(nn.Module):
156
+ def __init__(self, obs_size: int, init_bias: float = -4.0):
157
+ super().__init__()
158
+ self.proj = nn.Linear(obs_size * 2, obs_size)
159
+ self.alpha = nn.Parameter(torch.ones(obs_size))
160
+ self.bias = nn.Parameter(torch.full((obs_size,), init_bias))
161
+ self.gamma = nn.Parameter(torch.ones(obs_size))
162
+
163
+ def forward(self, retrieved: torch.Tensor, x: torch.Tensor) -> torch.Tensor:
164
+ logits = self.proj(torch.cat([retrieved, x], dim=-1))
165
+ gate = self.gamma * ((torch.erf(self.alpha * logits + self.bias) + 1.0) / 2.0)
166
+ return retrieved * gate
167
+
168
+
169
+ class EngramModule(nn.Module):
170
+ def __init__(self, cfg: SpikeWhaleConfig):
171
+ super().__init__()
172
+ self.compressor = TokenCompressor(cfg.hidden_size, cfg.engram_compress_dim)
173
+ self.lookup = MultiHeadHashLookup(
174
+ cfg.engram_num_heads, cfg.engram_table_size,
175
+ cfg.engram_compress_dim, cfg.hidden_size, cfg.engram_max_ngram,
176
+ )
177
+ self.gate = DERFContextGate(cfg.hidden_size, cfg.engram_gate_init_bias)
178
+
179
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
180
+ compressed = self.compressor(x.detach())
181
+ retrieved = self.lookup(compressed)
182
+ return self.gate(retrieved, x)
183
+
184
+
185
+ # ---------------------------------------------------------------------------
186
+ # Hyper-Connections
187
+ # ---------------------------------------------------------------------------
188
+
189
+ class HyperConnectionLayer(nn.Module):
190
+ """Simplified HC: softmax pre-mix / post-distribute over hc_mult streams.
191
+ Asymmetric init (v1 bugfix) so streams diverge and gradients flow."""
192
+ def __init__(self, hidden_size: int, hc_mult: int,
193
+ sinkhorn_iters: int = 20, eps: float = 1e-6):
194
+ super().__init__()
195
+ self.hc_mult = hc_mult
196
+ self.pre_weight = nn.Parameter(
197
+ torch.linspace(0.5, -0.5, hc_mult) / max(hc_mult, 1)
198
+ )
199
+ self.post_weight = nn.Parameter(
200
+ torch.linspace(-0.5, 0.5, hc_mult) / max(hc_mult, 1)
201
+ )
202
+
203
+ def pre_op(self, copies: torch.Tensor) -> torch.Tensor:
204
+ w = F.softmax(self.pre_weight, dim=0)
205
+ return (copies * w.view(1, -1, 1, 1)).sum(dim=1)
206
+
207
+ def post_op(self, copies: torch.Tensor, delta: torch.Tensor) -> torch.Tensor:
208
+ w = F.softmax(self.post_weight, dim=0)
209
+ return copies + delta.unsqueeze(1) * w.view(1, -1, 1, 1)
210
+
211
+
212
+ class HCOutputMix(nn.Module):
213
+ """
214
+ NEW (v2): learned combination of the hc_mult streams at the model output,
215
+ replacing the v1 mean(dim=1). Mean forces the streams toward redundancy at
216
+ exactly the point where you want them specialized. Initialized uniform so
217
+ it starts identical to mean() -- a strict generalization, zero risk.
218
+ """
219
+ def __init__(self, hc_mult: int):
220
+ super().__init__()
221
+ self.weight = nn.Parameter(torch.zeros(hc_mult)) # softmax(0)=uniform=mean
222
+
223
+ def forward(self, copies: torch.Tensor) -> torch.Tensor:
224
+ w = F.softmax(self.weight, dim=0)
225
+ return (copies * w.view(1, -1, 1, 1)).sum(dim=1)
226
+
227
+
228
+ # ---------------------------------------------------------------------------
229
+ # MLA + (DERF) + XSA Attention, now with QK-Norm
230
+ # ---------------------------------------------------------------------------
231
+
232
+ class MLADerfXSAAttention(nn.Module):
233
+ """
234
+ v2 additions:
235
+ - QK-Norm (cfg.use_qk_norm, default True): per-head RMSNorm applied to Q
236
+ and K BEFORE the rope/nope split. Bounds attention logits, the standard
237
+ modern stability fix; composes cleanly with SDPA and partial RoPE.
238
+
239
+ XSA NOTE (unchanged mechanics, important caveat): with num_kv_heads == 1
240
+ (MQA) every query head shares the same value vector, so the self-projection
241
+ subtraction removes the SAME rank-1 value subspace from all heads -- much
242
+ more aggressive than per-head XSA. Ablate use_xsa on/off at 50M before
243
+ locking the base config.
244
+ """
245
+
246
+ def __init__(self, cfg: SpikeWhaleConfig):
247
+ super().__init__()
248
+ self.num_heads = cfg.num_attention_heads
249
+ self.num_kv_heads = cfg.num_key_value_heads
250
+ self.head_dim = cfg.head_dim
251
+ self.qk_rope_head_dim = cfg.qk_rope_head_dim
252
+ self.nope_head_dim = cfg.nope_head_dim
253
+ self.hidden_size = cfg.hidden_size
254
+ self.use_derf = cfg.use_derf
255
+ # Jet-Long dynamic bifocal RoPE (arXiv:2607.07740), inference-time only.
256
+ self.use_jetlong = getattr(cfg, 'use_jetlong', False)
257
+ self.jetlong_w_pretrained = int(getattr(cfg, 'jetlong_w_pretrained', 4096))
258
+ self.jetlong_w0 = int(getattr(cfg, 'jetlong_w0', 2048))
259
+ self.use_xsa = cfg.use_xsa
260
+ self.dropout_p = cfg.attention_dropout
261
+ self.kv_groups = self.num_heads // self.num_kv_heads
262
+ self.use_qk_norm = getattr(cfg, "use_qk_norm", True)
263
+
264
+ self.q_a_proj = nn.Linear(cfg.hidden_size, cfg.q_lora_rank, bias=False)
265
+ self.q_a_norm = RMSNorm(cfg.q_lora_rank, cfg.rms_norm_eps)
266
+ self.q_b_proj = nn.Linear(cfg.q_lora_rank, self.num_heads * self.head_dim, bias=False)
267
+
268
+ self.k_proj = nn.Linear(cfg.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
269
+ self.v_proj = nn.Linear(cfg.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
270
+
271
+ self.o_a_proj = nn.Linear(self.num_heads * self.head_dim, cfg.o_lora_rank, bias=False)
272
+ self.o_b_proj = nn.Linear(cfg.o_lora_rank, cfg.hidden_size, bias=False)
273
+
274
+ # QK-Norm: one RMSNorm over head_dim, shared across heads (Gemma-2 style).
275
+ if self.use_qk_norm:
276
+ self.q_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps)
277
+ self.k_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps)
278
+
279
+ self.rope = RotaryEmbedding(
280
+ self.qk_rope_head_dim,
281
+ max_positions=cfg.max_position_embeddings,
282
+ theta=cfg.rope_theta,
283
+ )
284
+
285
+ if self.use_derf:
286
+ self.derf_alpha = nn.Parameter(torch.ones(self.num_heads))
287
+ self.derf_bias = nn.Parameter(torch.zeros(self.num_heads))
288
+ self.derf_gamma = nn.Parameter(torch.ones(self.num_heads))
289
+
290
+ for m in (self.q_a_proj, self.q_b_proj, self.k_proj,
291
+ self.v_proj, self.o_a_proj, self.o_b_proj):
292
+ nn.init.normal_(m.weight, std=cfg.initializer_range)
293
+
294
+ def forward(
295
+ self,
296
+ x: torch.Tensor,
297
+ position_ids: torch.Tensor,
298
+ attention_mask: Optional[torch.Tensor] = None,
299
+ past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
300
+ use_cache: bool = False,
301
+ ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
302
+ B, S, _ = x.shape
303
+
304
+ q = self.q_a_norm(self.q_a_proj(x))
305
+ q = self.q_b_proj(q).view(B, S, self.num_heads, self.head_dim).transpose(1, 2)
306
+
307
+ k = self.k_proj(x).view(B, S, self.num_kv_heads, self.head_dim).transpose(1, 2)
308
+ v = self.v_proj(x).view(B, S, self.num_kv_heads, self.head_dim).transpose(1, 2)
309
+
310
+ # QK-Norm before RoPE (v2). Cache stores the NORMALIZED k so prefill and
311
+ # incremental decode agree.
312
+ if self.use_qk_norm:
313
+ q = self.q_norm(q)
314
+ k = self.k_norm(k)
315
+
316
+ q_nope = q[..., :self.nope_head_dim]
317
+ q_rope = q[..., self.nope_head_dim:]
318
+ k_nope = k[..., :self.nope_head_dim]
319
+ k_rope = k[..., self.nope_head_dim:]
320
+
321
+ q_rope = self.rope(q_rope, position_ids)
322
+ k_rope = self.rope(k_rope, position_ids)
323
+
324
+ q = torch.cat([q_nope, q_rope], dim=-1)
325
+ k = torch.cat([k_nope, k_rope], dim=-1)
326
+
327
+ if past_key_value is not None:
328
+ k = torch.cat([past_key_value[0], k], dim=2)
329
+ v = torch.cat([past_key_value[1], v], dim=2)
330
+ present = (k, v) if use_cache else None
331
+ N = k.shape[2]
332
+
333
+ if self.kv_groups > 1:
334
+ k = k.unsqueeze(2).expand(-1, -1, self.kv_groups, -1, -1).reshape(
335
+ B, self.num_heads, N, self.head_dim)
336
+ v = v.unsqueeze(2).expand(-1, -1, self.kv_groups, -1, -1).reshape(
337
+ B, self.num_heads, N, self.head_dim)
338
+
339
+ # --- Jet-Long dynamic bifocal RoPE (arXiv:2607.07740) ---
340
+ # No-op within the native window (G==1); position-aliased remote
341
+ # window beyond it. Correction rotation composes on the already-RoPE'd
342
+ # cache, so the KV cache (and TriAttention compression) is untouched.
343
+ y = None
344
+ if getattr(self, 'use_jetlong', False) and not self.use_derf:
345
+ _G = max(1, math.ceil(N / self.jetlong_w_pretrained))
346
+ if _G > 1:
347
+ y = self._jetlong_attend(q, k, v, position_ids, S, N, _G)
348
+ if y is not None:
349
+ pass
350
+ elif self.use_derf:
351
+ scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
352
+
353
+ if attention_mask is None and past_key_value is None:
354
+ is_masked = torch.triu(
355
+ torch.ones(S, N, dtype=torch.bool, device=scores.device),
356
+ diagonal=N - S + 1,
357
+ ).unsqueeze(0).unsqueeze(0)
358
+ else:
359
+ is_masked = (attention_mask < -1.0) if attention_mask is not None \
360
+ else torch.zeros_like(scores, dtype=torch.bool)
361
+
362
+ safe_scores = scores.masked_fill(is_masked, -10000.0)
363
+
364
+ a = self.derf_alpha.view(1, -1, 1, 1)
365
+ b = self.derf_bias.view(1, -1, 1, 1)
366
+ g = self.derf_gamma.view(1, -1, 1, 1)
367
+
368
+ attn_weights = g * torch.erf(a * safe_scores + b)
369
+ attn_weights = (attn_weights + g) / 2.0
370
+ attn_weights = attn_weights.masked_fill(is_masked, 0.0)
371
+ attn_weights = attn_weights / (attn_weights.sum(dim=-1, keepdim=True) + 1e-8)
372
+
373
+ if self.dropout_p > 0 and self.training:
374
+ attn_weights = F.dropout(attn_weights, p=self.dropout_p)
375
+
376
+ y = torch.matmul(attn_weights, v)
377
+ else:
378
+ q = q.contiguous()
379
+ k = k.contiguous()
380
+ v = v.contiguous()
381
+ drop = self.dropout_p if self.training else 0.0
382
+ if past_key_value is None and attention_mask is None:
383
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True, dropout_p=drop)
384
+ else:
385
+ if attention_mask is not None:
386
+ is_masked = (attention_mask < -1.0)
387
+ else:
388
+ is_masked = torch.triu(
389
+ torch.ones(S, N, dtype=torch.bool, device=q.device),
390
+ diagonal=N - S + 1,
391
+ ).unsqueeze(0).unsqueeze(0)
392
+ y = F.scaled_dot_product_attention(
393
+ q, k, v, attn_mask=~is_masked, dropout_p=drop)
394
+
395
+ if self.use_xsa:
396
+ past_len = N - S
397
+ v_self = v[:, :, past_len:past_len + S, :]
398
+ vn = v_self / (v_self.norm(dim=-1, keepdim=True) + 1e-8)
399
+ projection = (y * vn).sum(dim=-1, keepdim=True) * vn
400
+ y = y - projection
401
+
402
+ y = y.transpose(1, 2).contiguous().view(B, S, self.num_heads * self.head_dim)
403
+ y = self.o_b_proj(self.o_a_proj(y))
404
+ return y, present
405
+
406
+ def _jetlong_attend(self, q, k, v, position_ids, S, N, G):
407
+ """Bifocal softmax attention (Jet-Long, arXiv:2607.07740).
408
+
409
+ q, k arrive already RoPE-rotated at their true absolute positions. We add
410
+ a *correction rotation* delta = floor(p/G) - p to realize the remote,
411
+ position-aliased view (RoPE composes additively, so this works directly on
412
+ the rotated -- and possibly TriAttention-compressed -- cache without
413
+ rewriting it), then merge the local and remote windows by query-key
414
+ distance. Handles prefill (S==N) and decode (S==1, N==cache+1) uniformly.
415
+ Only reached when use_jetlong and G>1, so behaviour within the native
416
+ window is byte-identical to the base model."""
417
+ import math as _m
418
+ B = q.shape[0]
419
+ nd = self.nope_head_dim
420
+ dev = q.device
421
+ if position_ids is not None:
422
+ q_pos = position_ids.reshape(B, S).to(dev).long()
423
+ else:
424
+ q_pos = torch.arange(N - S, N, device=dev).view(1, S).expand(B, S)
425
+ _kp = getattr(self, '_jl_key_pos', None)
426
+ if _kp is not None and _kp.numel() == N:
427
+ k_pos = _kp.to(dev).long().view(1, N).expand(B, N)
428
+ else:
429
+ k_pos = torch.arange(N, device=dev).view(1, N).expand(B, N)
430
+ dq = torch.div(q_pos, G, rounding_mode='floor') - q_pos # [B,S]
431
+ dk = torch.div(k_pos, G, rounding_mode='floor') - k_pos # [B,N]
432
+ inv_freq = self.rope.inv_freq.to(dev).float() # [rd/2]
433
+
434
+ def rot_by(x, delta):
435
+ d = x.shape[-1] // 2
436
+ ang = delta.float().unsqueeze(1).unsqueeze(-1) * inv_freq # [B,1,T,d]
437
+ cos, sin = ang.cos(), ang.sin()
438
+ x1, x2 = x[..., :d], x[..., d:]
439
+ return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
440
+
441
+ q_nope, q_rope = q[..., :nd], q[..., nd:]
442
+ k_nope, k_rope = k[..., :nd], k[..., nd:]
443
+ q_rope_rem = rot_by(q_rope, dq)
444
+ k_rope_rem = rot_by(k_rope, dk)
445
+
446
+ scale = 1.0 / _m.sqrt(self.head_dim)
447
+ s_nope = torch.matmul(q_nope, k_nope.transpose(-2, -1))
448
+ s_loc = (s_nope + torch.matmul(q_rope, k_rope.transpose(-2, -1))) * scale
449
+ s_rem = (s_nope + torch.matmul(q_rope_rem, k_rope_rem.transpose(-2, -1))) * scale
450
+
451
+ dist = q_pos.view(B, 1, S, 1) - k_pos.view(B, 1, 1, N) # i - j
452
+ local_mask = (dist <= self.jetlong_w0) & (dist >= 0)
453
+ causal = dist >= 0
454
+ scores = torch.where(local_mask, s_loc, s_rem)
455
+ scores = scores.masked_fill(~causal, float('-inf'))
456
+ attn = torch.softmax(scores.float(), dim=-1).to(v.dtype)
457
+ return torch.matmul(attn, v)
458
+
459
+
460
+ # ---------------------------------------------------------------------------
461
+ # MoE FFN -- v2: sort-based dispatch + fused shared expert
462
+ # ---------------------------------------------------------------------------
463
+
464
+ class ExpertFFN(nn.Module):
465
+ """Single SwiGLU expert."""
466
+ def __init__(self, hidden_size: int, intermediate_size: int):
467
+ super().__init__()
468
+ self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
469
+ self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
470
+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
471
+
472
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
473
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
474
+
475
+
476
+ def sqrtsoftplus(x: torch.Tensor) -> torch.Tensor:
477
+ return torch.sqrt(F.softplus(x) + 1e-8)
478
+
479
+
480
+ class SparseMoEFFN(nn.Module):
481
+ """
482
+ v2 changes:
483
+ - FUSED shared expert: one ExpertFFN with width n_shared * intermediate,
484
+ scaled by 1/n_shared on output -- equivalent to v1's averaged Python
485
+ loop, one fused matmul set. (state-dict key changes: shared_expert.*)
486
+ - SORT-BASED dispatch for routed experts: flatten (token, slot) pairs,
487
+ argsort by expert id, run each expert on ONE contiguous slice, weighted
488
+ index_add_ back. No boolean masks, no nonzero(), no per-expert scatter.
489
+ Routing logic (hash routing, sqrtsoftplus, aux loss) is unchanged.
490
+ """
491
+ def __init__(self, cfg: SpikeWhaleConfig, layer_idx: int = 0):
492
+ super().__init__()
493
+ self.n_routed_experts = cfg.n_routed_experts
494
+ self.n_shared_experts = cfg.n_shared_experts
495
+ self.num_experts_per_tok = cfg.num_experts_per_tok
496
+ self.norm_topk_prob = cfg.norm_topk_prob
497
+ self.scoring_func = cfg.scoring_func
498
+ self.routed_scaling_factor = cfg.routed_scaling_factor
499
+ self.use_hash_routing = layer_idx < cfg.num_hash_layers
500
+ self.aux_loss_coef = cfg.moe_aux_loss_coef
501
+
502
+ self.router = nn.Linear(cfg.hidden_size, cfg.n_routed_experts, bias=False)
503
+ self.experts = nn.ModuleList([
504
+ ExpertFFN(cfg.hidden_size, cfg.moe_intermediate_size)
505
+ for _ in range(cfg.n_routed_experts)
506
+ ])
507
+ # Fused shared expert (v2)
508
+ self.shared_expert = (
509
+ ExpertFFN(cfg.hidden_size,
510
+ cfg.moe_intermediate_size * cfg.n_shared_experts)
511
+ if cfg.n_shared_experts > 0 else None
512
+ )
513
+
514
+ self._last_aux_loss: Optional[torch.Tensor] = None
515
+
516
+ def forward(self, x: torch.Tensor,
517
+ position_ids: Optional[torch.Tensor] = None) -> torch.Tensor:
518
+ B, S, H = x.shape
519
+ x_flat = x.view(B * S, H)
520
+ T = B * S
521
+ K = self.num_experts_per_tok
522
+
523
+ # Shared expert: always active, single fused pass.
524
+ if self.shared_expert is not None:
525
+ shared_out = self.shared_expert(x_flat)
526
+ if self.n_shared_experts > 1:
527
+ shared_out = shared_out / self.n_shared_experts
528
+ else:
529
+ shared_out = None
530
+
531
+ # ---- Routing (unchanged logic) ----
532
+ if self.use_hash_routing:
533
+ if position_ids is not None:
534
+ base = (position_ids.reshape(T, 1) % self.n_routed_experts).long()
535
+ else:
536
+ base = (torch.arange(T, device=x.device) % self.n_routed_experts).unsqueeze(1)
537
+ offsets = torch.arange(K, device=x.device)
538
+ top_k_indices = (base + offsets.unsqueeze(0)) % self.n_routed_experts # [T, K]
539
+ top_k_weights = torch.full((T, K), 1.0 / K, device=x.device, dtype=x_flat.dtype)
540
+ self._last_aux_loss = None
541
+ else:
542
+ router_logits = self.router(x_flat)
543
+ if self.scoring_func == "sqrtsoftplus":
544
+ routing_scores = sqrtsoftplus(router_logits)
545
+ else:
546
+ routing_scores = F.softmax(router_logits, dim=-1)
547
+
548
+ top_k_scores, top_k_indices = torch.topk(routing_scores, K, dim=-1)
549
+ if self.norm_topk_prob:
550
+ top_k_weights = top_k_scores / (top_k_scores.sum(dim=-1, keepdim=True) + 1e-8)
551
+ else:
552
+ top_k_weights = top_k_scores
553
+ top_k_weights = top_k_weights * self.routed_scaling_factor
554
+
555
+ softmax_probs = F.softmax(router_logits, dim=-1)
556
+ expert_mask = torch.zeros_like(softmax_probs)
557
+ expert_mask.scatter_(1, top_k_indices, 1.0)
558
+ f_e = expert_mask.mean(0)
559
+ p_e = softmax_probs.mean(0)
560
+ self._last_aux_loss = self.n_routed_experts * (f_e * p_e).sum() * self.aux_loss_coef
561
+
562
+ # ---- Sort-based dispatch (v2) ----
563
+ # Flatten the (token, slot) assignment: T*K rows total.
564
+ flat_expert = top_k_indices.reshape(-1) # [T*K]
565
+ flat_weight = top_k_weights.reshape(-1, 1) # [T*K, 1]
566
+ flat_token = torch.arange(T, device=x.device).repeat_interleave(K) # [T*K]
567
+
568
+ order = torch.argsort(flat_expert, stable=True) # group by expert
569
+ sorted_expert = flat_expert[order]
570
+ sorted_token = flat_token[order]
571
+ sorted_weight = flat_weight[order]
572
+
573
+ counts = torch.bincount(sorted_expert, minlength=self.n_routed_experts)
574
+ # boundaries per expert in the sorted order (CPU sync once per forward;
575
+ # unavoidable without grouped-GEMM, still vastly cheaper than v1's
576
+ # per-expert nonzero/masking)
577
+ counts_list = counts.tolist()
578
+
579
+ gathered = x_flat[sorted_token] # [T*K, H]
580
+ out_flat = torch.zeros_like(x_flat)
581
+
582
+ start = 0
583
+ for expert_idx, cnt in enumerate(counts_list):
584
+ if cnt == 0:
585
+ continue
586
+ end = start + cnt
587
+ seg = gathered[start:end]
588
+ seg_out = self.experts[expert_idx](seg) * sorted_weight[start:end]
589
+ out_flat.index_add_(0, sorted_token[start:end], seg_out.to(out_flat.dtype))
590
+ start = end
591
+
592
+ if shared_out is not None:
593
+ out_flat = out_flat + shared_out
594
+ return out_flat.view(B, S, H)
595
+
596
+ def get_aux_loss(self) -> Optional[torch.Tensor]:
597
+ return self._last_aux_loss
598
+
599
+
600
+ class DenseFFN(nn.Module):
601
+ def __init__(self, cfg: SpikeWhaleConfig):
602
+ super().__init__()
603
+ self.gate_proj = nn.Linear(cfg.hidden_size, cfg.moe_intermediate_size, bias=False)
604
+ self.up_proj = nn.Linear(cfg.hidden_size, cfg.moe_intermediate_size, bias=False)
605
+ self.down_proj = nn.Linear(cfg.moe_intermediate_size, cfg.hidden_size, bias=False)
606
+
607
+ def forward(self, x: torch.Tensor,
608
+ position_ids: Optional[torch.Tensor] = None) -> torch.Tensor:
609
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
610
+
611
+ def get_aux_loss(self) -> Optional[torch.Tensor]:
612
+ return None
613
+
614
+
615
+ # ---------------------------------------------------------------------------
616
+ # Transformer block
617
+ # ---------------------------------------------------------------------------
618
+
619
+ class TransformerBlock(nn.Module):
620
+ def __init__(self, cfg: SpikeWhaleConfig, layer_idx: int):
621
+ super().__init__()
622
+ self.use_hc = cfg.use_hyper_connections
623
+ self.hidden_dropout = cfg.hidden_dropout
624
+ self.use_value_embed = getattr(cfg, "use_value_embed", False)
625
+
626
+ self.attn_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
627
+ self.attn = MLADerfXSAAttention(cfg)
628
+ self.ffn_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
629
+
630
+ if cfg.use_moe and layer_idx in cfg.moe_layers:
631
+ self.ffn = SparseMoEFFN(cfg, layer_idx)
632
+ self.is_moe = True
633
+ else:
634
+ self.ffn = DenseFFN(cfg)
635
+ self.is_moe = False
636
+
637
+ if self.use_hc:
638
+ self.hc_attn = HyperConnectionLayer(cfg.hidden_size, cfg.hc_mult,
639
+ cfg.hc_sinkhorn_iters, cfg.hc_eps)
640
+ self.hc_ffn = HyperConnectionLayer(cfg.hidden_size, cfg.hc_mult,
641
+ cfg.hc_sinkhorn_iters, cfg.hc_eps)
642
+
643
+ # NEW (v2, opt-in): value-embedding residual. Zero-init gate -> exact
644
+ # no-op at init; learns to mix raw token-embedding signal into each
645
+ # block's input (nanoGPT-speedrun "value embedding"/U-net skip family;
646
+ # consistent wins at the 50-500M scale).
647
+ if self.use_value_embed:
648
+ self.ve_gate = nn.Parameter(torch.zeros(1))
649
+
650
+ def forward(
651
+ self,
652
+ x: torch.Tensor, # [B, hc_mult, S, H] if HC else [B, S, H]
653
+ position_ids: torch.Tensor,
654
+ attention_mask: Optional[torch.Tensor] = None,
655
+ past_key_value: Optional[Tuple] = None,
656
+ use_cache: bool = False,
657
+ token_embed: Optional[torch.Tensor] = None, # [B, S, H] (value-embed)
658
+ ) -> Tuple[torch.Tensor, Optional[Tuple], Optional[torch.Tensor]]:
659
+
660
+ # --- Attention sub-layer ---
661
+ if self.use_hc:
662
+ h = self.hc_attn.pre_op(x)
663
+ else:
664
+ h = x
665
+
666
+ if self.use_value_embed and token_embed is not None:
667
+ h = h + torch.tanh(self.ve_gate) * token_embed
668
+
669
+ attn_out, present = self.attn(
670
+ self.attn_norm(h), position_ids, attention_mask, past_key_value, use_cache
671
+ )
672
+ attn_out = F.dropout(attn_out, p=self.hidden_dropout, training=self.training)
673
+
674
+ if self.use_hc:
675
+ x = self.hc_attn.post_op(x, attn_out)
676
+ h = self.hc_ffn.pre_op(x)
677
+ else:
678
+ h = h + attn_out
679
+
680
+ # --- FFN sub-layer ---
681
+ ffn_out = self.ffn(self.ffn_norm(h), position_ids)
682
+ ffn_out = F.dropout(ffn_out, p=self.hidden_dropout, training=self.training)
683
+
684
+ if self.use_hc:
685
+ x = self.hc_ffn.post_op(x, ffn_out)
686
+ else:
687
+ x = h + ffn_out
688
+
689
+ return x, present, self.ffn.get_aux_loss()
690
+
691
+
692
+ # ---------------------------------------------------------------------------
693
+ # HRM refinement (unchanged)
694
+ # ---------------------------------------------------------------------------
695
+
696
+ class HRMRefinementBlock(nn.Module):
697
+ def __init__(self, hidden_size: int, refine_dim: int, steps: int, eps: float = 1e-6):
698
+ super().__init__()
699
+ self.steps = steps
700
+ self.norm = RMSNorm(hidden_size, eps)
701
+ self.down = nn.Linear(hidden_size * 2, refine_dim, bias=False)
702
+ self.up = nn.Linear(refine_dim, hidden_size, bias=False)
703
+ self.gate = nn.Parameter(torch.zeros(steps))
704
+ nn.init.normal_(self.down.weight, std=0.02)
705
+ nn.init.zeros_(self.up.weight)
706
+
707
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
708
+ anchor = x
709
+ h = x
710
+ for t in range(self.steps):
711
+ inp = torch.cat([self.norm(h), anchor], dim=-1)
712
+ update = self.up(F.silu(self.down(inp)))
713
+ h = h + torch.tanh(self.gate[t]) * update
714
+ return h
715
+
716
+
717
+ # ---------------------------------------------------------------------------
718
+ # Full model
719
+ # ---------------------------------------------------------------------------
720
+
721
+ class SpikeWhaleModel(nn.Module):
722
+ """Decoder stack without LM head."""
723
+
724
+ def __init__(self, cfg: SpikeWhaleConfig):
725
+ super().__init__()
726
+ self.cfg = cfg
727
+ self.embed_tokens = nn.Embedding(cfg.vocab_size, cfg.hidden_size)
728
+ nn.init.normal_(self.embed_tokens.weight, std=cfg.initializer_range)
729
+
730
+ self.engram = EngramModule(cfg) if cfg.use_engram else None
731
+ self.layers = nn.ModuleList([
732
+ TransformerBlock(cfg, layer_idx=i)
733
+ for i in range(cfg.num_hidden_layers)
734
+ ])
735
+ self.norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
736
+ self.hc_out_mix = (
737
+ HCOutputMix(cfg.hc_mult) if cfg.use_hyper_connections else None
738
+ )
739
+ self.hrm_refine = (
740
+ HRMRefinementBlock(cfg.hidden_size, cfg.hrm_refine_dim, cfg.hrm_refine_steps,
741
+ cfg.rms_norm_eps)
742
+ if getattr(cfg, "use_hrm_refine", False) else None
743
+ )
744
+ self.use_value_embed = getattr(cfg, "use_value_embed", False)
745
+ self.gradient_checkpointing = False
746
+
747
+ def forward(
748
+ self,
749
+ input_ids: torch.Tensor,
750
+ attention_mask: Optional[torch.Tensor] = None,
751
+ position_ids: Optional[torch.Tensor] = None,
752
+ past_key_values: Optional[List[Tuple]] = None,
753
+ use_cache: bool = False,
754
+ engram_context_ids: Optional[torch.Tensor] = None,
755
+ ) -> Tuple[torch.Tensor, Optional[List[Tuple]], torch.Tensor]:
756
+ B, S = input_ids.shape
757
+ device = input_ids.device
758
+
759
+ if position_ids is None:
760
+ past_len = past_key_values[0][0].shape[2] if past_key_values else 0
761
+ position_ids = torch.arange(
762
+ past_len, past_len + S, device=device
763
+ ).unsqueeze(0).expand(B, -1)
764
+
765
+ x = self.embed_tokens(input_ids)
766
+ token_embed = x if self.use_value_embed else None
767
+
768
+ if self.engram is not None:
769
+ if engram_context_ids is not None and engram_context_ids.numel() > 0:
770
+ # Cached decode: the n-gram hashes need the (max_ngram - 1)
771
+ # tokens BEFORE this window, which a KV cache does not carry.
772
+ # Prepend their embeddings, run the engram over the joined
773
+ # window, and keep only this window's positions.
774
+ ctx = self.embed_tokens(engram_context_ids)
775
+ n_ctx = ctx.shape[1]
776
+ x = x + self.engram(torch.cat([ctx, x], dim=1))[:, n_ctx:, :]
777
+ else:
778
+ x = x + self.engram(x)
779
+
780
+ if self.cfg.use_hyper_connections:
781
+ x = x.unsqueeze(1).expand(-1, self.cfg.hc_mult, -1, -1).clone()
782
+
783
+ present_key_values = [] if use_cache else None
784
+ total_aux_loss = torch.tensor(0.0, device=device)
785
+
786
+ # Gradient checkpointing is incompatible with use_cache (the cache from
787
+ # the discarded forward would be silently wrong on recompute).
788
+ assert not (self.gradient_checkpointing and self.training and use_cache), \
789
+ "use_cache=True is not supported with gradient checkpointing"
790
+
791
+ for layer_idx, layer in enumerate(self.layers):
792
+ pkv = past_key_values[layer_idx] if past_key_values else None
793
+
794
+ if self.gradient_checkpointing and self.training:
795
+ x, present, aux_loss = gradient_checkpoint(
796
+ layer, x, position_ids, attention_mask, None, False, token_embed,
797
+ use_reentrant=False,
798
+ )
799
+ else:
800
+ x, present, aux_loss = layer(
801
+ x, position_ids, attention_mask, pkv, use_cache, token_embed)
802
+
803
+ if use_cache:
804
+ present_key_values.append(present)
805
+ if aux_loss is not None:
806
+ total_aux_loss = total_aux_loss + aux_loss
807
+
808
+ if self.cfg.use_hyper_connections:
809
+ x = self.hc_out_mix(x) # v2: learned mix (init == mean)
810
+
811
+ if self.hrm_refine is not None:
812
+ x = self.hrm_refine(x)
813
+
814
+ x = self.norm(x)
815
+ return x, present_key_values, total_aux_loss
816
+
817
+
818
+ class MTPHead(nn.Module):
819
+ """
820
+ v2 MTP head: small zero-init H x H projection feeding the SHARED lm_head.
821
+ Cost per head: H^2 params (e.g. 1M at H=1024) instead of H*V (e.g. 50M+).
822
+ Zero-init means at step 0 the head predicts exactly what lm_head predicts
823
+ for the residual path = 0, i.e. uniform-ish gradient pressure; the residual
824
+ form (x + proj(x)) keeps it anchored to the trunk representation.
825
+ """
826
+ def __init__(self, hidden_size: int):
827
+ super().__init__()
828
+ self.proj = nn.Linear(hidden_size, hidden_size, bias=False)
829
+ nn.init.zeros_(self.proj.weight)
830
+
831
+ def forward(self, hidden: torch.Tensor) -> torch.Tensor:
832
+ return hidden + self.proj(hidden)
833
+
834
+
835
+ class SpikeWhaleLM(PreTrainedModel):
836
+ """
837
+ v2 loss = CE + zloss_coef * z-loss
838
+ + mtp_loss_weight * mean(MTP CE)
839
+ + MoE aux loss
840
+ """
841
+ config_class = SpikeWhaleConfig
842
+ base_model_prefix = "model"
843
+ supports_gradient_checkpointing = True
844
+ _no_split_modules = ["TransformerBlock"]
845
+
846
+ def __init__(self, cfg: SpikeWhaleConfig):
847
+ super().__init__(cfg)
848
+ self.model = SpikeWhaleModel(cfg)
849
+ self.lm_head = nn.Linear(cfg.hidden_size, cfg.vocab_size, bias=False)
850
+ nn.init.normal_(self.lm_head.weight, std=cfg.initializer_range)
851
+
852
+ self.zloss_coef = getattr(cfg, "zloss_coef", 1e-4)
853
+ self.mtp_loss_weight = getattr(cfg, "mtp_loss_weight", 0.3)
854
+
855
+ # v2 MTP: H x H residual projections sharing lm_head (see MTPHead).
856
+ self.mtp_heads = nn.ModuleList([
857
+ MTPHead(cfg.hidden_size)
858
+ for _ in range(cfg.num_nextn_predict_layers)
859
+ ]) if cfg.num_nextn_predict_layers > 0 else None
860
+
861
+ self.post_init()
862
+
863
+ def get_input_embeddings(self):
864
+ return self.model.embed_tokens
865
+
866
+ def set_input_embeddings(self, value):
867
+ self.model.embed_tokens = value
868
+
869
+ def get_output_embeddings(self):
870
+ return self.lm_head
871
+
872
+ def set_output_embeddings(self, new_embeddings):
873
+ self.lm_head = new_embeddings
874
+
875
+ def tie_weights(self, **kwargs):
876
+ if self.config.tie_word_embeddings:
877
+ self.lm_head.weight = self.model.embed_tokens.weight
878
+
879
+ def save_pretrained(self, *args, **kwargs):
880
+ tied = (
881
+ self.config.tie_word_embeddings
882
+ and self.lm_head.weight.data_ptr() == self.model.embed_tokens.weight.data_ptr()
883
+ )
884
+ if tied:
885
+ self.lm_head.weight = nn.Parameter(self.model.embed_tokens.weight.detach().clone())
886
+ try:
887
+ super().save_pretrained(*args, **kwargs)
888
+ finally:
889
+ if tied:
890
+ self.lm_head.weight = self.model.embed_tokens.weight
891
+
892
+ def _set_gradient_checkpointing(self, module, value=False):
893
+ if isinstance(module, SpikeWhaleModel):
894
+ module.gradient_checkpointing = value
895
+
896
+ def forward(
897
+ self,
898
+ input_ids: Optional[torch.Tensor] = None,
899
+ attention_mask: Optional[torch.Tensor] = None,
900
+ position_ids: Optional[torch.Tensor] = None,
901
+ past_key_values: Optional[List[Tuple]] = None,
902
+ labels: Optional[torch.Tensor] = None,
903
+ use_cache: bool = False,
904
+ engram_context_ids: Optional[torch.Tensor] = None,
905
+ **kwargs,
906
+ ) -> CausalLMOutputWithPast:
907
+ hidden, present_kvs, aux_loss = self.model(
908
+ input_ids=input_ids,
909
+ attention_mask=attention_mask,
910
+ position_ids=position_ids,
911
+ past_key_values=past_key_values,
912
+ use_cache=use_cache,
913
+ engram_context_ids=engram_context_ids,
914
+ )
915
+
916
+ logits = self.lm_head(hidden)
917
+ loss = None
918
+
919
+ if labels is not None:
920
+ shift_logits = logits[..., :-1, :].contiguous()
921
+ shift_labels = labels[..., 1:].contiguous()
922
+ flat_logits = shift_logits.view(-1, shift_logits.size(-1))
923
+ flat_labels = shift_labels.view(-1)
924
+ loss = F.cross_entropy(flat_logits, flat_labels, ignore_index=-100)
925
+
926
+ # z-loss (v2): penalize log^2 of the partition function on valid
927
+ # positions. Keeps logits from drifting; pairs well with Muon.
928
+ if self.zloss_coef > 0:
929
+ valid = flat_labels != -100
930
+ if valid.any():
931
+ log_z = torch.logsumexp(flat_logits[valid].float(), dim=-1)
932
+ loss = loss + self.zloss_coef * (log_z ** 2).mean()
933
+
934
+ # MTP (v2): residual H x H head -> shared lm_head, down-weighted.
935
+ if self.mtp_heads is not None and self.mtp_loss_weight > 0:
936
+ mtp_total = torch.tensor(0.0, device=loss.device)
937
+ n_active = 0
938
+ for k, head in enumerate(self.mtp_heads, start=1):
939
+ offset = k + 1
940
+ if hidden.size(1) > offset:
941
+ mtp_hidden = head(hidden[..., :-offset, :])
942
+ mtp_logits = self.lm_head(mtp_hidden)
943
+ mtp_labels = labels[..., offset:].contiguous()
944
+ mtp_total = mtp_total + F.cross_entropy(
945
+ mtp_logits.reshape(-1, mtp_logits.size(-1)),
946
+ mtp_labels.reshape(-1),
947
+ ignore_index=-100,
948
+ )
949
+ n_active += 1
950
+ if n_active > 0:
951
+ loss = loss + self.mtp_loss_weight * mtp_total / n_active
952
+
953
+ loss = loss + aux_loss
954
+
955
+ return CausalLMOutputWithPast(
956
+ loss=loss,
957
+ logits=logits,
958
+ past_key_values=present_kvs,
959
+ )
960
+
961
+ def count_parameters(self) -> int:
962
+ 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
+ }