Simplified Self-Attention with Dot Products
Weight parameters are learned coefficients optimized during training, while attention weights are dynamic, context-specific values that determine how much a context vector depends on each part of the input.
Two Meanings of the Word Weight
Many learners first meet the word weight in a neural network course and come away with one meaning: a number that training adjusts until the model works. That meaning is reasonable, but it causes a real confusion once self-attention enters the picture. 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.
What do you think happens?
Suppose the second input token is the query. Which key does it pair with when the scores are computed?
Reveal answer
keys[1]
Python starts indexing at 0, so the second key is keys[1], not keys[2]. The second token's query is paired with the key at index 1.
The rest of this article follows the path from input tokens to attention weights. Keep the two meanings of weight separate as you read, because each value in the computation is either fixed by training or produced fresh for each query.
What Training Fixes and What Each Query Changes
Weight parameters are the values of a neural network that get optimized during training. They are the fundamental, learned coefficients that define the network's connections. Once training has set them, they describe how the network connects its inputs to its outputs. In a self-attention layer, the weight matrices are these learned parameters.
Attention weights are a separate quantity. They determine the extent to which a context vector depends on the different parts of the input. In other words, 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 hold on to the distinction is to ask when each value is determined. Weight parameters are settled by training and then used. Attention weights are produced during a forward computation, so the same weight matrix can yield different attention weights for different queries.
Suppose the goal is only to compute a single context vector for one input token. It is still necessary to have key and value vectors for every input element, because all of the keys take part in computing the attention weights with respect to the query. Keys and values for the full set of inputs can be obtained together by matrix multiplication with the key and value weight matrices. This projection maps each input token from its original embedding space into the space of keys and values. The source example projects six input tokens from a three-dimensional space onto a two-dimensional embedding space, so each token ends up with a two-dimensional key and a two-dimensional value.
Scoring Each Key With a Dot Product
The second step is to compute attention scores. An attention score is computed as a dot product between a query vector and a key vector. 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. The result is a raw, unnormalized number that measures how well the query matches that key.
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. Because the second key is keys[1], an index-based mistake here would pair the query with the wrong key and produce a different score.
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 second entry of the resulting row matches the single score computed for index 1, which is a useful quick check when you write this code yourself.
2.0
[1. 2. 3.]- keys.T is the transposed key matrix, so the query dot-multiplies every key in one step
- scores[1] is the entry for keys[1], matching a single dot product computed by hand
- These are raw scores, not yet attention weights
Worked Example: Scores for One Query
Scores and weights for the query [1, 2]
Keys are k0 = [1, 0], k1 = [0, 1], and k2 = [1, 1]. The query is q = [1, 2]. The key dimension is d_k = 2. Compute the unnormalized scores, scale them, and convert them into attention weights.
- 1Compute each dot product
q dot k0 = 1 times 1 plus 2 times 0 = 1. q dot k1 = 1 times 0 plus 2 times 1 = 2. q dot k2 = 1 times 1 plus 2 times 1 = 3. The raw scores are 1, 2, 3.
- 2Scale by the square root of d_k
The square root of 2 is about 1.414. Dividing each score gives about 0.707, 1.414, and 2.121.
- 3Apply softmax along the last dimension
Exponentiating gives about 2.028, 4.113, and 8.343. Their sum is about 14.484. Dividing each value by that sum gives about 0.140, 0.284, and 0.576.
- 4Check the distribution
The weights are positive and add up to 1.00, so they form a distribution over the three keys. The key k2 receives the largest weight because it has the largest score.
The attention weights for the query [1, 2] are approximately 0.140, 0.284, and 0.576 for keys k0, k1, and k2.
Scaling and Softmax Turn Scores Into Weights
Attention scores are not yet attention weights. The next step converts the scores by scaling them and then applying the softmax function. Scaling divides each score by the square root of the embedding dimension of the keys. Taking the square root is the same as raising to the power 0.5, so the relationship can be written as follows.
- 1omega_i is the unnormalized attention score for key i, the dot product of 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 every score before normalization
- 4softmax converts the scaled scores into positive values that add up to one along the last dimension
- 5w_i is the resulting attention weight for key i
[0.14 0.284 0.576]
1.0- d_k is read from the shape of the keys, not typed in by hand
- The division happens before softmax, so the scores are scaled first
- axis=-1 is the last dimension, where the normalization is applied
Softmax is what converts the scaled scores into the weights. Its output values are positive and add up to one across the dimension it is applied to. Larger weights mean the context vector leans more heavily on those tokens. Each attention weight routes a fraction of its input embedding into the output context vector for that query, so the context vector is a weighted combination of the input embeddings.
Mistakes Beginners Make with Scores and Weights
Common mistake Treating attention weights as if they were learned parameters
What it looks likeAssuming the weights for the query [1, 2] will be the same as the weights for a different query.
Why it failsWeight parameters are fixed by training, but attention weights are recomputed for each query from the current input.
Do this insteadAsk when each value is determined. Parameters are set by training; attention weights are produced during the forward computation.
Common mistake Pairing the query with the wrong key index
What it looks likeUsing keys[2] when the second input token's query should be paired with keys[1].
Why it failsPython starts indexing at 0, so the second key is at index 1.
Do this insteadCheck the index in code before comparing a score with the expected value.
Common mistake Forgetting the scaling step
What it looks likePassing the raw scores 1, 2, 3 directly into softmax.
Why it failsThe scores are meant to be divided by the square root of the key embedding dimension before softmax.
Do this insteadRead d_k from the key shape and divide the scores by its square root before normalizing.
Common mistake Applying softmax along the wrong dimension
Why it failsSoftmax has to normalize across the input tokens so that the weights for one query sum to one.
Do this insteadConfirm the softmax axis is the last dimension of the score matrix.
Common mistake Confusing scores with weights, so that a raw dot product is reported as if it were a probability-like attention weight
Locating Where a Result Goes Wrong
When a computed score or weight does not match the expected value, the source recommends checking four things in a fixed order. Working through them in sequence narrows down the stage where the divergence starts.
- 1Check the query source: confirm which input token produced the query vector, and that the query was computed from the right weight matrix.
- 2Check the key index: confirm that the key paired with the query is the intended one, remembering that Python indexes from 0 so the second key is keys[1].
- 3Check the scaling step: confirm the scores were divided by the square root of the key embedding dimension, taken from the shape of the keys.
- 4Check the softmax axis: confirm softmax was applied along the last dimension, so the weights for one query sum to one across the input tokens.
A query [1, 2] is paired with keys[1] = [0, 1]. Your code prints a raw score of 1 for this pair. Using the four-step order above, which check should you make first, and what value should the raw score be?
Hints
- Start with the query source before looking at the scaling step.
- Compute the dot product of [1, 2] with [0, 1] by hand.
What do you think happens?
After dividing the raw scores 1, 2, 3 by the square root of 2 and applying softmax, will the weights for one query sum to 1?
Reveal answer
Yes
Softmax outputs are positive and add up to one across the dimension it is applied to. Here that dimension is the last one, across the three keys.
Key Takeaways
- Weight parameters are learned during training and then fixed, while attention weights are dynamic values computed fresh for each query.
- An attention score is an unnormalized dot product between a query vector and a key vector, and the key at index 1 is keys[1] in Python.
- Attention weights come from dividing the scores by the square root of the key embedding dimension and then applying softmax along the last dimension.
- When a result looks wrong, check the query source, the key index, the scaling step, and the softmax axis, in that order.