Download src/part1/model.py from sahoosk/anlp-assignment2: direct link, hf CLI and curl.
- Browser
- Download file 6.94 kB
-
https://huggingface.co/sahoosk/anlp-assignment2/resolve/main/src/part1/model.py
- Command line
-
hf download hf://sahoosk/anlp-assignment2/src/part1/model.py
-
curl -L -o model.py https://huggingface.co/sahoosk/anlp-assignment2/resolve/main/src/part1/model.py
6.94 kB
| """A decoder-only transformer whose feed-forward block is swappable. | |
| Attention, RoPE and RMSNorm are carried over from Assignment 1 and are held | |
| fixed across all five ablations -- the feed-forward network is the only thing | |
| that changes, which is what makes the comparison mean anything. | |
| """ | |
| import math | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from .config import TransformerConfig | |
| from .ffn import build_ffn, MoE | |
| class RMSNorm(nn.Module): | |
| def __init__(self, d_model, eps=1e-6): | |
| super().__init__() | |
| self.gamma = nn.Parameter(torch.ones(d_model)) | |
| self.eps = eps | |
| def forward(self, x): | |
| dtype = x.dtype | |
| x = x.float() | |
| rms = torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) | |
| return (self.gamma.float() * x * rms).to(dtype) | |
| class RotaryEmbedding(nn.Module): | |
| """Rotary position embedding, applied to q and k inside attention.""" | |
| def __init__(self, head_dim, max_len, base=10000.0): | |
| super().__init__() | |
| inv = 1.0 / (base ** (torch.arange(0, head_dim, 2).float() / head_dim)) | |
| t = torch.arange(max_len).float() | |
| freqs = torch.outer(t, inv) | |
| emb = torch.cat((freqs, freqs), dim=-1) | |
| self.register_buffer("cos", emb.cos()[None, None], persistent=False) | |
| self.register_buffer("sin", emb.sin()[None, None], persistent=False) | |
| def _rotate_half(x): | |
| x1, x2 = x.chunk(2, dim=-1) | |
| return torch.cat((-x2, x1), dim=-1) | |
| def forward(self, q, k, offset=0): | |
| n = q.size(-2) | |
| cos = self.cos[..., offset:offset + n, :].to(q.dtype) | |
| sin = self.sin[..., offset:offset + n, :].to(q.dtype) | |
| q = q * cos + self._rotate_half(q) * sin | |
| k = k * cos + self._rotate_half(k) * sin | |
| return q, k | |
| class CausalSelfAttention(nn.Module): | |
| def __init__(self, config: TransformerConfig, rope: RotaryEmbedding): | |
| super().__init__() | |
| self.n_heads = config.n_heads | |
| self.n_kv = config.n_kv_heads or config.n_heads | |
| assert self.n_heads % self.n_kv == 0 | |
| self.n_groups = self.n_heads // self.n_kv | |
| self.d_head = config.d_head | |
| self.rope = rope | |
| d = config.d_model | |
| self.wq = nn.Linear(d, self.n_heads * self.d_head, bias=False) | |
| self.wk = nn.Linear(d, self.n_kv * self.d_head, bias=False) | |
| self.wv = nn.Linear(d, self.n_kv * self.d_head, bias=False) | |
| self.wo = nn.Linear(self.n_heads * self.d_head, d, bias=False) | |
| self.dropout = config.dropout | |
| def forward(self, x): | |
| B, N, _ = x.shape | |
| q = self.wq(x).view(B, N, self.n_heads, self.d_head).transpose(1, 2) | |
| k = self.wk(x).view(B, N, self.n_kv, self.d_head).transpose(1, 2) | |
| v = self.wv(x).view(B, N, self.n_kv, self.d_head).transpose(1, 2) | |
| q, k = self.rope(q, k) | |
| if self.n_groups > 1: | |
| k = k.repeat_interleave(self.n_groups, dim=1) | |
| v = v.repeat_interleave(self.n_groups, dim=1) | |
| 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).reshape(B, N, -1) | |
| return self.wo(out) | |
| class Block(nn.Module): | |
| """Pre-norm decoder block; ``self.ffn`` is the MoE/MLP drop-in point.""" | |
| def __init__(self, config: TransformerConfig, rope: RotaryEmbedding): | |
| super().__init__() | |
| self.norm1 = RMSNorm(config.d_model) | |
| self.attn = CausalSelfAttention(config, rope) | |
| self.norm2 = RMSNorm(config.d_model) | |
| self.ffn = build_ffn(config) | |
| def forward(self, x): | |
| x = x + self.attn(self.norm1(x)) | |
| x = x + self.ffn(self.norm2(x)) | |
| return x | |
| class Transformer(nn.Module): | |
| def __init__(self, config: TransformerConfig): | |
| super().__init__() | |
| self.config = config | |
| self.embed = nn.Embedding(config.vocab_size, config.d_model) | |
| self.rope = RotaryEmbedding(config.d_head, config.n_ctx, config.rope_base) | |
| self.blocks = nn.ModuleList( | |
| [Block(config, self.rope) for _ in range(config.n_layers)]) | |
| self.norm_f = RMSNorm(config.d_model) | |
| self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False) | |
| if config.tie_embeddings: | |
| self.lm_head.weight = self.embed.weight | |
| self.apply(self._init) | |
| # Scale the residual-path output projections by 1/sqrt(2*L) so the | |
| # residual stream does not blow up with depth (GPT-2 initialisation). | |
| for name, p in self.named_parameters(): | |
| if name.endswith("wo.weight") or name.endswith("fc2.weight"): | |
| nn.init.normal_(p, std=0.02 / math.sqrt(2 * config.n_layers)) | |
| def _init(m): | |
| if isinstance(m, nn.Linear): | |
| nn.init.normal_(m.weight, std=0.02) | |
| if m.bias is not None: | |
| nn.init.zeros_(m.bias) | |
| elif isinstance(m, nn.Embedding): | |
| nn.init.normal_(m.weight, std=0.02) | |
| def forward(self, idx, targets=None, loss_mask=None): | |
| # Input shape: (B, N_ctx); logits shape: (B, N_ctx, N_vocab) | |
| x = self.embed(idx) | |
| for block in self.blocks: | |
| x = block(x) | |
| logits = self.lm_head(self.norm_f(x)) | |
| if targets is None: | |
| return logits, None, self.aux_losses() | |
| loss = F.cross_entropy( | |
| logits.view(-1, logits.size(-1)).float(), targets.reshape(-1), | |
| ignore_index=-100, reduction="none") | |
| if loss_mask is not None: | |
| m = loss_mask.reshape(-1).float() | |
| loss = (loss * m).sum() / m.sum().clamp_min(1.0) | |
| else: | |
| loss = loss[targets.reshape(-1) != -100].mean() | |
| return logits, loss, self.aux_losses() | |
| def aux_losses(self): | |
| """Summed router losses over every MoE layer (zero for the dense model).""" | |
| aux = torch.zeros((), device=self.embed.weight.device) | |
| z = torch.zeros((), device=self.embed.weight.device) | |
| for block in self.blocks: | |
| if isinstance(block.ffn, MoE): | |
| aux = aux + block.ffn.aux_loss | |
| z = z + block.ffn.z_loss | |
| return aux, z | |
| def expert_counts(self): | |
| """Per-layer token counts from the last forward, or None if dense.""" | |
| out = [] | |
| for block in self.blocks: | |
| if isinstance(block.ffn, MoE): | |
| out.append(block.ffn.last_counts) | |
| return out or None | |
| def expert_assignments(self): | |
| """Per-layer (B, N, k) expert ids from the last forward.""" | |
| out = [] | |
| for block in self.blocks: | |
| if isinstance(block.ffn, MoE): | |
| out.append(block.ffn.last_assign) | |
| return out or None | |
| def num_params(self, non_embedding=False): | |
| n = sum(p.numel() for p in self.parameters()) | |
| if non_embedding: | |
| n -= self.embed.weight.numel() | |
| return n | |