Quazim0t0 commited on
Commit
5d079a9
·
verified ·
1 Parent(s): 2bffd3e

Upload folder using huggingface_hub

Browse files
Files changed (8) hide show
  1. README.md +50 -0
  2. config.json +63 -0
  3. config.py +152 -0
  4. model.safetensors +3 -0
  5. model_v2.py +882 -0
  6. spike_tokenizer.py +117 -0
  7. tokenizer.json +0 -0
  8. tokenizer_config.json +204 -0
README.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language: [en]
4
+ tags: [text-generation, small-models, mla, jepa, experimental]
5
+ pipeline_tag: text-generation
6
+ library_name: transformers
7
+ ---
8
+
9
+ # Byrne-86M-Base
10
+
11
+ The **base** model of the Byrne-86M pair (OPD v2). A ~86M-parameter, from-scratch `SpikeWhaleLM` decoder (Multi-head Latent Attention,
12
+ n-gram engram memory, hash-lookup layers, hyper-connections, HRM refinement, MTP) with a
13
+ custom ChatML-aware tokenizer. Trained with **Modal** credits during the **Small Models,
14
+ Big Adventures Hackathon**.
15
+
16
+ > **Related:** main model → [Byrne-86M](https://huggingface.co/Quazim0t0/Byrne-86M)
17
+
18
+ ## Usage
19
+
20
+ Custom architecture + tokenizer — load with `trust_remote_code=True` (modeling + tokenizer
21
+ code ship in this repo via `auto_map`):
22
+
23
+ ```python
24
+ from transformers import AutoModelForCausalLM, AutoTokenizer
25
+ tok = AutoTokenizer.from_pretrained("Quazim0t0/Byrne-86M-Base", trust_remote_code=True)
26
+ model = AutoModelForCausalLM.from_pretrained("Quazim0t0/Byrne-86M-Base", trust_remote_code=True)
27
+ ```
28
+
29
+ ## Evaluation
30
+
31
+ Zero-shot, lm-eval-harness-style scoring over full splits (`acc` = raw continuation
32
+ log-likelihood, `acc_norm` = byte-length-normalized).
33
+
34
+ | Task | acc | acc_norm |
35
+ |---|---|---|
36
+ | arc_easy | 0.3670 | 0.3468 |
37
+ | arc_challenge | 0.1894 | 0.2355 |
38
+ | hellaswag | 0.2815 | 0.2858 |
39
+ | winogrande | 0.5201 | — |
40
+ | piqa | 0.5756 | 0.5593 |
41
+ | openbookqa | 0.1460 | 0.2440 |
42
+ | boolq | 0.3865 | — |
43
+
44
+ **ArithMark-2.0** ([AxiomicLabs](https://huggingface.co/datasets/AxiomicLabs/ArithMark-2.0))
45
+ — official metric is **raw `acc`**: **0.3096**.
46
+
47
+ **Language modeling:** WikiText-2 byte_ppl (↓) **2.6839** · BLiMP (↑) **0.7033**.
48
+
49
+ > MC numbers come from a local harness validated to match the real EleutherAI lm-eval
50
+ > within ~1 pt; ArithMark uses AxiomicLabs' official raw-log-likelihood `acc`.
config.json ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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": 4096,
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
+ }
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:31a82f44283d8123537258d1dd047d545c230239ecb6fedc8569c2baa24011e0
3
+ size 387815772
model_v2.py ADDED
@@ -0,0 +1,882 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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())
80
+ self.register_buffer("sin_cache", freqs.sin())
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
+ self.use_xsa = cfg.use_xsa
256
+ self.dropout_p = cfg.attention_dropout
257
+ self.kv_groups = self.num_heads // self.num_kv_heads
258
+ self.use_qk_norm = getattr(cfg, "use_qk_norm", True)
259
+
260
+ self.q_a_proj = nn.Linear(cfg.hidden_size, cfg.q_lora_rank, bias=False)
261
+ self.q_a_norm = RMSNorm(cfg.q_lora_rank, cfg.rms_norm_eps)
262
+ self.q_b_proj = nn.Linear(cfg.q_lora_rank, self.num_heads * self.head_dim, bias=False)
263
+
264
+ self.k_proj = nn.Linear(cfg.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
265
+ self.v_proj = nn.Linear(cfg.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
266
+
267
+ self.o_a_proj = nn.Linear(self.num_heads * self.head_dim, cfg.o_lora_rank, bias=False)
268
+ self.o_b_proj = nn.Linear(cfg.o_lora_rank, cfg.hidden_size, bias=False)
269
+
270
+ # QK-Norm: one RMSNorm over head_dim, shared across heads (Gemma-2 style).
271
+ if self.use_qk_norm:
272
+ self.q_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps)
273
+ self.k_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps)
274
+
275
+ self.rope = RotaryEmbedding(
276
+ self.qk_rope_head_dim,
277
+ max_positions=cfg.max_position_embeddings,
278
+ theta=cfg.rope_theta,
279
+ )
280
+
281
+ if self.use_derf:
282
+ self.derf_alpha = nn.Parameter(torch.ones(self.num_heads))
283
+ self.derf_bias = nn.Parameter(torch.zeros(self.num_heads))
284
+ self.derf_gamma = nn.Parameter(torch.ones(self.num_heads))
285
+
286
+ for m in (self.q_a_proj, self.q_b_proj, self.k_proj,
287
+ self.v_proj, self.o_a_proj, self.o_b_proj):
288
+ nn.init.normal_(m.weight, std=cfg.initializer_range)
289
+
290
+ def forward(
291
+ self,
292
+ x: torch.Tensor,
293
+ position_ids: torch.Tensor,
294
+ attention_mask: Optional[torch.Tensor] = None,
295
+ past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
296
+ use_cache: bool = False,
297
+ ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
298
+ B, S, _ = x.shape
299
+
300
+ q = self.q_a_norm(self.q_a_proj(x))
301
+ q = self.q_b_proj(q).view(B, S, self.num_heads, self.head_dim).transpose(1, 2)
302
+
303
+ k = self.k_proj(x).view(B, S, self.num_kv_heads, self.head_dim).transpose(1, 2)
304
+ v = self.v_proj(x).view(B, S, self.num_kv_heads, self.head_dim).transpose(1, 2)
305
+
306
+ # QK-Norm before RoPE (v2). Cache stores the NORMALIZED k so prefill and
307
+ # incremental decode agree.
308
+ if self.use_qk_norm:
309
+ q = self.q_norm(q)
310
+ k = self.k_norm(k)
311
+
312
+ q_nope = q[..., :self.nope_head_dim]
313
+ q_rope = q[..., self.nope_head_dim:]
314
+ k_nope = k[..., :self.nope_head_dim]
315
+ k_rope = k[..., self.nope_head_dim:]
316
+
317
+ q_rope = self.rope(q_rope, position_ids)
318
+ k_rope = self.rope(k_rope, position_ids)
319
+
320
+ q = torch.cat([q_nope, q_rope], dim=-1)
321
+ k = torch.cat([k_nope, k_rope], dim=-1)
322
+
323
+ if past_key_value is not None:
324
+ k = torch.cat([past_key_value[0], k], dim=2)
325
+ v = torch.cat([past_key_value[1], v], dim=2)
326
+ present = (k, v) if use_cache else None
327
+ N = k.shape[2]
328
+
329
+ if self.kv_groups > 1:
330
+ k = k.unsqueeze(2).expand(-1, -1, self.kv_groups, -1, -1).reshape(
331
+ B, self.num_heads, N, self.head_dim)
332
+ v = v.unsqueeze(2).expand(-1, -1, self.kv_groups, -1, -1).reshape(
333
+ B, self.num_heads, N, self.head_dim)
334
+
335
+ if self.use_derf:
336
+ scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
337
+
338
+ if attention_mask is None and past_key_value is None:
339
+ is_masked = torch.triu(
340
+ torch.ones(S, N, dtype=torch.bool, device=scores.device),
341
+ diagonal=N - S + 1,
342
+ ).unsqueeze(0).unsqueeze(0)
343
+ else:
344
+ is_masked = (attention_mask < -1.0) if attention_mask is not None \
345
+ else torch.zeros_like(scores, dtype=torch.bool)
346
+
347
+ safe_scores = scores.masked_fill(is_masked, -10000.0)
348
+
349
+ a = self.derf_alpha.view(1, -1, 1, 1)
350
+ b = self.derf_bias.view(1, -1, 1, 1)
351
+ g = self.derf_gamma.view(1, -1, 1, 1)
352
+
353
+ attn_weights = g * torch.erf(a * safe_scores + b)
354
+ attn_weights = (attn_weights + g) / 2.0
355
+ attn_weights = attn_weights.masked_fill(is_masked, 0.0)
356
+ attn_weights = attn_weights / (attn_weights.sum(dim=-1, keepdim=True) + 1e-8)
357
+
358
+ if self.dropout_p > 0 and self.training:
359
+ attn_weights = F.dropout(attn_weights, p=self.dropout_p)
360
+
361
+ y = torch.matmul(attn_weights, v)
362
+ else:
363
+ q = q.contiguous()
364
+ k = k.contiguous()
365
+ v = v.contiguous()
366
+ drop = self.dropout_p if self.training else 0.0
367
+ if past_key_value is None and attention_mask is None:
368
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True, dropout_p=drop)
369
+ else:
370
+ if attention_mask is not None:
371
+ is_masked = (attention_mask < -1.0)
372
+ else:
373
+ is_masked = torch.triu(
374
+ torch.ones(S, N, dtype=torch.bool, device=q.device),
375
+ diagonal=N - S + 1,
376
+ ).unsqueeze(0).unsqueeze(0)
377
+ y = F.scaled_dot_product_attention(
378
+ q, k, v, attn_mask=~is_masked, dropout_p=drop)
379
+
380
+ if self.use_xsa:
381
+ past_len = N - S
382
+ v_self = v[:, :, past_len:past_len + S, :]
383
+ vn = v_self / (v_self.norm(dim=-1, keepdim=True) + 1e-8)
384
+ projection = (y * vn).sum(dim=-1, keepdim=True) * vn
385
+ y = y - projection
386
+
387
+ y = y.transpose(1, 2).contiguous().view(B, S, self.num_heads * self.head_dim)
388
+ y = self.o_b_proj(self.o_a_proj(y))
389
+ return y, present
390
+
391
+
392
+ # ---------------------------------------------------------------------------
393
+ # MoE FFN -- v2: sort-based dispatch + fused shared expert
394
+ # ---------------------------------------------------------------------------
395
+
396
+ class ExpertFFN(nn.Module):
397
+ """Single SwiGLU expert."""
398
+ def __init__(self, hidden_size: int, intermediate_size: int):
399
+ super().__init__()
400
+ self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
401
+ self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
402
+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
403
+
404
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
405
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
406
+
407
+
408
+ def sqrtsoftplus(x: torch.Tensor) -> torch.Tensor:
409
+ return torch.sqrt(F.softplus(x) + 1e-8)
410
+
411
+
412
+ class SparseMoEFFN(nn.Module):
413
+ """
414
+ v2 changes:
415
+ - FUSED shared expert: one ExpertFFN with width n_shared * intermediate,
416
+ scaled by 1/n_shared on output -- equivalent to v1's averaged Python
417
+ loop, one fused matmul set. (state-dict key changes: shared_expert.*)
418
+ - SORT-BASED dispatch for routed experts: flatten (token, slot) pairs,
419
+ argsort by expert id, run each expert on ONE contiguous slice, weighted
420
+ index_add_ back. No boolean masks, no nonzero(), no per-expert scatter.
421
+ Routing logic (hash routing, sqrtsoftplus, aux loss) is unchanged.
422
+ """
423
+ def __init__(self, cfg: SpikeWhaleConfig, layer_idx: int = 0):
424
+ super().__init__()
425
+ self.n_routed_experts = cfg.n_routed_experts
426
+ self.n_shared_experts = cfg.n_shared_experts
427
+ self.num_experts_per_tok = cfg.num_experts_per_tok
428
+ self.norm_topk_prob = cfg.norm_topk_prob
429
+ self.scoring_func = cfg.scoring_func
430
+ self.routed_scaling_factor = cfg.routed_scaling_factor
431
+ self.use_hash_routing = layer_idx < cfg.num_hash_layers
432
+ self.aux_loss_coef = cfg.moe_aux_loss_coef
433
+
434
+ self.router = nn.Linear(cfg.hidden_size, cfg.n_routed_experts, bias=False)
435
+ self.experts = nn.ModuleList([
436
+ ExpertFFN(cfg.hidden_size, cfg.moe_intermediate_size)
437
+ for _ in range(cfg.n_routed_experts)
438
+ ])
439
+ # Fused shared expert (v2)
440
+ self.shared_expert = (
441
+ ExpertFFN(cfg.hidden_size,
442
+ cfg.moe_intermediate_size * cfg.n_shared_experts)
443
+ if cfg.n_shared_experts > 0 else None
444
+ )
445
+
446
+ self._last_aux_loss: Optional[torch.Tensor] = None
447
+
448
+ def forward(self, x: torch.Tensor,
449
+ position_ids: Optional[torch.Tensor] = None) -> torch.Tensor:
450
+ B, S, H = x.shape
451
+ x_flat = x.view(B * S, H)
452
+ T = B * S
453
+ K = self.num_experts_per_tok
454
+
455
+ # Shared expert: always active, single fused pass.
456
+ if self.shared_expert is not None:
457
+ shared_out = self.shared_expert(x_flat)
458
+ if self.n_shared_experts > 1:
459
+ shared_out = shared_out / self.n_shared_experts
460
+ else:
461
+ shared_out = None
462
+
463
+ # ---- Routing (unchanged logic) ----
464
+ if self.use_hash_routing:
465
+ if position_ids is not None:
466
+ base = (position_ids.reshape(T, 1) % self.n_routed_experts).long()
467
+ else:
468
+ base = (torch.arange(T, device=x.device) % self.n_routed_experts).unsqueeze(1)
469
+ offsets = torch.arange(K, device=x.device)
470
+ top_k_indices = (base + offsets.unsqueeze(0)) % self.n_routed_experts # [T, K]
471
+ top_k_weights = torch.full((T, K), 1.0 / K, device=x.device, dtype=x_flat.dtype)
472
+ self._last_aux_loss = None
473
+ else:
474
+ router_logits = self.router(x_flat)
475
+ if self.scoring_func == "sqrtsoftplus":
476
+ routing_scores = sqrtsoftplus(router_logits)
477
+ else:
478
+ routing_scores = F.softmax(router_logits, dim=-1)
479
+
480
+ top_k_scores, top_k_indices = torch.topk(routing_scores, K, dim=-1)
481
+ if self.norm_topk_prob:
482
+ top_k_weights = top_k_scores / (top_k_scores.sum(dim=-1, keepdim=True) + 1e-8)
483
+ else:
484
+ top_k_weights = top_k_scores
485
+ top_k_weights = top_k_weights * self.routed_scaling_factor
486
+
487
+ softmax_probs = F.softmax(router_logits, dim=-1)
488
+ expert_mask = torch.zeros_like(softmax_probs)
489
+ expert_mask.scatter_(1, top_k_indices, 1.0)
490
+ f_e = expert_mask.mean(0)
491
+ p_e = softmax_probs.mean(0)
492
+ self._last_aux_loss = self.n_routed_experts * (f_e * p_e).sum() * self.aux_loss_coef
493
+
494
+ # ---- Sort-based dispatch (v2) ----
495
+ # Flatten the (token, slot) assignment: T*K rows total.
496
+ flat_expert = top_k_indices.reshape(-1) # [T*K]
497
+ flat_weight = top_k_weights.reshape(-1, 1) # [T*K, 1]
498
+ flat_token = torch.arange(T, device=x.device).repeat_interleave(K) # [T*K]
499
+
500
+ order = torch.argsort(flat_expert, stable=True) # group by expert
501
+ sorted_expert = flat_expert[order]
502
+ sorted_token = flat_token[order]
503
+ sorted_weight = flat_weight[order]
504
+
505
+ counts = torch.bincount(sorted_expert, minlength=self.n_routed_experts)
506
+ # boundaries per expert in the sorted order (CPU sync once per forward;
507
+ # unavoidable without grouped-GEMM, still vastly cheaper than v1's
508
+ # per-expert nonzero/masking)
509
+ counts_list = counts.tolist()
510
+
511
+ gathered = x_flat[sorted_token] # [T*K, H]
512
+ out_flat = torch.zeros_like(x_flat)
513
+
514
+ start = 0
515
+ for expert_idx, cnt in enumerate(counts_list):
516
+ if cnt == 0:
517
+ continue
518
+ end = start + cnt
519
+ seg = gathered[start:end]
520
+ seg_out = self.experts[expert_idx](seg) * sorted_weight[start:end]
521
+ out_flat.index_add_(0, sorted_token[start:end], seg_out.to(out_flat.dtype))
522
+ start = end
523
+
524
+ if shared_out is not None:
525
+ out_flat = out_flat + shared_out
526
+ return out_flat.view(B, S, H)
527
+
528
+ def get_aux_loss(self) -> Optional[torch.Tensor]:
529
+ return self._last_aux_loss
530
+
531
+
532
+ class DenseFFN(nn.Module):
533
+ def __init__(self, cfg: SpikeWhaleConfig):
534
+ super().__init__()
535
+ self.gate_proj = nn.Linear(cfg.hidden_size, cfg.moe_intermediate_size, bias=False)
536
+ self.up_proj = nn.Linear(cfg.hidden_size, cfg.moe_intermediate_size, bias=False)
537
+ self.down_proj = nn.Linear(cfg.moe_intermediate_size, cfg.hidden_size, bias=False)
538
+
539
+ def forward(self, x: torch.Tensor,
540
+ position_ids: Optional[torch.Tensor] = None) -> torch.Tensor:
541
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
542
+
543
+ def get_aux_loss(self) -> Optional[torch.Tensor]:
544
+ return None
545
+
546
+
547
+ # ---------------------------------------------------------------------------
548
+ # Transformer block
549
+ # ---------------------------------------------------------------------------
550
+
551
+ class TransformerBlock(nn.Module):
552
+ def __init__(self, cfg: SpikeWhaleConfig, layer_idx: int):
553
+ super().__init__()
554
+ self.use_hc = cfg.use_hyper_connections
555
+ self.hidden_dropout = cfg.hidden_dropout
556
+ self.use_value_embed = getattr(cfg, "use_value_embed", False)
557
+
558
+ self.attn_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
559
+ self.attn = MLADerfXSAAttention(cfg)
560
+ self.ffn_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
561
+
562
+ if cfg.use_moe and layer_idx in cfg.moe_layers:
563
+ self.ffn = SparseMoEFFN(cfg, layer_idx)
564
+ self.is_moe = True
565
+ else:
566
+ self.ffn = DenseFFN(cfg)
567
+ self.is_moe = False
568
+
569
+ if self.use_hc:
570
+ self.hc_attn = HyperConnectionLayer(cfg.hidden_size, cfg.hc_mult,
571
+ cfg.hc_sinkhorn_iters, cfg.hc_eps)
572
+ self.hc_ffn = HyperConnectionLayer(cfg.hidden_size, cfg.hc_mult,
573
+ cfg.hc_sinkhorn_iters, cfg.hc_eps)
574
+
575
+ # NEW (v2, opt-in): value-embedding residual. Zero-init gate -> exact
576
+ # no-op at init; learns to mix raw token-embedding signal into each
577
+ # block's input (nanoGPT-speedrun "value embedding"/U-net skip family;
578
+ # consistent wins at the 50-500M scale).
579
+ if self.use_value_embed:
580
+ self.ve_gate = nn.Parameter(torch.zeros(1))
581
+
582
+ def forward(
583
+ self,
584
+ x: torch.Tensor, # [B, hc_mult, S, H] if HC else [B, S, H]
585
+ position_ids: torch.Tensor,
586
+ attention_mask: Optional[torch.Tensor] = None,
587
+ past_key_value: Optional[Tuple] = None,
588
+ use_cache: bool = False,
589
+ token_embed: Optional[torch.Tensor] = None, # [B, S, H] (value-embed)
590
+ ) -> Tuple[torch.Tensor, Optional[Tuple], Optional[torch.Tensor]]:
591
+
592
+ # --- Attention sub-layer ---
593
+ if self.use_hc:
594
+ h = self.hc_attn.pre_op(x)
595
+ else:
596
+ h = x
597
+
598
+ if self.use_value_embed and token_embed is not None:
599
+ h = h + torch.tanh(self.ve_gate) * token_embed
600
+
601
+ attn_out, present = self.attn(
602
+ self.attn_norm(h), position_ids, attention_mask, past_key_value, use_cache
603
+ )
604
+ attn_out = F.dropout(attn_out, p=self.hidden_dropout, training=self.training)
605
+
606
+ if self.use_hc:
607
+ x = self.hc_attn.post_op(x, attn_out)
608
+ h = self.hc_ffn.pre_op(x)
609
+ else:
610
+ h = h + attn_out
611
+
612
+ # --- FFN sub-layer ---
613
+ ffn_out = self.ffn(self.ffn_norm(h), position_ids)
614
+ ffn_out = F.dropout(ffn_out, p=self.hidden_dropout, training=self.training)
615
+
616
+ if self.use_hc:
617
+ x = self.hc_ffn.post_op(x, ffn_out)
618
+ else:
619
+ x = h + ffn_out
620
+
621
+ return x, present, self.ffn.get_aux_loss()
622
+
623
+
624
+ # ---------------------------------------------------------------------------
625
+ # HRM refinement (unchanged)
626
+ # ---------------------------------------------------------------------------
627
+
628
+ class HRMRefinementBlock(nn.Module):
629
+ def __init__(self, hidden_size: int, refine_dim: int, steps: int, eps: float = 1e-6):
630
+ super().__init__()
631
+ self.steps = steps
632
+ self.norm = RMSNorm(hidden_size, eps)
633
+ self.down = nn.Linear(hidden_size * 2, refine_dim, bias=False)
634
+ self.up = nn.Linear(refine_dim, hidden_size, bias=False)
635
+ self.gate = nn.Parameter(torch.zeros(steps))
636
+ nn.init.normal_(self.down.weight, std=0.02)
637
+ nn.init.zeros_(self.up.weight)
638
+
639
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
640
+ anchor = x
641
+ h = x
642
+ for t in range(self.steps):
643
+ inp = torch.cat([self.norm(h), anchor], dim=-1)
644
+ update = self.up(F.silu(self.down(inp)))
645
+ h = h + torch.tanh(self.gate[t]) * update
646
+ return h
647
+
648
+
649
+ # ---------------------------------------------------------------------------
650
+ # Full model
651
+ # ---------------------------------------------------------------------------
652
+
653
+ class SpikeWhaleModel(nn.Module):
654
+ """Decoder stack without LM head."""
655
+
656
+ def __init__(self, cfg: SpikeWhaleConfig):
657
+ super().__init__()
658
+ self.cfg = cfg
659
+ self.embed_tokens = nn.Embedding(cfg.vocab_size, cfg.hidden_size)
660
+ nn.init.normal_(self.embed_tokens.weight, std=cfg.initializer_range)
661
+
662
+ self.engram = EngramModule(cfg) if cfg.use_engram else None
663
+ self.layers = nn.ModuleList([
664
+ TransformerBlock(cfg, layer_idx=i)
665
+ for i in range(cfg.num_hidden_layers)
666
+ ])
667
+ self.norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
668
+ self.hc_out_mix = (
669
+ HCOutputMix(cfg.hc_mult) if cfg.use_hyper_connections else None
670
+ )
671
+ self.hrm_refine = (
672
+ HRMRefinementBlock(cfg.hidden_size, cfg.hrm_refine_dim, cfg.hrm_refine_steps,
673
+ cfg.rms_norm_eps)
674
+ if getattr(cfg, "use_hrm_refine", False) else None
675
+ )
676
+ self.use_value_embed = getattr(cfg, "use_value_embed", False)
677
+ self.gradient_checkpointing = False
678
+
679
+ def forward(
680
+ self,
681
+ input_ids: torch.Tensor,
682
+ attention_mask: Optional[torch.Tensor] = None,
683
+ position_ids: Optional[torch.Tensor] = None,
684
+ past_key_values: Optional[List[Tuple]] = None,
685
+ use_cache: bool = False,
686
+ ) -> Tuple[torch.Tensor, Optional[List[Tuple]], torch.Tensor]:
687
+ B, S = input_ids.shape
688
+ device = input_ids.device
689
+
690
+ if position_ids is None:
691
+ past_len = past_key_values[0][0].shape[2] if past_key_values else 0
692
+ position_ids = torch.arange(
693
+ past_len, past_len + S, device=device
694
+ ).unsqueeze(0).expand(B, -1)
695
+
696
+ x = self.embed_tokens(input_ids)
697
+ token_embed = x if self.use_value_embed else None
698
+
699
+ if self.engram is not None:
700
+ x = x + self.engram(x)
701
+
702
+ if self.cfg.use_hyper_connections:
703
+ x = x.unsqueeze(1).expand(-1, self.cfg.hc_mult, -1, -1).clone()
704
+
705
+ present_key_values = [] if use_cache else None
706
+ total_aux_loss = torch.tensor(0.0, device=device)
707
+
708
+ # Gradient checkpointing is incompatible with use_cache (the cache from
709
+ # the discarded forward would be silently wrong on recompute).
710
+ assert not (self.gradient_checkpointing and self.training and use_cache), \
711
+ "use_cache=True is not supported with gradient checkpointing"
712
+
713
+ for layer_idx, layer in enumerate(self.layers):
714
+ pkv = past_key_values[layer_idx] if past_key_values else None
715
+
716
+ if self.gradient_checkpointing and self.training:
717
+ x, present, aux_loss = gradient_checkpoint(
718
+ layer, x, position_ids, attention_mask, None, False, token_embed,
719
+ use_reentrant=False,
720
+ )
721
+ else:
722
+ x, present, aux_loss = layer(
723
+ x, position_ids, attention_mask, pkv, use_cache, token_embed)
724
+
725
+ if use_cache:
726
+ present_key_values.append(present)
727
+ if aux_loss is not None:
728
+ total_aux_loss = total_aux_loss + aux_loss
729
+
730
+ if self.cfg.use_hyper_connections:
731
+ x = self.hc_out_mix(x) # v2: learned mix (init == mean)
732
+
733
+ if self.hrm_refine is not None:
734
+ x = self.hrm_refine(x)
735
+
736
+ x = self.norm(x)
737
+ return x, present_key_values, total_aux_loss
738
+
739
+
740
+ class MTPHead(nn.Module):
741
+ """
742
+ v2 MTP head: small zero-init H x H projection feeding the SHARED lm_head.
743
+ Cost per head: H^2 params (e.g. 1M at H=1024) instead of H*V (e.g. 50M+).
744
+ Zero-init means at step 0 the head predicts exactly what lm_head predicts
745
+ for the residual path = 0, i.e. uniform-ish gradient pressure; the residual
746
+ form (x + proj(x)) keeps it anchored to the trunk representation.
747
+ """
748
+ def __init__(self, hidden_size: int):
749
+ super().__init__()
750
+ self.proj = nn.Linear(hidden_size, hidden_size, bias=False)
751
+ nn.init.zeros_(self.proj.weight)
752
+
753
+ def forward(self, hidden: torch.Tensor) -> torch.Tensor:
754
+ return hidden + self.proj(hidden)
755
+
756
+
757
+ class SpikeWhaleLM(PreTrainedModel):
758
+ """
759
+ v2 loss = CE + zloss_coef * z-loss
760
+ + mtp_loss_weight * mean(MTP CE)
761
+ + MoE aux loss
762
+ """
763
+ config_class = SpikeWhaleConfig
764
+ base_model_prefix = "model"
765
+ supports_gradient_checkpointing = True
766
+ _no_split_modules = ["TransformerBlock"]
767
+
768
+ def __init__(self, cfg: SpikeWhaleConfig):
769
+ super().__init__(cfg)
770
+ self.model = SpikeWhaleModel(cfg)
771
+ self.lm_head = nn.Linear(cfg.hidden_size, cfg.vocab_size, bias=False)
772
+ nn.init.normal_(self.lm_head.weight, std=cfg.initializer_range)
773
+
774
+ self.zloss_coef = getattr(cfg, "zloss_coef", 1e-4)
775
+ self.mtp_loss_weight = getattr(cfg, "mtp_loss_weight", 0.3)
776
+
777
+ # v2 MTP: H x H residual projections sharing lm_head (see MTPHead).
778
+ self.mtp_heads = nn.ModuleList([
779
+ MTPHead(cfg.hidden_size)
780
+ for _ in range(cfg.num_nextn_predict_layers)
781
+ ]) if cfg.num_nextn_predict_layers > 0 else None
782
+
783
+ self.post_init()
784
+
785
+ def get_input_embeddings(self):
786
+ return self.model.embed_tokens
787
+
788
+ def set_input_embeddings(self, value):
789
+ self.model.embed_tokens = value
790
+
791
+ def get_output_embeddings(self):
792
+ return self.lm_head
793
+
794
+ def set_output_embeddings(self, new_embeddings):
795
+ self.lm_head = new_embeddings
796
+
797
+ def tie_weights(self, **kwargs):
798
+ if self.config.tie_word_embeddings:
799
+ self.lm_head.weight = self.model.embed_tokens.weight
800
+
801
+ def save_pretrained(self, *args, **kwargs):
802
+ tied = (
803
+ self.config.tie_word_embeddings
804
+ and self.lm_head.weight.data_ptr() == self.model.embed_tokens.weight.data_ptr()
805
+ )
806
+ if tied:
807
+ self.lm_head.weight = nn.Parameter(self.model.embed_tokens.weight.detach().clone())
808
+ try:
809
+ super().save_pretrained(*args, **kwargs)
810
+ finally:
811
+ if tied:
812
+ self.lm_head.weight = self.model.embed_tokens.weight
813
+
814
+ def _set_gradient_checkpointing(self, module, value=False):
815
+ if isinstance(module, SpikeWhaleModel):
816
+ module.gradient_checkpointing = value
817
+
818
+ def forward(
819
+ self,
820
+ input_ids: Optional[torch.Tensor] = None,
821
+ attention_mask: Optional[torch.Tensor] = None,
822
+ position_ids: Optional[torch.Tensor] = None,
823
+ past_key_values: Optional[List[Tuple]] = None,
824
+ labels: Optional[torch.Tensor] = None,
825
+ use_cache: bool = False,
826
+ **kwargs,
827
+ ) -> CausalLMOutputWithPast:
828
+ hidden, present_kvs, aux_loss = self.model(
829
+ input_ids=input_ids,
830
+ attention_mask=attention_mask,
831
+ position_ids=position_ids,
832
+ past_key_values=past_key_values,
833
+ use_cache=use_cache,
834
+ )
835
+
836
+ logits = self.lm_head(hidden)
837
+ loss = None
838
+
839
+ if labels is not None:
840
+ shift_logits = logits[..., :-1, :].contiguous()
841
+ shift_labels = labels[..., 1:].contiguous()
842
+ flat_logits = shift_logits.view(-1, shift_logits.size(-1))
843
+ flat_labels = shift_labels.view(-1)
844
+ loss = F.cross_entropy(flat_logits, flat_labels, ignore_index=-100)
845
+
846
+ # z-loss (v2): penalize log^2 of the partition function on valid
847
+ # positions. Keeps logits from drifting; pairs well with Muon.
848
+ if self.zloss_coef > 0:
849
+ valid = flat_labels != -100
850
+ if valid.any():
851
+ log_z = torch.logsumexp(flat_logits[valid].float(), dim=-1)
852
+ loss = loss + self.zloss_coef * (log_z ** 2).mean()
853
+
854
+ # MTP (v2): residual H x H head -> shared lm_head, down-weighted.
855
+ if self.mtp_heads is not None and self.mtp_loss_weight > 0:
856
+ mtp_total = torch.tensor(0.0, device=loss.device)
857
+ n_active = 0
858
+ for k, head in enumerate(self.mtp_heads, start=1):
859
+ offset = k + 1
860
+ if hidden.size(1) > offset:
861
+ mtp_hidden = head(hidden[..., :-offset, :])
862
+ mtp_logits = self.lm_head(mtp_hidden)
863
+ mtp_labels = labels[..., offset:].contiguous()
864
+ mtp_total = mtp_total + F.cross_entropy(
865
+ mtp_logits.reshape(-1, mtp_logits.size(-1)),
866
+ mtp_labels.reshape(-1),
867
+ ignore_index=-100,
868
+ )
869
+ n_active += 1
870
+ if n_active > 0:
871
+ loss = loss + self.mtp_loss_weight * mtp_total / n_active
872
+
873
+ loss = loss + aux_loss
874
+
875
+ return CausalLMOutputWithPast(
876
+ loss=loss,
877
+ logits=logits,
878
+ past_key_values=present_kvs,
879
+ )
880
+
881
+ def count_parameters(self) -> int:
882
+ 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
+ }