Multi-Head Attention
Two self-attention implementations can be made equivalent by assigning the weights from one instance to the other, with attention to the transposed storage form used by nn.Linear.
Two Attention Versions That Must Agree
When you write a self-attention module from scratch, you often end up with more than one version of it. An early version might store its trainable projection weights in one layout, while a later, cleaner version organizes the same mathematical operation differently. This article follows one practical question through the whole topic: if you have a trained or carefully set-up instance of one version, how do you move its weights into an instance of the other version so that both produce the same outputs?
The goal is not to make the two classes look alike. The goal is to make them behave alike. Once the weights are assigned correctly, feeding the same input into each instance should give the same output, and that shared output is the check that tells you the transfer worked. The rest of this article uses that check to build up to multi-head attention, the causal masking that a GPT-style model depends on, and the transformer blocks assembled into the full architecture.
What do you think happens?
In a sequence of several tokens, which tokens can the first token attend to once a causal mask is applied?
Reveal answer
Only itself
Causal attention masks out the attention weights above the diagonal, so the row for the first token has only one visible position, itself. After normalization that row receives the full weight of 1.0.
Copying Weights Between Two Attention Classes
The transfer runs from an instance of the second version, called SelfAttention_v2, into an instance of the first version, called SelfAttention_v1. Each class holds trainable weight matrices for its query, key, and value projections. Copying weights is therefore a matter of identifying which matrix in one class corresponds to which matrix in the other, and then assigning values pairwise: the query matrix to the query matrix, the key matrix to the key matrix, and the value matrix to the value matrix.
Pairing the matrices is the easy part. The difficulty is that a matrix you can see in one class may not be stored in the same orientation in the other, and that is where the transfer usually fails. The next section explains the storage layout that causes this.
The Transposed Storage Trap in nn.Linear
The step that most often causes trouble is the storage layout. nn.Linear stores its weight matrix in a transposed form. A matrix that you would write with rows for input features and columns for output features is held the other way around inside nn.Linear. If you assign a weight matrix directly without accounting for this, the shapes may still fit by accident in some cases. The values, however, end up arranged in the wrong orientation, and the two instances will disagree even though the code ran without error.
| Aspect | Matrix as you would write it | Matrix as nn.Linear holds it |
|---|---|---|
| Row meaning | Rows index input features | Rows index output features |
| Column meaning | Columns index output features | Columns index input features |
| Orientation | Input by output | Transposed relative to the written form |
| Direct assignment | Values land in the wrong orientation | Must transpose before assigning |
The same weight matrix seen in its written form and in its nn.Linear storage form
Verifying the Transfer With One Input
Successful transfer is confirmed by running the same input through both instances and checking that they give the same outputs. You do not need to inspect the classes to decide whether the copy worked. If the outputs match for a shared input, the weights are behaving the same way, and that is the only standard the transfer needs to meet.
- 1Create an instance of SelfAttention_v2 and an instance of SelfAttention_v1.
- 2Pair each query, key, and value matrix in v2 with its counterpart in v1.
- 3Transpose each weight as needed to account for nn.Linear's storage form, then assign it.
- 4Feed the same input tensor into both instances.
- 5Compare the two output tensors. Matching outputs confirm the transfer.
Why Causal Attention Hides Future Tokens
The next enhancement adds a causal aspect to self-attention. Causal attention modifies the mechanism so that the model cannot access future information in the sequence. This matters for language modeling, where each prediction should depend only on the words that came before it. If a model could peek at upcoming words while it predicts the current one, it would learn a shortcut that does not exist at generation time.
In GPT-style models, each token is processed in turn. For the current token, the tokens that come after it in the input are masked out. Concretely, the attention weights above the diagonal of the attention matrix are masked out, and the remaining weights in each row are normalized so that they sum to 1. The row for a given token therefore only distributes its attention over itself and the tokens before it.
Building the Masked Attention Weight Matrix
One way to obtain the masked attention weight matrix is to work in three stages. First, apply the softmax function to the attention scores. Second, zero out the elements above the diagonal so that future positions contribute nothing. Third, normalize the resulting matrix row by row so that each row again sums to 1. The result is a matrix in which every row describes a valid distribution over the tokens that are allowed to be seen.
The diagram above shows the row for the first word. Its only visible position is itself, so after normalization it receives the full weight of 1.0. Rows for later words see more positions. The second-row word, for example, keeps the weight for the word before it and for itself, and nothing beyond that.
Masking and normalizing a three-token attention matrix
Starting from a softmax-normalized 3 by 3 matrix, build the masked attention weight matrix for a three-token sequence. The numbers below are illustrative and chosen only to show the arithmetic.
- 1Start with softmax output
Row 1 is [0.50, 0.30, 0.20], row 2 is [0.25, 0.45, 0.30], and row 3 is [0.30, 0.30, 0.40]. Each row already sums to 1 after softmax.
- 2Zero the upper triangle
Set the entries above the diagonal to zero. Row 1 becomes [0.50, 0, 0], row 2 becomes [0.25, 0.45, 0], and row 3 stays [0.30, 0.30, 0.40].
- 3Check the row sums
After zeroing, row 1 sums to 0.50 and row 2 sums to 0.70. Row 3 still sums to 1.0. Rows 1 and 2 are no longer valid distributions.
- 4Normalize each row
Divide each row by its own sum. Row 1 becomes [1.0, 0, 0]. Row 2 becomes approximately [0.36, 0.64, 0]. Row 3 is already normalized and stays [0.30, 0.30, 0.40].
The first row gives all its weight to itself, the second row spreads its weight over the first two tokens only, and the third row sees all three tokens. Every row sums to 1 and no row uses a future position.
Why Masking Comes Before Normalization
The ordering in the three-stage method matters. Zeroing the upper triangle after the softmax leaves rows that no longer sum to 1, which is why the normalization stage follows the masking stage. The masked matrix is unnormalized right after the zeroing and becomes normalized only at the end.
Splitting Attention Into Several Heads
The third enhancement is the multi-head component. Here the attention mechanism is split into several heads. Each head learns different aspects of the data, and the heads together let the model attend to information from different representation subspaces at different positions at the same time. The source presents this as a way to improve performance on complex tasks.
Causal masking and multi-head splitting are presented together because a practical GPT-like model uses both. The mask controls which positions a token may see, while the heads let it look at those positions in several different ways. Each head in the diagram has its own weights and its own attention computation, and the heads' results are brought back together into one output.
Multi-Head Attention as Stacked Causal Attention
The attention modules in transformer-based LLMs involve multiple instances of causal attention, and this arrangement is called multi-head attention. A single causal attention module processes its input once. Multi-head attention runs several such instances side by side, so the model's attention is organized as a group of parallel causal attention units rather than one unit.
The implementation idea is equally direct. A multi-head attention module is created by stacking multiple instances of causal attention modules. You do not need a new kind of attention to get the multi-head version. You reuse the causal attention module you already built and stack several copies of it into one larger module, with each copy acting as one head.
Transformer Blocks in a GPT-Like Model
Multi-head attention is one of the core components of an LLM, but it is not the whole model. The next part of the work is to code the other building blocks of an LLM and assemble them into a GPT-like model. Those assembled units are transformer blocks. The source names transformer blocks as the building blocks used in LLM architecture, and implementing them is necessary for creating GPT models of various sizes.
The phrase of various sizes is worth taking seriously. A transformer block is a reusable unit, so the same kind of block can be repeated or configured to produce models of different scales. The learning objective here is to understand how to implement transformer blocks, and the source treats that implementation as a required step rather than an optional refinement.
Where the Architecture Step Sits in the Roadmap
The LLM architecture is introduced top-down as several building blocks before the individual components are examined. The roadmap that places this chapter inside a larger process has three stages. Read it as a sequence rather than a checklist of unrelated tasks.
- Stage 1: building an LLM. Within this stage, step 1 and step 2 prepare the data and the attention machinery.
- Stage 1, step 3: implementing the LLM architecture, which assembles attention and the other pieces into a GPT-like model. This is the step this article covers.
- Stage 1, later steps: a training loop, pretraining the model, and evaluating it.
- Stage 2: turning the built model into a foundation model.
- Stage 3: turning it into a personal assistant by adapting it with labeled or instruction data.
The chapter ends at the architecture. The source states that the next chapter adds the training loop and pretrains the LLM, so the model assembled here is not yet trained. Its purpose at this point is to exist as a complete, correctly structured GPT-like model that can later be trained to generate human-like text.
Mistakes When Moving Weights and Stacking Heads
Common mistake Assigning an nn.Linear weight matrix directly without accounting for its transposed storage
What it looks likeCopying a matrix written as input-by-output straight into nn.Linear's weight slot without transposing it
Why it failsThe shapes can still fit, so no error appears, but the values end up in the wrong orientation and the outputs disagree
Do this insteadTranspose the matrix so its orientation matches nn.Linear's storage, then confirm the copy by comparing outputs on the same input
Common mistake Judging a weight transfer by whether the code runs or the shapes match
Why it failsMatching shapes and error-free execution do not show that the two instances compute the same thing
Do this insteadFeed the same input into both instances and check that the outputs are identical
Common mistake Zeroing the upper triangle and skipping the final normalization
Why it failsRows no longer sum to 1, so they are not valid distributions over the visible tokens
Do this insteadApply softmax, zero the elements above the diagonal, then normalize each row so it sums to 1
Common mistake Treating multi-head attention as a new kind of attention mechanism
Why it failsThe source describes it as multiple instances of causal attention stacked into one module
Do this insteadReuse the causal attention module and stack several copies, one per head
Checking Your Understanding of the Masked Matrix
A four-token sequence has a softmax-normalized attention matrix. You zero the entries above the diagonal, and the row for token 2 sums to 0.6 after zeroing. What must you do to that row, and why does the row for token 4 need no change beyond the masking itself?
Hints
- Recall the three stages: softmax, zero the upper triangle, then normalize each row.
- Token 4 is the last position, so its row already has every position visible.
- Normalizing divides each element by the row's sum so the row totals 1.
Key Takeaways
- Two self-attention implementations can produce identical outputs when their trainable query, key, and value weights are copied pairwise, and matching outputs on a shared input confirm the transfer.
- nn.Linear stores its weight transposed relative to the written form, so direct assignment can put values in the wrong orientation even when shapes fit.
- Causal attention masks the upper triangle of the attention matrix, then normalizes each row; the masked matrix is built by softmax, zeroing, and normalizing, in that order.
- Multi-head attention splits attention into several heads and is built by stacking multiple causal attention instances, and transformer blocks are assembled into a GPT-like model within stage 1, step 3 of building an LLM.