Attention Scores and Weights
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.
Start with the input. The input is a matrix with one row per token, and each row is an embedding vector. Before reading any internals, decide what the output should look like. Ask how many rows the output has, and what determines how many columns it has. Those two questions are the foundation of every check that follows.
Two Meanings of Weight
Many learners meet the word 'weight' for the first time in a neural network course and assume it always means the same thing: a number that training adjusts until the model works. That assumption is reasonable, but once attention enters the picture it becomes a source of confusion. The word appears in two places that play very different roles. One place is the set of weight matrices that a self-attention layer learns. The other is the attention weights that the mechanism computes while it runs.
Weight parameters are the learned coefficients of a neural network. Training optimizes them, and once training has set them they describe how the network connects its inputs to its outputs.
Attention weights are the values that determine the extent to which a context vector depends on the different parts of the input. They express how strongly the network focuses on each input element when it builds the context vector for one query. Because they are computed fresh for each query, they are dynamic and context-specific rather than fixed coefficients.
The clean way to keep the two apart is to ask when each value is determined. Weight parameters are settled by training and then reused. Attention weights are produced during a forward computation, so the same weight matrix can yield different attention weights for different queries. The diagram below places the two side by side.
What the Class Stores in Its 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. The three matrices start from random values, and the same input vectors are multiplied by each one to produce three different views of the data.
The constructor only sets up the starting values. As the model is exposed to more data during training, it adjusts these trainable weights. Learning changes them later, which is why they are called trainable. Nothing in the forward method changes the matrices themselves; it only reads them.
Tracing the Forward Method
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: projection, scores, scaled softmax weights, and context vectors.
Read each arrow as one matrix operation. The input X is multiplied by three weight matrices to produce queries, keys, and values. Queries and keys are combined into the attention score matrix. Scores are scaled and normalized into attention weights, and those weights are then applied to the values to produce the context vectors Z. The numbers shown in the shape labels are the ones that matter here; the source's figure also notes that its attention weight values are placed for visual consistency rather than being real weights, so the structure is what you should memorize.
Predicting every shape from the input and weight sizes
The input X has six tokens with an input dimension d_in of 3. The output dimension d_out is 2. What is the shape of each intermediate tensor and of the final context matrix?
- 1Read the input shape
X has one row per token, so it is 6 x 3.
- 2Project through each weight matrix
Each weight matrix maps
d_in= 3 tod_out= 2, so it has shape 3 x 2. Multiplying a 6 x 3 matrix by a 3 x 2 matrix gives 6 x 2. Queries, keys, and values are each 6 x 2. - 3Compute the score matrix
Queries (6 x 2) times the transposed keys (2 x 6) gives a 6 x 6 matrix. Each row corresponds to one query and each column to one key.
- 4Apply softmax to get weights
Scaling and softmax keep the shape, so the attention weights are also 6 x 6.
- 5Combine with values
Attention weights (6 x 6) times values (6 x 2) gives context vectors of shape 6 x 2.
The context matrix Z has 6 rows, one per input token, and 2 columns, set by d_out.
Notice what this trace reveals. The number of rows in the output comes from the number of input tokens, and the number of columns comes from d_out, the output dimension chosen for the weight matrices. Context vectors have one row per input token and d_out columns, which makes the output shape a useful first check against what you expected.
Dot Products as Unnormalized Scores
The second stage computes attention scores. An attention score is a dot product between a query vector and a key vector, and it is a raw, unnormalized number. This is the same kind of dot-product computation used in simplified self-attention, with one new aspect: the query and the key are not the raw input elements. They are obtained by transforming the inputs through their respective weight matrices.
Index positions matter here. The source computes the score for the second input token first. The query comes from that second token and is paired with the key at index 1. Python starts indexing at 0, so the second key is keys[1], not keys[2]. The score is the dot product of the two vectors, giving one number that measures how well that query matches that key.
Rather than repeating that dot product for every key, the same result can be obtained for all keys at once by multiplying the query by the transposed key matrix. The entry at index 1 of the resulting row matches the single score computed earlier, which is a useful quick check when you write this code yourself. The diagram below shows how the matrix is laid out: each row belongs to one query and each column to one key.
The highlighted cell is the score for the second query, which in zero-based Python indexing is row 1, paired with key 1. Because there are six queries and six keys, the full score matrix has one row per query and one column per key.
From Scores to Weights: Scaling and Softmax
Attention scores are not yet attention weights. The next step converts the scores by scaling them and then applying softmax. Scaling divides each score by the square root of the embedding dimension of the keys. Taking a square root is the same as raising to the power 0.5, so the scaling can be written as a single equation.
- 1omega_i is the unnormalized attention score for key i, the raw dot product between the query and that key.
- 2d_k is the embedding dimension of the keys, read from the shape of the key matrix.
- 3Dividing by the square root of d_k scales each score before normalization.
- 4Softmax converts the scaled scores into positive values that add up to one along the chosen dimension.
- 5w_i is the resulting attention weight for that key.
The source code performs the same steps. It reads the key dimension from the shape of the keys, divides the scores by its square root, and applies softmax along the last dimension. Softmax is what turns the scaled scores into the weights. Its outputs are positive and sum to one across the dimension it is applied to, so the attention weights for one query form a distribution over the input tokens. Larger weights mean the context vector leans more heavily on those tokens.
Mistakes That Hide Divergence
When a result looks wrong, the cause often sits in a step that seems routine. The following mistakes are the ones most likely to produce a value that differs from what you expected.
Common mistake Treating the weight matrices and the attention weights as the same thing
What it looks likeAssuming that changing W_query during a forward pass is how the attention weights for a new query get computed
Why it failsW_query is a learned parameter set by training, while attention weights are recomputed from context on every forward pass
Do this insteadKeep the two apart by asking when each value is determined: training fixes the matrices, and the forward computation produces the weights
Common mistake Using one-based indexing for keys
What it looks likePairing the second query with keys[2] instead of keys[1]
Why it failsPython starts indexing at 0, so the second key is keys[1]
Do this insteadCheck the key index first when a score looks off, and confirm it against the matching row of the transposed key matrix
Common mistake Skipping the scaling step
What it looks likeApplying softmax directly to the raw dot products
Why it failsThe scores are meant to be divided by the square root of the key embedding dimension before softmax, so skipping it changes every weight
Do this insteadVerify that the scores were divided by the square root of d_k before normalization
Common mistake Normalizing along the wrong axis
What it looks likeRunning softmax over the query axis instead of the key axis
Why it failsEach query's weights must form a distribution over the input tokens, which is a normalization across keys
Do this insteadConfirm that each row of the attention weight matrix sums to 1
Common mistake Assuming a correct-looking line of code is correct in context, without checking the shape or the row-by-row output
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 its output at each stage rather than only at the end.
Locating Where a Value First Diverges
The source offers a simple verification. In the output matrix, the second row should match the context vector computed for the second input token in the single-token computation. Matching that row is a strong check, because it confirms that the batched matrix version and the single-token computation agree.
When a score or weight differs from the expected value, work through a fixed checklist in this order: the query source, the key index (remembering that Python indexes from 0), the scaling step, and the softmax axis. Stop at the first checkpoint that fails. That step is where the value first diverges.
What do you think happens?
Six input tokens have d_in = 3 and d_out = 2. Before reading further, what shape does the context matrix Z have?
Reveal answer
6 rows by 2 columns
There is one row per input token, so 6 rows. The columns are set by d_out, which is 2.
A colleague's self-attention class returns context vectors whose second row does not match the single-token computation for the second input token. Describe the order in which you would check the query source, key index, scaling, and softmax axis, and state what each check would confirm.
Hints
- Start by confirming the shape of each intermediate tensor before reading any numbers
- Remember that the second key is at index 1 in Python
- Each query's attention weights should sum to 1 across the key axis
Key Takeaways
- The forward method projects the input through W_query, W_key, and W_value, computes scores, scales and applies softmax to get weights, and combines the weights with the values to produce context vectors.
- Weight parameters are learned during training and then fixed for use, while attention weights are computed fresh for each query from context.
- An attention score is an unnormalized dot product between a query and a key, scaled by the square root of the key dimension and normalized by softmax along the key axis.
- When a result diverges, check shapes first, then the query source, key index, scaling step, and softmax axis, in that order, to find the first failing step.