Computing Attention Weights for All Input Tokens
Attention scores are dot products between input vectors and can be computed with nested loops or with inputs @ inputs.T.
Why Every Token Is Compared With Every Other Token
Self-attention starts with a simple question: how strongly does each input token relate to every other input token? If a sequence contains six input vectors, every vector must be compared against every other vector. That produces a grid with six rows and six columns, so there are 36 comparisons in total. This article follows that grid through three stages: raw dot-product scores, a matrix-multiplication shortcut that produces the same grid, and a normalization step that turns each row into proportions called attention weights.
What do you think happens?
Before any computation, what do you expect about the score for token 0 compared with token 1, versus the score for token 1 compared with token 0?
Reveal answer
They are equal
A dot product does not depend on the order of its two vectors, so the raw score grid is symmetric across the diagonal. The normalized weights are a different matter, and that difference is explored later in this article.
Filling the Score Grid With Nested Loops
The first version of the computation allocates an empty six by six tensor. Two nested loops visit every pair of input vectors. The outer loop index i selects a row, the inner loop index j selects a column, and each cell receives the dot product of the two vectors that index it. The dot product of vector i and vector j is the score that the cell at row i, column j stores.
- scores starts as an empty T x T tensor that will hold one dot product per cell
- The outer loop chooses the row i and the inner loop chooses the column j
- torch.dot combines the two row vectors indexed by i and j into a single number
Read the diagram as a map from loop position to memory of the grid. Cells on the diagonal, such as scores[0, 0] and scores[1, 1], pair a vector with itself. Off-diagonal cells, such as scores[0, 1] and scores[1, 0], compare two different tokens. Because the loop visits all pairs, the finished grid contains 36 entries, one per cell.
Computing the Same Grid in One Matrix Multiplication
The loop version is correct, but for loops are generally slow. The same grid can be produced with a single matrix multiplication: inputs @ inputs.T. The input matrix multiplied by its own transpose contains, in each cell, the dot product of the corresponding row vectors. The transpose turns the columns of the second copy into rows, so the product lines up every vector against every other vector in one operation.
The shape check explains why the result is square. The input has T rows and d columns, and the transpose has d rows and T columns. The shared inner dimension d is summed away by the multiplication, leaving a T by T matrix. Each entry is indexed by a pair of tokens, which is exactly the token-to-token grid the loops produced.
True- inputs.T swaps the two dimensions so the product compares row vectors with row vectors
- allclose checks the two grids element by element, which is the verification habit to keep
When you rewrite a computation, compare the new output against the old one, either element by element or as a whole grid. A matching result is strong evidence that the rewrite is correct before you rely on it in later stages.
Turning Scores Into Attention Weights
The second stage normalizes each row of the score grid so that its values behave like proportions. The normalized result is the attention weight tensor. Attention weights are the softmax-normalized scores, so each row of weights describes how one token distributes its attention across all tokens. In the course pipeline, these weights are the output of stage two, and the context vectors are formed from them in stage three.
The rule the weights must satisfy is that each row sums to 1 across its columns. In symbols, for a row i of the weight tensor, the entries satisfy $\sum_{j} w_{ij} = 1$, where $w_{ij}$ is the weight at row i and column j.
The before and after values come from the same course output. Before normalization, both cells hold 0.9544 because the dot product is symmetric. After normalization, the cell at row 0, column 1 holds 0.2006 and the cell at row 1, column 0 holds 0.1385. Each row was normalized by a different total, so the mirror symmetry of the scores is lost in the weights. Symmetry is a property of the raw scores, not of the weights.
Each entry is 1, up to floating-point rounding- softmax turns each row of raw scores into positive values that add up to 1
sum(dim=-1)adds the values across each row, giving one total per row
Why the Normalization Axis Matters
The dim argument decides which axis softmax runs across. With dim=-1, softmax works along the last dimension. For a two-dimensional score tensor with rows and columns, that means each row is normalized across its columns, and the values inside one row sum to 1. Changing dim changes the direction of normalization, which changes what each row means. The choice of dim is therefore part of whether the result is correct, not a cosmetic setting.
| Setting | Direction normalized | Sums that equal 1 | Meaning of each row |
|---|---|---|---|
| dim=-1 (correct here) | Across each row's columns | Every row | One token's distribution over all tokens |
| dim=0 | Down each column | Every column | Not the per-token distribution the stage needs |
Softmax axis choice and the sums it produces on a T x T score tensor
Checking row and column sums on a small weight matrix
A two-token illustrative weight tensor is [[0.6, 0.4], [0.5, 0.5]]. Does it satisfy the row-sum rule, and what happens to the column sums?
- 1Sum row 0
0.6 + 0.4 = 1.0, so row 0 satisfies the rule.
- 2Sum row 1
0.5 + 0.5 = 1.0, so row 1 satisfies the rule.
- 3Sum column 0
0.6 + 0.5 = 1.1, which is not 1.
- 4Sum column 1
0.4 + 0.5 = 0.9, which is also not 1.
Every row sums to 1, so this tensor was normalized along the correct axis. The column sums do not equal 1, which is expected and is not an error.
Mistakes When Checking the Weight Tensor
Common mistake Expecting the weight tensor to be symmetric because the score tensor is symmetric
What it looks likescores[0, 1] and scores[1, 0] both hold 0.9544, but the weights at those positions are 0.2006 and 0.1385.
Why it failsEach row is normalized by its own total, so different rows are scaled by different amounts.
Do this insteadExpect symmetry in the raw scores, and expect it to disappear after row-wise normalization.
Common mistake Summing down the columns to check the weights
What it looks likeColumn sums of [[0.6, 0.4], [0.5, 0.5]] are 1.1 and 0.9, so the check appears to fail.
Why it failsThe required property is that each row sums to 1, not each column.
Do this insteadSum across each row, using sum(dim=-1), and treat a column-sum failure as a sign of the wrong axis only if rows also fail.
Common mistake Treating raw scores as weights
What it looks likeA raw dot-product score can be larger than one, so it cannot be read as a proportion.
Why it failsRaw scores are unnormalized dot products and do not sum to 1 by row.
Do this insteadApply the softmax stage before interpreting any row as proportions.
Locating the Failing Step in a Pipeline
The computation has checkpoints. Stage one produces raw dot-product scores with shape T x T. Stage two applies softmax along dim=-1 to produce weights whose rows sum to 1. Stage three forms context vectors from those weights. Knowing the expected output of each stage tells you where a result went wrong.
A colleague computes weights = torch.softmax(scores, dim=0) and then checks weights.sum(dim=-1). Predict which stage is the first to fail, and state what the row sums would look like.
Hints
- Stage one, the raw score grid, is unaffected by the softmax axis.
- Ask which direction dim=0 normalizes, and what that does to the row sums.
Key Takeaways
- Attention scores are dot products between every pair of input vectors, and a nested loop or the single product inputs @ inputs.T fills the same T by T grid.
- Raw scores are symmetric across the diagonal, but softmax normalizes each row separately, so the attention weights generally are not symmetric.
- Softmax with dim=-1 normalizes across each row's columns, so every row of the weight tensor should sum to 1; the axis choice is part of correctness.
- If the row-sum check fails, check the normalization axis first, since a column-wise softmax is the usual cause.