Devatri's picture
Upload model.py
f36b8eb verified
Raw History Blame Contribute Delete
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
@dataclass(frozen=True)
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
@classmethod
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
@staticmethod
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}
@torch.no_grad()
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())