model.py
model.py · the finished partthe file's own shape
The model is five pieces: RMSNorm, CausalSelfAttention, MLP, Block and GPT, with a GPTConfig dataclass holding every default. Read top to bottom, it goes from the smallest normalization up to the loop that generates tokens.
RMSNorm instead of LayerNorm
Instead of subtracting a mean, it divides by a root-mean-square. The comment explains the reasoning: it keeps activations at a stable scale so gradients do not vanish or explode, and the bias is optional because modern models usually leave it off.
inv_rms = torch.rsqrt(mean_square + self.eps)
y = x_norm * self.weight
attention: RoPE on top of grouped queries
One c_attn linear produces queries plus two groups of keys and values, sized by n_kv_head, that is grouped-query attention, where several query heads share one key-value head. Positions are handled by rotary embeddings applied to the new tokens only, which is what lets a cached forward pass append a single token without recomputing the whole context.
Two details worth keeping. qk_rmsnorm optionally normalizes queries and keys on their last dimension before the score is taken. And the causal rule is handed to scaled dot-product attention as is_causal=True for a plain forward pass; an explicit additive mask is built only when a KV cache makes the key length longer than the query length.
attn_mask = torch.triu(attn_mask, diagonal=1)
attn_mask[attn_mask == 1] = -torch.inf
attn_mask[attn_mask == 0] = 0.0
That comment is the exercise in the matrix below, drawn out token by token.
MLP: a gated block
The hidden width is two thirds of the classic four-times rule, rounded up to a multiple of 64, then doubled by the gate split, so the parameter count stays near a GELU MLP's while using fewer matrix multiplies.
x = a * self.silu(b) # SwiGLU
the defaults it ships with
- block_size
- 1024 positions of context
- vocab_size
- 50304, the GPT-2 vocabulary padded to a multiple of 64
- n_layer / n_head
- 12 and 12
- n_kv_head
- 3, so twelve query heads share three key-value heads
- bias
- False, unlike GPT-2
- rope_base
- 10000.0, with a
rope_scale_factorfor longer inference contexts - qk_rmsnorm
- False by default, switchable per run
train.py overrides these with much smaller numbers for a CPU run, four layers, four heads, 256 wide, 128 positions at a time. That is the configuration this worksheet's figures are sized for.