Queries, Keys, and Values
The SelfAttention_v1 class stores three trainable matrices, W_query, W_key, and W_value, each mapping the input dimension to the output dimension.
Predicting the Output Before Reading the Code
A self-attention class can look correct line by line and still produce a result you did not expect. The fastest way to catch that problem is to form a prediction first and then check it against what the code actually does. This article builds that habit using a compact self-attention class written as a subclass of nn.Module.
The input to the class is a matrix with one row per token, and each row is an embedding vector. Before you read the internals, decide what the output should look like. Ask two questions: how many rows does the output have, and what determines how many columns it has?
What do you think happens?
The input has 6 tokens, each embedding has dimension d_in = 3, and the output dimension is d_out = 2. What shape should the context vector matrix have?
Reveal answer
It has 6 rows, one per input token, and 2 columns, one per output dimension. The shape is 6 x 2.
The context vectors have one row per input token and d_out columns. The number of rows follows the number of tokens, and the number of columns follows d_out, not d_in. Notice that the input dimension does not appear in the output shape.
Three Trainable Matrices in the Constructor
The class SelfAttention_v1 inherits from nn.Module. The source describes this base class as a fundamental building block of PyTorch models that supplies the functionality needed to create and manage model layers. Inheriting from it is what lets the class hold trainable parameters in a way PyTorch can track.
Inside the constructor, three trainable weight matrices are created: W_query, W_key, and W_value. Each one is wrapped in nn.Parameter and sized so that it maps the input dimension d_in to the output dimension d_out. In shape terms, each matrix has d_in rows and d_out columns, so multiplying the input matrix by one of them produces a matrix with d_out columns.
The three matrices start from random values, and the same input vectors are multiplied by each one. That produces three different views of the same data: queries from W_query, keys from W_key, and values from W_value. The three matrices are separate parameters, so each view can be shaped differently during training even though they all start from the same input.
The constructor only sets up starting values. The source explains that as the model is exposed to more data during training, it adjusts these trainable weights. This is why the matrices are called trainable: the constructor creates them, and learning changes them later.
The Forward Method as a Five-Stage Pipeline
The forward method is where the input actually flows through the mechanism. It takes the input matrix x, projects it into keys, queries, and values, computes a score for every pair of tokens, converts those scores into weights, and then uses the weights to combine the values. Read the method as a pipeline with five stages, where each stage's output is the next stage's input.
The diagram follows the same sequence the forward method performs, written as a data flow rather than as code. The input matrix X is multiplied by three weight matrices to produce queries, keys, and values. Queries and keys are combined into the attention weight matrix, and those weights are then applied to the values to produce the context vectors Z.
Each of the three stages below corresponds to a group of lines in the forward method. Each is explained in its own subsection so you can locate the line that produces each kind of output.
Tracing Tensor Shapes Through a Six-Token Example
Shape tracing is the most reliable way to find a bug before you read any numbers. The example below was written for this article, not taken from the source. It uses six input tokens, an input dimension of 3, and an output dimension of 2. Trace each intermediate tensor and record which dimension changes at each step.
Shapes through self-attention for 6 tokens
The input X has 6 rows (tokens) and 3 columns (d_in). Each trainable matrix W_query, W_key, and W_value is 3 x 2 because it maps d_in = 3 to d_out = 2. Determine the shape of every intermediate tensor and the final context matrix.
- 1Input X
X has one row per token and one column per embedding dimension, so its shape is $6 \times 3$.
- 2Queries, keys, and values
Each projection multiplies X by a $3 \times 2$ matrix. The inner dimensions match at 3, and the result has 6 rows and 2 columns. So Q, K, and V each have shape $6 \times 2$. The number of rows is unchanged, and the columns change from 3 to 2.
- 3Scores
Scores compare every token with every token. Combining queries with keys requires lining up the shared dimension of size 2, which gives a result with 6 rows and 6 columns. The score matrix has shape $6 \times 6$.
- 4Attention weights
Scaled softmax changes the values but not the shape, so the weights also have shape $6 \times 6$.
- 5Context vectors
The weight matrix $6 \times 6$ is applied to the value matrix $6 \times 2$. The shared dimension of size 6 lines up, leaving 6 rows and 2 columns. The context matrix Z has shape $6 \times 2$.
The output has shape $6 \times 2$: one row per token and d_out = 2 columns. The score and weight matrices are $6 \times 6$ and do not appear in the final output.
Reading the shapes this way gives you a check you can perform without running any numbers. If your output has 6 rows and 2 columns, the row count and the output dimension are correct. If it has 3 columns, a projection probably used d_in where it should have used d_out. If it has 6 rows but a wrong column count, the context stage is the first place to look.
Why Queries and Keys Decide the Weights
The three projections play different roles even though they all come from the same input. The queries and keys are compared with each other to produce scores and then weights. The values are not used when computing the weights. They are only used in the final stage, where the weights combine them into context vectors.
The weights come from queries and keys. The weights then act on the values. If you swap the roles of the projections in your head, you will predict the wrong output shape or the wrong dependency between lines.
This dependency explains a useful debugging fact. A mistake in how queries or keys are built will change the attention weights, and therefore change every context vector. A mistake that only affects the values changes the context vectors directly, but leaves the weights alone.
Locating Where a Result Goes Wrong
Suppose the context vectors look wrong. The source offers a verification method: in the output matrix, the second row should match the context vector computed for the second input token in the previous single-token computation. Matching a row like this is a strong check, because it confirms that the batched matrix version and the single-token computation agree.
If the rows do not match, the mismatch tells you which part of the pipeline to examine. Because the forward method is a fixed sequence of matrix operations, you can check output at each stage rather than only at the end. Work backward from the context vectors, or forward from the projections, until you find the first stage where the values diverge from what you predicted.
Mistakes When Tracing Self-Attention
Common mistake Predicting the output width from the input dimension
What it looks likeAssuming a 6 x 3 input gives a 6 x 3 context matrix.
Why it failsThe projections map
d_intod_out, so the context matrix takes its columns fromd_out.Do this insteadSet the column count from d_out, which is 2 in the example, and keep the row count equal to the number of tokens.
Common mistake Treating the constructor as where the outputs are computed
Why it failsThe constructor only creates the trainable matrices and sets their starting values. Outputs are computed when forward runs on an input.
Do this insteadTrace outputs through the forward method and treat the W matrices as inputs to that method.
Common mistake Assuming the attention weights depend on the values
What it looks likeLooking for a multiplication involving the values when computing the score matrix.
Why it failsThe scores come from queries compared with keys. The values only enter in the final context stage.
Do this insteadLocate the score line first, since it compares queries and keys, and then find the line that applies the weights to the values.
Common mistake Reading the figure's weight numbers as real results
Why it failsThe source states that the weight values in its figure were placed for visual consistency.
Do this insteadUse the figure to understand the structure of the computation, not the specific numbers.
Common mistake Checking only the final output, which makes it impossible to tell which stage introduced the error
Practice: Predict Then Trace
A self-attention class receives an input with 8 tokens, each embedding of dimension d_in = 4, and it uses d_out = 5. Before tracing, predict the shape of each of these: the three projection outputs, the score matrix, the attention weight matrix, and the context matrix. Then trace the forward method stage by stage and write the shape after each stage.
Hints
- Each W matrix has shape
d_inbyd_out, so here it is 4 x 5. - The number of rows in the projections and in the context matrix equals the number of tokens.
- The score and weight matrices compare every token with every token.
After tracing the shapes, your colleague says the second row of the output does not match the single-token context vector for the second input. Name the first stage you would check, and explain why the score and weight stages should be checked before the context stage if the projections look correct.
Hints
- Think about which stage the context vectors depend on directly.
- The weights depend on queries and keys, so an error there changes every context row.
Key Takeaways
- The SelfAttention_v1 class, a subclass of nn.Module, creates three trainable matrices, W_query, W_key, and W_value, each mapping
d_intod_out. Their values start random and are adjusted during training. - The forward method runs five stages: projecting the input into keys, queries, and values; computing scores for every token pair; converting scores into weights with scaled softmax; and combining values using the weights to produce context vectors.
- For an input with one row per token, the context matrix has one row per token and
d_outcolumns. Shape tracing lets you predict this output and catch the first dimension that goes wrong. - Weights come from queries compared with keys, and then act on the values. A bug in the score or weight stages changes every context vector, so check intermediate stages in forward order.
- To locate a divergence, verify the output shape, compare a row such as row 2 with the single-token computation, and then inspect each stage in turn until the first mismatch appears.