Download model.py from Devatri/anlp-assignment2-part2-muon: direct link, hf CLI and curl.
- Browser
- Download file 8.21 kB
-
https://huggingface.co/Devatri/anlp-assignment2-part2-muon/resolve/main/model.py
- Command line
-
hf download hf://Devatri/anlp-assignment2-part2-muon/model.py
-
curl -L -o model.py https://huggingface.co/Devatri/anlp-assignment2-part2-muon/resolve/main/model.py
8.21 kB
| """Dense decoder-only Transformer adapted from the Part 1 Model 1 architecture.""" | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import torch | |
| from torch import nn | |
| from torch.nn import functional as F | |
| class ModelConfig: | |
| vocab_size: int | |
| d_model: int = 512 | |
| n_heads: int = 8 | |
| n_layers: int = 6 | |
| mlp_hidden_size: int = 2048 | |
| max_sequence_length: int = 256 | |
| dropout: float = 0.1 | |
| tie_embeddings: bool = True | |
| def from_mapping(cls, values: dict, vocab_size: int | None = None) -> ModelConfig: | |
| clean = dict(values) | |
| if vocab_size is not None: | |
| clean["vocab_size"] = vocab_size | |
| return cls(**clean) | |
| def __post_init__(self) -> None: | |
| if self.d_model % self.n_heads: | |
| raise ValueError("d_model must be divisible by n_heads") | |
| if self.d_model // self.n_heads % 2: | |
| raise ValueError("attention head size must be even for rotary embeddings") | |
| if self.max_sequence_length < 2: | |
| raise ValueError("max_sequence_length must be at least 2") | |
| class DenseMLP(nn.Module): | |
| """Part 1 Model 1's standard two-layer GELU feed-forward network.""" | |
| def __init__(self, config: ModelConfig) -> None: | |
| super().__init__() | |
| self.up = nn.Linear(config.d_model, config.mlp_hidden_size, bias=False) | |
| self.down = nn.Linear(config.mlp_hidden_size, config.d_model, bias=False) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return self.down(F.gelu(self.up(x))) | |
| class CausalSelfAttention(nn.Module): | |
| def __init__(self, config: ModelConfig) -> None: | |
| super().__init__() | |
| self.n_heads = config.n_heads | |
| self.head_size = config.d_model // config.n_heads | |
| self.dropout = config.dropout | |
| self.qkv = nn.Linear(config.d_model, 3 * config.d_model, bias=False) | |
| self.out = nn.Linear(config.d_model, config.d_model, bias=False) | |
| positions = torch.arange(config.max_sequence_length) | |
| frequencies = 10000.0 ** (-torch.arange(0, self.head_size, 2).float() / self.head_size) | |
| angles = torch.outer(positions, frequencies) | |
| self.register_buffer("rope_cos", angles.cos(), persistent=False) | |
| self.register_buffer("rope_sin", angles.sin(), persistent=False) | |
| def _apply_rope(self, tensor: torch.Tensor, position_offset: int) -> torch.Tensor: | |
| length = tensor.shape[-2] | |
| stop = position_offset + length | |
| cosine = self.rope_cos[position_offset:stop].to(tensor.dtype)[None, None] | |
| sine = self.rope_sin[position_offset:stop].to(tensor.dtype)[None, None] | |
| even, odd = tensor[..., ::2], tensor[..., 1::2] | |
| return torch.stack( | |
| (even * cosine - odd * sine, even * sine + odd * cosine), dim=-1 | |
| ).flatten(-2) | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| cache: tuple[torch.Tensor, torch.Tensor] | None = None, | |
| ) -> tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]: | |
| batch, length, width = x.shape | |
| query, key, value = ( | |
| self.qkv(x) | |
| .view(batch, length, 3, self.n_heads, self.head_size) | |
| .permute(2, 0, 3, 1, 4) | |
| .unbind(0) | |
| ) | |
| cached_length = 0 if cache is None else cache[0].shape[-2] | |
| query = self._apply_rope(query, cached_length) | |
| key = self._apply_rope(key, cached_length) | |
| if cache is not None: | |
| if length != 1: | |
| raise ValueError("cached attention accepts one new token at a time") | |
| key = torch.cat((cache[0], key), dim=-2) | |
| value = torch.cat((cache[1], value), dim=-2) | |
| attended = F.scaled_dot_product_attention( | |
| query, | |
| key, | |
| value, | |
| is_causal=cache is None, | |
| dropout_p=self.dropout if self.training else 0.0, | |
| ) | |
| output = self.out(attended.transpose(1, 2).reshape(batch, length, width)) | |
| return output, (key, value) | |
| class TransformerBlock(nn.Module): | |
| def __init__(self, config: ModelConfig) -> None: | |
| super().__init__() | |
| self.attention_norm = nn.LayerNorm(config.d_model) | |
| self.mlp_norm = nn.LayerNorm(config.d_model) | |
| self.attention = CausalSelfAttention(config) | |
| self.mlp = DenseMLP(config) | |
| self.dropout = nn.Dropout(config.dropout) | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| cache: tuple[torch.Tensor, torch.Tensor] | None = None, | |
| ) -> tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]: | |
| attended, next_cache = self.attention(self.attention_norm(x), cache) | |
| x = x + self.dropout(attended) | |
| return x + self.dropout(self.mlp(self.mlp_norm(x))), next_cache | |
| class DenseTransformer(nn.Module): | |
| def __init__(self, config: ModelConfig) -> None: | |
| super().__init__() | |
| self.config = config | |
| self.embedding = nn.Embedding(config.vocab_size, config.d_model) | |
| self.dropout = nn.Dropout(config.dropout) | |
| self.blocks = nn.ModuleList([TransformerBlock(config) for _ in range(config.n_layers)]) | |
| self.final_norm = nn.LayerNorm(config.d_model) | |
| self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False) | |
| self.apply(self._initialize) | |
| if config.tie_embeddings: | |
| self.lm_head.weight = self.embedding.weight | |
| def _initialize(module: nn.Module) -> None: | |
| if isinstance(module, (nn.Linear, nn.Embedding)): | |
| nn.init.normal_(module.weight, mean=0.0, std=0.02) | |
| if isinstance(module, nn.Linear) and module.bias is not None: | |
| nn.init.zeros_(module.bias) | |
| def forward( | |
| self, | |
| input_ids: torch.Tensor, | |
| labels: torch.Tensor | None = None, | |
| *, | |
| cache: list[tuple[torch.Tensor, torch.Tensor]] | None = None, | |
| use_cache: bool = False, | |
| ) -> dict[str, object]: | |
| if input_ids.ndim != 2: | |
| raise ValueError("input_ids must have shape [batch, sequence]") | |
| cached_length = 0 if cache is None else cache[0][0].shape[-2] | |
| if cached_length + input_ids.shape[1] > self.config.max_sequence_length: | |
| raise ValueError("input exceeds configured maximum sequence length") | |
| if cache is not None and len(cache) != len(self.blocks): | |
| raise ValueError("cache must contain one entry per Transformer block") | |
| hidden = self.dropout(self.embedding(input_ids)) | |
| next_cache = [] | |
| for index, block in enumerate(self.blocks): | |
| hidden, block_cache = block(hidden, None if cache is None else cache[index]) | |
| if use_cache: | |
| next_cache.append(block_cache) | |
| logits = self.lm_head(self.final_norm(hidden)) | |
| loss = None | |
| if labels is not None: | |
| if labels.shape != input_ids.shape: | |
| raise ValueError("labels and input_ids must have the same shape") | |
| loss = F.cross_entropy( | |
| logits.reshape(-1, logits.shape[-1]).float(), | |
| labels.reshape(-1), | |
| ignore_index=-100, | |
| ) | |
| return {"logits": logits, "loss": loss, "cache": next_cache if use_cache else None} | |
| def greedy_generate(self, prompt: torch.Tensor, max_new_tokens: int) -> torch.Tensor: | |
| """Generate with the model's forward pass and deterministic greedy decoding.""" | |
| if prompt.ndim != 2: | |
| raise ValueError("prompt must have shape [batch, sequence]") | |
| if prompt.shape[1] + max_new_tokens > self.config.max_sequence_length: | |
| raise ValueError("prompt and continuation exceed the model context window") | |
| generated = prompt | |
| output = self(prompt, use_cache=True) | |
| for step in range(max_new_tokens): | |
| next_token = output["logits"][:, -1].argmax(dim=-1, keepdim=True) | |
| generated = torch.cat((generated, next_token), dim=1) | |
| if step + 1 < max_new_tokens: | |
| output = self(next_token, cache=output["cache"], use_cache=True) | |
| return generated | |
| def parameter_count(model: nn.Module) -> int: | |
| return sum(parameter.numel() for parameter in model.parameters()) | |