Transformers from First PrinciplesPost 5 of 18 · 18 of 18 written
- How to use this guide
- 01The machinery: how a neural network computes and learns
- 02Making deep networks trainable
- 03Turning text into numbers
- 04Attention from first principles
- 05The Transformer block
- 06Position: telling the model where each token is
- 07Architecture families and training objectives
- 08Pretraining at scale
- 09Inference and serving
- 10Efficiency variants: MoE, long context and beyond attention
- 11Post-training: from text predictor to assistant
- 12Adapting models: prompting, fine-tuning, LoRA and distillation
- 13Embeddings, retrieval and RAG
- 14Beyond text: vision, speech and multimodal models
- 15Inside the model: interpretability
- 16Evaluation, failure modes and security
- 17From model to system
- 18Fluency kit
5. The Transformer block
A transformer is a stack of identical blocks. Each block does two things in turn: multi-head attention moves information between positions, then an MLP processes each position on its own. Both read from a shared vector per token - the residual stream - and add their results back into it.
Multi-head attention
One attention operation gives each token one weighted average, so it can follow one kind of relationship at a time. Language needs many at once: the subject of this verb, the previous token, the matching bracket, the earlier mention of this name. Multi-head attention runs h attention operations in parallel, each in a subspace of size d_h = d / h, then combines them:
The total cost equals one big head of width d, because each head works in d/h dimensions. W_O (d × d) mixes the heads’ outputs back into the residual stream.
In code nobody loops over heads. One matrix multiply produces Q, K and V for all heads (d → 3d), and a reshape splits them:
qkv = x @ W_qkv # (B, T, 3d)
q, k, v = qkv.split(d, dim=-1) # each (B, T, d)
q = q.view(B, T, h, d_h).transpose(1, 2) # (B, h, T, d_h); same for k, v
y = F.scaled_dot_product_attention(q, k, v, is_causal=True) # (B, h, T, d_h)
y = y.transpose(1, 2).reshape(B, T, d) @ W_o # (B, T, d)The MLP (feed-forward network)
After attention, each token passes independently through the same two-layer network:
W₁ expands d to a hidden size (classically 4d), σ is the nonlinearity, and W₂ projects back to d. Most modern LLMs use a gated version, SwiGLU, with three matrices and a hidden size near (8/3)·d so the parameter count stays the same:
The division of labour: attention is communication between tokens; the MLP is computation within a token. MLPs hold about two thirds of the parameters and appear to store much of the model’s factual association (Part 15).
Residual stream and pre-norm
Each block applies:
This is the pre-norm arrangement: normalize each sublayer’s input but leave the residual path untouched. The 2017 original used post-norm, x ← Norm(x + F(x)), which puts a normalization on the main path; it trains less stably at depth and needs careful warmup. Pre-norm is now standard, with one final norm after the last block.
[embedded content: decoder-only forward pass · one block shown, repeated L times]
The lines on the left are the residual (skip) path: each sublayer’s output is added to an untouched copy of its input at the ⊕, so the stream flows straight from embedding to unembedding.
The full forward pass of a decoder-only model
- Token ids (B, T) from the tokenizer.
- Embedding lookup → (B, T, d). With learned absolute positions, a position vector is added here; with RoPE, position is applied inside attention instead.
- L blocks, each: norm → causal multi-head attention → add; norm → MLP → add. The shape stays (B, T, d) throughout.
- Final norm.
- Unembedding → logits (B, T, V).
- Training: cross-entropy between the logits at position t and the actual token at t + 1, averaged over all positions - T training examples from one pass. Inference: take the logits at the last position, sample a token, append it, repeat.
Counting parameters
Per block, attention has four d × d matrices (Q, K, V, O), which is 4d², and the MLP has two d × 4d matrices, which is 8d². So:
Check against GPT-2 small (L = 12, d = 768, V = 50,257): 12 × 12 × 768² ≈ 85M in the blocks, plus 38.6M token embeddings and 0.8M position embeddings, ≈ 124M - the published size. For GPT-3 (L = 96, d = 12,288): 12 × 96 × 12,288² ≈ 174B, matching the 175B headline. Doing this in your head is a fluency marker.
Design knobs you will see in model cards
| Knob | Typical choice | What it trades |
|---|---|---|
| Depth L vs width d | deep-and-narrow vs wide-and-shallow | depth adds serial latency; width parallelizes better |
| Heads h and head size d_h | d_h = 64 or 128 | more heads = more relation types, each smaller |
| KV heads | fewer than query heads (GQA) | smaller KV cache at inference (Part 9) |
| MLP type and ratio | SwiGLU, about (8/3)·d or larger | quality vs parameters |
| Norm | RMSNorm, pre-norm | stability and speed |
| Position | RoPE | extrapolation and long context (Part 6) |
| Bias terms | often removed | slightly fewer parameters, more stable |
| Tied embeddings | common in small models | saves V × d parameters |
Questions you should now be able to answer
- Why does multi-head attention cost the same as single-head attention of width d?
- What can attention do that the MLP cannot, and vice versa?
- Why is pre-norm more stable than post-norm? Point to the residual path.
- Estimate the parameter count for L = 32, d = 4096, V = 32,000, with a 4d MLP and no GQA.
- Trace the tensor’s shape from token ids to logits.