Self-Attention
Every token builds a query, a key, and a value, then scores its query against every key in the sequence to decide how much of each other token's value to blend in.
Why Does This Exist?
The attention mechanism that fixed sequence-to-sequence translation had a decoder looking back at an encoder — two different sequences, one reading the other. But nothing about "score, weigh, blend" actually requires two separate sequences. Nothing stops a sequence from attending to itself: every token scoring itself against every other token in the same sequence, including its own position.
That single reframing is self-attention, and it turns out to matter enormously. A recurrent network builds context about a sentence by passing a hidden state one token at a time — token 50's understanding of token 2 has to survive 48 sequential updates to arrive. Self-attention lets every token look directly at every other token in one step, regardless of how far apart they sit in the sequence. Distance stops being a cost.
Think of It Like This
A room where everyone can hear everyone
Picture a meeting where, instead of information passing person to person around a table, every person can hear every other person directly and simultaneously — and each person decides, moment to moment, whose voice to actually weight most heavily given what they're currently trying to figure out.
Someone discussing "it" in a sentence doesn't have to wait for that context to relay through six intermediate speakers. They listen to the whole room at once and turn up the volume on whichever earlier statement actually resolves what "it" refers to. That's self-attention: every position, listening to every position, weighting each one by relevance to itself, right now.
How It Actually Works
Three vectors per token
Every input token's embedding is projected through three separate learned weight matrices into three roles:
The query () represents what token is looking for. The key () represents what token offers to be found by. The value () is the actual content token contributes if it's found relevant. All three come from the same token, but , , are different matrices, so the three vectors end up capturing different things about it.
Score, scale, softmax, blend
For every query , score it against every key in the sequence with a dot product, then scale, softmax, and blend against the values — the diagram above shows exactly this for token 2:
computes every query-key dot product at once — an matrix of raw scores, one row per query, one column per key. Dividing by (the key dimension) before the softmax keeps those scores from growing too large as increases; unscaled, large dot products push softmax toward a near one-hot spike and gradients toward zero. Softmax turns each row into weights that sum to 1, and multiplying by blends the value vectors by those weights — exactly the score-weigh-blend sequence from the earlier attention mechanism, just computed as one matrix expression covering every position in the sequence at once.
Why it's called "self"
In cross-attention, queries come from one sequence and keys/values come from a different one. In self-attention, , , and all come from the same sequence — every token attends over the sequence it's a member of, itself included. That's the entire distinction, and it's why a single self-attention layer can build a representation of a token that already accounts for its entire surrounding context, in one operation.
What the weights actually encode
Attention weights are learned, not designed — nothing hand-specifies that pronouns should attend to their antecedents. The training signal (predicting the next token, or whatever the objective is) simply rewards weight patterns that help the prediction, and patterns like "attend to the subject of the sentence" turn out to help a great deal, so gradient descent finds them. The score is genuinely just a dot product between two learned vectors; the linguistic structure it ends up capturing is emergent, not engineered.
Show Me the Code
Scaled dot-product attention over a toy sequence of four tokens, with , , built from one shared input and three separate projections.
import numpy as np
def softmax(z: np.ndarray) -> np.ndarray: e = np.exp(z - z.max(axis=-1, keepdims=True)) return e / e.sum(axis=-1, keepdims=True)
def self_attention(x: np.ndarray, w_q: np.ndarray, w_k: np.ndarray, w_v: np.ndarray) -> np.ndarray: q, k, v = x @ w_q, x @ w_k, x @ w_v # (n, d_k) each, one row per token scores = q @ k.T / np.sqrt(k.shape[-1]) # (n, n) — every query against every key weights = softmax(scores) # each row sums to exactly 1 return weights @ v # (n, d_k) — blended output per token
rng = np.random.default_rng(0)x = rng.normal(size=(4, 8)) # 4 tokens, embedding dim 8w_q, w_k, w_v = (rng.normal(scale=0.3, size=(8, 4)) for _ in range(3))output = self_attention(x, w_q, w_k, w_v)print(output.shape) # -> (4, 4) — one output vector per token, in the value dimensionEvery row of scores before the softmax spans all four tokens — token 0's row includes its score against itself, at position 0, exactly as the diagram's diagonal shows.
Watch Out For
Forgetting the √d_k scale and blaming the optimizer
Drop the scaling and, for larger key dimensions, raw dot products grow large enough that softmax saturates almost immediately — one weight near 1, the rest near 0, and gradients through the softmax collapse toward zero everywhere except that one position. Training then looks like a learning-rate problem: loss barely moves, and lowering the rate doesn't help, because the actual issue is upstream of the optimizer entirely. Check for the scale factor before touching hyperparameters.
Assuming self-attention understands order on its own
Self-attention is permutation-equivariant: shuffle the input tokens and, absent anything else, the same set of output vectors comes back in the shuffled order — the mechanism has no built-in notion of which token came first. "Dog bites man" and "man bites dog" would score identically token-for-token if nothing told the model which position each token occupied. That's not a bug to fix inside self-attention; it's why positional encoding has to be added separately, before attention ever runs.
The Quick Version
- Self-attention projects every token into a query, a key, and a value using three separate learned matrices.
- Every query scores against every key via a dot product, scaled by , then softmaxed into weights summing to 1.
- Those weights blend the value vectors into one output per token, in a single matrix operation covering the whole sequence.
- "Self" means , , and all come from the same sequence — contrast with cross-attention, where they come from two different sequences.
- Self-attention has no inherent sense of token order; that has to be injected separately.
What to Read Next
- Multi-Head Attention runs several self-attention computations in parallel, each in its own subspace.
- Causal Masking restricts self-attention so a position can't look ahead — what turns this mechanism into a generator.
- Attention Complexity measures the cost of the matrix this page builds.
- Attention Mechanism is the score-weigh-blend sequence self-attention specializes to one sequence attending to itself.
- Positional Encoding is the fix for the order-blindness this page's second pitfall names.