Concepts / Single-Head Attention Mechanism

Single-Head Attention Mechanism

The GPT-2 initialization in the source passes block_size, d_in, d_out, a dropout value of 0.0, and num_heads into MultiHeadAttention, and each argument should be checked by position.

  • Programming

Starting From the Smallest GPT-2 Setup

This article follows a single practical task: setting up an attention module for the smallest GPT-2 model and then checking that the result matches what you expected. Attention modules are easy to build and hard to debug, because a wrong configuration rarely fails with a loud error. A module can be built from the wrong settings, or from an incomplete set of weights, and still run. The only way to catch this is to know which values were supplied, in which order, and what each stage should produce.

The lesson is built around a before-you-theorize approach. Read the initialization for the smallest GPT-2 model, set up its values, construct the module, and watch what the trace reports at each step. The goal is to understand what you are looking at, so that when a result does not match your expectation you know which line to question first.

The Five Slots of the Initialization Call

The multi-head attention call supplies five values by position: the input dimension d_in, the output dimension d_out, the block size, a dropout value of 0.0, and the number of heads num_heads. The lesson does not give a full definition of each argument beyond these values. Treat the call as a contract: the input dimension, output dimension, block size, dropout rate, and head count must all be supplied together, and none of them can be left out or swapped silently.

d_ininput dimensiond_outoutput dimensionblock_sizeblock size0.0dropout valuenum_headshead countthenthenthenthen
Which value in the call maps to which parameter, and in what order should each position be checked so that no argument is swapped?

Each slot should be checked one at a time. When you copy a configuration from one example to another, do not assume the positions line up. Read the call from left to right, name each value as you go, and confirm that it matches the parameter it is meant to fill. Two of these values, d_in and d_out, are the ones most often confused, because both are widths and both are plain integers.

Choosing d_out for a Target Output Width

A common assumption is that a module's output dimension always equals the d_out value you pass in. The lesson shows a different situation. It uses a wrapper that takes num_heads=2 and asks for an output dimension of 2, which matches the result that single-head attention produced earlier. To get that output width with two heads, the lesson changes the projection dimension d_out to 1. The value you pass for d_out is therefore not the same thing as the final width you see, and d_out must be reasoned about together with the head count.

Head 1width 1Combined outputwidth 2Head 2width 11 + 1 = 21 + 1 = 2
How does a total output width get divided into equal per-head slices, and how do those slices recombine into the output?
Worked example

Reaching an output width of 2 with two heads

The target output dimension is 2, and the wrapper uses num_heads=2. What value should the projection dimension d_out take?

  1. 1
    Identify the target

    The learner wants a final output dimension of 2, matching the single-head result.

  2. 2
    Identify the head count

    The wrapper uses num_heads=2, so the output is produced from two heads.

  3. 3
    Divide the target across heads

    Two equal head slices must together reach a width of 2, so each head contributes a width of 1.

  4. 4
    Set d_out

    The lesson changes the projection dimension d_out to 1, which yields one unit per head across the two heads.

Result

d_out is set to 1, not 2, so the combined output reaches the target width of 2.

The rule to keep in mind is that d_out is a total width that the heads share, not a per-head width you can set independently. If you change num_heads without revisiting d_out, the combined output width will change too. This is why the lesson treats the head count and d_out as a pair, and why a setup that passes the right d_out value for one head count can produce the wrong output width after the head count is changed.

Copying Query, Key, and Value Weights Between Modules

The second setup task is copying learned weights from one attention module into another. The lesson states that the correct assignment transposes the weights of the query, key, and value matrices taken from another attention mechanism. Each target attribute is wrapped in torch.nn.Parameter, so that the copied tensor is registered as a parameter of the new module rather than as a plain tensor.

Source W_queryTransposeParameter wrapTarget W_querySource W_keyTransposeParameter wrapTarget W_keySource W_valueTransposeParameter wrapTarget W_valuetransposewrapassigntransposewrapassigntransposewrapassign
Where do the W_query, W_key, and W_value weights travel from the source module to the target module, and where does the transposed matrix land?

Read the three assignments as one repeated operation applied to three different matrices. The same pattern covers the query, the key, and the value weights. Because the lesson presents them as a set, a missing line or a line written differently produces a module built from an incomplete set of weights. Nothing in that situation is a syntax error, so the problem stays hidden until the outputs are compared.

On the transpose, the lesson states that the copy is transposed before it is wrapped and assigned. This passage does not spell out the reasoning behind the transpose, so treat it as a required step of the transfer. The transposed matrix is what gets placed into the target attribute, so skipping the transpose or applying it to only one of the three matrices changes what the new module holds.

Mistakes That Make a Setup Diverge

  • Common mistake

    Assuming the output width always equals the d_out value passed in

    What it looks like

    Passing d_out=2 with num_heads=2 and expecting the output to be width 2 because the number matches.

    Why it fails

    The lesson's two-head wrapper reaches an output width of 2 by setting d_out to 1, so d_out and the head count together determine the final width.

    Do this instead

    Work out the per-head width first, then check that the combined output reaches the target dimension.

  • Common mistake

    Reading the five positional values in one assumed order

    What it looks like

    Copying a configuration and assuming d_in and block_size sit in the same positions as in the earlier listing.

    Why it fails

    The lesson lists the five values in different orders in two places, so position alone is not a reliable guide.

    Do this instead

    Name each positional value against the parameter in the exact call you are running.

  • Common mistake

    Transferring only some of the query, key, and value weights

    What it looks like

    Copying W_query and W_value but leaving W_key at its default values.

    Why it fails

    The module is built from an incomplete set of weights, and no syntax error signals the problem.

    Do this instead

    Confirm that all three assignments are present and follow the same transpose-and-wrap pattern.

  • Common mistake

    Leaving out the transpose during the weight copy

Practice: Locating the First Divergence

What do you think happens?

You build a two-head module that should produce an output of width 2, and the output width comes out as 4. Before checking anything else, which stage of the setup should you question first?

  • The initialization arguments, to confirm the five values are in the right slots

  • The d_out choice, to confirm the per-head width was set correctly

  • The weight transfers, to confirm the transposes were applied

Reveal answer
Answer:

The d_out choice, to confirm the per-head width was set correctly

The output width is wrong, which points to the d_out stage. A width of 4 from two heads means each head contributes 2 rather than 1, so d_out was likely left at 2 when it should have been 1. The arguments and the weight transfers are checked afterward if d_out is correct.

MEDIUM

Write down what you expect for each of the three setup stages before you run the trace: the argument values, the d_out value for a two-head module targeting output width 2, and the presence of the transposed query, key, and value weights. Then run the setup and compare each stage's actual result to your note. Identify the first stage that differs.

Hints
  • Divide the target output width by the number of heads to find the per-head contribution.
  • Check the five positional values against their parameter names in the call you are running.
  • Confirm that all three weight assignments exist, not just the first.

Key Takeaways

  • The multi-head attention call takes five positional values, d_in, d_out, block_size, a dropout value of 0.0, and num_heads, and each must be checked by its position in the exact call you are running.
  • d_out is a total width shared across heads, not the final output width by itself: a two-head module reaching output width 2 uses d_out set to 1.
  • Query, key, and value weights are copied from another attention module by transposing each matrix and wrapping it in torch.nn.Parameter, and all three assignments must be present.
  • When a setup diverges, check the initialization arguments, then d_out, then the weight transfers, and stop at the first stage whose result differs from your expectation.

Related concepts

More concepts starting with S