GODELEV commited on
Commit
39d14fa
Β·
verified Β·
1 Parent(s): c8b539b

step 10 | ppl=inf

Browse files
config.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vocab_size": 4096,
3
+ "hidden_size": 256,
4
+ "tie_embeddings": true,
5
+ "num_layers": 11,
6
+ "intermediate_size": 768,
7
+ "num_heads": 4,
8
+ "num_kv_heads": 2,
9
+ "max_seq_len": 1024,
10
+ "rope_theta": 10000.0,
11
+ "qk_norm": true,
12
+ "use_bias": false,
13
+ "ffn_type": "swiglu",
14
+ "activation": "silu",
15
+ "xsa": false,
16
+ "xsa_window": 128,
17
+ "dropout": 0.0,
18
+ "norm_eps": 1e-05,
19
+ "model_type": "slm",
20
+ "tie_word_embeddings": true,
21
+ "auto_map": {
22
+ "AutoConfig": "model.SLMHFConfig",
23
+ "AutoModelForCausalLM": "model.SLMHFForCausalLM"
24
+ },
25
+ "bos_token_id": 2,
26
+ "eos_token_id": 3,
27
+ "pad_token_id": 3,
28
+ "torch_dtype": "float32",
29
+ "transformers_version": "4.40.0"
30
+ }
generation_config.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 2,
3
+ "eos_token_id": 3,
4
+ "max_length": 1024
5
+ }
model.py ADDED
@@ -0,0 +1,575 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ model.py β€” SLM Custom Architecture (v6.5 β€” fully HF-compatible)
3
+ =================================================================
4
+ Auto-written by the training notebook. Required for HF auto_map.
5
+
6
+ v6.5 fixes:
7
+ 1. _tied_weights_keys is a LIST β€” works on transformers 4.x AND 5.x.
8
+ 2. RoPE buffers: persistent=False β€” NOT saved to safetensors.
9
+ 3. tie_weights() properly delegates so HF re-ties weights on load.
10
+ 4. Full HF forward() signature (return_dict, attention_mask, etc.).
11
+ 5. CausalLMOutputWithPast used β€” generation helpers (.generate()) work.
12
+ 6. prepare_inputs_for_generation() added.
13
+ 7. Weights saved WITH model. prefix (matches SLMHFForCausalLM layout).
14
+ """
15
+ from __future__ import annotations
16
+ import math
17
+ from dataclasses import dataclass, asdict
18
+ from typing import Optional, Tuple, List
19
+
20
+ import torch
21
+ import torch.nn as nn
22
+ import torch.nn.functional as F
23
+
24
+ try:
25
+ from transformers import PretrainedConfig, PreTrainedModel
26
+ from transformers.modeling_outputs import CausalLMOutputWithPast
27
+ _HF_AVAILABLE = True
28
+ except ImportError:
29
+ _HF_AVAILABLE = False
30
+
31
+
32
+ # ══════════════════════════════════════════════════════════════════════════════
33
+ # CONFIG DATACLASS
34
+ # ══════════════════════════════════════════════════════════════════════════════
35
+
36
+ @dataclass
37
+ class SLMConfig:
38
+ # Vocab / embedding
39
+ vocab_size: int = 32000
40
+ hidden_size: int = 576
41
+ tie_embeddings: bool = True
42
+
43
+ # Depth / width
44
+ num_layers: int = 12
45
+ intermediate_size: int = 1536
46
+
47
+ # Attention
48
+ num_heads: int = 9
49
+ num_kv_heads: int = 3
50
+ max_seq_len: int = 2048
51
+ rope_theta: float = 500000.0
52
+ qk_norm: bool = True
53
+ use_bias: bool = False
54
+
55
+ # FFN type: 'swiglu' | 'geglu' | 'mlp'
56
+ ffn_type: str = 'swiglu'
57
+ activation: str = 'silu' # used by 'mlp' only
58
+
59
+ # XSA
60
+ xsa: bool = False
61
+ xsa_window: int = 128
62
+
63
+ # Regularisation
64
+ dropout: float = 0.0
65
+ norm_eps: float = 1e-5
66
+
67
+ @property
68
+ def head_dim(self) -> int:
69
+ return self.hidden_size // self.num_heads
70
+
71
+ def validate(self) -> 'SLMConfig':
72
+ assert self.hidden_size % self.num_heads == 0, \
73
+ f"hidden_size {self.hidden_size} % num_heads {self.num_heads} != 0"
74
+ assert self.num_heads % self.num_kv_heads == 0, \
75
+ f"num_heads {self.num_heads} % num_kv_heads {self.num_kv_heads} != 0"
76
+ assert self.ffn_type in ('swiglu', 'geglu', 'mlp'), \
77
+ f"ffn_type must be 'swiglu'|'geglu'|'mlp', got {self.ffn_type!r}"
78
+ assert self.activation in ('silu', 'gelu', 'relu'), \
79
+ f"activation must be 'silu'|'gelu'|'relu', got {self.activation!r}"
80
+ if self.xsa:
81
+ assert self.xsa_window > 0, "xsa_window must be > 0"
82
+ return self
83
+
84
+ def to_dict(self) -> dict:
85
+ d = asdict(self)
86
+ d['model_type'] = 'slm'
87
+ return d
88
+
89
+ @classmethod
90
+ def from_dict(cls, d: dict) -> 'SLMConfig':
91
+ valid = {f for f in cls.__dataclass_fields__}
92
+ return cls(**{k: v for k, v in d.items() if k in valid})
93
+
94
+
95
+ # ══════════════════════════════════════════════════════════════════════════════
96
+ # PRIMITIVES
97
+ # ══════════════════════════════════════════════════════════════════════════════
98
+
99
+ class RMSNorm(nn.Module):
100
+ def __init__(self, dim: int, eps: float = 1e-5):
101
+ super().__init__()
102
+ self.eps = eps
103
+ self.w = nn.Parameter(torch.ones(dim))
104
+
105
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
106
+ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.w
107
+
108
+
109
+ def precompute_rope(head_dim: int, max_len: int,
110
+ theta: float = 10000.0) -> Tuple[torch.Tensor, torch.Tensor]:
111
+ freqs = 1.0 / (theta ** (torch.arange(0, head_dim, 2).float() / head_dim))
112
+ t = torch.arange(max_len, dtype=torch.float32)
113
+ cos = torch.cos(torch.outer(t, freqs))
114
+ sin = torch.sin(torch.outer(t, freqs))
115
+ return cos, sin
116
+
117
+
118
+ def apply_rope(x: torch.Tensor, cos: torch.Tensor,
119
+ sin: torch.Tensor) -> torch.Tensor:
120
+ """Correct split-half RoPE rotation. x: [B,H,T,D]"""
121
+ T = x.shape[2]
122
+ c = cos[:T].unsqueeze(0).unsqueeze(0)
123
+ s = sin[:T].unsqueeze(0).unsqueeze(0)
124
+ x1, x2 = x[..., :x.shape[-1]//2], x[..., x.shape[-1]//2:]
125
+ return torch.cat([x1 * c - x2 * s, x2 * c + x1 * s], dim=-1)
126
+
127
+
128
+ # ══════════════════════════════════════════════════════════════════════════════
129
+ # FFN (SwiGLU / GeGLU / MLP)
130
+ # ══════════════════════════════════════════════════════════════════════════════
131
+
132
+ class FFN(nn.Module):
133
+ def __init__(self, cfg: SLMConfig):
134
+ super().__init__()
135
+ H, I, b = cfg.hidden_size, cfg.intermediate_size, cfg.use_bias
136
+ self.ffn_type = cfg.ffn_type
137
+
138
+ if cfg.ffn_type in ('swiglu', 'geglu'):
139
+ self.gate = nn.Linear(H, I, bias=b)
140
+ self.up = nn.Linear(H, I, bias=b)
141
+ self.down = nn.Linear(I, H, bias=b)
142
+ self._act = F.silu if cfg.ffn_type == 'swiglu' else \
143
+ lambda x: F.gelu(x, approximate='tanh')
144
+ else: # 'mlp'
145
+ self.up = nn.Linear(H, I, bias=b)
146
+ self.down = nn.Linear(I, H, bias=b)
147
+ act = cfg.activation
148
+ self._act = (F.silu if act == 'silu' else
149
+ (lambda x: F.gelu(x, approximate='tanh')) if act == 'gelu'
150
+ else F.relu)
151
+
152
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
153
+ if self.ffn_type in ('swiglu', 'geglu'):
154
+ return self.down(self._act(self.gate(x)) * self.up(x))
155
+ return self.down(self._act(self.up(x)))
156
+
157
+
158
+ # ══════════════════════════════════════════════════════════════════════════════
159
+ # ATTENTION (GQA + RoPE + optional QK-Norm)
160
+ # ══════════════════════════════════════════════════════════════════════════════
161
+
162
+ class GQAttention(nn.Module):
163
+ """GQA with optional QK-Norm and two backends: sdpa / chunked."""
164
+
165
+ def __init__(self, cfg: SLMConfig):
166
+ super().__init__()
167
+ H = cfg.hidden_size
168
+ b = cfg.use_bias
169
+ self.nh = cfg.num_heads
170
+ self.nkv = cfg.num_kv_heads
171
+ self.d = cfg.head_dim
172
+ self.rep = self.nh // self.nkv
173
+ KVD = self.nkv * self.d
174
+
175
+ self.q = nn.Linear(H, H, bias=b)
176
+ self.k = nn.Linear(H, KVD, bias=b)
177
+ self.v = nn.Linear(H, KVD, bias=b)
178
+ self.o = nn.Linear(H, H, bias=b)
179
+ self.dp = nn.Dropout(cfg.dropout)
180
+
181
+ self.qk_norm = cfg.qk_norm
182
+ if self.qk_norm:
183
+ self.q_norm = RMSNorm(self.d, cfg.norm_eps)
184
+ self.k_norm = RMSNorm(self.d, cfg.norm_eps)
185
+
186
+ cos, sin = precompute_rope(self.d, cfg.max_seq_len, cfg.rope_theta)
187
+ # FIX: persistent=False so RoPE buffers are NOT saved to safetensors.
188
+ # They are deterministically recomputed from config at load time.
189
+ self.register_buffer('cos', cos, persistent=False)
190
+ self.register_buffer('sin', sin, persistent=False)
191
+
192
+ self._backend = 'chunked'
193
+ self._chunk_size = 256
194
+
195
+ def _qk_normalize(self, q, k):
196
+ B, nh, T, d = q.shape
197
+ q = self.q_norm(q.reshape(-1, d)).reshape(B, nh, T, d)
198
+ B, nkv, T, d = k.shape
199
+ k = self.k_norm(k.reshape(-1, d)).reshape(B, self.nkv, T, d)
200
+ return q, k
201
+
202
+ def _sdpa(self, q, k, v):
203
+ p = self.dp.p if self.training else 0.0
204
+ return F.scaled_dot_product_attention(q, k, v, is_causal=True, dropout_p=p)
205
+
206
+ def _chunked(self, q, k, v):
207
+ B, H, T, D = q.shape
208
+ out = torch.zeros_like(q)
209
+ scale = D ** -0.5
210
+ cs = self._chunk_size
211
+ mask = torch.tril(torch.ones(T, T, device=q.device, dtype=torch.bool))
212
+ for start in range(0, T, cs):
213
+ end = min(start + cs, T)
214
+ q_c = q[:, :, start:end, :]
215
+ k_c = k[:, :, :end, :]
216
+ v_c = v[:, :, :end, :]
217
+ attn = torch.matmul(q_c, k_c.transpose(-2, -1)) * scale
218
+ attn = attn.masked_fill(
219
+ ~mask[start:end, :end].unsqueeze(0).unsqueeze(0), float('-inf')
220
+ )
221
+ attn = F.softmax(attn.float(), dim=-1).to(q.dtype)
222
+ if self.training and self.dp.p > 0:
223
+ attn = self.dp(attn)
224
+ out[:, :, start:end, :] = torch.matmul(attn, v_c)
225
+ return out
226
+
227
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
228
+ B, T, _ = x.shape
229
+ q = self.q(x).view(B, T, self.nh, self.d).transpose(1, 2)
230
+ k = self.k(x).view(B, T, self.nkv, self.d).transpose(1, 2)
231
+ v = self.v(x).view(B, T, self.nkv, self.d).transpose(1, 2)
232
+ if self.qk_norm:
233
+ q, k = self._qk_normalize(q, k)
234
+ q = apply_rope(q, self.cos, self.sin)
235
+ k = apply_rope(k, self.cos, self.sin)
236
+ k = k.repeat_interleave(self.rep, dim=1)
237
+ v = v.repeat_interleave(self.rep, dim=1)
238
+ o = self._sdpa(q, k, v) if self._backend == 'sdpa' else self._chunked(q, k, v)
239
+ return self.o(o.transpose(1, 2).contiguous().view(B, T, self.nh * self.d))
240
+
241
+ def set_backend(self, backend: str, chunk_size: int = 256):
242
+ self._backend = backend
243
+ self._chunk_size = chunk_size
244
+
245
+
246
+ # ══════════════════════════════════════════════════════════════════════════════
247
+ # XSA (sliding-window cross-sequence attention)
248
+ # ══════════════════════════════════════════════════════════════════════════════
249
+
250
+ class XSALayer(nn.Module):
251
+ """
252
+ Optional sliding-window cross-sequence attention.
253
+ K/V projections are SHARED with the paired GQAttention (parameter sharing).
254
+ Stored in a plain Python list to avoid duplicate state_dict keys.
255
+ """
256
+
257
+ def __init__(self, cfg: SLMConfig, paired_attn: GQAttention):
258
+ super().__init__()
259
+ H = cfg.hidden_size
260
+ b = cfg.use_bias
261
+ self.window = cfg.xsa_window
262
+ self.nh = cfg.num_heads
263
+ self.nkv = cfg.num_kv_heads
264
+ self.d = cfg.head_dim
265
+ self.rep = self.nh // self.nkv
266
+
267
+ self.norm = RMSNorm(H, cfg.norm_eps)
268
+ self.q = nn.Linear(H, H, bias=b)
269
+ self.o = nn.Linear(H, H, bias=b)
270
+ self.dp = nn.Dropout(cfg.dropout)
271
+
272
+ # Plain list β€” NOT registered as sub-modules β†’ no duplicate state_dict keys
273
+ self._shared: List[nn.Linear] = [paired_attn.k, paired_attn.v]
274
+ self._paired: List[GQAttention] = [paired_attn]
275
+
276
+ self.qk_norm = cfg.qk_norm
277
+ if self.qk_norm:
278
+ self.q_norm = RMSNorm(self.d, cfg.norm_eps)
279
+ self.k_norm = RMSNorm(self.d, cfg.norm_eps)
280
+
281
+ @property
282
+ def _k(self) -> nn.Linear: return self._shared[0]
283
+
284
+ @property
285
+ def _v(self) -> nn.Linear: return self._shared[1]
286
+
287
+ @property
288
+ def cos(self) -> torch.Tensor: return self._paired[0].cos
289
+
290
+ @property
291
+ def sin(self) -> torch.Tensor: return self._paired[0].sin
292
+
293
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
294
+ B, T, H = x.shape
295
+ W = self.window
296
+ xn = self.norm(x)
297
+
298
+ q = self.q(xn).view(B, T, self.nh, self.d).transpose(1, 2)
299
+ if self.qk_norm:
300
+ B_, nh, T_, d = q.shape
301
+ q = self.q_norm(q.reshape(-1, d)).reshape(B_, nh, T_, d)
302
+ q = apply_rope(q, self.cos, self.sin)
303
+
304
+ pad = torch.zeros(B, W, H, device=x.device, dtype=x.dtype)
305
+ x_padded = torch.cat([pad, xn], dim=1)
306
+ ctx = x_padded.unfold(1, W, 1)
307
+ ctx = ctx[:, :T, :, :].permute(0, 1, 3, 2).contiguous()
308
+ ctx_flat = ctx.reshape(B * T, W, H)
309
+
310
+ k_flat = self._k(ctx_flat).view(B*T, W, self.nkv, self.d).transpose(1, 2)
311
+ v_flat = self._v(ctx_flat).view(B*T, W, self.nkv, self.d).transpose(1, 2)
312
+ k_flat = k_flat.repeat_interleave(self.rep, dim=1)
313
+ v_flat = v_flat.repeat_interleave(self.rep, dim=1)
314
+ if self.qk_norm:
315
+ BT, nh, W_, d = k_flat.shape
316
+ k_flat = self.k_norm(k_flat.reshape(-1, d)).reshape(BT, nh, W_, d)
317
+
318
+ q_flat = q.permute(0, 2, 1, 3).reshape(B * T, self.nh, 1, self.d)
319
+ scale = self.d ** -0.5
320
+ attn = torch.matmul(q_flat, k_flat.transpose(-2, -1)) * scale
321
+ attn = F.softmax(attn.float(), dim=-1).to(q.dtype)
322
+ if self.training and self.dp.p > 0:
323
+ attn = self.dp(attn)
324
+ out = torch.matmul(attn, v_flat).reshape(B, T, self.nh * self.d)
325
+ return self.o(out)
326
+
327
+
328
+ # ══════════════════════════════════════════════════════════════════════════════
329
+ # TRANSFORMER BLOCK
330
+ # ══════════════════════════════════════════════════════════════════════════════
331
+
332
+ class Block(nn.Module):
333
+ """Pre-LN transformer block. XSA is inserted between attn and FFN."""
334
+ def __init__(self, cfg: SLMConfig):
335
+ super().__init__()
336
+ self.n1 = RMSNorm(cfg.hidden_size, cfg.norm_eps)
337
+ self.attn = GQAttention(cfg)
338
+ self.n2 = RMSNorm(cfg.hidden_size, cfg.norm_eps)
339
+ self.ffn = FFN(cfg)
340
+ self.use_xsa = cfg.xsa
341
+ if self.use_xsa:
342
+ self.xsa = XSALayer(cfg, self.attn)
343
+
344
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
345
+ x = x + self.attn(self.n1(x))
346
+ if self.use_xsa:
347
+ x = x + self.xsa(x)
348
+ x = x + self.ffn(self.n2(x))
349
+ return x
350
+
351
+ def set_backend(self, backend: str, chunk_size: int = 256):
352
+ self.attn.set_backend(backend, chunk_size)
353
+ if self.use_xsa:
354
+ self.xsa._paired[0]._backend = backend
355
+ self.xsa._paired[0]._chunk_size = chunk_size
356
+
357
+
358
+ # ══════════════════════════════════════════════════════════════════════════════
359
+ # FULL MODEL
360
+ # ══════════════════════════════════════════════════════════════════════════════
361
+
362
+ class SLMModel(nn.Module):
363
+ def __init__(self, cfg: SLMConfig):
364
+ super().__init__()
365
+ cfg.validate()
366
+ self.cfg = cfg
367
+ self.embed = nn.Embedding(cfg.vocab_size, cfg.hidden_size)
368
+ self.drop = nn.Dropout(cfg.dropout)
369
+ self.layers = nn.ModuleList([Block(cfg) for _ in range(cfg.num_layers)])
370
+ self.norm = RMSNorm(cfg.hidden_size, cfg.norm_eps)
371
+ self._init_weights()
372
+
373
+ def _init_weights(self):
374
+ for m in self.modules():
375
+ if isinstance(m, nn.Linear):
376
+ nn.init.normal_(m.weight, std=0.02)
377
+ if m.bias is not None: nn.init.zeros_(m.bias)
378
+ elif isinstance(m, nn.Embedding):
379
+ nn.init.normal_(m.weight, std=0.02)
380
+
381
+ def forward(self, ids: torch.Tensor) -> torch.Tensor:
382
+ x = self.drop(self.embed(ids))
383
+ for layer in self.layers:
384
+ x = layer(x)
385
+ return self.norm(x)
386
+
387
+ def set_attention_backend(self, backend: str, chunk_size: int = 256):
388
+ for blk in self.layers:
389
+ blk.set_backend(backend, chunk_size)
390
+
391
+
392
+ class SLMForCausalLM(nn.Module):
393
+ """
394
+ SLM with LM head. Used during training.
395
+
396
+ State-dict keys (these get 'model.' prefix added by export_safetensors):
397
+ backbone.embed.weight
398
+ backbone.layers.{i}.{n1,n2}.w
399
+ backbone.layers.{i}.attn.{q,k,v,o}.weight
400
+ backbone.layers.{i}.attn.{q_norm,k_norm}.w (if qk_norm)
401
+ backbone.layers.{i}.ffn.{gate,up,down}.weight
402
+ backbone.layers.{i}.xsa.{norm,q,o}.* (if xsa)
403
+ backbone.norm.w
404
+ lm_head.weight (only if NOT tie_embeddings)
405
+ RoPE cos/sin: persistent=False β†’ NOT in state_dict.
406
+ """
407
+ def __init__(self, cfg: SLMConfig):
408
+ super().__init__()
409
+ self.cfg = cfg
410
+ self.backbone = SLMModel(cfg)
411
+ self.lm_head = nn.Linear(cfg.hidden_size, cfg.vocab_size, bias=False)
412
+ if cfg.tie_embeddings:
413
+ self.lm_head.weight = self.backbone.embed.weight
414
+ else:
415
+ nn.init.normal_(self.lm_head.weight, std=0.02)
416
+
417
+ def tie_weights(self) -> None:
418
+ """Re-apply weight tying β€” called by HF after load_state_dict."""
419
+ if self.cfg.tie_embeddings:
420
+ self.lm_head.weight = self.backbone.embed.weight
421
+
422
+ def forward(self, input_ids: torch.Tensor,
423
+ labels: Optional[torch.Tensor] = None
424
+ ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
425
+ hidden = self.backbone(input_ids)
426
+ logits = self.lm_head(hidden)
427
+ loss = None
428
+ if labels is not None:
429
+ loss = F.cross_entropy(
430
+ logits.reshape(-1, logits.size(-1)),
431
+ labels.reshape(-1),
432
+ ignore_index=-100,
433
+ )
434
+ return logits, loss
435
+
436
+ def set_attention_backend(self, backend: str, chunk_size: int = 256):
437
+ self.backbone.set_attention_backend(backend, chunk_size)
438
+
439
+ def n_params(self) -> int:
440
+ return sum(p.numel() for p in self.parameters())
441
+
442
+
443
+ # ══════════════════════════════════════════════════════════════════════════════
444
+ # HUGGINGFACE WRAPPERS
445
+ # ══════════════════════════════════════════════════════════════════════════════
446
+
447
+ if _HF_AVAILABLE:
448
+ class SLMHFConfig(PretrainedConfig):
449
+ model_type = "slm"
450
+
451
+ def __init__(self, **kwargs):
452
+ valid = set(SLMConfig.__dataclass_fields__)
453
+ slm_kw = {k: kwargs.pop(k, getattr(SLMConfig(), k)) for k in valid}
454
+ tie_emb = slm_kw.get('tie_embeddings', True)
455
+ # Accept canonical HF name coming from a saved config.json
456
+ if 'tie_word_embeddings' in kwargs:
457
+ tie_emb = kwargs.pop('tie_word_embeddings')
458
+ super().__init__(tie_word_embeddings=tie_emb, **kwargs)
459
+ for k, v in slm_kw.items():
460
+ setattr(self, k, v)
461
+ self.tie_embeddings = self.tie_word_embeddings
462
+
463
+ def to_slm_config(self) -> SLMConfig:
464
+ return SLMConfig(**{f: getattr(self, f)
465
+ for f in SLMConfig.__dataclass_fields__})
466
+
467
+ class SLMHFForCausalLM(PreTrainedModel):
468
+ config_class = SLMHFConfig
469
+ base_model_prefix = "model"
470
+ supports_gradient_checkpointing = False
471
+
472
+ # ── FIX: LIST format β€” compatible with transformers 4.x AND 5.x ────
473
+ # transformers 4.x: iterates as list of keys to re-tie after loading.
474
+ # transformers 5.x: _get_tied_weight_keys() calls .keys() β€” BUT it first
475
+ # checks isinstance(tied, dict); for a list it falls back to iteration.
476
+ # A list is the safest format across ALL versions.
477
+ _tied_weights_keys = ["model.lm_head.weight"]
478
+
479
+ def __init__(self, config: SLMHFConfig):
480
+ super().__init__(config)
481
+ self.model = SLMForCausalLM(config.to_slm_config())
482
+ # post_init() sets up all_tied_weights_keys from _tied_weights_keys.
483
+ # Must be called AFTER all submodules are initialised.
484
+ self.post_init()
485
+
486
+ def forward(
487
+ self,
488
+ input_ids=None,
489
+ attention_mask=None, # accepted, ignored β€” we use causal mask
490
+ position_ids=None,
491
+ past_key_values=None,
492
+ inputs_embeds=None,
493
+ labels=None,
494
+ use_cache=None,
495
+ output_attentions=None,
496
+ output_hidden_states=None,
497
+ return_dict=None,
498
+ **kwargs,
499
+ ):
500
+ logits, loss = self.model(input_ids, labels)
501
+ # Always return CausalLMOutputWithPast so .generate() / pipelines work
502
+ return CausalLMOutputWithPast(loss=loss, logits=logits)
503
+
504
+ def get_input_embeddings(self):
505
+ return self.model.backbone.embed
506
+
507
+ def set_input_embeddings(self, v):
508
+ self.model.backbone.embed = v
509
+ if self.model.cfg.tie_embeddings:
510
+ self.model.lm_head.weight = v.weight
511
+
512
+ def get_output_embeddings(self):
513
+ return self.model.lm_head
514
+
515
+ def set_output_embeddings(self, v):
516
+ self.model.lm_head = v
517
+
518
+ def tie_weights(self):
519
+ # ── FIX: Delegate to inner model ─────────────────────────────
520
+ # HF calls tie_weights() on SLMHFForCausalLM after loading weights.
521
+ # Without delegation, embed->lm_head link is never restored on load.
522
+ self.model.tie_weights()
523
+
524
+ def prepare_inputs_for_generation(self, input_ids, **kwargs):
525
+ # ── FIX: Required for model.generate() to work ───────────────
526
+ return {"input_ids": input_ids}
527
+
528
+ def resize_token_embeddings(self, new_num_tokens, pad_to_multiple_of=None):
529
+ result = super().resize_token_embeddings(new_num_tokens, pad_to_multiple_of)
530
+ if self.model.cfg.tie_embeddings:
531
+ self.model.lm_head.weight = self.model.backbone.embed.weight
532
+ return result
533
+
534
+ try:
535
+ from transformers import AutoConfig, AutoModelForCausalLM
536
+ SLMHFConfig.register_for_auto_class("AutoConfig")
537
+ SLMHFForCausalLM.register_for_auto_class("AutoModelForCausalLM")
538
+ except Exception:
539
+ pass
540
+
541
+
542
+ # ══════════════════════════════════════════════════════════════════════════════
543
+ # PUBLIC HELPERS
544
+ # ══════════════════════════════════════════════════════════════════════════════
545
+
546
+ def build_model(cfg_dict: dict, device: torch.device) -> SLMForCausalLM:
547
+ cfg = SLMConfig.from_dict(cfg_dict)
548
+ cfg.validate()
549
+ return SLMForCausalLM(cfg).to(device)
550
+
551
+
552
+ def count_params(model: nn.Module) -> dict:
553
+ total = sum(p.numel() for p in model.parameters())
554
+ trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
555
+ return {'total': total, 'trainable': trainable}
556
+
557
+
558
+ def build_hf_config_json(cfg: SLMConfig, tokenizer) -> dict:
559
+ bos = getattr(tokenizer, 'bos_token_id', None) or 0
560
+ eos = getattr(tokenizer, 'eos_token_id', None) or 2
561
+ pad = getattr(tokenizer, 'pad_token_id', None) or eos
562
+ d = cfg.to_dict()
563
+ d['tie_word_embeddings'] = cfg.tie_embeddings
564
+ d.update({
565
+ 'auto_map': {
566
+ 'AutoConfig': 'model.SLMHFConfig',
567
+ 'AutoModelForCausalLM': 'model.SLMHFForCausalLM',
568
+ },
569
+ 'bos_token_id': bos,
570
+ 'eos_token_id': eos,
571
+ 'pad_token_id': pad,
572
+ 'torch_dtype': 'float32',
573
+ 'transformers_version': '4.40.0',
574
+ })
575
+ return d
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0fc7dd1aae3436bafa949e25f6d6531dc5c1db020e34263d9a60989cfd6879f1
3
+ size 38839496
resume/ckpt.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6fc9f035286e4a56246cd3890fb6c081c0ebd90b790079da95c036a6698d7583
3
+ size 116623825
resume/latest_step.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ 10
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|bos|>",
4
+ "clean_up_tokenization_spaces": false,
5
+ "eos_token": "<|eos|>",
6
+ "is_local": false,
7
+ "local_files_only": false,
8
+ "mask_token": "<|mask|>",
9
+ "model_max_length": 4096,
10
+ "pad_token": "<|pad|>",
11
+ "tokenizer_class": "TokenizersBackend",
12
+ "unk_token": "<|unk|>"
13
+ }
training_meta.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "step": 10,
3
+ "val_loss": Infinity,
4
+ "val_ppl": Infinity,
5
+ "params_M": 9.707,
6
+ "tokens_seen": 10475520,
7
+ "pushed_at": "2026-06-10T17:35:40.399616",
8
+ "arch": {
9
+ "ffn_type": "swiglu",
10
+ "qk_norm": true,
11
+ "xsa": false,
12
+ "xsa_window": 128,
13
+ "num_layers": 11,
14
+ "hidden_size": 256,
15
+ "num_heads": 4,
16
+ "num_kv_heads": 2,
17
+ "max_seq_len": 1024
18
+ },
19
+ "optimizer": "adamw",
20
+ "lr_scheduler": "wsd"
21
+ }