| """Modernized dense decoder-only transformer. |
| |
| Block recipe (pre-norm): x = x + attn(rmsnorm(x)); x = x + swiglu(rmsnorm(x)). |
| Modernizations baked in from the start (validated free on Colab before any |
| paid run): RoPE, RMSNorm, SwiGLU, GQA, QK-Norm, weight tying, residual-scaled |
| init. This is the architecture the long A100 run uses. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import math |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
|
|
| from .config import ModelConfig |
|
|
|
|
| class RMSNorm(nn.Module): |
| """RMSNorm with the reduction done in fp32 for mixed-precision safety.""" |
|
|
| def __init__(self, dim: int, eps: float = 1e-6): |
| super().__init__() |
| self.eps = eps |
| self.weight = nn.Parameter(torch.ones(dim)) |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| dtype = x.dtype |
| x = x.float() |
| rms = x.pow(2).mean(dim=-1, keepdim=True).add(self.eps).rsqrt() |
| |
| return ((x * rms) * self.weight.float()).to(dtype) |
|
|
|
|
| def build_rope_cache(head_dim: int, max_seq_len: int, theta: float, |
| device=None, dtype=torch.float32): |
| """Precompute (cos, sin) of shape (max_seq_len, head_dim). |
| |
| Half-rotation (Llama) convention: freqs are computed for head_dim/2 pairs |
| and concatenated with themselves so they line up with rotate_half. |
| """ |
| i = torch.arange(0, head_dim, 2, device=device, dtype=torch.float32) |
| inv_freq = 1.0 / (theta ** (i / head_dim)) |
| t = torch.arange(max_seq_len, device=device, dtype=torch.float32) |
| freqs = torch.outer(t, inv_freq) |
| emb = torch.cat([freqs, freqs], dim=-1) |
| return emb.cos().to(dtype), emb.sin().to(dtype) |
|
|
|
|
| def _rotate_half(x: torch.Tensor) -> torch.Tensor: |
| x1, x2 = x.chunk(2, dim=-1) |
| return torch.cat([-x2, x1], dim=-1) |
|
|
|
|
| def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: |
| |
| cos = cos[None, None, :, :] |
| sin = sin[None, None, :, :] |
| return (x * cos) + (_rotate_half(x) * sin) |
|
|
|
|
| class Attention(nn.Module): |
| """Causal grouped-query attention with optional QK-Norm and RoPE.""" |
|
|
| def __init__(self, cfg: ModelConfig): |
| super().__init__() |
| self.n_heads = cfg.n_heads |
| self.n_kv_heads = cfg.n_kv_heads |
| self.head_dim = cfg.head_dim |
| self.n_rep = cfg.n_heads // cfg.n_kv_heads |
|
|
| self.wq = nn.Linear(cfg.d_model, cfg.n_heads * self.head_dim, bias=False) |
| self.wk = nn.Linear(cfg.d_model, cfg.n_kv_heads * self.head_dim, bias=False) |
| self.wv = nn.Linear(cfg.d_model, cfg.n_kv_heads * self.head_dim, bias=False) |
| self.wo = nn.Linear(cfg.n_heads * self.head_dim, cfg.d_model, bias=False) |
|
|
| self.qk_norm = cfg.qk_norm |
| if cfg.qk_norm: |
| self.q_norm = RMSNorm(self.head_dim, cfg.norm_eps) |
| self.k_norm = RMSNorm(self.head_dim, cfg.norm_eps) |
| self.dropout = cfg.dropout |
| self.softcap = cfg.attn_logit_softcap |
|
|
| def forward(self, x, cos, sin): |
| B, T, _ = x.shape |
| q = self.wq(x).view(B, T, self.n_heads, self.head_dim) |
| k = self.wk(x).view(B, T, self.n_kv_heads, self.head_dim) |
| v = self.wv(x).view(B, T, self.n_kv_heads, self.head_dim) |
|
|
| if self.qk_norm: |
| q = self.q_norm(q) |
| k = self.k_norm(k) |
|
|
| |
| q = q.transpose(1, 2) |
| k = k.transpose(1, 2) |
| v = v.transpose(1, 2) |
|
|
| q = apply_rope(q, cos, sin).to(v.dtype) |
| k = apply_rope(k, cos, sin).to(v.dtype) |
|
|
| |
| if self.n_rep > 1: |
| k = k.repeat_interleave(self.n_rep, dim=1) |
| v = v.repeat_interleave(self.n_rep, dim=1) |
|
|
| if self.softcap > 0: |
| out = self._attn_softcap(q, k, v, T) |
| else: |
| out = F.scaled_dot_product_attention( |
| q, k, v, is_causal=True, |
| dropout_p=self.dropout if self.training else 0.0, |
| ) |
| out = out.transpose(1, 2).contiguous().view(B, T, -1) |
| return self.wo(out) |
|
|
| def _attn_softcap(self, q, k, v, T): |
| |
| |
| scale = 1.0 / math.sqrt(self.head_dim) |
| scores = torch.matmul(q, k.transpose(-2, -1)) * scale |
| scores = self.softcap * torch.tanh(scores / self.softcap) |
| mask = torch.ones(T, T, dtype=torch.bool, device=q.device).tril() |
| scores = scores.masked_fill(~mask, float("-inf")) |
| attn = F.softmax(scores, dim=-1) |
| return torch.matmul(attn, v) |
|
|
|
|
| class SwiGLU(nn.Module): |
| def __init__(self, cfg: ModelConfig): |
| super().__init__() |
| hidden = int(cfg.mlp_ratio * cfg.d_model) |
| m = cfg.mlp_multiple_of |
| hidden = ((hidden + m - 1) // m) * m |
| self.gate = nn.Linear(cfg.d_model, hidden, bias=False) |
| self.up = nn.Linear(cfg.d_model, hidden, bias=False) |
| self.down = nn.Linear(hidden, cfg.d_model, bias=False) |
|
|
| def forward(self, x): |
| return self.down(F.silu(self.gate(x)) * self.up(x)) |
|
|
|
|
| class Block(nn.Module): |
| def __init__(self, cfg: ModelConfig): |
| super().__init__() |
| self.norm1 = RMSNorm(cfg.d_model, cfg.norm_eps) |
| self.attn = Attention(cfg) |
| self.norm2 = RMSNorm(cfg.d_model, cfg.norm_eps) |
| self.mlp = SwiGLU(cfg) |
|
|
| def forward(self, x, cos, sin): |
| x = x + self.attn(self.norm1(x), cos, sin) |
| x = x + self.mlp(self.norm2(x)) |
| return x |
|
|
|
|
| class Transformer(nn.Module): |
| def __init__(self, cfg: ModelConfig): |
| super().__init__() |
| self.cfg = cfg |
| self.embed = nn.Embedding(cfg.vocab_size, cfg.d_model) |
| self.blocks = nn.ModuleList([Block(cfg) for _ in range(cfg.n_layers)]) |
| self.norm_f = RMSNorm(cfg.d_model, cfg.norm_eps) |
| self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False) |
| if cfg.tie_weights: |
| self.lm_head.weight = self.embed.weight |
|
|
| cos, sin = build_rope_cache(cfg.head_dim, cfg.max_seq_len, cfg.rope_theta) |
| self.register_buffer("rope_cos", cos, persistent=False) |
| self.register_buffer("rope_sin", sin, persistent=False) |
|
|
| self.apply(self._init_weights) |
| |
| |
| scale = 1.0 / math.sqrt(2 * cfg.n_layers) |
| for name, p in self.named_parameters(): |
| if name.endswith("wo.weight") or name.endswith("down.weight"): |
| with torch.no_grad(): |
| p.mul_(scale) |
|
|
| def _init_weights(self, module): |
| if isinstance(module, nn.Linear): |
| nn.init.normal_(module.weight, mean=0.0, std=self.cfg.init_std) |
| if module.bias is not None: |
| nn.init.zeros_(module.bias) |
| elif isinstance(module, nn.Embedding): |
| nn.init.normal_(module.weight, mean=0.0, std=self.cfg.init_std) |
|
|
| def forward(self, idx: torch.Tensor, targets: torch.Tensor | None = None): |
| B, T = idx.shape |
| assert T <= self.cfg.max_seq_len, "sequence longer than rope cache" |
| x = self.embed(idx) |
| cos = self.rope_cos[:T] |
| sin = self.rope_sin[:T] |
| for block in self.blocks: |
| x = block(x, cos, sin) |
| x = self.norm_f(x) |
| logits = self.lm_head(x) |
|
|
| loss = None |
| if targets is not None: |
| loss = F.cross_entropy( |
| logits.view(-1, logits.size(-1)), |
| targets.view(-1), |
| ignore_index=-1, |
| ) |
| return logits, loss |
|
|
| def num_params(self, non_embedding: bool = True) -> int: |
| n = sum(p.numel() for p in self.parameters()) |
| if non_embedding: |
| |
| n -= self.embed.weight.numel() |
| return n |
|
|