Concepts / Implementing Multi-Head Attention

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.

  • Programming

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.

Attention heads12Input and outputembeddings768 dimensionsContext length1,024 tokens
Which three fixed values define the attention configuration?

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 settingMultiHeadAttention argumentWhat it specifies
768-dimensional input embeddingd_in = 768Width accepted for each input token representation
768-dimensional output embeddingd_out = 768Width produced by each query, key, and value projection, and by the returned output
12 attention headsnum_heads = 12Number of head slices used to organize each projected representation
1,024-token contextcontext_length = 1,024Context setting supplied to the module

The smallest GPT-2 settings mapped to the MultiHeadAttention arguments.

12 headsnum_heads768 dimensionsd_in7681,024 tokensd_out768context_length1,024setssets input widthsets output widthsets
Which module argument receives each GPT-2 setting?

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.

Worked example

Check the GPT-2-sized configuration

Choose the MultiHeadAttention settings that match the smallest GPT-2 model, then determine the width of one head.

  1. 1
    Set the embedding widths

    Use d_in = 768 and d_out = 768 to match the input and output embedding dimensions.

  2. 2
    Set the head count

    Use num_heads = 12. Check that d_out divides evenly by this value.

  3. 3
    Find the per-head width

    Divide 768 by 12. The result is a head_dim of 64.

  4. 4
    Set the context

    Use context_length = 1,024 to match the model’s supported context.

Result

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
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.

Input x(b, num_tokens, d_in)Q, K, V projections(b, num_tokens, d_out)view(b, num_tokens, num_heads,head_dim)transpose(b, num_heads, num_tokens,head_dim)Attention scores(b, num_heads, num_tokens,num_tokens)Head contexts(b, num_heads, num_tokens,head_dim)transpose back(b, num_tokens, num_heads,head_dim)Merge heads(b, num_tokens, d_out)out_proj(b, num_tokens, d_out)
What shape does the tensor have after each major forward-pass step?

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).

Worked example

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.

  1. 1
    Project

    The query, key, and value projections each produce shape (b, num_tokens, 768).

  2. 2
    Split the projected width

    Since d_out is divided into 12 heads of width 64, view produces shape (b, num_tokens, 12, 64).

  3. 3
    Put heads before tokens

    transpose(1, 2) produces shape (b, 12, num_tokens, 64).

  4. 4
    Form attention scores

    Multiplying queries by transposed keys produces a score shape of (b, 12, num_tokens, num_tokens).

  5. 5
    Merge head contexts

    After attention, transpose restores the order (b, num_tokens, 12, 64), and view merges the last two dimensions into 768.

Result

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.

Projected widthd_out = 768Reshaped axesnum_heads = 12; head_dim =64Head axis12 headsAxis order aftertranspose(b, heads, tokens,head_dim)Within-head axis64 features per headviewmoves before tokens
How does the single projected-width axis become a head axis and a within-head axis?

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.

Single-head layer 1own weight matricesStacked head outputsOne projection perrolequery, key, valueInternal head splitSingle-head layer 2own weight matrices
Where do the projections and head-specific structure live in each design?

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.

Input shapeProjection shapeShape after viewShape after transposeScore shapeContext shapeMerged shapeOutput shapeInspect this stepmatchesdoes not matchmatchesdoes not matchmatchesdoes not matchmatchesdoes not matchmatchesdoes not matchmatchesdoes not matchmatchesdoes not match
If the output shape is wrong, which intermediate shape should you check next?
  • Common mistake

    Treating context length as an embedding width.

    What it looks like

    Using 1,024 as the feature width because the context is 1,024 tokens.

    Why it fails

    The embedding width is 768 dimensions per token; context length counts tokens.

    Do this instead

    Keep 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 like

    Choosing values that do not divide evenly.

    Why it fails

    The class requires d_out to be divisible by num_heads so that head_dim is well-defined.

    Do this instead

    Check 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 fails

    transpose(1, 2) changes axis order so the head axis comes before the token axis.

    Do this instead

    Compare the axis order before and after transpose, while keeping num_heads and head_dim unchanged.

Practice the Shape Trace

MEDIUM

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_dim by dividing d_out by num_heads.
  • The score shape has one num_tokens axis for queries and one for keys.
  • After the heads are merged, the feature width is d_out.
Key point

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, and context_length = 1,024.
  • The class splits each projected d_out width into num_heads and head_dim; for this configuration, head_dim is 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.

More concepts starting with I