GODELEV commited on
Commit
afd3dda
·
verified ·
1 Parent(s): 8e78027

Delete model.py

Browse files
Files changed (1) hide show
  1. model.py +0 -575
model.py DELETED
@@ -1,575 +0,0 @@
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 = 4096
40
- hidden_size: int = 256
41
- tie_embeddings: bool = True
42
-
43
- # Depth / width
44
- num_layers: int = 11
45
- intermediate_size: int = 768
46
-
47
- # Attention
48
- num_heads: int = 4
49
- num_kv_heads: int = 2
50
- max_seq_len: int = 1024
51
- rope_theta: float = 10000.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