Implementing Multi-Head Attention
The smallest GPT-2 model has 12 attention heads, 768-dimensional input and output embeddings, and supports a context length of 1,024 tokens.
A Real Model’s Attention Settings
A useful way to understand a module is to configure it to match a real model. The smallest GPT-2 model fixes three attention settings: 12 heads, 768-dimensional input and output embeddings, and a context length of 1,024 tokens. The task is to supply those settings to MultiHeadAttention and then follow the tensors through its forward pass.
Map Settings to Constructor Arguments
The module arguments connect the model settings to the dimensions used inside the attention computation. Set d_in to 768 so the module accepts GPT-2’s input embedding width. Set d_out to 768 so its projected representations and returned output use that width. Set num_heads to 12. Set context_length to 1,024 to match the model’s supported token context.
| GPT-2 setting | MultiHeadAttention argument | What it specifies |
|---|---|---|
| 768-dimensional input embedding | d_in = 768 | Width accepted for each input token representation |
| 768-dimensional output embedding | d_out = 768 | Width produced by each query, key, and value projection, and by the returned output |
| 12 attention heads | num_heads = 12 | Number of head slices used to organize each projected representation |
| 1,024-token context | context_length = 1,024 | Context setting supplied to the module |
The smallest GPT-2 settings mapped to the MultiHeadAttention arguments.
The class requires d_out to divide evenly by num_heads. With these settings, 768 divided by 12 gives a head_dim of 64. This is the width assigned to each head. Check this divisibility requirement rather than assuming that every possible output width can be split evenly.
Check the GPT-2-sized configuration
Choose the MultiHeadAttention settings that match the smallest GPT-2 model, then determine the width of one head.
- 1Set the embedding widths
Use
d_in= 768 andd_out= 768 to match the input and output embedding dimensions. - 2Set the head count
Use
num_heads= 12. Check thatd_outdivides evenly by this value. - 3Find the per-head width
Divide 768 by 12. The result is a
head_dimof 64. - 4Set the context
Use
context_length= 1,024 to match the model’s supported context.
The matching settings are d_in = 768, d_out = 768, num_heads = 12, and context_length = 1,024. Each head has width 64.
Follow the Forward-Pass Shapes
What do you think happens?
If x has shape (b, num_tokens, d_in), and this module uses d_out for its projection width, what shape does the forward method return?
Reveal answer
(b, num_tokens, d_out)
The method merges the head outputs into the d_out feature width and applies out_proj. The batch and token dimensions remain in the returned shape.
The forward pass uses one projection for each role: query, key, and value. Each projection maps from d_in to d_out, so each projected tensor has shape (b, num_tokens, d_out). The method then reshapes and transposes those tensors to make the head dimension explicit. The same shape changes apply to queries, keys, and values.
After the query, key, and value projections, each tensor is reshaped with view so its d_out axis becomes num_heads and head_dim. The next transpose(1, 2) moves the head axis ahead of the token axis. Queries and keys can then be used to form scores per head: queries are multiplied by the transposed keys. With query and key tensors shaped (b, num_heads, num_tokens, head_dim), the score tensor has shape (b, num_heads, num_tokens, num_tokens).
After attention weights are applied to the values, each head’s context has shape (b, num_heads, num_tokens, head_dim). The method transposes the head and token axes back, calls contiguous(), and uses view to merge num_heads and head_dim into d_out. The output projection then produces the returned context tensor with shape (b, num_tokens, d_out).
Trace a GPT-2-sized input
Suppose an input has shape (b, num_tokens, 768) and the module uses 12 heads with d_out = 768. Trace the shapes through the main stages.
- 1Project
The query, key, and value projections each produce shape (b,
num_tokens, 768). - 2Split the projected width
Since
d_outis divided into 12 heads of width 64, view produces shape (b,num_tokens, 12, 64). - 3Put heads before tokens
transpose(1, 2)produces shape (b, 12,num_tokens, 64). - 4Form attention scores
Multiplying queries by transposed keys produces a score shape of (b, 12,
num_tokens,num_tokens). - 5Merge head contexts
After attention, transpose restores the order (b,
num_tokens, 12, 64), and view merges the last two dimensions into 768.
The returned shape is (b, num_tokens, 768), after out_proj.
How view and transpose Divide the Width
The projected tensor initially keeps all head features together in one d_out axis. The view operation reorganizes that axis into two axes: num_heads and head_dim. For the smallest GPT-2 configuration, the 768-wide projection is represented as 12 heads with 64 features per head. The following transpose(1, 2) changes the axis order from (b, num_tokens, num_heads, head_dim) to (b, num_heads, num_tokens, head_dim). It rearranges the axes; it does not change the head count or per-head width.
Integrated Heads and the Earlier Wrapper
MultiHeadAttentionWrapper builds multiple heads by stacking complete single-head attention layers. Each of those layers has its own weight matrices. The integrated MultiHeadAttention design instead uses one projection per role—query, key, and value—and splits each projected width into heads internally. In other words, the head structure is created by reshaping a shared wide projection, rather than by giving every head a separate single-head layer.
Both designs organize computation into multiple heads, but their parameters live in different arrangements. In the wrapper, each complete single-head layer owns its matrices. In the integrated design, each role has one wide projection that is divided into heads afterward. The same integrated approach applies to keys and values as well as queries.
Find the First Shape Mismatch
When the final output has an unexpected shape, inspect the forward pass in order rather than guessing at the last line. Check the input, then the projected tensors, the result of view, the result of transpose, the attention scores, the head contexts, the merged tensor, and finally out_proj. The first point where the observed shape differs from the expected shape is where to focus your investigation.
Common mistake Treating context length as an embedding width.
What it looks likeUsing 1,024 as the feature width because the context is 1,024 tokens.
Why it failsThe embedding width is 768 dimensions per token; context length counts tokens.
Do this insteadKeep d_in and d_out at 768 and context_length at 1,024 for this configuration.
Common mistake Assuming the head split works for any d_out and num_heads.
What it looks likeChoosing values that do not divide evenly.
Why it failsThe class requires
d_outto be divisible bynum_headsso thathead_dimis well-defined.Do this insteadCheck divisibility before relying on the split; for this configuration, 768 divides evenly into 12 heads of 64.
Common mistake Expecting transpose to change the number of heads or features.
Why it failstranspose(1, 2)changes axis order so the head axis comes before the token axis.Do this insteadCompare the axis order before and after transpose, while keeping num_heads and head_dim unchanged.
Practice the Shape Trace
A MultiHeadAttention module receives x with shape (b, num_tokens, 768). It uses d_out = 768 and num_heads = 12. Write down the expected shape after projection, after view, after transpose(1, 2), for the attention scores, after merging the heads, and at the output.
Hints
- Compute
head_dimby dividingd_outbynum_heads. - The score shape has one
num_tokensaxis for queries and one for keys. - After the heads are merged, the feature width is
d_out.
A reliable debugging habit is to record the expected shape after every operation that projects, reshapes, transposes, or merges a tensor. Comparing those checkpoints helps identify the earliest step where the shape stops matching the intended forward-pass path.
Key Takeaways
- The smallest GPT-2 attention configuration uses 12 heads, 768-dimensional input and output embeddings, and a 1,024-token context.
- For MultiHeadAttention, those settings map to
d_in= 768,d_out= 768,num_heads= 12, andcontext_length= 1,024. - The class splits each projected
d_outwidth intonum_headsandhead_dim; for this configuration,head_dimis 64. - view creates the head and within-head axes, while
transpose(1, 2)moves the head axis before the token axis. - Trace expected tensor shapes in forward-pass order to find the first point where a shape mismatch appears.