7.3 — The forward pass at scale: shapes for GPT-2-small
Goal: Walk the forward pass of GPT-2-small (12 layers, 12 heads, d=768, n_ctx=1024, V=50257) shape-by-shape; recover the 124M parameter count; count the FLOPs and the per-step memory footprint.
1. Why this session exists
Chapter 6 derived the architecture in symbolic form: this, that, . This session pins it to one concrete model — GPT-2-small — and walks every tensor shape and every parameter from input ids to output logits. After this session you can:
- Draw the GPT-2-small computation graph from memory.
- Reproduce the 124M parameter headline from first principles.
- State the per-step FLOPs and memory cost for training, in numbers.
- Adapt the same recipe to any other model size (GPT-2-medium/large/xl, LLaMA-7B, GPT-3) — the shapes scale, the recipe doesn't change.
GPT-2-small is the right reference for two reasons: it is small enough to fit on a single GPU (so the shapes are concrete, not hypothetical), and it is what the nanoGPT walkthrough of 6.16 implements. The numbers here should match what you saw running model.eval() in that session.
2. The GPT-2-small configuration
From Radford et al. (2019), Language Models are Unsupervised Multitask Learners (GPT-2 paper), Table 2 — the smallest of four GPT-2 variants:
| Hyperparameter | Symbol | GPT-2-small value | |---|---|---:| | Vocabulary size | | 50,257 | | Maximum context length | | 1,024 | | Residual stream width | | 768 | | Number of layers | | 12 | | Number of attention heads | | 12 | | Head dimension | | 64 | | FFN inner width | | 3,072 | | Total parameters | | ~124M |
GPT-2-small is the reference model for "small but not tiny" Transformer experiments. nanoGPT trains it on Shakespeare; CS336 uses it; every research codebase has a gpt2-small config.
A few notes:
- is GPT-2's byte-level BPE vocabulary (7.2 §6). The choice was made before training the model.
- is the training context length. Inference can use up to tokens of context; longer prompts must be truncated.
- are the three free architectural numbers; everything else derives from them and the choice .
3. Shape-by-shape forward pass
Let = batch size, = sequence length (up to ). Pick for concreteness.
3.1 Input ids
input_ids ∈ ℤ^{B × T} # shape: (8, 1024); each entry is an integer in [0, V)
A token ID array. After 7.2, each entry is the BPE id of one token. Shape (B, T) = (8, 1024).
3.2 Embedding
GPT-2 uses learned absolute positional embeddings (Appendix 6.B), so the input embedding is the sum of a token embedding and a position embedding:
tok_emb = E[input_ids] # shape: (B, T, d) = (8, 1024, 768)
pos_emb = W_pos[:T] # shape: (T, d) = (1024, 768)
X^(0) = tok_emb + pos_emb # broadcast along batch: (8, 1024, 768)
Two parameter tensors:
- — the token embedding matrix. parameters.
- — the position embedding matrix. parameters.
Together: M parameters. About of the total — a big chunk for such a small model.
3.3 One Transformer block
Each block is the pre-LN block of 6.10:
# Input to block ell:
X ∈ (B, T, d) = (8, 1024, 768)
# --- Attention sub-layer ---
X1 = LayerNorm_1(X) # (B, T, d), uses γ_1, β_1 ∈ (d,)
QKV = X1 @ W_attn # (B, T, 3d) = (8, 1024, 2304); W_attn shape (d, 3d) = (768, 2304)
Q, K, V = split(QKV, axis=-1) # each (B, T, d) = (8, 1024, 768)
Q = Q.view(B, T, h, d_h).transpose(1, 2) # (B, h, T, d_h) = (8, 12, 1024, 64)
K, V = same reshape # (B, h, T, d_h) = (8, 12, 1024, 64)
S = Q @ K.transpose(-2, -1) / sqrt(d_h) # (B, h, T, T) = (8, 12, 1024, 1024)
S = S + causal_mask # causal mask is upper triangle = -inf
A = softmax(S, axis=-1) # (B, h, T, T)
Z = A @ V # (B, h, T, d_h) = (8, 12, 1024, 64)
Z = Z.transpose(1, 2).view(B, T, d) # (B, T, d) = (8, 1024, 768)
attn_out = Z @ W_O # W_O shape (d, d) = (768, 768)
X = X + attn_out # residual: (B, T, d)
# --- FFN sub-layer ---
X2 = LayerNorm_2(X) # (B, T, d), uses γ_2, β_2 ∈ (d,)
H = X2 @ W_1 + b_1 # (B, T, 4d) = (8, 1024, 3072); W_1 (d, 4d) = (768, 3072)
H = GELU(H)
ffn_out = H @ W_2 + b_2 # (B, T, d) = (8, 1024, 768); W_2 (4d, d) = (3072, 768)
X = X + ffn_out # residual: (B, T, d)
Twelve such blocks. After block 12, .
3.4 Final LN and LM head
Z = LayerNorm_final(X^(12)) # (B, T, d) = (8, 1024, 768)
logits = Z @ E^T # (B, T, V) = (8, 1024, 50257), weight-tied to E
The LM head is tied to the embedding matrix — same parameters, used at both ends. The output logits are a probability distribution over the vocabulary at every position.
3.5 Loss
For a training step, the target is target_ids ∈ ℤ^{B × T} — usually the same as input_ids shifted by one position (next-token prediction). The loss is row-wise cross-entropy (1.8):
A single scalar.
4. Parameter count, from first principles
The 124M figure breaks down as follows.
4.1 Per-block parameters
| Component | Shape | Count |
|---|---|---:|
| LayerNorm_1: | | |
| W_attn (combined Q, K, V) | | |
| b_attn (QKV bias) | | |
| W_O (attention output) | | |
| b_O (attention output bias) | | |
| LayerNorm_2: | | |
| W_1 (FFN expand) | | |
| b_1 (FFN expand bias) | | |
| W_2 (FFN contract) | | |
| b_2 (FFN contract bias) | | |
| Per block total | | |
So one block is M parameters. Times blocks: M.
4.2 Embeddings and final LN
| Component | Shape | Count | |---|---|---:| | Token embedding | | | | Position embedding | | | | Final LayerNorm: | | | | LM head (tied to ) | — | | | Embeddings + LN total | | |
4.3 Grand total
This matches OpenAI's headline number. The often-quoted 124M is the round-down; the precise count is M.
4.4 Cross-check against the rule
The Cap 6 first-order rule was . Plug in:
That's M — within of the exact M. The discrepancy is the biases (which the rule dropped) and the per-block LNs. The formula is accurate to a few percent at this scale; you can use it for quick parameter estimates and worry about the corrections only when designing precisely.
5. FLOPs and memory at training time
From 6.12:
5.1 GPT-2-small forward FLOPs
At :
Times blocks: FLOPs per forward pass per batch slot. Per batch of : FLOPs. Plus the LM head, . Total forward FLOPs per training batch.
5.2 Backward FLOPs
The backward pass is roughly twice the forward — every matrix multiply in the forward pass turns into two matrix multiplies in the backward (one for the input gradient, one for the weight gradient). The standard rule of thumb:
So GPT-2-small at : FLOPs per training step.
The Chinchilla heuristic of (6.12 Aside) gives: — close enough.
5.3 Memory: parameters
params, fp32: MB just for the weights. In fp16 (or bf16): MB.
5.4 Memory: optimiser state
Adam (3.5) tracks first moment and second moment , each the same shape as the parameters. Plus the master copy of weights in fp32 (for mixed-precision training, 7.4):
For GPT-2-small: GB.
5.5 Memory: activations
The dominant memory cost at training time. Per training step, every intermediate activation is stored for the backward pass:
- Input embedding output: entries per layer.
- Attention scores: entries per block ← the dominant term.
- FFN hidden state: entries per block.
In fp16 (2 bytes per entry), per block:
Times blocks: GB of activations. Memory is dominated by activations, not by parameters or optimiser state.
This is the structural reason for activation checkpointing (7.7) — recompute activations on demand instead of storing them.
5.6 Full training memory budget for one GPT-2-small step
| Component | Size (fp16/bf16 with fp32 Adam master) | |---|---:| | Parameters (fp16) | MB | | Gradients (fp16) | MB | | Optimizer state (fp32) | GB | | Activations (fp16) | GB | | Total | GB |
Fits comfortably on a 24 GB consumer GPU (RTX 3090, 4090) with batch size 8. The headroom is what lets you push to larger contexts or batch sizes. For larger models (GPT-2-medium and up), the activation memory dominates and forces activation checkpointing.
6. Scaling the recipe
The exact same calculation works for any decoder-only Transformer; only the constants change. Quick table for the GPT-2 family and beyond:
| Model | | | | | | | |---|---:|---:|---:|---:|---:|---:| | GPT-2-small | 12 | 12 | 768 | 3,072 | 1,024 | 124M | | GPT-2-medium | 24 | 16 | 1,024 | 4,096 | 1,024 | 355M | | GPT-2-large | 36 | 20 | 1,280 | 5,120 | 1,024 | 774M | | GPT-2-xl | 48 | 25 | 1,600 | 6,400 | 1,024 | 1.5B | | GPT-3-125M | 12 | 12 | 768 | 3,072 | 2,048 | 125M | | GPT-3-350M | 24 | 16 | 1,024 | 4,096 | 2,048 | 350M | | GPT-3-1.3B | 24 | 24 | 2,048 | 8,192 | 2,048 | 1.3B | | GPT-3-6.7B (curie) | 32 | 32 | 4,096 | 16,384 | 2,048 | 6.7B | | GPT-3-13B | 40 | 40 | 5,120 | 20,480 | 2,048 | 13B | | GPT-3-175B (davinci) | 96 | 96 | 12,288 | 49,152 | 2,048 | 175B | | LLaMA-7B | 32 | 32 | 4,096 | 11,008 (≈ ) | 4,096 | 6.7B | | LLaMA-70B | 80 | 64 | 8,192 | 28,672 | 4,096 | 70B |
A few patterns to notice:
- GPT-2 → GPT-3 architectural change is minimal. Same shape, larger and , longer . The "scale is what matters" thesis is this.
- LLaMA's is slightly less than . Because it uses SwiGLU (Cap 8.4), which uses three matrices in the FFN. Adjusting down keeps the parameter count similar to standard-FFN models.
- The ratio (or for SwiGLU) is constant across the family — this is the only thing tied to a "shape rule"; everything else scales freely.
7. What we keep going forward
Three things from this session:
- Every Transformer is a small set of hyperparameters that determine everything else. Memorise GPT-2-small as .
- Parameter count is approximately . Accurate to a few percent. For GPT-2-small: 39M embed + 85M blocks ≈ 124M total.
- Memory cost is dominated by activations, not parameters or optimiser state, at training time. This will be the central concern of 7.6 and 7.7.
Next session opens up the precision axis: training a 124M model in pure fp32 is wasteful; modern training uses fp16 or bf16 for forward/backward and fp32 for the optimiser. Each choice has a numerical-stability story we have to know.
(a) Reproduce the GPT-2-small parameter count from scratch using the per-block table in §4.1. Identify the single largest term.
(b) A larger model has . Estimate the parameter count using the rule. Now compute the exact count using §4.1 (including biases and LNs). Compare.
(c) For GPT-2-small at , the FLOPs per training step are roughly . On an A100 GPU at ~300 TFLOPs/s fp16, what is the wall-clock per step? Compare with the same calculation for GPT-3-175B at .
(d) The activation memory dominates the training memory budget (§5.5). State which activation contributes the largest term and why it scales as .
(e) A GPT-2-small model has 50,257 vocabulary entries × 768 dim = 38.6M embedding parameters. What fraction of the total is this? Now repeat for GPT-3-175B (V same, d=12288). What does this say about the "embedding-dominated" regime?
References
- [1]
Chen, Z., Deng, Y., Yuan, H., et al. (2024). "Self-play fine-tuning converts weak language models to strong language models" (SPIN). arXiv:2401.01335 — Iterative DPO with SFT data as (9.11 §7.3).
↩