← All tutorials

Attention

How each token decides which tokens to attend to

On this page

1 Long dependencies break sequential models 2 Each token learns which other tokens to attend to 3 Scores become weights, and the weights average the values 4 One attention layer, walked through 5 One head learns one relationship, so run several in parallel 6 One PyTorch call runs a whole attention layer 7 References

Attention computes a new vector for every token as a weighted average of all the tokens' vectors. The weights say how much each token attends to, or uses, each other token. Training learns how to set those weights. Several copies of attention, called heads, run side by side in one layer, each with its own learned weights. Attention runs in every layer on every forward pass, over all the tokens at once in training.

1 Long dependencies break sequential models

Older sequence models like RNNs (recurrent neural networks) read one token at a time, a token being a word or part of a word, so they cannot run in parallel. If the word that decides the meaning showed up 50 words ago, an RNN carries it through 50 sequential steps. Every step is a chance to forget.

CNNs (convolutional neural networks, which slide a small learned filter across the input) see only a local neighborhood, so many layers must stack before one end of a sentence can reach the other.

In "the cat sat on the mat because it was tired", attention lets "it" look straight at "cat" in one step. That is self-attention.

RNN, one step at a time6 stepscatitCNN, layer by layercatit3 layers in, cat isstill out of reachAttention, one step1 stepcatit
Figure 1. From "cat" to "it": the RNN passes it along 6 steps, the CNN (3-wide filter) one layer at a time, attention in one link.

2 Each token learns which other tokens to attend to

In "the cat sat on the mat", when the model processes "sat", which tokens should it attend to? Probably "cat" more than "the". Attention is the model learning those attention weights by itself. Each token gets a query, a key and a value; stacked over tokens they form the matrices Q, K and V. The query is what the token looks for, a key is what a token offers to be matched on, and a value is what it hands over when it is picked, like search text, page titles and the pages themselves.

one query, three keysquery, satkey, catkey, satkey, thecat is closest in angle to thequery; all keys have equal lengthvalue weightscatsattheoutput for sat
Figure 2. The purple "sat" query lines up best with the orange "cat" key, so the green "cat" value bar is longest and the output for "sat" is mostly cat's value.

All three come from the input embedding matrix X: one row per token, each row a learned vector of 512 numbers. One learned weight matrix per role turns X into queries, keys and values. Queries and keys need the same length, because each query is multiplied entry by entry with each key and summed (the dot product of Section 3). With 6 tokens and a query length of 64:

One projection per roleX6 × 512×WQ512 × 64=Q6 × 64same forK and V
Figure 3. WQ turns each of the 6 rows from 512 numbers into 64; WK and WV do the same for K and V.

3 Scores become weights, and the weights average the values

Attention turns each token's query into one output vector in three moves: score it against every key, turn the scores into weights, and take the weighted average of the values. The score of a query against a key is their dot product: multiply the two vectors entry by entry and add up. A softmax turns each query's row of scores into weights that are positive and sum to 1. The token's output is the average of the values under those weights.

Project the tokens, then score every query against every key.
Q = XWQ
K = XWK
V = XWV
Each token's row of X is multiplied by three learned matrices WQ, WK and WV, giving its query, key and value. Three separate matrices let what a token looks for, what it offers and what it hands over differ: "sat" can look for a subject while offering itself as a verb. Each projection is a plain linear map, so all the learning in attention lives in these three matrices.
S = QKT√dk
Entry i, j of the score grid S is query i dotted with key j, large when the two point the same way. Transposing K (the T) lines keys up as columns, so one matrix product scores every pair at once. Dividing by √dk, the square root of the key length, stops the scores from growing as keys get longer; the paragraph after Figure 4 explains why that matters.
Then scores become weights, and the weights average the values.
A = softmax(S)
Each row of A is one query's attention weights: softmax raises e to each score of the row from the line before and divides by the row's total. The exponent keeps every share positive and stretches gaps, so a score 2 higher gets about 7 times the weight; the division makes each row sum to 1. Raising one score takes weight from every other key in that row.
out = A·V
Each output row is a weighted average of the value rows of V, with that query's row of A as the weights (the dot is a matrix product). A key's score decides how much is taken, but the value decides what is taken, which is why keys and values are separate. With one weight near 1, the output nearly copies one token; with even weights, it is a plain average.
Attention(Q, K, V)
= softmax(QKT√dk)V
Attention is a weighted average of the values, with weights from how well each query matches each key. Substitute S into A and A into the output and the four lines collapse into this one, as Vaswani et al. (2017) write it. The softmax part decides how much each token attends to each other token; V decides what information is passed on.

The attention equation in PyTorch, starting from queries, keys and values that are already projected: six tokens go in, one output row per token comes out, and the three lines inside the function carry the idea.

import torch

def attention(Q, K, V):
    d_k = Q.size(-1)
    S = Q @ K.transpose(-2, -1) / d_k ** 0.5  # (n, n)
    A = S.softmax(dim=-1)
    return A @ V                               # (n, d_v)

n, d_k, d_v = 6, 64, 32
Q, K = torch.randn(n, d_k), torch.randn(n, d_k)
V = torch.randn(n, d_v)
print(attention(Q, K, V).shape)  # torch.Size([6, 32])
  1. 1Scores each query against each key, eq card 1 in Section 3, line 4.
  2. 2Softmax over each row gives the weights, eq card 2 in Section 3, line 1.
  3. 3Averages the values with those weights, eq card 2 in Section 3, line 2.
  4. 4Six random tokens go in and one output row per query comes out, n rows of length dv, the out of eq card 2 in Section 3, line 2.
1. Every query meets every key×=one row ofscoresper queryQn × dkKTdk × nscoresn × n2. The weights mix the values×=out row ibelongs toquery iweightsn × nVn × dvoutn × dv123123
Figure 4. 1 Each shade is one query, numbered 1 to 3, and its own row of scores. 2 That row of weights mixes V into the query's own out row, n rows of length dv.

Before the softmax, the scores are divided by √dk, where dk is the length of each query and key. A dot product adds one term per dimension, so scores grow with dk. Big scores make the softmax nearly one-hot (one weight near 1, the rest near 0), and the gradients, the signals training uses to adjust the weights, become tiny. The paper's authors give this reasoning as a likely cause, not a proof.

If the entries of a query and a key are independent with mean 0 and variance 1 (variance is the average squared distance from the mean), their dot product has variance dk, and dividing by √dk brings it back to 1; the variance snippet after this paragraph checks it on 10,000 random pairs at dk = 64. Look at the two printed variances, about dk before the division and about 1 after.

import torch

NUM_PAIRS, d_k = 10_000, 64
q = torch.randn(NUM_PAIRS, d_k)
k = torch.randn(NUM_PAIRS, d_k)
dots = (q * k).sum(dim=-1)

print(dots.var())                 # about 64, which is d_k
print((dots / d_k ** 0.5).var())  # about 1
  1. 1Dot products of 10,000 random query and key pairs, the numerator in eq card 1 in Section 3, line 4.
  2. 2Their variance is about dk, here 64, eq card 1 in Section 3, line 4.
  3. 3Dividing by √dk brings it back to about 1, the denominator in eq card 1 in Section 3, line 4.
raw scores8, 2, −3one-hot,tiny gradients1.00cat0.00the0.00satdivided by √16 = 42, 0.5, −0.75weights spread out0.78cat0.17the0.05sat
Figure 5. Three scores at dk = 16, raw on top and divided by 4 below.

4 One attention layer, walked through

Real models split text into subword pieces, parts of a word, with 512 numbers per token, and add position information to each vector so the model knows where the token sits; Positional Encoding and Masking explains it.

Figure 6. A toy layer on "the cat sat" with illustrative values; step through to see which token "sat" weights most.

Attention in four lines, one per step.
Q = XWQ
K = XWK
V = XWV
Each token vector, a row of X, is multiplied by three learned matrices into a query (what it looks for), a key (what it offers) and a value (what it hands over). Separate matrices let the three roles differ for the same word.
S = QKT√dk
Each cell of the score grid S is one query dotted with one key: high when they point the same way. Dividing by √dk, the key size, keeps the scores in a range the softmax can still mix.
A = softmax(S)
Softmax turns each query's row of scores in S into weights A that are positive and sum to 1, so the biggest score gets the biggest share, and raising one share lowers the others.
out = A·V
Each token's output is a weighted average of the values V, using its own row of A; the output for "sat" is mostly the value of "cat" because "cat" got the biggest share.
Q the queriesK the keysX the token vectorsS the scoresA the attention weightsV the valuesout the output, a weighted averageWQ, WK, WV the learned projectionsdk the key sizesoftmax turns scores into weightsT transpose; √ square root of· matrix product: mix rows by columns

5 One head learns one relationship, so run several in parallel

One set of attention weights captures one type of relationship. But "cat" relates to "the" by grammar and to "sat" by meaning. One head can't do both well.

Multi-head attention runs several copies of attention side by side, called heads, each with its own projections into a smaller subspace, a slice of the model's dimensions. The 2017 transformer uses a model width dmodel of 512 and h = 8 heads, so each head works in 64 dimensions.

Split 512 dims into 8 heads of 646464646464646464one token's 512 dims, one color per headone head per sliceConcat the 8 head outputs8 head outputs, 512 dims× WOoutput, 512 dims, heads mixed
Figure 7. Split and concat only reshape; the learning lives in each head's projections and in WO, which mixes the heads.

Each head runs the attention line from Section 3 on its own projections:

Each head attends on its own projections, then the heads are joined and mixed.
headi = Attention(Qi, Ki, Vi)
Qi = XWQ,i
Each head projects the token matrix X into narrow queries, keys and values of its own and runs ordinary attention on them. Head i uses its own matrices, each dmodel × dk: Qi as written, and Ki, Vi the same way with WK,i and WV,i. Stacking the h matrices WQ,i side by side gives one dmodel × dmodel matrix, so projecting once and cutting the result into h column blocks gives every Qi at once. Because the matrices differ, each head scores the tokens in its own subspace and can attend to a different relationship.
H = Concat(head1, …, headh)
MultiHead(X) = H WO
The output is one dmodel-wide vector per token that carries what every head found. Concat places the h narrow head outputs side by side as H, back to dmodel columns; on its own that would leave each head's findings in separate columns. WO mixes the columns, so one head's finding can shape every output dimension.

Multi-head attention in PyTorch: one projection per role, cut into h heads, then the built-in scaled_dot_product_attention runs the attention equation of Section 3 in every head.

import torch
import torch.nn as nn
import torch.nn.functional as F

B, T, d, h = 2, 6, 512, 8  # batch, tokens, width, heads
d_k = d // h
X = torch.randn(B, T, d)
W_Q, W_K, W_V, W_O = (nn.Linear(d, d, bias=False) for _ in range(4))

def split(M):
    # (B, T, d) to (B, h, T, d_k): column block i goes to head i
    return M.view(B, T, h, d_k).transpose(1, 2)

Qh, Kh, Vh = split(W_Q(X)), split(W_K(X)), split(W_V(X))
H = F.scaled_dot_product_attention(Qh, Kh, Vh)
H = H.transpose(1, 2).reshape(B, T, d)  # concat the heads
out = W_O(H)                            # (B, T, d)
  1. 1The tokens X and the four learned matrices, eq card in Section 5, lines 2 and 4.
  2. 2Projects X once per role and slices the columns into h heads; slice i is XWQ,i, eq card in Section 5, line 2.
  3. 3Runs attention in every head at once, eq card in Section 5, line 1.
  4. 4Joins the heads back side by side, eq card in Section 5, line 3.
  5. 5Mixes them with WO, eq card in Section 5, line 4.

Because each head scores in its own subspace, two heads can attend to different tokens. In "The animal didn't cross the street because it was too tired", "it" needs coreference, finding which noun a pronoun refers to, and the local clause around it.

Head A, coreference Head B, local syntax

Figure 8. With illustrative weights, Head A puts its weight on "animal"; Head B stays on the clause around "it".

More heads do not mean more parameters: with model width d (short for dmodel), each head's slice dk is d divided by the head count h.

The attention layer's parameter count, which has no h in it.
total = 3d2 + d2 = 4d2
The layer always holds 4d² weights, ignoring biases: 3d² for the query, key and value projections and d² for WO. Each of the h heads owns d/h columns of each projection, so doubling the heads halves each head's width and the total stays put. Heads change how the work is sliced, never how big the layer is; only a wider d adds parameters, and it adds them quadratically.

More heads, same parameter count

the 512 dimensions, one numbered shade per head
attention parameters, 4d² in all
Q, K, V projections, 3d²
WO, d²

Figure 9. Sixteen 32-dim heads or one 512-dim head: the slices change, the parameter count does not.

Every head on this page takes its queries, keys and values from the same sequence, which makes it self-attention. When the queries come from one stream and the keys and values from another, say text asking about an image, it becomes cross-attention, covered in Cross-Attention.


6 One PyTorch call runs a whole attention layer

One self-attention layer on a padded batch, first as PyTorch's module, then by hand with the module's own projection weights and the fused function, so out2 matches out.

import torch
import torch.nn as nn
import torch.nn.functional as F

B, n, d_model, h = 2, 6, 512, 8
x = torch.randn(B, n, d_model)
pad = torch.tensor([[False] * 6, [False] * 4 + [True] * 2])  # True = padding

mha = nn.MultiheadAttention(d_model, h, batch_first=True)
out, _ = mha(x, x, x, key_padding_mask=pad, need_weights=False)

d_k = d_model // h
q, k, v = F.linear(x, mha.in_proj_weight, mha.in_proj_bias).chunk(3, dim=-1)
q, k, v = (t.view(B, n, h, d_k).transpose(1, 2) for t in (q, k, v))
keep = ~pad[:, None, None, :]  # True = may attend
heads = F.scaled_dot_product_attention(q, k, v, attn_mask=keep)
out2 = mha.out_proj(heads.transpose(1, 2).reshape(B, n, d_model))
  1. 1A batch of two sequences, the input X of eq card 1 in Section 3, line 1; True marks the second one's 2 padding tokens.
  2. 2The module runs the projections, all heads and WO in one call, eq card in Section 5, lines 1 to 4.
  3. 3The same projections by hand with the module's weights, cut into heads, eq card in Section 5, line 2.
  4. 4The fused function runs attention in every head, eq card in Section 5, line 1; keep is True where attending is allowed.
  5. 5Joins the heads and mixes them with the module's out_proj, eq card in Section 5, lines 3 and 4.
Self-attentionEvery layer, every forward pass, over all n tokens at once; nothing is kept between passes; n2 scores per head.
Multi-headAll h heads run side by side inside each attention layer, 4d2 weights per layer whatever h is.
hCuts dmodel into h slices of dk = dmodel/h. More heads give more attention patterns from thinner slices, fewer give fewer wider ones; weights stay 4d2. At 1, one pattern does everything; at dmodel, each head scores with one number. The base Transformer uses 8.
√dkDivides every score (Section 3 explains why). With no divisor a large dk gives one winner per row; a much larger divisor flattens the softmax toward a plain average. The base Transformer uses dk = 64, a divisor of 8, and PyTorch scales by 1/√dk by default.
Codenn.MultiheadAttention, F.scaled_dot_product_attention (PyTorch docs).

Attention as built here has no idea which token came first. How does it learn order, and how does a decoder avoid seeing later tokens? Positional Encoding and Masking.


7 References

  1. Vaswani, A., et al. (2017). Attention Is All You Need. NeurIPS 2017. arXiv:1706.03762.