kmamaroziqov commited on
Commit
80c3430
·
verified ·
1 Parent(s): c145765

MilliyLM-5B: instruction-tuned Uzbek chat model (SFT of NeuronAI-5B-Base)

Browse files
README.md ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - uz
5
+ - en
6
+ - ru
7
+ pipeline_tag: text-generation
8
+ tags:
9
+ - uzbek
10
+ - o'zbek
11
+ - chat
12
+ - instruction-tuned
13
+ base_model: kmamaroziqov/NeuronAI-5B-Base
14
+ library_name: transformers
15
+ ---
16
+
17
+ # MilliyLM-5B
18
+
19
+ An instruction-tuned Uzbek chat model, supervised fine-tuned from
20
+ [`kmamaroziqov/NeuronAI-5B-Base`](https://huggingface.co/kmamaroziqov/NeuronAI-5B-Base).
21
+
22
+ MilliyLM-5B is a **chat and text-classification model**. It follows Uzbek instructions
23
+ reliably, writes fluent Uzbek in both Latin and Cyrillic script, and is strong on
24
+ sentiment and news classification. It is **not** a knowledge model: on multiple-choice
25
+ knowledge benchmarks it performs at chance. Read the
26
+ [Evaluation](#evaluation) and [Limitations](#limitations) sections before using it —
27
+ they are specific about what works and what does not.
28
+
29
+ | | |
30
+ |---|---|
31
+ | Parameters | 5.17 B |
32
+ | Architecture | `NeuronLMForCausalLM` (custom, ships with the repo) |
33
+ | Layers / hidden | 36 / 3584 |
34
+ | Attention | GQA, 28 query heads : 4 KV heads, head_dim 128, QK-norm |
35
+ | Position encoding | RoPE, θ = 500000 |
36
+ | Context length | 4096 tokens |
37
+ | Vocabulary | 48,000 (BPE) |
38
+ | Embeddings | untied |
39
+ | Weights dtype | bfloat16 |
40
+ | Languages | Uzbek (Latin + Cyrillic), English, Russian |
41
+
42
+ ---
43
+
44
+ ## Quick start
45
+
46
+ The architecture is custom, so `trust_remote_code=True` is **required** — the modeling
47
+ code ships inside this repository.
48
+
49
+ ```python
50
+ import torch
51
+ from transformers import AutoModelForCausalLM, AutoTokenizer
52
+
53
+ model_id = "NeuronUz/MilliyLM-5B"
54
+
55
+ tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
56
+ model = AutoModelForCausalLM.from_pretrained(
57
+ model_id,
58
+ trust_remote_code=True,
59
+ dtype=torch.bfloat16, # weights are bf16; do not load in fp32
60
+ device_map="cuda",
61
+ ).eval()
62
+
63
+ messages = [{"role": "user", "content": "O'zbekistonning poytaxti qaysi shahar?"}]
64
+ inputs = tokenizer.apply_chat_template(
65
+ messages,
66
+ add_generation_prompt=True,
67
+ return_tensors="pt",
68
+ return_dict=True,
69
+ ).to(model.device)
70
+
71
+ with torch.no_grad():
72
+ out = model.generate(
73
+ **inputs,
74
+ max_new_tokens=256,
75
+ do_sample=False, # greedy; see Generation settings below
76
+ eos_token_id=5, # <|im_end|> -- NOT the config's </s>
77
+ pad_token_id=3, # <pad>
78
+ )
79
+
80
+ print(tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
81
+ ```
82
+
83
+ ```
84
+ Oʻzbekistonning poytaxti - Toshkent.
85
+ ```
86
+
87
+ ### Chat template
88
+
89
+ The model uses ChatML. `tokenizer.apply_chat_template` applies it for you; the raw form is:
90
+
91
+ ```
92
+ <|im_start|>system
93
+ {system}<|im_end|>
94
+ <|im_start|>user
95
+ {user}<|im_end|>
96
+ <|im_start|>assistant
97
+ {assistant}<|im_end|>
98
+ ```
99
+
100
+ A system turn is optional. Uzbek-language system prompts work best — that is what the
101
+ model was trained with.
102
+
103
+ ### Generation settings
104
+
105
+ | setting | value | why |
106
+ |---|---|---|
107
+ | `eos_token_id` | **5** (`<|im_end|>`) | The turn terminator. Token id 1 (`</s>`) is the *pretraining* EOS and never appears in chat data — using it means generation runs to `max_new_tokens`. |
108
+ | `pad_token_id` | 3 (`<pad>`) | |
109
+ | `do_sample` | `False` for classification/extraction; `True`, `temperature≈0.7`, `top_p≈0.9` for open chat | Every benchmark below was measured greedy. |
110
+ | `dtype` | `torch.bfloat16` | Trained in bf16. |
111
+
112
+ Memory: ~10.5 GB for weights in bf16, so a single 16 GB GPU is enough for inference.
113
+
114
+ ### Serving
115
+
116
+ **vLLM and SGLang cannot load this model.** They reimplement each architecture
117
+ internally rather than executing a repository's Python, and `NeuronLMForCausalLM` is not
118
+ in their model registries — `trust_remote_code` only covers the config and tokenizer
119
+ there. Use the `transformers` backend, or convert the weights (the architecture is
120
+ Qwen3-equivalent apart from *fused* `qkv_proj` / `gate_up_proj` and `out_proj` naming;
121
+ splitting those tensors and renaming to the Qwen3 layout yields a checkpoint vLLM will
122
+ serve).
123
+
124
+ ---
125
+
126
+ ## Training
127
+
128
+ Supervised fine-tuning only — no continued pretraining was performed on top of the base.
129
+
130
+ | | |
131
+ |---|---|
132
+ | Method | Full-parameter SFT (no LoRA) |
133
+ | Data | 190,939 instruction/chat examples |
134
+ | Epochs | 3 (17,701 steps); released checkpoint is from epoch 2.75 |
135
+ | Effective batch | 32 (micro-batch 8 × grad-accum 4) |
136
+ | Sequence length | 2048 |
137
+ | Optimizer | AdamW fused, β₁ 0.9, β₂ 0.95, weight decay 0.0 |
138
+ | LR schedule | 1e-5, cosine, 3% warmup, grad-norm clip 1.0 |
139
+ | Precision | bf16 mixed precision, gradient checkpointing |
140
+ | Hardware | 1 × NVIDIA RTX PRO 6000 Blackwell (96 GB), ~10 h |
141
+
142
+ ### Data composition
143
+
144
+ | slice | rows | share |
145
+ |---|---:|---:|
146
+ | Uzbek instruction/chat backbone (curated + filtered) | 169,919 | 89.0% |
147
+ | Uzbek **Cyrillic** chat (transliterated) | 12,000 | 6.3% |
148
+ | Russian-instructed translation | 5,000 | 2.6% |
149
+ | Latin ↔ Cyrillic script conversion | 3,000 | 1.6% |
150
+ | Cyrillic identity/social | 1,020 | 0.5% |
151
+
152
+ The backbone mixes general Uzbek assistant data, benchmark-format task data
153
+ (MCQ, classification, spelling), English↔Uzbek translation pairs, and an English
154
+ retention slice. Third-person rubric-grading text was filtered out of the backbone
155
+ before training.
156
+
157
+ The Cyrillic and Russian slices exist because the base model reads and writes Uzbek
158
+ Cyrillic *better* than Latin (bits-per-byte 0.2288 vs 0.3868) yet had almost no Cyrillic
159
+ chat behaviour attached to it, and because Russian-language instructions were nearly
160
+ absent. Checkpoint selection was done by running the full benchmark suite on all 12
161
+ saved checkpoints, not by held-out loss — held-out loss was flat (1.784–1.796) across
162
+ the last two epochs while benchmark scores were still moving.
163
+
164
+ ---
165
+
166
+ ## Evaluation
167
+
168
+ Full public benchmark suite, greedy decoding, `transformers` backend, seed 42, complete
169
+ test sets (no subsampling). Scores are accuracy unless noted.
170
+
171
+ ### Uzbek benchmarks
172
+
173
+ | benchmark | n | score | invalid rate |
174
+ |---|---:|---:|---:|
175
+ | uzlib (Uzbek linguistic MCQ) | 1,861 | 0.2875 | 0.0000 |
176
+ | TUMLU-Uzbek (Uzbek MMLU) | 700 | 0.3286 | 0.0000 |
177
+ | MMLU-Uz (translated MMLU) | 14,042 | 0.2584 | 0.0000 |
178
+ | News topic classification (10-way) | 96,970 | **0.6531** | 0.0000 |
179
+ | Sentiment (binary) | 10,000 | **0.9259** | 0.0001 |
180
+
181
+ Random baselines: 0.25 for the 4-way MCQ tasks, 0.10 for news, 0.50 for sentiment.
182
+
183
+ ### English
184
+
185
+ | benchmark | n | score | invalid rate |
186
+ |---|---:|---:|---:|
187
+ | MMLU (English) | 14,042 | 0.2619 | 0.0000 |
188
+
189
+ ### Translation (FLORES+)
190
+
191
+ | direction | n | BLEU | COMET | length ratio |
192
+ |---|---:|---:|---:|---:|
193
+ | English → Uzbek | 2,009 | 5.17 | 0.7397 | 1.018 |
194
+ | Uzbek → English | 2,009 | 1.83 | 0.5376 | 1.229 |
195
+
196
+ ### uzlib, per split
197
+
198
+ | split | n | score |
199
+ |---|---:|---:|
200
+ | fill_in | 52 | 0.3077 |
201
+ | correct_word (orthography) | 1,501 | 0.3011 |
202
+ | meaning_in_context | 72 | 0.2639 |
203
+ | meaning | 236 | 0.2034 |
204
+
205
+ ### News, per class
206
+
207
+ | class | n | score |
208
+ |---|---:|---:|
209
+ | Sport | 16,113 | 0.8743 |
210
+ | class 2 | 5,177 | 0.7309 |
211
+ | class 4 | 2,405 | 0.7081 |
212
+ | class 0 | 29,500 | 0.6794 |
213
+ | class 1 | 10,755 | 0.6596 |
214
+ | class 5 | 3,505 | 0.6579 |
215
+ | class 7 | 1,987 | 0.6548 |
216
+ | class 8 | 1,784 | 0.5667 |
217
+ | class 9 | 11,732 | 0.5124 |
218
+ | Oila va Jamiyat (Family & Society) | 14,012 | 0.4273 |
219
+
220
+ ### Comparison with prior SFTs of the same base
221
+
222
+ Same benchmark suite, same conditions.
223
+
224
+ | benchmark | **MilliyLM-5B** | NeuronAI-5B-v4 | NeuronAI-5B (v1) |
225
+ |---|---:|---:|---:|
226
+ | uzlib | **0.2875** | 0.2708 | 0.2638 |
227
+ | TUMLU-Uz | **0.3286** | 0.2100 | 0.1914 |
228
+ | MMLU-Uz | **0.2584** | 0.2136 | 0.2154 |
229
+ | MMLU (English) | **0.2619** | 0.2142 | 0.2196 |
230
+ | News | **0.6531** | 0.1834 | 0.1542 |
231
+ | Sentiment | **0.9259** | 0.1186 | 0.2647 |
232
+ | FLORES en→uz BLEU | 5.17 | **7.68** | 7.37 |
233
+ | FLORES uz→en BLEU | 1.83 | **4.21** | 5.15 |
234
+
235
+ Most of the classification gain comes from **format compliance** rather than raw
236
+ capability: the earlier models emitted unparseable answers on 63–78% of sentiment items
237
+ and 6–13% of TUMLU items, while MilliyLM-5B's invalid rate is ≤0.0001 across every task.
238
+ Translation is the one axis where the earlier models are better — see Limitations.
239
+
240
+ ### Contamination check
241
+
242
+ 44.1% of the sentiment evaluation set also appears in the training data, because the
243
+ benchmark scores the dataset's `train` split and the task-format training rows were drawn
244
+ from the same pool. This was tested rather than assumed:
245
+
246
+ | slice | n | score |
247
+ |---|---:|---:|
248
+ | items seen in training | 1,200 | 0.9342 |
249
+ | items not seen (exact match excluded) | 1,200 | 0.9300 |
250
+ | items not seen (exact **and** normalized match excluded) | 1,500 | 0.9347 |
251
+
252
+ Performance on strictly unseen data is identical to performance on memorized data, so
253
+ the sentiment score reflects genuine capability. The news benchmark has **zero** overlap
254
+ with training data.
255
+
256
+ ---
257
+
258
+ ## Limitations
259
+
260
+ **Multiple-choice knowledge tasks perform at chance.** uzlib, MMLU-Uz and MMLU-English
261
+ all sit within noise of their 0.25 random baseline, across roughly 30,000 questions.
262
+ Invalid rates near zero mean the model answers in the correct format every time and is
263
+ still wrong — this is missing knowledge, not broken parsing. The base model completed a
264
+ single pretraining epoch, and supervised fine-tuning cannot add facts that were never
265
+ learned. **Do not use this model for factual question answering, exams, or retrieval-free
266
+ knowledge tasks.** TUMLU-Uzbek at 0.3286 is the only MCQ result above chance, and its
267
+ 700-item sample gives it a ±3.5% confidence interval.
268
+
269
+ **Uzbek → English translation is weak and regressed against the base's earlier SFTs.**
270
+ BLEU 1.83 with a 1.229 length ratio and 12.5% unigram precision means the model
271
+ over-generates English that mostly does not match the reference. English → Uzbek is
272
+ usable (COMET 0.7397) but not competitive with dedicated translation systems.
273
+
274
+ **Script conversion does not work despite being trained for it.** Asked to transliterate
275
+ Latin Uzbek to Cyrillic, the model frequently returns the input unchanged. The 3,000-row
276
+ slice was too small.
277
+
278
+ **The Cyrillic slice was machine-transliterated, and its artifacts are visible in
279
+ output.** Loanwords and brand names inside Cyrillic text can come out mangled
280
+ (e.g. `Facebook` → `Факебоок`), and occasional single Cyrillic characters leak into
281
+ Latin words. Cyrillic *chat* is coherent and does not degenerate, but Cyrillic
282
+ *orthography* is less reliable than Latin.
283
+
284
+ **Self-identification.** The identity training data names the model "NeuronAI 5B", so
285
+ asked who it is, it answers with that name rather than "MilliyLM-5B".
286
+
287
+ **News classification is uneven.** The "Oila va Jamiyat" (Family & Society) class scores
288
+ 0.4273 across 14,012 items — a semantically diffuse catch-all the model handles poorly,
289
+ against 0.8743 for the lexically distinctive Sport class.
290
+
291
+ **Safety.** No safety alignment, RLHF, or red-teaming was performed. The model has no
292
+ refusal training beyond what the instruction data incidentally contains. It can produce
293
+ incorrect, biased, or unsafe content, and — given the benchmark results above — will
294
+ state false facts fluently and confidently. Evaluate it for your own use case before
295
+ deploying it anywhere user-facing.
296
+
297
+ ---
298
+
299
+ ## Intended use
300
+
301
+ **Suitable for:** Uzbek-language chat and assistance; text classification (sentiment,
302
+ topic); Uzbek text generation and rewriting in Latin or Cyrillic; English → Uzbek
303
+ translation where approximate meaning suffices; a base for further fine-tuning.
304
+
305
+ **Not suitable for:** factual question answering or anything knowledge-intensive;
306
+ exam-style multiple choice; Uzbek → English translation; script transliteration; any
307
+ application where a confidently-stated wrong fact causes harm (medical, legal, financial
308
+ advice).
309
+
310
+ ## License
311
+
312
+ Apache 2.0, inherited from the base model. Training data licensing follows the sources
313
+ of the underlying public datasets.
314
+
315
+ ## Citation
316
+
317
+ ```bibtex
318
+ @misc{milliylm5b,
319
+ title = {MilliyLM-5B: an instruction-tuned Uzbek language model},
320
+ author = {NeuronUz},
321
+ year = {2026},
322
+ url = {https://huggingface.co/NeuronUz/MilliyLM-5B}
323
+ }
324
+ ```
attention.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from torch import Tensor, nn
8
+ from transformers import Cache
9
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
10
+
11
+ from .configuration_neuron_lm import NeuronLMConfig
12
+ from .layers import RMSNorm
13
+ from .rotary import apply_rotary_pos_emb
14
+
15
+ __all__ = ["NeuronLMAttention"]
16
+
17
+
18
+ def _repeat_kv(hidden_states: Tensor, repeats: int) -> Tensor:
19
+ if repeats == 1:
20
+ return hidden_states
21
+ return hidden_states.repeat_interleave(repeats, dim=1)
22
+
23
+
24
+ def eager_attention_forward(
25
+ module: NeuronLMAttention,
26
+ query: Tensor,
27
+ key: Tensor,
28
+ value: Tensor,
29
+ attention_mask: Tensor | None,
30
+ *,
31
+ scaling: float,
32
+ dropout: float = 0.0,
33
+ **_: Any,
34
+ ) -> tuple[Tensor, Tensor]:
35
+ """Numerically clear GQA reference used for attention-weight outputs."""
36
+
37
+ key = _repeat_kv(key, module.num_key_value_groups)
38
+ value = _repeat_kv(value, module.num_key_value_groups)
39
+ attention_weights = (
40
+ torch.matmul(
41
+ query,
42
+ key.transpose(-2, -1),
43
+ )
44
+ * scaling
45
+ )
46
+
47
+ fully_masked: Tensor | None = None
48
+ if attention_mask is not None:
49
+ if attention_mask.dtype == torch.bool:
50
+ fully_masked = ~attention_mask.any(
51
+ dim=-1,
52
+ keepdim=True,
53
+ )
54
+ attention_weights = attention_weights.masked_fill(
55
+ ~attention_mask,
56
+ torch.finfo(attention_weights.dtype).min,
57
+ )
58
+ else:
59
+ minimum = torch.finfo(attention_mask.dtype).min
60
+ fully_masked = (
61
+ torch.isneginf(attention_mask) | (attention_mask == minimum)
62
+ ).all(dim=-1, keepdim=True)
63
+ attention_weights = attention_weights + attention_mask
64
+
65
+ if fully_masked is not None:
66
+ attention_weights = attention_weights.masked_fill(
67
+ fully_masked,
68
+ 0.0,
69
+ )
70
+
71
+ attention_weights = F.softmax(
72
+ attention_weights,
73
+ dim=-1,
74
+ dtype=torch.float32,
75
+ ).to(query.dtype)
76
+ attention_weights = torch.nan_to_num(
77
+ attention_weights,
78
+ nan=0.0,
79
+ )
80
+ if fully_masked is not None:
81
+ attention_weights = attention_weights.masked_fill(
82
+ fully_masked,
83
+ 0.0,
84
+ )
85
+ attention_weights = F.dropout(
86
+ attention_weights,
87
+ p=dropout,
88
+ training=module.training,
89
+ )
90
+ attention_output = torch.matmul(attention_weights, value)
91
+ return (
92
+ attention_output.transpose(1, 2).contiguous(),
93
+ attention_weights,
94
+ )
95
+
96
+
97
+ class NeuronLMAttention(nn.Module):
98
+ """Fused, bias-free GQA using a checkpoint-stable ``[Q, K, V]`` layout."""
99
+
100
+ def __init__(
101
+ self,
102
+ config: NeuronLMConfig,
103
+ layer_idx: int = 0,
104
+ ) -> None:
105
+ super().__init__()
106
+
107
+ if type(layer_idx) is not int or layer_idx < 0:
108
+ raise ValueError(
109
+ f"layer_idx must be a non-negative integer, got {layer_idx!r}"
110
+ )
111
+
112
+ self.config = config
113
+ self.hidden_size = config.hidden_size
114
+ self.num_heads = config.num_attention_heads
115
+ self.num_key_value_heads = config.num_key_value_heads
116
+ self.num_key_value_groups = self.num_heads // self.num_key_value_heads
117
+ self.head_dim = config.head_dim
118
+ self.scaling = self.head_dim**-0.5
119
+ self.attention_dropout = config.attention_dropout
120
+ self.layer_idx = layer_idx
121
+ self.is_causal = True
122
+
123
+ self.query_size = self.num_heads * self.head_dim
124
+ self.key_value_size = self.num_key_value_heads * self.head_dim
125
+
126
+ # State-dict contract: rows are Q, then K, then V.
127
+ self.qkv_proj = nn.Linear(
128
+ in_features=self.hidden_size,
129
+ out_features=config.qkv_projection_size,
130
+ bias=False,
131
+ )
132
+ self.out_proj = nn.Linear(
133
+ in_features=self.query_size,
134
+ out_features=self.hidden_size,
135
+ bias=False,
136
+ )
137
+
138
+ # Per-head normalization of queries and keys before RoPE, as in
139
+ # Qwen3 / OLMo-2 / Gemma-3. Bounds the growth of q.k during long bf16
140
+ # runs, which depth-scaled initialization does not address: init
141
+ # controls the residual stream at step 0, while attention logits
142
+ # drift as the projection norms are learned.
143
+ self.use_qk_norm = config.use_qk_norm
144
+ if self.use_qk_norm:
145
+ self.q_norm = RMSNorm(
146
+ hidden_size=self.head_dim,
147
+ eps=config.rms_norm_eps,
148
+ )
149
+ self.k_norm = RMSNorm(
150
+ hidden_size=self.head_dim,
151
+ eps=config.rms_norm_eps,
152
+ )
153
+
154
+ def forward(
155
+ self,
156
+ hidden_states: Tensor,
157
+ position_embeddings: tuple[Tensor, Tensor],
158
+ attention_mask: Tensor | None = None,
159
+ past_key_values: Cache | None = None,
160
+ output_attentions: bool = False,
161
+ **kwargs: Any,
162
+ ) -> Tensor | tuple[Tensor, Tensor | None]:
163
+ if hidden_states.ndim != 3:
164
+ raise ValueError(
165
+ "hidden_states must have shape "
166
+ "(batch_size, sequence_length, hidden_size), "
167
+ f"got shape={tuple(hidden_states.shape)}"
168
+ )
169
+
170
+ batch_size, sequence_length, hidden_size = hidden_states.shape
171
+ if hidden_size != self.hidden_size:
172
+ raise ValueError(
173
+ f"Expected hidden_size={self.hidden_size}, "
174
+ f"got hidden_size={hidden_size}"
175
+ )
176
+ if sequence_length == 0:
177
+ raise ValueError("sequence_length must be greater than zero")
178
+
179
+ cos, sin = position_embeddings
180
+ query_states, key_states, value_states = self._project_qkv(hidden_states)
181
+ query_states, key_states = apply_rotary_pos_emb(
182
+ query=query_states,
183
+ key=key_states,
184
+ cos=cos,
185
+ sin=sin,
186
+ )
187
+
188
+ if past_key_values is not None:
189
+ # Transformers v5 caches track their own write offset; passing
190
+ # cache_position here was removed from the library's convention.
191
+ key_states, value_states = past_key_values.update(
192
+ key_states,
193
+ value_states,
194
+ self.layer_idx,
195
+ )
196
+
197
+ implementation = self.config._attn_implementation or "sdpa"
198
+ if output_attentions:
199
+ implementation = "eager"
200
+
201
+ attention_interface = ALL_ATTENTION_FUNCTIONS.get_interface(
202
+ implementation,
203
+ eager_attention_forward,
204
+ )
205
+ attention_output, attention_weights = attention_interface(
206
+ self,
207
+ query_states,
208
+ key_states,
209
+ value_states,
210
+ attention_mask,
211
+ dropout=(self.attention_dropout if self.training else 0.0),
212
+ scaling=self.scaling,
213
+ output_attentions=output_attentions,
214
+ **kwargs,
215
+ )
216
+
217
+ attention_output = attention_output.reshape(
218
+ batch_size,
219
+ sequence_length,
220
+ self.query_size,
221
+ )
222
+ attention_output = self.out_proj(attention_output)
223
+
224
+ if output_attentions:
225
+ return attention_output, attention_weights
226
+ return attention_output
227
+
228
+ def _project_qkv(
229
+ self,
230
+ hidden_states: Tensor,
231
+ ) -> tuple[Tensor, Tensor, Tensor]:
232
+ batch_size, sequence_length, _ = hidden_states.shape
233
+ qkv_states = self.qkv_proj(hidden_states)
234
+ query_states, key_states, value_states = qkv_states.split(
235
+ (
236
+ self.query_size,
237
+ self.key_value_size,
238
+ self.key_value_size,
239
+ ),
240
+ dim=-1,
241
+ )
242
+
243
+ query_states = query_states.view(
244
+ batch_size,
245
+ sequence_length,
246
+ self.num_heads,
247
+ self.head_dim,
248
+ ).transpose(1, 2)
249
+ key_states = key_states.view(
250
+ batch_size,
251
+ sequence_length,
252
+ self.num_key_value_heads,
253
+ self.head_dim,
254
+ ).transpose(1, 2)
255
+ value_states = value_states.view(
256
+ batch_size,
257
+ sequence_length,
258
+ self.num_key_value_heads,
259
+ self.head_dim,
260
+ ).transpose(1, 2)
261
+
262
+ # Applied before RoPE so the rotation acts on unit-scale vectors and
263
+ # the norm never sees position-dependent structure.
264
+ if self.use_qk_norm:
265
+ query_states = self.q_norm(query_states)
266
+ key_states = self.k_norm(key_states)
267
+
268
+ return query_states, key_states, value_states
269
+
270
+ def _load_from_state_dict(
271
+ self,
272
+ state_dict: dict[str, Tensor],
273
+ prefix: str,
274
+ local_metadata: dict[str, Any],
275
+ strict: bool,
276
+ missing_keys: list[str],
277
+ unexpected_keys: list[str],
278
+ error_msgs: list[str],
279
+ ) -> None:
280
+ qkv_key = f"{prefix}qkv_proj.weight"
281
+ qkv_weight = state_dict.get(qkv_key)
282
+ expected_shape = tuple(self.qkv_proj.weight.shape)
283
+ if qkv_weight is not None and tuple(qkv_weight.shape) != expected_shape:
284
+ error_msgs.append(
285
+ f"{qkv_key} must use fused [Q, K, V] layout with shape "
286
+ f"{expected_shape}, got {tuple(qkv_weight.shape)}"
287
+ )
288
+
289
+ super()._load_from_state_dict(
290
+ state_dict,
291
+ prefix,
292
+ local_metadata,
293
+ strict,
294
+ missing_keys,
295
+ unexpected_keys,
296
+ error_msgs,
297
+ )
298
+
299
+ def extra_repr(self) -> str:
300
+ return (
301
+ f"hidden_size={self.hidden_size}, "
302
+ f"num_heads={self.num_heads}, "
303
+ f"num_key_value_heads={self.num_key_value_heads}, "
304
+ f"head_dim={self.head_dim}, "
305
+ f"attention_dropout={self.attention_dropout}, "
306
+ f"use_qk_norm={self.use_qk_norm}, "
307
+ f"layer_idx={self.layer_idx}"
308
+ )
chat_template.jinja ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {%- for m in messages %}{{ '<|im_start|>' + m['role'] + '
2
+ ' + m['content'] + '<|im_end|>' + '
3
+ ' }}{%- endfor %}{%- if add_generation_prompt %}{{ '<|im_start|>assistant
4
+ ' }}{%- endif %}
config.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "NeuronLMForCausalLM"
4
+ ],
5
+ "attention_dropout": 0.0,
6
+ "auto_map": {
7
+ "AutoConfig": "configuration_neuron_lm.NeuronLMConfig",
8
+ "AutoModelForCausalLM": "modeling_neuron_lm.NeuronLMForCausalLM"
9
+ },
10
+ "bos_token_id": 0,
11
+ "dtype": "bfloat16",
12
+ "eos_token_id": 5,
13
+ "hidden_size": 3584,
14
+ "initializer_range": 0.02,
15
+ "intermediate_size": 9728,
16
+ "is_causal": true,
17
+ "is_decoder": true,
18
+ "max_position_embeddings": 4096,
19
+ "model_type": "neuron_lm",
20
+ "num_attention_heads": 28,
21
+ "num_hidden_layers": 36,
22
+ "num_key_value_heads": 4,
23
+ "pad_token_id": 3,
24
+ "residual_dropout": 0.0,
25
+ "rms_norm_eps": 1e-05,
26
+ "rope_parameters": {
27
+ "rope_theta": 500000.0,
28
+ "rope_type": "default"
29
+ },
30
+ "tie_word_embeddings": false,
31
+ "transformers_version": "5.12.1",
32
+ "use_cache": false,
33
+ "use_qk_norm": true,
34
+ "vocab_size": 48000
35
+ }
configuration_neuron_lm.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from copy import deepcopy
5
+ from pathlib import Path
6
+ from typing import Any, ClassVar
7
+
8
+ import yaml
9
+ from transformers import PreTrainedConfig
10
+
11
+ # Rotary base frequency. 500000 follows Llama-3 rather than Llama-2's 10000:
12
+ # it trades a little short-range frequency resolution for enough headroom to
13
+ # extend context past the frozen 4K presets later. RoPE scaling is not
14
+ # implemented, so this value is fixed for the lifetime of a pretrained model.
15
+ DEFAULT_ROPE_THETA = 500_000.0
16
+
17
+
18
+ class NeuronLMConfig(PreTrainedConfig):
19
+ """Configuration for the NeuronLM decoder-only language model.
20
+
21
+ ``rope_theta`` remains accepted as a compatibility alias, but new
22
+ configurations serialize rotary settings through Transformers v5's
23
+ ``rope_parameters`` field.
24
+ """
25
+
26
+ model_type = "neuron_lm"
27
+ keys_to_ignore_at_inference = ["past_key_values"]
28
+
29
+ # Frozen 4K-context training presets live as YAML files under this
30
+ # directory (one `<name>.yaml` file per preset), not in this module.
31
+ # Add or change a preset by editing/adding a YAML file, not this class.
32
+ PRESETS_DIR: ClassVar[Path] = Path("configs/model")
33
+
34
+ def __init__(
35
+ self,
36
+ vocab_size: int = 32_000,
37
+ hidden_size: int = 768,
38
+ intermediate_size: int = 2_048,
39
+ num_hidden_layers: int = 12,
40
+ num_attention_heads: int = 12,
41
+ num_key_value_heads: int | None = None,
42
+ max_position_embeddings: int = 2_048,
43
+ rope_parameters: dict[str, Any] | None = None,
44
+ rope_theta: float | None = None,
45
+ rms_norm_eps: float = 1e-5,
46
+ use_qk_norm: bool = True,
47
+ attention_dropout: float = 0.0,
48
+ residual_dropout: float = 0.0,
49
+ initializer_range: float = 0.02,
50
+ tie_word_embeddings: bool = True,
51
+ use_cache: bool = True,
52
+ # <s>=0, </s>=1, <unk>=2, <pad>=3 is the fixed special-token order
53
+ # every NeuronLM tokenizer trains with (pretokenization.py's
54
+ # DEFAULT_SPECIAL_TOKENS). Defaulting here means .generate() stops at
55
+ # EOS without the caller having to pass it explicitly, and it can
56
+ # still be overridden for a tokenizer with a different layout.
57
+ pad_token_id: int | None = 3,
58
+ bos_token_id: int | None = 0,
59
+ eos_token_id: int | list[int] | None = 1,
60
+ **kwargs: Any,
61
+ ) -> None:
62
+ if rope_parameters is not None and not isinstance(
63
+ rope_parameters,
64
+ dict,
65
+ ):
66
+ raise TypeError(
67
+ "rope_parameters must be a dictionary or None, "
68
+ f"got {type(rope_parameters).__name__}"
69
+ )
70
+
71
+ resolved_rope_parameters = deepcopy(rope_parameters) or {}
72
+ if (
73
+ rope_theta is not None
74
+ and "rope_theta" in resolved_rope_parameters
75
+ and resolved_rope_parameters["rope_theta"] != rope_theta
76
+ ):
77
+ raise ValueError(
78
+ "rope_theta and rope_parameters['rope_theta'] disagree: "
79
+ f"{rope_theta!r} != "
80
+ f"{resolved_rope_parameters['rope_theta']!r}"
81
+ )
82
+ resolved_rope_parameters.setdefault(
83
+ "rope_theta",
84
+ DEFAULT_ROPE_THETA if rope_theta is None else rope_theta,
85
+ )
86
+ resolved_rope_parameters.setdefault("rope_type", "default")
87
+
88
+ self.vocab_size = vocab_size
89
+ self.hidden_size = hidden_size
90
+ self.intermediate_size = intermediate_size
91
+ self.num_hidden_layers = num_hidden_layers
92
+ self.num_attention_heads = num_attention_heads
93
+ self.num_key_value_heads = (
94
+ num_attention_heads if num_key_value_heads is None else num_key_value_heads
95
+ )
96
+ self.max_position_embeddings = max_position_embeddings
97
+ self.rope_parameters = resolved_rope_parameters
98
+ self.rms_norm_eps = rms_norm_eps
99
+ self.use_qk_norm = use_qk_norm
100
+ self.attention_dropout = attention_dropout
101
+ self.residual_dropout = residual_dropout
102
+ self.initializer_range = initializer_range
103
+ self.is_decoder = True
104
+ self.is_encoder_decoder = False
105
+ self.is_causal = True
106
+ self.use_cache = use_cache
107
+
108
+ self._validate_dimensions()
109
+
110
+ kwargs.update(
111
+ {
112
+ "pad_token_id": pad_token_id,
113
+ "bos_token_id": bos_token_id,
114
+ "eos_token_id": eos_token_id,
115
+ "tie_word_embeddings": tie_word_embeddings,
116
+ }
117
+ )
118
+ super().__init__(**kwargs)
119
+
120
+ self._validate()
121
+ self.validate_rope()
122
+
123
+ @classmethod
124
+ def available_presets(cls, presets_dir: str | Path | None = None) -> list[str]:
125
+ """List preset names discoverable as YAML files in ``presets_dir``."""
126
+
127
+ directory = Path(presets_dir) if presets_dir is not None else cls.PRESETS_DIR
128
+ if not directory.is_dir():
129
+ return []
130
+ return sorted(path.stem for path in directory.glob("*.yaml"))
131
+
132
+ @classmethod
133
+ def _load_preset(cls, name: str, presets_dir: str | Path | None) -> dict[str, Any]:
134
+ directory = Path(presets_dir) if presets_dir is not None else cls.PRESETS_DIR
135
+ preset_path = directory / f"{name}.yaml"
136
+ try:
137
+ with preset_path.open("r", encoding="utf-8") as handle:
138
+ preset = yaml.safe_load(handle)
139
+ except OSError as error:
140
+ available = ", ".join(cls.available_presets(directory))
141
+ raise ValueError(
142
+ f"Unknown NeuronLM preset {name!r}; available presets: {available}"
143
+ ) from error
144
+
145
+ if not isinstance(preset, dict):
146
+ raise ValueError(
147
+ f"preset file {preset_path} must contain a YAML mapping of "
148
+ "structural fields"
149
+ )
150
+ return preset
151
+
152
+ @classmethod
153
+ def from_preset(
154
+ cls,
155
+ name: str,
156
+ *,
157
+ vocab_size: int = 32_000,
158
+ presets_dir: str | Path | None = None,
159
+ **overrides: Any,
160
+ ) -> NeuronLMConfig:
161
+ """Construct one of the frozen 4K-context training presets.
162
+
163
+ Presets are loaded from YAML files under ``presets_dir`` (defaults
164
+ to ``cls.PRESETS_DIR``), one file per preset named ``<name>.yaml``.
165
+ """
166
+
167
+ preset = deepcopy(cls._load_preset(name, presets_dir))
168
+
169
+ structural_fields = set(preset)
170
+ conflicting = structural_fields.intersection(overrides)
171
+ if conflicting:
172
+ names = ", ".join(sorted(conflicting))
173
+ raise ValueError(
174
+ f"Preset {name!r} has frozen structural fields and cannot "
175
+ f"override: {names}"
176
+ )
177
+
178
+ return cls(vocab_size=vocab_size, **preset, **overrides)
179
+
180
+ def _validate(self) -> None:
181
+ self._validate_dimensions()
182
+
183
+ if self.head_dim % 2 != 0:
184
+ raise ValueError(
185
+ f"RoPE requires an even head dimension, got head_dim={self.head_dim}"
186
+ )
187
+
188
+ if self.rope_parameters.get("rope_type") != "default":
189
+ raise ValueError(
190
+ "NeuronLM currently supports only default RoPE; context "
191
+ "extrapolation methods are intentionally deferred"
192
+ )
193
+
194
+ if not _is_positive_finite_number(self.rope_theta):
195
+ raise ValueError(f"rope_theta must be positive, got {self.rope_theta}")
196
+
197
+ if not _is_positive_finite_number(self.rms_norm_eps):
198
+ raise ValueError(f"rms_norm_eps must be positive, got {self.rms_norm_eps}")
199
+
200
+ if type(self.use_qk_norm) is not bool:
201
+ raise ValueError(f"use_qk_norm must be a boolean, got {self.use_qk_norm!r}")
202
+
203
+ for name, value in {
204
+ "attention_dropout": self.attention_dropout,
205
+ "residual_dropout": self.residual_dropout,
206
+ }.items():
207
+ if (
208
+ isinstance(value, bool)
209
+ or not isinstance(value, (int, float))
210
+ or not math.isfinite(float(value))
211
+ or not 0.0 <= value < 1.0
212
+ ):
213
+ raise ValueError(f"{name} must be in [0, 1), got {value}")
214
+
215
+ if not _is_positive_finite_number(self.initializer_range):
216
+ raise ValueError(
217
+ f"initializer_range must be positive, got {self.initializer_range}"
218
+ )
219
+
220
+ def _validate_dimensions(self) -> None:
221
+ positive_int_fields = {
222
+ "vocab_size": self.vocab_size,
223
+ "hidden_size": self.hidden_size,
224
+ "intermediate_size": self.intermediate_size,
225
+ "num_hidden_layers": self.num_hidden_layers,
226
+ "num_attention_heads": self.num_attention_heads,
227
+ "num_key_value_heads": self.num_key_value_heads,
228
+ "max_position_embeddings": self.max_position_embeddings,
229
+ }
230
+
231
+ for name, value in positive_int_fields.items():
232
+ if type(value) is not int or value <= 0:
233
+ raise ValueError(f"{name} must be a positive integer, got {value!r}")
234
+
235
+ if self.hidden_size % self.num_attention_heads != 0:
236
+ raise ValueError(
237
+ f"hidden_size={self.hidden_size} must be divisible by "
238
+ f"num_attention_heads={self.num_attention_heads}"
239
+ )
240
+
241
+ if self.num_attention_heads % self.num_key_value_heads != 0:
242
+ raise ValueError(
243
+ "num_attention_heads must be divisible by "
244
+ "num_key_value_heads, got "
245
+ f"{self.num_attention_heads} and "
246
+ f"{self.num_key_value_heads}"
247
+ )
248
+
249
+ @property
250
+ def head_dim(self) -> int:
251
+ return self.hidden_size // self.num_attention_heads
252
+
253
+ @property
254
+ def qkv_projection_size(self) -> int:
255
+ return (self.num_attention_heads + 2 * self.num_key_value_heads) * self.head_dim
256
+
257
+ @property
258
+ def rope_theta(self) -> float:
259
+ return float(self.rope_parameters["rope_theta"])
260
+
261
+ @property
262
+ def d_model(self) -> int:
263
+ return self.hidden_size
264
+
265
+ @property
266
+ def d_ff(self) -> int:
267
+ return self.intermediate_size
268
+
269
+ @property
270
+ def num_layers(self) -> int:
271
+ return self.num_hidden_layers
272
+
273
+ @property
274
+ def num_heads(self) -> int:
275
+ return self.num_attention_heads
276
+
277
+ @property
278
+ def context_length(self) -> int:
279
+ return self.max_position_embeddings
280
+
281
+
282
+ def _is_positive_finite_number(value: Any) -> bool:
283
+ return (
284
+ not isinstance(value, bool)
285
+ and isinstance(value, (int, float))
286
+ and math.isfinite(float(value))
287
+ and value > 0
288
+ )
generation_config.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 0,
4
+ "do_sample": true,
5
+ "eos_token_id": 5,
6
+ "output_attentions": false,
7
+ "output_hidden_states": false,
8
+ "pad_token_id": 3,
9
+ "transformers_version": "5.12.1",
10
+ "use_cache": true
11
+ }
layers.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import torch
4
+ import torch.nn.functional as F
5
+ from torch import Tensor, nn
6
+
7
+ __all__ = [
8
+ "RMSNorm",
9
+ "SwiGLU",
10
+ ]
11
+
12
+
13
+ class RMSNorm(nn.RMSNorm):
14
+ def __init__(
15
+ self,
16
+ hidden_size: int,
17
+ eps: float = 1e-5,
18
+ *,
19
+ device: torch.device | str | None = None,
20
+ dtype: torch.dtype | None = None,
21
+ ) -> None:
22
+ if hidden_size <= 0:
23
+ raise ValueError(f"hidden_size must be positive, got {hidden_size}")
24
+
25
+ if eps <= 0.0:
26
+ raise ValueError(f"eps must be positive, got {eps}")
27
+
28
+ super().__init__(
29
+ normalized_shape=hidden_size,
30
+ eps=eps,
31
+ elementwise_affine=True,
32
+ device=device,
33
+ dtype=dtype,
34
+ )
35
+
36
+ self.hidden_size = hidden_size
37
+
38
+
39
+ class SwiGLU(nn.Module):
40
+ def __init__(
41
+ self,
42
+ hidden_size: int,
43
+ intermediate_size: int,
44
+ *,
45
+ device: torch.device | str | None = None,
46
+ dtype: torch.dtype | None = None,
47
+ ) -> None:
48
+ super().__init__()
49
+
50
+ if hidden_size <= 0:
51
+ raise ValueError(f"hidden_size must be positive, got {hidden_size}")
52
+
53
+ if intermediate_size <= 0:
54
+ raise ValueError(
55
+ f"intermediate_size must be positive, got {intermediate_size}"
56
+ )
57
+
58
+ self.hidden_size = hidden_size
59
+ self.intermediate_size = intermediate_size
60
+
61
+ self.gate_up_proj = nn.Linear(
62
+ in_features=hidden_size,
63
+ out_features=2 * intermediate_size,
64
+ bias=False,
65
+ device=device,
66
+ dtype=dtype,
67
+ )
68
+
69
+ self.down_proj = nn.Linear(
70
+ in_features=intermediate_size,
71
+ out_features=hidden_size,
72
+ bias=False,
73
+ device=device,
74
+ dtype=dtype,
75
+ )
76
+
77
+ def forward(self, hidden_states: Tensor) -> Tensor:
78
+ gate, up = self.gate_up_proj(hidden_states).chunk(2, dim=-1)
79
+
80
+ hidden_states = F.silu(gate) * up
81
+ return self.down_proj(hidden_states)
82
+
83
+ def extra_repr(self) -> str:
84
+ return (
85
+ f"hidden_size={self.hidden_size}, "
86
+ f"intermediate_size={self.intermediate_size}, "
87
+ "bias=False"
88
+ )
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4ac18b7d0f870b7d82c774b9cca60e246031683ade0c54d8dac8b741bbbd51d6
3
+ size 11022175080
modeling_neuron_lm.py ADDED
@@ -0,0 +1,644 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from copy import copy
5
+ from typing import Any, cast
6
+
7
+ import torch
8
+ from torch import Tensor, nn
9
+ from torch.utils.checkpoint import checkpoint
10
+ from transformers import Cache, DynamicCache, PreTrainedModel
11
+ from transformers.generation.utils import GenerationMixin
12
+ from transformers.masking_utils import create_causal_mask
13
+ from transformers.modeling_outputs import (
14
+ BaseModelOutputWithPast,
15
+ CausalLMOutputWithPast,
16
+ )
17
+
18
+ from .attention import NeuronLMAttention
19
+ from .configuration_neuron_lm import NeuronLMConfig
20
+ from .layers import RMSNorm, SwiGLU
21
+ from .rotary import RotaryEmbedding
22
+
23
+ __all__ = [
24
+ "NeuronLMDecoderLayer",
25
+ "NeuronLMPreTrainedModel",
26
+ "NeuronLMModel",
27
+ "NeuronLMForCausalLM",
28
+ ]
29
+
30
+ # Marks the linear projection that writes a residual branch back into the
31
+ # residual stream. ``_init_weights`` scales these down by
32
+ # ``1 / sqrt(2 * num_hidden_layers)`` so residual-stream variance stays
33
+ # roughly constant with depth at initialization (GPT-2 / OLMo convention).
34
+ # Set through ``setattr`` because ``nn.Module.__setattr__`` is typed for
35
+ # parameters, buffers, and submodules only.
36
+ RESIDUAL_PROJECTION_FLAG = "_neuron_lm_residual_projection"
37
+
38
+
39
+ def _cache_seq_length(cache: Cache | None, layer_idx: int = 0) -> int:
40
+ if cache is None:
41
+ return 0
42
+ length = cache.get_seq_length(layer_idx)
43
+ if isinstance(length, Tensor):
44
+ # ``.item()`` is a graph break under torch.compile. Callers only reach
45
+ # this path when they did not supply position_ids, and generation
46
+ # always supplies them.
47
+ length = length.item()
48
+ return int(length)
49
+
50
+
51
+ class NeuronLMDecoderLayer(nn.Module):
52
+ def __init__(
53
+ self,
54
+ config: NeuronLMConfig,
55
+ layer_idx: int,
56
+ ) -> None:
57
+ super().__init__()
58
+
59
+ if type(layer_idx) is not int or layer_idx < 0:
60
+ raise ValueError(
61
+ f"layer_idx must be a non-negative integer, got {layer_idx!r}"
62
+ )
63
+
64
+ self.hidden_size = config.hidden_size
65
+ self.layer_idx = layer_idx
66
+
67
+ self.input_layernorm = RMSNorm(
68
+ hidden_size=config.hidden_size,
69
+ eps=config.rms_norm_eps,
70
+ )
71
+
72
+ self.self_attn = NeuronLMAttention(
73
+ config,
74
+ layer_idx=layer_idx,
75
+ )
76
+
77
+ self.post_attention_layernorm = RMSNorm(
78
+ hidden_size=config.hidden_size,
79
+ eps=config.rms_norm_eps,
80
+ )
81
+
82
+ self.mlp = SwiGLU(
83
+ hidden_size=config.hidden_size,
84
+ intermediate_size=config.intermediate_size,
85
+ )
86
+
87
+ self.residual_dropout = nn.Dropout(
88
+ p=config.residual_dropout,
89
+ )
90
+
91
+ # Both branches of this layer write through these two projections.
92
+ setattr(self.self_attn.out_proj, RESIDUAL_PROJECTION_FLAG, True)
93
+ setattr(self.mlp.down_proj, RESIDUAL_PROJECTION_FLAG, True)
94
+
95
+ def forward(
96
+ self,
97
+ hidden_states: Tensor,
98
+ position_embeddings: tuple[Tensor, Tensor],
99
+ attention_mask: Tensor | None = None,
100
+ past_key_values: Cache | None = None,
101
+ output_attentions: bool = False,
102
+ ) -> Tensor | tuple[Tensor, Tensor | None]:
103
+ residual = hidden_states
104
+
105
+ hidden_states = self.input_layernorm(hidden_states)
106
+ attention_outputs = self.self_attn(
107
+ hidden_states=hidden_states,
108
+ position_embeddings=position_embeddings,
109
+ attention_mask=attention_mask,
110
+ past_key_values=past_key_values,
111
+ output_attentions=output_attentions,
112
+ )
113
+ if output_attentions:
114
+ hidden_states, attention_weights = attention_outputs
115
+ else:
116
+ hidden_states = attention_outputs
117
+ attention_weights = None
118
+ hidden_states = residual + self.residual_dropout(hidden_states)
119
+
120
+ residual = hidden_states
121
+
122
+ hidden_states = self.post_attention_layernorm(hidden_states)
123
+ hidden_states = self.mlp(hidden_states)
124
+ hidden_states = residual + self.residual_dropout(hidden_states)
125
+
126
+ if output_attentions:
127
+ return hidden_states, attention_weights
128
+ return hidden_states
129
+
130
+
131
+ class NeuronLMPreTrainedModel(PreTrainedModel):
132
+ config_class = NeuronLMConfig
133
+ base_model_prefix = "model"
134
+
135
+ supports_gradient_checkpointing = True
136
+
137
+ _no_split_modules = ["NeuronLMDecoderLayer"]
138
+
139
+ # Backends verified against the SDPA reference in tests/test_attention.py.
140
+ # `_supports_flash_attn` stays unset: flash-attn is not installed here, so
141
+ # the claim cannot be tested, and SDPA already dispatches flash kernels on
142
+ # recent hardware. FlexAttention is what intra-document masking compiles
143
+ # its BlockMask through.
144
+ _supports_sdpa = True
145
+ _supports_flex_attn = True
146
+
147
+ # The forward is free of data-dependent control flow, so Transformers may
148
+ # use its compiled generation path. Regression coverage:
149
+ # tests/test_modeling.py::test_forward_compiles_as_a_full_graph.
150
+ _can_compile_fullgraph = True
151
+
152
+ # _tp_plan is intentionally unset. The fused qkv_proj packs three blocks
153
+ # whose sizes follow the GQA head counts (num_attention_heads,
154
+ # num_key_value_heads, num_key_value_heads), while Transformers'
155
+ # "packed_colwise" style assumes two equally sized blocks -- it would cut
156
+ # through the K block. Supporting tensor parallelism here needs both a
157
+ # custom sharding style and a _project_qkv that splits on per-rank head
158
+ # counts.
159
+ #
160
+ # That work is not on the critical path: FSDP2 (configs/accelerate/
161
+ # fsdp2.yaml) shards an 8B AdamW run to roughly 30 GiB per GPU across
162
+ # four devices, so memory is not the binding constraint at the sizes this
163
+ # model targets. Revisit if serving latency or a much larger model makes
164
+ # tensor parallelism necessary; TrainingArguments.parallelism_config is
165
+ # the entry point.
166
+
167
+ def residual_initializer_std(self) -> float:
168
+ """Initialization std for projections feeding the residual stream.
169
+
170
+ Scaling by ``1 / sqrt(2 * num_hidden_layers)`` keeps the variance of
171
+ the residual stream from growing with depth. There are two residual
172
+ branches per decoder layer, hence the factor of two.
173
+ """
174
+
175
+ depth_scale = math.sqrt(2.0 * self.config.num_hidden_layers)
176
+ return self.config.initializer_range / depth_scale
177
+
178
+ def _init_weights(self, module: nn.Module) -> None:
179
+
180
+ if isinstance(module, nn.Linear):
181
+ if getattr(module, RESIDUAL_PROJECTION_FLAG, False):
182
+ std = self.residual_initializer_std()
183
+ else:
184
+ std = self.config.initializer_range
185
+
186
+ module.weight.data.normal_(
187
+ mean=0.0,
188
+ std=std,
189
+ )
190
+
191
+ if module.bias is not None:
192
+ module.bias.data.zero_()
193
+
194
+ elif isinstance(module, nn.Embedding):
195
+ module.weight.data.normal_(
196
+ mean=0.0,
197
+ std=self.config.initializer_range,
198
+ )
199
+
200
+ if module.padding_idx is not None:
201
+ module.weight.data[module.padding_idx].zero_()
202
+
203
+ elif isinstance(module, nn.RMSNorm):
204
+ if module.elementwise_affine:
205
+ module.weight.data.fill_(1.0)
206
+
207
+ elif isinstance(module, RotaryEmbedding):
208
+ module.reset_parameters()
209
+
210
+
211
+ class NeuronLMModel(NeuronLMPreTrainedModel):
212
+ def __init__(self, config: NeuronLMConfig) -> None:
213
+ super().__init__(config)
214
+
215
+ self.padding_idx = config.pad_token_id
216
+ self.vocab_size = config.vocab_size
217
+
218
+ self.embed_tokens = nn.Embedding(
219
+ num_embeddings=config.vocab_size,
220
+ embedding_dim=config.hidden_size,
221
+ padding_idx=config.pad_token_id,
222
+ )
223
+
224
+ self.layers = nn.ModuleList(
225
+ [
226
+ NeuronLMDecoderLayer(
227
+ config=config,
228
+ layer_idx=layer_idx,
229
+ )
230
+ for layer_idx in range(config.num_hidden_layers)
231
+ ]
232
+ )
233
+
234
+ self.norm = RMSNorm(
235
+ hidden_size=config.hidden_size,
236
+ eps=config.rms_norm_eps,
237
+ )
238
+
239
+ # RoPE frequencies are computed once per model forward and shared by
240
+ # all decoder layers.
241
+ self.rotary_emb = RotaryEmbedding(
242
+ head_dim=config.head_dim,
243
+ base=config.rope_theta,
244
+ )
245
+
246
+ # PreTrainedModel.gradient_checkpointing_enable() updates this flag
247
+ # and assigns self._gradient_checkpointing_func.
248
+ self.gradient_checkpointing = False
249
+
250
+ self.post_init()
251
+
252
+ def get_input_embeddings(self) -> nn.Embedding:
253
+ return self.embed_tokens
254
+
255
+ def set_input_embeddings(
256
+ self,
257
+ value: nn.Embedding,
258
+ ) -> None:
259
+ self.embed_tokens = value
260
+
261
+ def forward(
262
+ self,
263
+ input_ids: Tensor | None = None,
264
+ attention_mask: Tensor | None = None,
265
+ position_ids: Tensor | None = None,
266
+ inputs_embeds: Tensor | None = None,
267
+ past_key_values: Cache | None = None,
268
+ use_cache: bool | None = None,
269
+ output_attentions: bool | None = None,
270
+ output_hidden_states: bool | None = None,
271
+ return_dict: bool | None = None,
272
+ **kwargs: Any,
273
+ ) -> BaseModelOutputWithPast | tuple[Tensor, ...]:
274
+ output_attentions = (
275
+ output_attentions
276
+ if output_attentions is not None
277
+ else self.config.output_attentions
278
+ )
279
+ output_hidden_states = (
280
+ output_hidden_states
281
+ if output_hidden_states is not None
282
+ else self.config.output_hidden_states
283
+ )
284
+ return_dict = (
285
+ return_dict if return_dict is not None else self.config.return_dict
286
+ )
287
+ use_cache = (
288
+ use_cache
289
+ if use_cache is not None
290
+ else (self.config.use_cache or past_key_values is not None)
291
+ )
292
+
293
+ if kwargs:
294
+ unsupported = ", ".join(sorted(kwargs))
295
+ raise TypeError(f"Unsupported model forward arguments: {unsupported}")
296
+
297
+ if past_key_values is not None and not isinstance(
298
+ past_key_values,
299
+ Cache,
300
+ ):
301
+ raise TypeError(
302
+ "past_key_values must be a Hugging Face Cache instance; "
303
+ "legacy tuple caches are not supported"
304
+ )
305
+
306
+ # Cache mutation is incompatible with recomputation during backward.
307
+ # This mirrors the behavior of the current Transformers decoder
308
+ # layers while keeping the public forward API convenient.
309
+ if self.gradient_checkpointing and self.training:
310
+ use_cache = False
311
+ past_key_values = None
312
+
313
+ if use_cache:
314
+ if past_key_values is None:
315
+ past_key_values = DynamicCache(config=self.config)
316
+ elif past_key_values is not None:
317
+ raise ValueError("past_key_values can only be used when use_cache=True")
318
+
319
+ if (input_ids is None) == (inputs_embeds is None):
320
+ raise ValueError("Specify exactly one of input_ids or inputs_embeds")
321
+
322
+ if input_ids is not None:
323
+ if input_ids.ndim != 2:
324
+ raise ValueError(
325
+ "input_ids must have shape "
326
+ "(batch_size, sequence_length), "
327
+ f"got shape={tuple(input_ids.shape)}"
328
+ )
329
+
330
+ inputs_embeds = self.embed_tokens(input_ids)
331
+
332
+ assert inputs_embeds is not None
333
+
334
+ if inputs_embeds.ndim != 3:
335
+ raise ValueError(
336
+ "inputs_embeds must have shape "
337
+ "(batch_size, sequence_length, hidden_size), "
338
+ f"got shape={tuple(inputs_embeds.shape)}"
339
+ )
340
+
341
+ batch_size, sequence_length, hidden_size = inputs_embeds.shape
342
+
343
+ if hidden_size != self.config.hidden_size:
344
+ raise ValueError(
345
+ f"Expected hidden_size={self.config.hidden_size}, "
346
+ f"got hidden_size={hidden_size}"
347
+ )
348
+
349
+ if sequence_length == 0:
350
+ raise ValueError("sequence_length must be greater than zero")
351
+
352
+ past_key_length = (
353
+ _cache_seq_length(past_key_values) if past_key_values is not None else 0
354
+ )
355
+ hidden_states = inputs_embeds
356
+
357
+ if position_ids is None:
358
+ position_ids = torch.arange(
359
+ past_key_length,
360
+ past_key_length + sequence_length,
361
+ dtype=torch.long,
362
+ device=hidden_states.device,
363
+ ).unsqueeze(0)
364
+ else:
365
+ if position_ids.ndim not in {1, 2}:
366
+ raise ValueError(
367
+ "position_ids must have shape (sequence_length,) or "
368
+ "(batch_size, sequence_length), "
369
+ f"got shape={tuple(position_ids.shape)}"
370
+ )
371
+ if position_ids.shape[-1] != sequence_length:
372
+ raise ValueError(
373
+ "The final position_ids dimension must equal the "
374
+ f"sequence length {sequence_length}, got "
375
+ f"{position_ids.shape[-1]}"
376
+ )
377
+ position_ids = position_ids.to(
378
+ device=hidden_states.device,
379
+ dtype=torch.long,
380
+ )
381
+ if position_ids.ndim == 1:
382
+ position_ids = position_ids.unsqueeze(0)
383
+
384
+ # Transformers derives packed-document boundaries from gaps in
385
+ # position_ids, and that detection requires a 2D tensor.
386
+ # See create_causal_mask / find_packed_sequence_indices.
387
+
388
+ # Reading position_ids.max() is data-dependent control flow, which
389
+ # torch.compile cannot trace in a full graph. The bound is a static
390
+ # property of the config, so the eager check is sufficient: any shape
391
+ # that would trip it also trips it before compilation warms up.
392
+ if (
393
+ not torch.compiler.is_compiling()
394
+ and position_ids.numel() > 0
395
+ and position_ids.max() >= self.config.max_position_embeddings
396
+ ):
397
+ raise ValueError(
398
+ "position_ids contain a position at or beyond "
399
+ f"max_position_embeddings={self.config.max_position_embeddings}"
400
+ )
401
+
402
+ position_embeddings = self.rotary_emb(
403
+ hidden_states,
404
+ position_ids=position_ids,
405
+ )
406
+
407
+ mask_config = self.config
408
+ if output_attentions and self.config._attn_implementation != "eager":
409
+ mask_config = copy(self.config)
410
+ mask_config._attn_implementation = "eager"
411
+
412
+ causal_attention_mask = create_causal_mask(
413
+ config=mask_config,
414
+ inputs_embeds=inputs_embeds,
415
+ attention_mask=attention_mask,
416
+ past_key_values=past_key_values,
417
+ position_ids=position_ids,
418
+ )
419
+
420
+ all_hidden_states: tuple[Tensor, ...] | None = (
421
+ () if output_hidden_states else None
422
+ )
423
+ all_self_attentions: tuple[Tensor, ...] | None = (
424
+ () if output_attentions else None
425
+ )
426
+
427
+ for decoder_layer in self.layers:
428
+ decoder_layer = cast(NeuronLMDecoderLayer, decoder_layer)
429
+ if all_hidden_states is not None:
430
+ all_hidden_states += (hidden_states,)
431
+
432
+ if self.gradient_checkpointing and self.training:
433
+
434
+ def custom_forward(
435
+ states: Tensor,
436
+ layer: NeuronLMDecoderLayer = decoder_layer,
437
+ ) -> Tensor | tuple[Tensor, Tensor | None]:
438
+ return layer(
439
+ hidden_states=states,
440
+ position_embeddings=position_embeddings,
441
+ attention_mask=causal_attention_mask,
442
+ past_key_values=None,
443
+ output_attentions=output_attentions,
444
+ )
445
+
446
+ checkpointing_function = getattr(
447
+ self,
448
+ "_gradient_checkpointing_func",
449
+ None,
450
+ )
451
+
452
+ if checkpointing_function is None:
453
+ layer_outputs = checkpoint(
454
+ custom_forward,
455
+ hidden_states,
456
+ use_reentrant=False,
457
+ )
458
+ else:
459
+ layer_outputs = checkpointing_function(
460
+ custom_forward,
461
+ hidden_states,
462
+ )
463
+ else:
464
+ layer_outputs = decoder_layer(
465
+ hidden_states=hidden_states,
466
+ position_embeddings=position_embeddings,
467
+ attention_mask=causal_attention_mask,
468
+ past_key_values=past_key_values,
469
+ output_attentions=output_attentions,
470
+ )
471
+
472
+ if output_attentions:
473
+ hidden_states, attention_weights = layer_outputs
474
+ assert all_self_attentions is not None
475
+ assert attention_weights is not None
476
+ all_self_attentions += (attention_weights,)
477
+ else:
478
+ hidden_states = layer_outputs
479
+
480
+ hidden_states = self.norm(hidden_states)
481
+
482
+ if all_hidden_states is not None:
483
+ all_hidden_states += (hidden_states,)
484
+
485
+ if not return_dict:
486
+ outputs: tuple[Any, ...] = (hidden_states,)
487
+
488
+ if use_cache:
489
+ outputs += (past_key_values,)
490
+
491
+ if output_hidden_states:
492
+ outputs += (all_hidden_states,)
493
+
494
+ if output_attentions:
495
+ outputs += (all_self_attentions,)
496
+
497
+ return outputs
498
+
499
+ return BaseModelOutputWithPast(
500
+ last_hidden_state=hidden_states,
501
+ past_key_values=past_key_values if use_cache else None,
502
+ hidden_states=cast(Any, all_hidden_states),
503
+ attentions=cast(Any, all_self_attentions),
504
+ )
505
+
506
+
507
+ class NeuronLMForCausalLM(
508
+ NeuronLMPreTrainedModel,
509
+ GenerationMixin,
510
+ ):
511
+ """NeuronLM decoder with a causal language-modeling head."""
512
+
513
+ _tied_weights_keys = {
514
+ "lm_head.weight": "model.embed_tokens.weight",
515
+ }
516
+
517
+ def __init__(self, config: NeuronLMConfig) -> None:
518
+ super().__init__(config)
519
+
520
+ self.model = NeuronLMModel(config)
521
+ self.vocab_size = config.vocab_size
522
+
523
+ self.lm_head = nn.Linear(
524
+ in_features=config.hidden_size,
525
+ out_features=config.vocab_size,
526
+ bias=False,
527
+ )
528
+
529
+ self.post_init()
530
+
531
+ def get_input_embeddings(self) -> nn.Embedding:
532
+ return self.model.embed_tokens
533
+
534
+ def set_input_embeddings(
535
+ self,
536
+ value: nn.Embedding,
537
+ ) -> None:
538
+ self.model.embed_tokens = value
539
+
540
+ def get_output_embeddings(self) -> nn.Linear:
541
+ return self.lm_head
542
+
543
+ def set_output_embeddings(
544
+ self,
545
+ value: nn.Linear,
546
+ ) -> None:
547
+ self.lm_head = value
548
+
549
+ def get_decoder(self) -> NeuronLMModel:
550
+ return self.model
551
+
552
+ def set_decoder(
553
+ self,
554
+ decoder: NeuronLMModel,
555
+ ) -> None:
556
+ self.model = decoder
557
+
558
+ def forward(
559
+ self,
560
+ input_ids: Tensor | None = None,
561
+ attention_mask: Tensor | None = None,
562
+ position_ids: Tensor | None = None,
563
+ inputs_embeds: Tensor | None = None,
564
+ labels: Tensor | None = None,
565
+ past_key_values: Cache | None = None,
566
+ use_cache: bool | None = None,
567
+ output_attentions: bool | None = None,
568
+ output_hidden_states: bool | None = None,
569
+ return_dict: bool | None = None,
570
+ num_items_in_batch: Tensor | int | None = None,
571
+ **kwargs: Any,
572
+ ) -> CausalLMOutputWithPast | tuple[Tensor, ...]:
573
+ return_dict = (
574
+ return_dict if return_dict is not None else self.config.return_dict
575
+ )
576
+
577
+ model_outputs = self.model(
578
+ input_ids=input_ids,
579
+ attention_mask=attention_mask,
580
+ position_ids=position_ids,
581
+ inputs_embeds=inputs_embeds,
582
+ past_key_values=past_key_values,
583
+ use_cache=use_cache,
584
+ output_attentions=output_attentions,
585
+ output_hidden_states=output_hidden_states,
586
+ return_dict=return_dict,
587
+ **kwargs,
588
+ )
589
+
590
+ if return_dict:
591
+ hidden_states = model_outputs.last_hidden_state
592
+ else:
593
+ hidden_states = model_outputs[0]
594
+
595
+ logits = self.lm_head(hidden_states)
596
+
597
+ loss: Tensor | None = None
598
+
599
+ if labels is not None:
600
+ if labels.ndim != 2:
601
+ raise ValueError(
602
+ "labels must have shape "
603
+ "(batch_size, sequence_length), "
604
+ f"got shape={tuple(labels.shape)}"
605
+ )
606
+
607
+ expected_shape = hidden_states.shape[:2]
608
+
609
+ if tuple(labels.shape) != tuple(expected_shape):
610
+ raise ValueError(
611
+ f"labels must have shape {tuple(expected_shape)}, "
612
+ f"got {tuple(labels.shape)}"
613
+ )
614
+
615
+ if labels.shape[1] < 2:
616
+ raise ValueError(
617
+ "At least two sequence positions are required "
618
+ "to compute causal language-modeling loss"
619
+ )
620
+
621
+ labels = labels.to(device=logits.device)
622
+
623
+ loss = self.loss_function(
624
+ logits=logits,
625
+ labels=labels,
626
+ vocab_size=self.config.vocab_size,
627
+ num_items_in_batch=num_items_in_batch,
628
+ )
629
+
630
+ if not return_dict:
631
+ output = (logits,) + model_outputs[1:]
632
+
633
+ if loss is not None:
634
+ return (loss,) + output
635
+
636
+ return output
637
+
638
+ return CausalLMOutputWithPast(
639
+ loss=cast(Any, loss),
640
+ logits=logits,
641
+ past_key_values=model_outputs.past_key_values,
642
+ hidden_states=model_outputs.hidden_states,
643
+ attentions=model_outputs.attentions,
644
+ )
rotary.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Adjacent-pair (GPT-J style) rotary position embeddings.
2
+
3
+ Convention, which matters when exporting a trained checkpoint: this module
4
+ rotates *adjacent* channel pairs ``(x0, x1), (x2, x3), ...``. Llama and most
5
+ Hugging Face models instead rotate *half-split* pairs ``(x0, x_{d/2}), ...``
6
+ ("NeoX style"). The two are related by a permutation of the query/key rows,
7
+ so a checkpoint trained here is NOT drop-in loadable as a Llama checkpoint
8
+ without permuting ``qkv_proj``.
9
+
10
+ Both conventions are first-class in the common inference runtimes -- select
11
+ GPT-J/``NORM``-style rotary rather than ``NEOX`` when converting. Concretely:
12
+ ``llama.cpp`` ``rope_type=NORM``, vLLM ``is_neox_style=False``.
13
+
14
+ ``cos``/``sin`` here have shape ``(..., sequence_length, head_dim / 2)``,
15
+ half the width of the Hugging Face convention, because adjacent-pair rotation
16
+ needs one angle per pair rather than a duplicated pair of angles. That makes
17
+ this form measurably cheaper than the half-split ``rotate_half`` formulation,
18
+ which needs full-width tables and a concatenation.
19
+
20
+ Regression coverage for the convention itself lives in
21
+ ``tests/test_rotary.py::manual_adjacent_pair_rotation``.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import math
27
+
28
+ import torch
29
+ from torch import Tensor, nn
30
+
31
+ __all__ = [
32
+ "RotaryEmbedding",
33
+ "apply_rotary_pos_emb",
34
+ ]
35
+
36
+
37
+ _INTEGER_DTYPES = {
38
+ torch.uint8,
39
+ torch.int8,
40
+ torch.int16,
41
+ torch.int32,
42
+ torch.int64,
43
+ }
44
+
45
+
46
+ class RotaryEmbedding(nn.Module):
47
+ def __init__(
48
+ self,
49
+ head_dim: int,
50
+ base: float = 10_000.0,
51
+ *,
52
+ device: torch.device | str | None = None,
53
+ ) -> None:
54
+ super().__init__()
55
+
56
+ if type(head_dim) is not int or head_dim <= 0:
57
+ raise ValueError(f"head_dim must be a positive integer, got {head_dim!r}")
58
+
59
+ if head_dim % 2 != 0:
60
+ raise ValueError(f"head_dim must be even, got head_dim={head_dim}")
61
+
62
+ if (
63
+ isinstance(base, bool)
64
+ or not isinstance(base, (int, float))
65
+ or not math.isfinite(float(base))
66
+ or base <= 0.0
67
+ ):
68
+ raise ValueError(f"base must be a positive finite number, got {base!r}")
69
+
70
+ self.head_dim = head_dim
71
+ self.base = float(base)
72
+
73
+ self.register_buffer(
74
+ "inv_freq",
75
+ torch.empty(
76
+ head_dim // 2,
77
+ dtype=torch.float32,
78
+ device=device,
79
+ ),
80
+ persistent=False,
81
+ )
82
+ self.reset_parameters()
83
+
84
+ def reset_parameters(self) -> None:
85
+ """Reconstruct inverse frequencies on the buffer's current device."""
86
+
87
+ frequency_indices = torch.arange(
88
+ start=0,
89
+ end=self.head_dim,
90
+ step=2,
91
+ dtype=torch.float32,
92
+ device=self.inv_freq.device,
93
+ )
94
+
95
+ inv_freq = self.base ** (-frequency_indices / self.head_dim)
96
+
97
+ # Assignment preserves the registered, non-persistent buffer while
98
+ # also replacing storage allocated by Transformers' meta-device
99
+ # loading path.
100
+ self.inv_freq = inv_freq
101
+
102
+ @torch.no_grad()
103
+ def forward(
104
+ self,
105
+ hidden_states: Tensor,
106
+ position_ids: Tensor | None = None,
107
+ ) -> tuple[Tensor, Tensor]:
108
+
109
+ if hidden_states.ndim < 2:
110
+ raise ValueError(
111
+ "hidden_states must have at least two dimensions, "
112
+ f"got shape={tuple(hidden_states.shape)}"
113
+ )
114
+
115
+ if not hidden_states.is_floating_point():
116
+ raise TypeError(
117
+ "hidden_states must be a floating-point tensor, "
118
+ f"got dtype={hidden_states.dtype}"
119
+ )
120
+
121
+ sequence_length = hidden_states.shape[-2]
122
+
123
+ if position_ids is None:
124
+ position_ids = torch.arange(
125
+ sequence_length,
126
+ device=hidden_states.device,
127
+ dtype=torch.long,
128
+ )
129
+ else:
130
+ if position_ids.ndim not in {1, 2}:
131
+ raise ValueError(
132
+ "position_ids must have shape "
133
+ "(sequence_length,) or "
134
+ "(batch_size, sequence_length), "
135
+ f"got shape={tuple(position_ids.shape)}"
136
+ )
137
+
138
+ if position_ids.shape[-1] != sequence_length:
139
+ raise ValueError(
140
+ "The final position_ids dimension must equal the "
141
+ f"sequence length {sequence_length}, "
142
+ f"got {position_ids.shape[-1]}"
143
+ )
144
+
145
+ if position_ids.dtype not in _INTEGER_DTYPES:
146
+ raise TypeError(
147
+ "position_ids must contain integers, "
148
+ f"got dtype={position_ids.dtype}"
149
+ )
150
+
151
+ position_ids = position_ids.to(
152
+ device=hidden_states.device,
153
+ )
154
+
155
+ # Compute frequencies in float32 even when the model is running in
156
+ # float16 or bfloat16. Cast only the final cosine/sine tensors.
157
+ inv_freq = self.inv_freq.to(
158
+ device=hidden_states.device,
159
+ dtype=torch.float32,
160
+ )
161
+
162
+ positions = position_ids.to(dtype=torch.float32)
163
+
164
+ angles = positions.unsqueeze(-1) * inv_freq
165
+
166
+ cos = angles.cos()
167
+ sin = angles.sin()
168
+
169
+ return (
170
+ cos.to(dtype=hidden_states.dtype),
171
+ sin.to(dtype=hidden_states.dtype),
172
+ )
173
+
174
+ def extra_repr(self) -> str:
175
+ return f"head_dim={self.head_dim}, base={self.base}"
176
+
177
+
178
+ def _reshape_frequencies_for_broadcast(
179
+ frequencies: Tensor,
180
+ target: Tensor,
181
+ ) -> Tensor:
182
+
183
+ extra_dimensions = target.ndim - frequencies.ndim
184
+
185
+ if extra_dimensions < 0:
186
+ raise ValueError(
187
+ "Rotary frequencies have too many dimensions for the target: "
188
+ f"frequencies.ndim={frequencies.ndim}, "
189
+ f"target.ndim={target.ndim}"
190
+ )
191
+
192
+ broadcast_shape = (
193
+ *frequencies.shape[:-2],
194
+ *((1,) * extra_dimensions),
195
+ *frequencies.shape[-2:],
196
+ )
197
+
198
+ return frequencies.reshape(broadcast_shape)
199
+
200
+
201
+ def _apply_rotary(
202
+ hidden_states: Tensor,
203
+ cos: Tensor,
204
+ sin: Tensor,
205
+ ) -> Tensor:
206
+
207
+ if hidden_states.shape[-1] % 2 != 0:
208
+ raise ValueError(
209
+ f"The final hidden dimension must be even, got {hidden_states.shape[-1]}"
210
+ )
211
+
212
+ even_states = hidden_states[..., 0::2]
213
+ odd_states = hidden_states[..., 1::2]
214
+
215
+ cos = _reshape_frequencies_for_broadcast(
216
+ cos,
217
+ even_states,
218
+ )
219
+ sin = _reshape_frequencies_for_broadcast(
220
+ sin,
221
+ even_states,
222
+ )
223
+
224
+ rotated_even = even_states * cos - odd_states * sin
225
+ rotated_odd = even_states * sin + odd_states * cos
226
+
227
+ return torch.stack(
228
+ (rotated_even, rotated_odd),
229
+ dim=-1,
230
+ ).flatten(start_dim=-2)
231
+
232
+
233
+ def apply_rotary_pos_emb(
234
+ query: Tensor,
235
+ key: Tensor,
236
+ cos: Tensor,
237
+ sin: Tensor,
238
+ ) -> tuple[Tensor, Tensor]:
239
+
240
+ if query.ndim < 2 or key.ndim < 2:
241
+ raise ValueError("query and key must each have at least two dimensions")
242
+
243
+ if query.shape[-2] != key.shape[-2]:
244
+ raise ValueError(
245
+ "query and key sequence lengths must match, "
246
+ f"got {query.shape[-2]} and {key.shape[-2]}"
247
+ )
248
+
249
+ if query.shape[-1] != key.shape[-1]:
250
+ raise ValueError(
251
+ "query and key head dimensions must match, "
252
+ f"got {query.shape[-1]} and {key.shape[-1]}"
253
+ )
254
+
255
+ if query.shape[-1] % 2 != 0:
256
+ raise ValueError(
257
+ f"The query/key head dimension must be even, got {query.shape[-1]}"
258
+ )
259
+
260
+ if query.device != key.device:
261
+ raise ValueError(
262
+ "query and key must be on the same device, "
263
+ f"got {query.device} and {key.device}"
264
+ )
265
+
266
+ if query.dtype != key.dtype:
267
+ raise ValueError(
268
+ f"query and key must have the same dtype, got {query.dtype} and {key.dtype}"
269
+ )
270
+
271
+ if cos.shape != sin.shape:
272
+ raise ValueError(
273
+ "cos and sin must have identical shapes, "
274
+ f"got {tuple(cos.shape)} and {tuple(sin.shape)}"
275
+ )
276
+
277
+ expected_frequency_shape = (
278
+ query.shape[-2],
279
+ query.shape[-1] // 2,
280
+ )
281
+
282
+ if cos.shape[-2:] != expected_frequency_shape:
283
+ raise ValueError(
284
+ "The final cosine/sine dimensions must be "
285
+ "(sequence_length, head_dim / 2), "
286
+ f"expected {expected_frequency_shape}, "
287
+ f"got {tuple(cos.shape[-2:])}"
288
+ )
289
+
290
+ if cos.device != query.device or sin.device != query.device:
291
+ raise ValueError("query, key, cos, and sin must be on the same device")
292
+
293
+ # PATCHED (see scripts/prepare_neuronai_5b_base.py): align cos/sin with
294
+ # the query dtype instead of rejecting the pair. Under mixed precision the
295
+ # qkv projections emit bf16 while hidden_states -- and therefore cos/sin --
296
+ # stay fp32, which is normal and which upstream HF models handle by
297
+ # implicit type promotion.
298
+ if cos.dtype != query.dtype:
299
+ cos = cos.to(dtype=query.dtype)
300
+ if sin.dtype != query.dtype:
301
+ sin = sin.to(dtype=query.dtype)
302
+
303
+ return (
304
+ _apply_rotary(query, sin=sin, cos=cos),
305
+ _apply_rotary(key, sin=sin, cos=cos),
306
+ )
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<s>",
4
+ "clean_up_tokenization_spaces": false,
5
+ "eos_token": "<|im_end|>",
6
+ "is_local": true,
7
+ "local_files_only": true,
8
+ "model_max_length": 4096,
9
+ "pad_token": "<pad>",
10
+ "tokenizer_class": "TokenizersBackend",
11
+ "unk_token": "<unk>"
12
+ }