Akahsizrr commited on
Commit
3ebfc10
·
verified ·
1 Parent(s): d8993f1

Upload model.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. model.py +360 -0
model.py ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Retriever500M - Decoder-only transformer built from scratch.
3
+
4
+ Architecture (LLaMA-style):
5
+ - vocab_size: 32,000
6
+ - d_model: 1,280
7
+ - n_layers: 23
8
+ - n_heads: 20
9
+ - d_ff: 3,456 (SwiGLU, 2/3 * 4 * d_model)
10
+ - RoPE positional encoding
11
+ - RMSNorm (no biases)
12
+ - Tied input/output embeddings
13
+ - Total parameters: ~497M
14
+ """
15
+
16
+ import math
17
+ from dataclasses import dataclass
18
+
19
+ import torch
20
+ import torch.nn as nn
21
+ import torch.nn.functional as F
22
+
23
+
24
+ @dataclass
25
+ class ModelConfig:
26
+ vocab_size: int = 32_000
27
+ d_model: int = 1_280
28
+ n_layers: int = 23
29
+ n_heads: int = 20
30
+ d_ff: int = 3_456
31
+ max_seq_len: int = 1_024
32
+ rope_theta: float = 10_000.0
33
+ rope_pct: float = 0.25 # fraction of d_model per head used for RoPE
34
+ dropout: float = 0.0
35
+ tie_embeddings: bool = True
36
+
37
+ def __post_init__(self):
38
+ assert self.d_model % self.n_heads == 0
39
+ self.d_head = self.d_model // self.n_heads # 64
40
+
41
+
42
+ class RMSNorm(nn.Module):
43
+ """RMSNorm with optional bias (no bias by default, LLaMA-style)."""
44
+
45
+ def __init__(self, dim: int, eps: float = 1e-6):
46
+ super().__init__()
47
+ self.weight = nn.Parameter(torch.ones(dim))
48
+ self.eps = eps
49
+
50
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
51
+ # Compute in float32 for stability, then cast back
52
+ orig_dtype = x.dtype
53
+ x = x.float()
54
+ rms = x.pow(2).mean(dim=-1, keepdim=True)
55
+ x = x * torch.rsqrt(rms + self.eps)
56
+ x = x.to(orig_dtype)
57
+ return x * self.weight
58
+
59
+
60
+ def precompute_rope_frequencies(
61
+ d_head: int,
62
+ max_seq_len: int,
63
+ theta: float = 10_000.0,
64
+ device: torch.device | None = None,
65
+ ) -> torch.Tensor:
66
+ """Precompute RoPE frequency table.
67
+
68
+ Returns tensor of shape (max_seq_len, d_head // 2) with complex
69
+ frequencies (cos, sin interleaved is handled in apply_rope).
70
+ """
71
+ inv_freq = 1.0 / (theta ** (torch.arange(0, d_head, 2, device=device).float() / d_head))
72
+ positions = torch.arange(max_seq_len, device=device).float()
73
+ freqs = torch.outer(positions, inv_freq) # (seq, d_head//2)
74
+ return freqs
75
+
76
+
77
+ def apply_rope(
78
+ x: torch.Tensor,
79
+ freqs: torch.Tensor,
80
+ ) -> torch.Tensor:
81
+ """Apply rotary position embeddings to tensor x.
82
+
83
+ x: (batch, n_heads, seq, d_head)
84
+ freqs: (seq, d_head // 2)
85
+ """
86
+ seq_len = x.shape[2]
87
+ d_head = x.shape[-1]
88
+ freqs = freqs[:seq_len] # (seq, d_head//2)
89
+
90
+ cos = freqs.cos()
91
+ sin = freqs.sin()
92
+
93
+ # Interleave cos/sin to match the rotate_half pattern
94
+ # x is split into two halves: x1 = x[..., :d//2], x2 = x[..., d//2:]
95
+ x1 = x[..., : d_head // 2]
96
+ x2 = x[..., d_head // 2 :]
97
+
98
+ # Broadcast cos/sin: (1, 1, seq, d_head//2)
99
+ cos = cos.unsqueeze(0).unsqueeze(0)
100
+ sin = sin.unsqueeze(0).unsqueeze(0)
101
+
102
+ rotated = torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
103
+ return rotated
104
+
105
+
106
+ class Attention(nn.Module):
107
+ """Multi-head self-attention with RoPE, no biases, causal masking."""
108
+
109
+ def __init__(self, config: ModelConfig):
110
+ super().__init__()
111
+ self.n_heads = config.n_heads
112
+ self.d_head = config.d_head
113
+ self.d_model = config.d_model
114
+ self.scale = 1.0 / math.sqrt(self.d_head)
115
+
116
+ # Fused QKV projection
117
+ self.qkv = nn.Linear(config.d_model, 3 * config.d_model, bias=False)
118
+ self.o_proj = nn.Linear(config.d_model, config.d_model, bias=False)
119
+ self.dropout = config.dropout
120
+
121
+ def forward(
122
+ self,
123
+ x: torch.Tensor,
124
+ rope_freqs: torch.Tensor,
125
+ mask: torch.Tensor | None = None,
126
+ ) -> torch.Tensor:
127
+ B, T, C = x.shape
128
+
129
+ qkv = self.qkv(x) # (B, T, 3*C)
130
+ q, k, v = qkv.chunk(3, dim=-1)
131
+
132
+ # Reshape to (B, n_heads, T, d_head)
133
+ q = q.view(B, T, self.n_heads, self.d_head).transpose(1, 2)
134
+ k = k.view(B, T, self.n_heads, self.d_head).transpose(1, 2)
135
+ v = v.view(B, T, self.n_heads, self.d_head).transpose(1, 2)
136
+
137
+ # Apply RoPE to Q and K
138
+ q = apply_rope(q, rope_freqs)
139
+ k = apply_rope(k, rope_freqs)
140
+
141
+ # Use PyTorch's scaled_dot_product_attention (uses Flash Attention on CUDA)
142
+ if mask is not None:
143
+ # mask: (1, 1, T, T) additive mask
144
+ attn_mask = mask
145
+ else:
146
+ attn_mask = None
147
+
148
+ out = F.scaled_dot_product_attention(
149
+ q, k, v,
150
+ attn_mask=attn_mask,
151
+ dropout_p=self.dropout if self.training else 0.0,
152
+ is_causal=(mask is None),
153
+ )
154
+
155
+ # (B, n_heads, T, d_head) -> (B, T, C)
156
+ out = out.transpose(1, 2).contiguous().view(B, T, C)
157
+ return self.o_proj(out)
158
+
159
+
160
+ class SwiGLU(nn.Module):
161
+ """SwiGLU feed-forward network: (xW_gate * SiLU(xW_up)) * W_down."""
162
+
163
+ def __init__(self, config: ModelConfig):
164
+ super().__init__()
165
+ self.w_gate = nn.Linear(config.d_model, config.d_ff, bias=False)
166
+ self.w_up = nn.Linear(config.d_model, config.d_ff, bias=False)
167
+ self.w_down = nn.Linear(config.d_ff, config.d_model, bias=False)
168
+
169
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
170
+ return self.w_down(F.silu(self.w_gate(x)) * self.w_up(x))
171
+
172
+
173
+ class TransformerBlock(nn.Module):
174
+ """One transformer decoder block: pre-norm attention + pre-norm FFN."""
175
+
176
+ def __init__(self, config: ModelConfig):
177
+ super().__init__()
178
+ self.norm1 = RMSNorm(config.d_model)
179
+ self.attn = Attention(config)
180
+ self.norm2 = RMSNorm(config.d_model)
181
+ self.ffn = SwiGLU(config)
182
+
183
+ def forward(
184
+ self,
185
+ x: torch.Tensor,
186
+ rope_freqs: torch.Tensor,
187
+ mask: torch.Tensor | None = None,
188
+ ) -> torch.Tensor:
189
+ x = x + self.attn(self.norm1(x), rope_freqs, mask)
190
+ x = x + self.ffn(self.norm2(x))
191
+ return x
192
+
193
+
194
+ class Retriever500M(nn.Module):
195
+ """Full decoder-only transformer model."""
196
+
197
+ def __init__(self, config: ModelConfig):
198
+ super().__init__()
199
+ self.config = config
200
+
201
+ # Token embedding (tied with output head)
202
+ self.token_embedding = nn.Embedding(config.vocab_size, config.d_model)
203
+
204
+ # Transformer blocks
205
+ self.layers = nn.ModuleList([
206
+ TransformerBlock(config) for _ in range(config.n_layers)
207
+ ])
208
+
209
+ # Final norm
210
+ self.norm_f = RMSNorm(config.d_model)
211
+
212
+ # Output projection (tied with embedding)
213
+ if config.tie_embeddings:
214
+ self.lm_head = None # use token_embedding weight
215
+ else:
216
+ self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
217
+
218
+ # Precompute RoPE frequencies (registered as buffer, moved with .to())
219
+ freqs = precompute_rope_frequencies(
220
+ config.d_head,
221
+ config.max_seq_len,
222
+ config.rope_theta,
223
+ )
224
+ self.register_buffer("rope_freqs", freqs, persistent=False)
225
+
226
+ # Causal mask buffer
227
+ mask = torch.full(
228
+ (1, 1, config.max_seq_len, config.max_seq_len),
229
+ float("-inf"),
230
+ )
231
+ mask = torch.triu(mask, diagonal=1)
232
+ self.register_buffer("causal_mask", mask, persistent=False)
233
+
234
+ # Initialize weights
235
+ self.apply(self._init_weights)
236
+
237
+ def _init_weights(self, module: nn.Module):
238
+ if isinstance(module, nn.Linear):
239
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
240
+ if module.bias is not None:
241
+ nn.init.zeros_(module.bias)
242
+ elif isinstance(module, nn.Embedding):
243
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
244
+
245
+ def get_output_weight(self):
246
+ """Return the weight matrix for the output projection."""
247
+ if self.config.tie_embeddings:
248
+ return self.token_embedding.weight
249
+ return self.lm_head.weight
250
+
251
+ def forward(
252
+ self,
253
+ input_ids: torch.Tensor,
254
+ targets: torch.Tensor | None = None,
255
+ use_checkpoint: bool = False,
256
+ ) -> dict:
257
+ B, T = input_ids.shape
258
+
259
+ # Token embeddings
260
+ x = self.token_embedding(input_ids) # (B, T, d_model)
261
+
262
+ # Get RoPE freqs and causal mask for current sequence length
263
+ rope_freqs = self.rope_freqs[:T]
264
+ mask = self.causal_mask[:, :, :T, :T]
265
+
266
+ # Transformer blocks (with optional gradient checkpointing)
267
+ for layer in self.layers:
268
+ if use_checkpoint and self.training:
269
+ # Gradient checkpointing: recompute activations during backward
270
+ x = torch.utils.checkpoint.checkpoint(
271
+ layer, x, rope_freqs, mask, use_reentrant=False,
272
+ )
273
+ else:
274
+ x = layer(x, rope_freqs, mask)
275
+
276
+ x = self.norm_f(x)
277
+
278
+ # Output logits
279
+ logits = F.linear(x, self.get_output_weight()) # (B, T, vocab_size)
280
+
281
+ loss = None
282
+ if targets is not None:
283
+ loss = F.cross_entropy(
284
+ logits.view(-1, logits.size(-1)),
285
+ targets.view(-1),
286
+ ignore_index=-100,
287
+ )
288
+
289
+ return {"logits": logits, "loss": loss}
290
+
291
+ @torch.no_grad()
292
+ def generate(
293
+ self,
294
+ input_ids: torch.Tensor,
295
+ max_new_tokens: int = 128,
296
+ temperature: float = 1.0,
297
+ top_k: int | None = None,
298
+ eos_token_id: int | None = None,
299
+ ) -> torch.Tensor:
300
+ """Simple autoregressive generation."""
301
+ self.eval()
302
+ for _ in range(max_new_tokens):
303
+ # Crop context if it exceeds max_seq_len
304
+ idx_cond = input_ids if input_ids.size(1) <= self.config.max_seq_len else \
305
+ input_ids[:, -self.config.max_seq_len:]
306
+
307
+ logits = self(idx_cond)["logits"]
308
+ logits = logits[:, -1, :] / max(temperature, 1e-6)
309
+
310
+ if top_k is not None:
311
+ v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
312
+ logits[logits < v[:, [-1]]] = float("-inf")
313
+
314
+ probs = F.softmax(logits, dim=-1)
315
+ next_token = torch.multinomial(probs, num_samples=1)
316
+ input_ids = torch.cat([input_ids, next_token], dim=1)
317
+
318
+ if eos_token_id is not None and next_token.item() == eos_token_id:
319
+ break
320
+
321
+ return input_ids
322
+
323
+ def count_parameters(self) -> int:
324
+ """Count total trainable parameters."""
325
+ return sum(p.numel() for p in self.parameters() if p.requires_grad)
326
+
327
+
328
+ def build_model(config: ModelConfig | None = None) -> Retriever500M:
329
+ """Build the Retriever500M model."""
330
+ if config is None:
331
+ config = ModelConfig()
332
+ model = Retriever500M(config)
333
+ return model
334
+
335
+
336
+ if __name__ == "__main__":
337
+ config = ModelConfig()
338
+ model = build_model(config)
339
+
340
+ total_params = model.count_parameters()
341
+ print(f"Model: Retriever500M")
342
+ print(f" d_model: {config.d_model}")
343
+ print(f" n_layers: {config.n_layers}")
344
+ print(f" n_heads: {config.n_heads}")
345
+ print(f" d_ff: {config.d_ff}")
346
+ print(f" d_head: {config.d_head}")
347
+ print(f" vocab_size: {config.vocab_size}")
348
+ print(f" max_seq_len: {config.max_seq_len}")
349
+ print(f" Total parameters: {total_params:,} ({total_params / 1e6:.1f}M)")
350
+
351
+ # Quick forward pass test
352
+ device = "cuda" if torch.cuda.is_available() else "cpu"
353
+ model = model.to(device)
354
+ model.eval()
355
+
356
+ input_ids = torch.randint(0, config.vocab_size, (2, 64), device=device)
357
+ with torch.no_grad():
358
+ out = model(input_ids)
359
+ print(f" Output logits shape: {out['logits'].shape}")
360
+ print(" Forward pass OK.")