Self-attention is the core mechanism of the transformer. It computes relationships between all pairs of positions in a sequence.
Queries, Keys, Values: Given input (sequence of token embeddings), compute:
Attention computation:
Step by step:
- — dot product of every query with every key → relevance scores
- — scale to prevent softmax saturation (large dot products → near-one-hot softmax)
- Softmax — normalize each row to a probability distribution (how much each position attends to every other)
- Multiply by — weighted sum of value vectors according to attention weights
Intuition: "soft lookup in a dictionary." Each position asks a query, compares it to all keys, and retrieves a weighted mix of values. The network learns what to look for (Q), what to advertise (K), and what to provide (V).
def scaled_dot_product_attention(q, k, v, mask):
# q, k: (batch, ..., seq_len, d_k); v: (batch, ..., seq_len, d_v)
d_k = q.shape[-1]
scores = (q @ k.transpose(-2, -1)) / math.sqrt(d_k)
scores = scores.masked_fill(~mask, -torch.inf) # mask True = attendable
return softmax(scores, dim=-1) @ vThe scaling matters because dot products grow like ; unscaled, large logits make the softmax peaky and resistant to gradient updates.
Complexity: — quadratic in sequence length. This is the main bottleneck for long sequences.
vs. RNNs: attention gives direct connections between any two positions regardless of distance, solving the long-range dependency problem.
See also: Multi-Head Attention, Causal Masking, Positional Encoding