D4niel commited on
Commit
c876b1b
·
verified ·
1 Parent(s): 3d235ef

Upload architecture.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. architecture.py +357 -0
architecture.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ model/architecture.py — Dizel causal Transformer (v1.5, Qwen-style).
3
+
4
+ Architecture highlights (v1.5)
5
+ -----------------------------
6
+ * Grouped Query Attention (GQA) — reduces KV cache vs MHA
7
+ * Rotary Positional Embeddings (RoPE) — applied to Q and K in attention
8
+ * SwiGLU activation in feed-forward (gated SiLU, 3-projection)
9
+ * Pre-RMSNorm (applied before attention/MLP, not after)
10
+ * Weight tying between token embedding and LM head
11
+ * No biases by default
12
+ * Dropout for overfitting mitigation
13
+
14
+ Backward-compatible: accepts both ModelConfig (v1.2) and ModelConfigV15 (v1.5)
15
+ at init time. Detects GQA via hasattr(cfg, 'kv_heads').
16
+ """
17
+
18
+ import math
19
+ import torch
20
+ import torch.nn as nn
21
+ import torch.nn.functional as F
22
+ from typing import Optional
23
+
24
+ import sys, os
25
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
26
+ from config import ModelConfig, ModelConfigV15
27
+ from model.rope import RotaryPositionalEmbedding
28
+
29
+ torch.serialization.add_safe_globals([ModelConfig, ModelConfigV15])
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # RMSNorm
34
+ # ---------------------------------------------------------------------------
35
+ class RMSNorm(nn.Module):
36
+ def __init__(self, dim: int, eps: float = 1e-6):
37
+ super().__init__()
38
+ self.weight = nn.Parameter(torch.ones(dim))
39
+ self.eps = eps
40
+
41
+ def forward(self, x):
42
+ input_dtype = x.dtype
43
+ x = x.float()
44
+ norm = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
45
+ return (norm * self.weight).to(input_dtype)
46
+
47
+ def extra_repr(self):
48
+ return f"dim={self.weight.shape[0]}, eps={self.eps}"
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # Grouped Query Attention
53
+ # ---------------------------------------------------------------------------
54
+ class GQAAttention(nn.Module):
55
+ """
56
+ Grouped Query Attention with RoPE.
57
+ Falls back to MHA when kv_heads == n_heads.
58
+ """
59
+
60
+ def __init__(self, cfg):
61
+ super().__init__()
62
+ d_model = cfg.d_model
63
+ n_heads = cfg.n_heads
64
+ kv_heads = getattr(cfg, 'kv_heads', n_heads)
65
+ head_dim = getattr(cfg, 'head_dim', d_model // n_heads)
66
+ bias = cfg.bias
67
+ dropout = cfg.dropout
68
+ rope_base = getattr(cfg, 'rope_theta', getattr(cfg, 'rope_base', 10000.0))
69
+
70
+ self.n_heads = n_heads
71
+ self.kv_heads = kv_heads
72
+ self.head_dim = head_dim
73
+ self.n_groups = n_heads // kv_heads if kv_heads > 0 else 1
74
+
75
+ self.q_proj = nn.Linear(d_model, n_heads * head_dim, bias=bias)
76
+ self.k_proj = nn.Linear(d_model, kv_heads * head_dim, bias=bias)
77
+ self.v_proj = nn.Linear(d_model, kv_heads * head_dim, bias=bias)
78
+ self.o_proj = nn.Linear(n_heads * head_dim, d_model, bias=bias)
79
+
80
+ self.rope = RotaryPositionalEmbedding(
81
+ dim=head_dim,
82
+ max_seq_len=cfg.context_length * 2,
83
+ base=rope_base,
84
+ )
85
+
86
+ self.attn_drop = nn.Dropout(dropout)
87
+ self.resid_drop = nn.Dropout(dropout)
88
+
89
+ def forward(self, x):
90
+ B, T, C = x.shape
91
+
92
+ q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
93
+ k = self.k_proj(x).view(B, T, self.kv_heads, self.head_dim).transpose(1, 2)
94
+ v = self.v_proj(x).view(B, T, self.kv_heads, self.head_dim).transpose(1, 2)
95
+
96
+ q, k = self.rope(q, k, seq_len=T)
97
+
98
+ if self.kv_heads != self.n_heads:
99
+ k = k.repeat_interleave(self.n_groups, dim=1)
100
+ v = v.repeat_interleave(self.n_groups, dim=1)
101
+
102
+ y = F.scaled_dot_product_attention(
103
+ q, k, v,
104
+ attn_mask=None,
105
+ dropout_p=self.attn_drop.p if self.training else 0.0,
106
+ is_causal=True,
107
+ )
108
+
109
+ y = y.transpose(1, 2).contiguous().view(B, T, C)
110
+ return self.resid_drop(self.o_proj(y))
111
+
112
+
113
+ # ---------------------------------------------------------------------------
114
+ # SwiGLU MLP
115
+ # ---------------------------------------------------------------------------
116
+ class SwiGLUMLP(nn.Module):
117
+ """
118
+ Gated SiLU feed-forward: down(silu(gate(x)) * up(x)).
119
+ """
120
+
121
+ def __init__(self, cfg):
122
+ super().__init__()
123
+ d_model = cfg.d_model
124
+ intermediate = getattr(cfg, 'intermediate_size', None) or int(d_model * cfg.ffn_mult)
125
+ bias = cfg.bias
126
+
127
+ self.gate_proj = nn.Linear(d_model, intermediate, bias=bias)
128
+ self.up_proj = nn.Linear(d_model, intermediate, bias=bias)
129
+ self.down_proj = nn.Linear(intermediate, d_model, bias=bias)
130
+
131
+ def forward(self, x):
132
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
133
+
134
+
135
+ # ---------------------------------------------------------------------------
136
+ # Decoder Layer
137
+ # ---------------------------------------------------------------------------
138
+ class DecoderLayer(nn.Module):
139
+ """
140
+ Pre-RMSNorm decoder layer: x + Attn(RMSNorm(x)), x + SwiGLU(RMSNorm(x)).
141
+ """
142
+
143
+ def __init__(self, cfg):
144
+ super().__init__()
145
+ d_model = cfg.d_model
146
+ eps = getattr(cfg, 'norm_eps', 1e-6)
147
+
148
+ self.input_norm = RMSNorm(d_model, eps)
149
+ self.attn = GQAAttention(cfg)
150
+ self.post_attn_norm = RMSNorm(d_model, eps)
151
+ self.mlp = SwiGLUMLP(cfg)
152
+
153
+ def forward(self, x):
154
+ x = x + self.attn(self.input_norm(x))
155
+ x = x + self.mlp(self.post_attn_norm(x))
156
+ return x
157
+
158
+
159
+ # ---------------------------------------------------------------------------
160
+ # Dizel Language Model
161
+ # ---------------------------------------------------------------------------
162
+ class DizelLM(nn.Module):
163
+ """
164
+ Dizel: A causal language model (v1.5, Qwen-style architecture).
165
+
166
+ Detects config type at init:
167
+ - hasattr(cfg, 'kv_heads') → v1.5 (GQA, SwiGLU, RMSNorm)
168
+ - no kv_heads → v1.2 (treated as GQA with kv_heads=n_heads)
169
+
170
+ Forward pass
171
+ ------------
172
+ input: idx (B, T) — integer token ids
173
+ output: logits (B, T, vocab_size)
174
+ loss (scalar, optional) — cross-entropy NLL
175
+
176
+ Generation
177
+ ----------
178
+ Use DizelLM.generate() for autoregressive sampling.
179
+ """
180
+
181
+ def __init__(self, cfg):
182
+ super().__init__()
183
+ self.cfg = cfg
184
+
185
+ self.transformer = nn.ModuleDict({
186
+ "tok_emb": nn.Embedding(cfg.vocab_size, cfg.d_model),
187
+ "emb_drop": nn.Dropout(cfg.dropout),
188
+ "blocks": nn.ModuleList([
189
+ DecoderLayer(cfg) for _ in range(cfg.n_layers)
190
+ ]),
191
+ "ln_f": RMSNorm(cfg.d_model, getattr(cfg, 'norm_eps', 1e-6)),
192
+ })
193
+
194
+ self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
195
+
196
+ if cfg.weight_tying:
197
+ self.lm_head.weight = self.transformer["tok_emb"].weight
198
+
199
+ self.apply(self._init_weights)
200
+ for name, param in self.named_parameters():
201
+ if name.endswith(("o_proj.weight", "down_proj.weight")):
202
+ nn.init.normal_(
203
+ param, mean=0.0,
204
+ std=0.02 / math.sqrt(2 * cfg.n_layers)
205
+ )
206
+
207
+ # ------------------------------------------------------------------
208
+ def _init_weights(self, module):
209
+ if isinstance(module, nn.Linear):
210
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
211
+ if module.bias is not None:
212
+ nn.init.zeros_(module.bias)
213
+ elif isinstance(module, nn.Embedding):
214
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
215
+ elif isinstance(module, RMSNorm):
216
+ nn.init.ones_(module.weight)
217
+
218
+ # ------------------------------------------------------------------
219
+ def forward(
220
+ self,
221
+ idx,
222
+ targets=None,
223
+ loss_mask=None,
224
+ ):
225
+ B, T = idx.shape
226
+ assert T <= self.cfg.context_length, \
227
+ f"Input length {T} exceeds context_length {self.cfg.context_length}"
228
+
229
+ x = self.transformer["tok_emb"](idx)
230
+ x = self.transformer["emb_drop"](x)
231
+
232
+ for block in self.transformer["blocks"]:
233
+ x = block(x)
234
+
235
+ x = self.transformer["ln_f"](x)
236
+ logits = self.lm_head(x)
237
+
238
+ if targets is None:
239
+ return logits, None
240
+
241
+ loss = F.cross_entropy(
242
+ logits.view(-1, logits.size(-1)),
243
+ targets.view(-1),
244
+ ignore_index=-1,
245
+ reduction="none",
246
+ )
247
+
248
+ if loss_mask is not None:
249
+ mask = loss_mask.view(-1).float()
250
+ loss = (loss * mask).sum() / (mask.sum() + 1e-8)
251
+ else:
252
+ loss = loss.mean()
253
+
254
+ return logits, loss
255
+
256
+ # ------------------------------------------------------------------
257
+ @torch.no_grad()
258
+ def generate(
259
+ self,
260
+ idx,
261
+ max_new_tokens=200,
262
+ temperature=0.8,
263
+ top_k=50,
264
+ top_p=0.92,
265
+ repetition_penalty=1.15,
266
+ eos_id=2,
267
+ eos_ids=None,
268
+ ):
269
+ self.eval()
270
+ generated = idx
271
+
272
+ stop_ids = set()
273
+ if eos_ids is not None:
274
+ stop_ids.update(eos_ids)
275
+ else:
276
+ stop_ids.add(eos_id)
277
+
278
+ for _ in range(max_new_tokens):
279
+ ctx = generated[:, -self.cfg.context_length:]
280
+
281
+ logits, _ = self(ctx)
282
+ logits = logits[:, -1, :].float()
283
+
284
+ if repetition_penalty != 1.0:
285
+ for token_id in set(generated[0].tolist()):
286
+ if logits[0, token_id] < 0:
287
+ logits[0, token_id] *= repetition_penalty
288
+ else:
289
+ logits[0, token_id] /= repetition_penalty
290
+
291
+ logits = logits / max(temperature, 1e-8)
292
+
293
+ if top_k > 0:
294
+ v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
295
+ logits[logits < v[:, [-1]]] = float("-inf")
296
+
297
+ if top_p < 1.0:
298
+ probs_sorted, sorted_idx = torch.sort(
299
+ F.softmax(logits, dim=-1), dim=-1, descending=True
300
+ )
301
+ cum_probs = probs_sorted.cumsum(dim=-1)
302
+ remove = cum_probs - probs_sorted > top_p
303
+ probs_sorted[remove] = 0.0
304
+ probs_sorted /= probs_sorted.sum(dim=-1, keepdim=True)
305
+ next_token = torch.multinomial(probs_sorted, num_samples=1)
306
+ next_token = sorted_idx.gather(-1, next_token)
307
+ else:
308
+ probs = F.softmax(logits, dim=-1)
309
+ next_token = torch.multinomial(probs, num_samples=1)
310
+
311
+ generated = torch.cat([generated, next_token], dim=1)
312
+
313
+ if next_token.item() in stop_ids:
314
+ break
315
+
316
+ return generated
317
+
318
+ # ------------------------------------------------------------------
319
+ def num_parameters(self, trainable_only=True):
320
+ return sum(
321
+ p.numel() for p in self.parameters()
322
+ if (not trainable_only or p.requires_grad)
323
+ )
324
+
325
+ def __repr__(self):
326
+ n = self.num_parameters()
327
+ kv = getattr(self.cfg, 'kv_heads', self.cfg.n_heads)
328
+ return (
329
+ f"DizelLM("
330
+ f"vocab={self.cfg.vocab_size}, "
331
+ f"d_model={self.cfg.d_model}, "
332
+ f"layers={self.cfg.n_layers}, "
333
+ f"heads={self.cfg.n_heads}/kv={kv}, "
334
+ f"params={n/1e6:.2f}M)"
335
+ )
336
+
337
+
338
+ # ---------------------------------------------------------------------------
339
+ # Quick sanity-check
340
+ # ---------------------------------------------------------------------------
341
+ if __name__ == "__main__":
342
+ from config import ModelConfig, ModelConfigV15
343
+
344
+ for name, cfg_cls in [("v1.2", ModelConfig), ("v1.5", ModelConfigV15)]:
345
+ cfg = cfg_cls()
346
+ model = DizelLM(cfg)
347
+ print(f"\n{model}")
348
+
349
+ B, T = 2, 64
350
+ idx = torch.randint(0, cfg.vocab_size, (B, T))
351
+ targets = torch.randint(0, cfg.vocab_size, (B, T))
352
+ logits, loss = model(idx, targets)
353
+ print(f" logits: {logits.shape}, loss: {loss.item():.4f}")
354
+
355
+ prompt = torch.zeros(1, 1, dtype=torch.long)
356
+ out = model.generate(prompt, max_new_tokens=10)
357
+ print(f" generated: {out.shape}")