Text Generation
Transformers
Safetensors
English
spike_whale
feature-extraction
small-models
mla
jepa
experimental
custom_code
Instructions to use Quazim0t0/Byrne-86M-Base with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Quazim0t0/Byrne-86M-Base with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="Quazim0t0/Byrne-86M-Base", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Quazim0t0/Byrne-86M-Base", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use Quazim0t0/Byrne-86M-Base with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Quazim0t0/Byrne-86M-Base" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Quazim0t0/Byrne-86M-Base", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/Quazim0t0/Byrne-86M-Base
- SGLang
How to use Quazim0t0/Byrne-86M-Base with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "Quazim0t0/Byrne-86M-Base" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Quazim0t0/Byrne-86M-Base", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "Quazim0t0/Byrne-86M-Base" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Quazim0t0/Byrne-86M-Base", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use Quazim0t0/Byrne-86M-Base with Docker Model Runner:
docker model run hf.co/Quazim0t0/Byrne-86M-Base
Upload folder using huggingface_hub
Browse files- README.md +50 -0
- config.json +63 -0
- config.py +152 -0
- model.safetensors +3 -0
- model_v2.py +882 -0
- spike_tokenizer.py +117 -0
- tokenizer.json +0 -0
- 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 |
+
}
|