CMPUT-466 · Lesson 8 of 12 · 35 min

Attention and transformers

The transformer, introduced by Ashish Vaswani and seven colleagues at Google in the 2017 paper Attention Is All You Need, is the architecture behind nearly every large language model, most state-of-the-art speech and vision systems, protein structure predictors and much else. It drops recurrence and convolution entirely and builds everything from attention, a mechanism that lets each position in a sequence gather information directly from every other position, combined with the feedforward layers, residual connections and normalization of earlier lessons. This lesson derives scaled dot-product attention, implements it with a causal mask, explains multi-head attention and positional encodings including rotary embeddings, assembles the transformer block, and works through its computational costs, including the quadratic cost of attention and the key-value cache used during generation. AI-340 covers the transformer's use in language, AI-410 large language models, and AI-350 vision transformers.

Attention as soft lookup

Attention can be understood as a differentiable dictionary lookup. A query vector is compared with a set of key vectors, each comparison produces a score, the scores are turned into weights by a softmax, and the result is the weighted average of the value vectors associated with the keys. A hard lookup would return the single value whose key matches; attention returns a blend, weighted toward the best matches, and because every step is differentiable the network can learn what to put in the queries, keys and values. Bahdanau attention (lesson 7) scored query-key pairs with a small network; Minh-Thang Luong and colleagues (2015) used dot products, which are cheaper and map well onto matrix hardware.

In self-attention, the queries, keys and values all come from the same sequence. Each position's representation \(\mathbf{x}_t\) is projected three ways, \(\mathbf{q}_t = W_Q^{\top}\mathbf{x}_t\), \(\mathbf{k}_t = W_K^{\top}\mathbf{x}_t\), \(\mathbf{v}_t = W_V^{\top}\mathbf{x}_t\), and with all positions stacked as rows of matrices Q, K and V, scaled dot-product attention is

\[\mathrm{Attention}(Q, K, V) = \mathrm{softmax}\Bigl(\frac{QK^{\top}}{\sqrt{d_k}}\Bigr)V\]

where the softmax is applied to each row. Row t of \(QK^{\top}\) holds the similarity of position t's query with every key; after the softmax it is a probability distribution saying where position t looks, and multiplying by V gathers the corresponding values. Every position is connected to every other in a single step, so the path length between any two positions is 1, against T for an RNN. And all positions are computed at once with matrix products, which GPUs do extremely well.

Causal self-attention: each position attends only to itself and earlier positions; without positional information, attention ignores order
# Scaled dot-product self-attention with a causal mask, from scratch in NumPy.
import numpy as np
rng = np.random.default_rng(466)
T, d_model, d_k = 5, 8, 4
X = rng.normal(size=(T, d_model))                       # 5 token representations
Wq, Wk, Wv = (rng.normal(0, d_model ** -0.5, (d_model, d_k)) for _ in range(3))
Q, K, V = X @ Wq, X @ Wk, X @ Wv
def attention(Q, K, V, causal):
    scores = Q @ K.T / np.sqrt(K.shape[1])               # T x T similarity of every query with every key
    if causal:
        scores = np.where(np.triu(np.ones_like(scores), 1) == 1, -np.inf, scores)   # hide the future
    scores -= scores.max(axis=1, keepdims=True)
    A = np.exp(scores); A /= A.sum(axis=1, keepdims=True)   # each row is a probability distribution
    return A @ V, A
out, A = attention(Q, K, V, causal=True)
np.set_printoptions(precision=3, suppress=True)
print("causal attention weights (row = query position, column = key position):")
print(A)
print("rows sum to 1:", np.allclose(A.sum(1), 1), "  output shape:", out.shape)
# permuting the tokens without a causal mask just permutes the outputs: attention has no notion of order
perm = rng.permutation(T)
o1, _ = attention(Q, K, V, causal=False); o2, _ = attention(Q[perm], K[perm], V[perm], causal=False)
print("without positions, self-attention is permutation-equivariant:", np.allclose(o1[perm], o2))
Output
causal attention weights (row = query position, column = key position):
[[1.    0.    0.    0.    0.   ]
 [0.772 0.228 0.    0.    0.   ]
 [0.447 0.405 0.148 0.    0.   ]
 [0.03  0.328 0.282 0.36  0.   ]
 [0.356 0.339 0.078 0.14  0.088]]
rows sum to 1: True   output shape: (5, 4)
without positions, self-attention is permutation-equivariant: True
A diagram of scaled dot-product attention. Input token vectors on the left are projected by three matrices into queries, keys and values. Queries and transposed keys multiply to form a square grid of scores, which is divided by the square root of d k, masked so that cells above the diagonal are grayed out, and passed through a row-wise softmax. The resulting weight grid multiplies the values to produce the outputs on the right.
Scaled dot-product attention: compare queries with keys, mask the future, normalize each row, and average the values

Why scale by the square root of the dimension

If the entries of \(\mathbf{q}\) and \(\mathbf{k}\) are independent with mean 0 and variance 1, their dot product is a sum of \(d_k\) terms of variance 1, so it has variance \(d_k\). With \(d_k = 64\) the scores have standard deviation 8, and the softmax of such widely spread scores is nearly one-hot: one key gets almost all the weight and the gradients with respect to the others vanish, the same saturation problem as with sigmoids. Dividing by \(\sqrt{d_k}\) restores unit variance and keeps the softmax in a regime where gradients flow. Later work sometimes normalizes queries and keys directly (QK-norm) to keep scores bounded during long training runs.

Unscaled dot products grow with the dimension and push the softmax toward one-hot; scaling keeps attention spread out
# Why divide by sqrt(d_k): dot products of random unit-variance vectors have variance d_k.
import numpy as np
rng = np.random.default_rng(466)
for d in (4, 64, 512):
    q = rng.normal(size=(5000, d)); k = rng.normal(size=(5000, d))
    dots = np.sum(q * k, axis=1)
    s = rng.normal(size=(2000, 16, d)) @ rng.normal(size=(d,))        # 16 keys per query
    def peak(scores):
        e = np.exp(scores - scores.max(1, keepdims=True)); return (e / e.sum(1, keepdims=True)).max(1).mean()
    print(f"d_k = {d:3}: std of q.k = {dots.std():6.2f} (sqrt(d) = {np.sqrt(d):5.2f});  "
          f"mean largest softmax weight over 16 keys: unscaled {peak(s):.3f}, scaled {peak(s / np.sqrt(d)):.3f}")
Output
d_k =   4: std of q.k =   2.00 (sqrt(d) =  2.00);  mean largest softmax weight over 16 keys: unscaled 0.514, scaled 0.281
d_k =  64: std of q.k =   8.01 (sqrt(d) =  8.00);  mean largest softmax weight over 16 keys: unscaled 0.829, scaled 0.220
d_k = 512: std of q.k =  22.81 (sqrt(d) = 22.63);  mean largest softmax weight over 16 keys: unscaled 0.938, scaled 0.232

Causal masking

A language model predicts each token from the ones before it. During training the whole sequence is processed at once, so position t must be prevented from looking at positions after t, or the model would learn to copy the answer. Setting the scores above the diagonal to \(-\infty\) before the softmax gives those positions zero weight, as in the program's output, whose upper triangle is zero. This causal mask is what makes a decoder-only transformer such as GPT a valid autoregressive model while still training on all positions of a sequence in parallel. Encoders such as BERT omit the mask, so every position sees the whole input, which suits understanding tasks but not left-to-right generation. Padding masks similarly hide padding tokens in batches of unequal-length sequences.

Multi-head attention

One attention distribution per position is limiting: a word may need to attend to its subject for agreement, to a nearby adjective for meaning and to a distant antecedent for reference, all at once. Multi-head attention runs h attention operations in parallel, each with its own projections into a smaller dimension \(d_k = d/h\), concatenates their outputs and applies an output projection \(W_O\):

\[\mathrm{MHA}(X) = \mathrm{Concat}(\mathrm{head}_1, \dots, \mathrm{head}_h)\,W_O, \qquad \mathrm{head}_i = \mathrm{Attention}(XW_Q^{(i)}, XW_K^{(i)}, XW_V^{(i)})\]

Because each head works in dimension d/h, the total cost is about the same as one full-width head. Different heads learn different patterns; interpretability studies have found heads that attend to the previous token, to matching brackets, to syntactic relations, and induction heads that look for an earlier occurrence of the current token and copy what followed it (Catherine Olsson and colleagues at Anthropic, 2022), a mechanism linked to in-context learning. To reduce memory during generation, multi-query attention (Noam Shazeer, 2019) shares one key and value projection across all heads, and grouped-query attention (Joshua Ainslie and colleagues, 2023) shares them within groups of heads; most recent large language models use grouped-query attention.

Positional information

Attention by itself is permutation-equivariant: shuffle the input positions and the outputs are shuffled the same way, as the first program verified. Word order must therefore be injected explicitly. The original transformer added fixed sinusoidal positional encodings to the input embeddings, with dimension pairs oscillating at geometrically spaced frequencies so that each position has a unique pattern and nearby positions have similar ones. BERT and GPT-2 instead learned a position embedding vector for each position up to a maximum length, which cannot extend beyond that length.

Rotary position embedding (RoPE; Jianlin Su and colleagues, 2021) applies position inside attention rather than to the input. It rotates each consecutive pair of query and key dimensions by an angle proportional to the position, with a different frequency for each pair. Because rotations compose, the dot product of a query rotated by position m and a key rotated by position n depends only on the offset m − n, so attention scores are a function of relative position. RoPE is used in most open large language models, including the Llama, Mistral and Qwen families, and variants that rescale its frequencies are used to extend context length after training. ALiBi (Ofir Press and colleagues, 2022) instead adds a penalty to the score proportional to distance.

Sinusoidal encodings give each position a distinct pattern; with RoPE, any query-key pair two positions apart gets the same score
# Sinusoidal positional encodings and rotary embeddings (RoPE): RoPE scores depend only on relative position.
import numpy as np
def sinusoidal(T, d):
    pos = np.arange(T)[:, None]; i = np.arange(d // 2)[None, :]
    ang = pos / 10000 ** (2 * i / d)
    pe = np.zeros((T, d)); pe[:, 0::2] = np.sin(ang); pe[:, 1::2] = np.cos(ang)
    return pe
pe = sinusoidal(50, 16)
print("sinusoidal PE, positions 0 and 1, first 6 dimensions:")
print(np.round(pe[:2, :6], 3))
def rope(x, pos):                                    # rotate each pair of dimensions by pos * frequency
    d = x.shape[-1]; freq = 1 / 10000 ** (np.arange(d // 2) * 2 / d)
    ang = pos * freq; c, s = np.cos(ang), np.sin(ang)
    x1, x2 = x[0::2], x[1::2]
    out = np.empty_like(x); out[0::2] = x1 * c - x2 * s; out[1::2] = x1 * s + x2 * c
    return out
rng = np.random.default_rng(466)
q, k = rng.normal(size=16), rng.normal(size=16)
for m, n in ((3, 1), (10, 8), (42, 40)):             # all have offset m - n = 2
    print(f"query at {m:2}, key at {n:2}: RoPE score {rope(q, m) @ rope(k, n):.6f}")
Output
sinusoidal PE, positions 0 and 1, first 6 dimensions:
[[0.    1.    0.    1.    0.    1.   ]
 [0.841 0.54  0.311 0.95  0.1   0.995]]
query at  3, key at  1: RoPE score -2.963328
query at 10, key at  8: RoPE score -2.963328
query at 42, key at 40: RoPE score -2.963328

The transformer block

A transformer layer combines multi-head attention, which mixes information across positions, with a position-wise feedforward network, which processes each position independently, usually expanding to four times the model width and back with a GELU or SwiGLU nonlinearity. Each sub-layer is wrapped in a residual connection with normalization. In the now-standard pre-norm form,

\[\mathbf{h} = \mathbf{x} + \mathrm{MHA}(\mathrm{Norm}(\mathbf{x})), \qquad \mathbf{y} = \mathbf{h} + \mathrm{FFN}(\mathrm{Norm}(\mathbf{h}))\]

A model stacks N such blocks between an embedding layer and an output layer. The original transformer was an encoder-decoder for translation: an encoder stack without masking, and a decoder stack with causal self-attention plus cross-attention, in which the decoder's queries attend to the encoder's outputs. Encoder-only models (BERT, 2018) are trained to fill in masked tokens and suit classification and retrieval. Decoder-only models (the GPT series from 2018) are trained on next-token prediction and dominate generation. Encoder-decoder models (T5, 2019) remain common for translation and speech recognition (Whisper).

A vertical diagram of a pre-norm transformer block. From the bottom, the input x splits: one path goes through a layer norm box and a multi-head attention box, then joins the skip path at a plus node to give h. From h, a second split goes through another layer norm and a feedforward box labeled d to 4d to d with GELU, then joins at a second plus node to give the output y. The two skip paths are drawn as straight vertical lines on the right.
A pre-norm transformer block: attention mixes positions, the feedforward network transforms each position, and residual paths carry the signal through

The feedforward layers hold about two thirds of a standard transformer's parameters, and evidence suggests they store much of a model's factual knowledge, acting like key-value memories (Mor Geva and colleagues, 2021). Mixture-of-experts layers replace the single feedforward network with many expert networks and a router that sends each token to a few of them, increasing parameters without increasing computation per token; AI-480 covers how they are distributed across hardware.

Costs: quadratic attention and the KV cache

For a sequence of length T and width d, the projections and feedforward layers cost on the order of \(Td^2\) operations per layer, while the attention scores cost on the order of \(T^2 d\) and need a \(T \times T\) matrix per head. For short sequences the \(d^2\) terms dominate; beyond T of a few thousand, attention's quadratic cost dominates and its memory becomes the bottleneck. FlashAttention (Tri Dao and colleagues, 2022) computes exact attention in blocks that fit in fast on-chip memory without ever writing the full score matrix to slow memory, making long contexts practical. Approximate methods (sparse, low-rank and linear attention) reduce the asymptotic cost with some loss of quality; the state space models of lesson 7 take a different route.

Generation adds a different cost. A decoder produces one token at a time, and each new token's query must attend to the keys and values of all previous tokens. Recomputing them at every step would be wasteful, so they are stored in a key-value cache and only the new token's keys and values are computed. The cache grows linearly with context length and with the number of layers and key-value heads, and for long contexts and many simultaneous users it, rather than the weights, often limits how many requests a server can handle. Grouped-query attention, quantized caches and paged memory management all target it; AI-410 covers inference serving.

A GPT-2-small-sized model has about 124 million parameters; training costs about 6 FLOPs per parameter per token, and the KV cache grows with context
# Parameter and compute accounting for a GPT-2-small-sized decoder (12 layers, d = 768, vocabulary 50,257).
L, d, V, T, heads = 12, 768, 50257, 1024, 12
attn = 4 * d * d + 4 * d                  # Wq, Wk, Wv, Wo and their biases
mlp = 2 * d * (4 * d) + 4 * d + d         # d -> 4d -> d with biases
norms = 2 * 2 * d                         # two layer norms (scale and shift) per block
block = attn + mlp + norms
emb = V * d + T * d                       # token embeddings (shared with the output layer) + learned positions
total = L * block + emb + 2 * d           # plus the final layer norm
print(f"per block: attention {attn:,}, MLP {mlp:,}, norms {norms:,} -> {block:,}")
print(f"embeddings {emb:,};  total {total:,} parameters (about {total / 1e6:.0f} M)")
N = L * block                             # non-embedding parameters
print(f"training compute per token is about 6N = {6 * N / 1e9:.2f} GFLOPs, plus attention scores 6*L*T*d = {6 * L * T * d / 1e9:.2f} GFLOPs")
kv = 2 * L * T * d * 2                    # keys and values, all layers, 16-bit numbers (2 bytes)
print(f"KV cache for one 1,024-token sequence in 16-bit: {kv / 2 ** 20:.0f} MiB")
print(f"attention score matrix per head per layer at T = 1,024: {T * T:,} entries; at T = 32,768: {32768 ** 2:,}")
Output
per block: attention 2,362,368, MLP 4,722,432, norms 3,072 -> 7,087,872
embeddings 39,383,808;  total 124,439,808 parameters (about 124 M)
training compute per token is about 6N = 0.51 GFLOPs, plus attention scores 6*L*T*d = 0.06 GFLOPs
KV cache for one 1,024-token sequence in 16-bit: 36 MiB
attention score matrix per head per layer at T = 1,024: 1,048,576 entries; at T = 32,768: 1,073,741,824

The count reproduces GPT-2 small's published 124 million parameters, of which about 39 million are the embeddings (the token embedding matrix is shared with the output layer). The rule of thumb that a forward and backward pass costs about 6N floating-point operations per token for N non-embedding parameters (2N forward, 4N backward) is due to Jared Kaplan and colleagues (2020) and underlies the compute estimates of lesson 9.

Why transformers won

Transformers make few assumptions about their input beyond what positional information supplies, which is a weakness on small data, where convolution's built-in locality helps, and a strength at scale, where the data can teach the model which interactions matter. They parallelize fully across positions during training, which suits GPUs, and their performance has kept improving predictably as data, parameters and compute grow (lesson 9). The same architecture, with only the input embedding changed, processes text tokens, image patches, audio frames, actions and amino acids, which made multimodal models straightforward. Whether attention will remain dominant as context lengths grow is an open question, but as of 2026 the transformer remains the default architecture for large models.

Worked examples

Worked problem

Attention by hand

One query \(\mathbf{q} = (1, 0)\) and two keys \(\mathbf{k}_1 = (2, 0)\), \(\mathbf{k}_2 = (0, 2)\) with values \(\mathbf{v}_1 = (1, 1)\), \(\mathbf{v}_2 = (5, -1)\), and \(d_k = 2\). Compute the output.

Show solution
  1. Scores: \(\mathbf{q}\cdot\mathbf{k}_1 = 2\), \(\mathbf{q}\cdot\mathbf{k}_2 = 0\); scaled by \(\sqrt{2}\): 1.414 and 0.
  2. Softmax: \(e^{1.414} = 4.11\); weights \(4.11/5.11 = 0.805\) and 0.195.
  3. Output: \(0.805(1, 1) + 0.195(5, -1) = (1.78, 0.61)\).

Answer: About (1.78, 0.61), mostly the first value because its key matches the query.

Worked problem

Multi-head dimensions

A model has d = 1,024 and 16 heads. What is d_k, and how many parameters do the four attention projections have (no biases)?

Show solution
  1. d_k = 1,024/16 = 64.
  2. Each of \(W_Q, W_K, W_V, W_O\) is 1,024 × 1,024 = 1,048,576.
  3. Total: 4 × 1,048,576 = 4,194,304.

Answer: d_k = 64; about 4.2 million parameters, the same as for a single full-width head.

Worked problem

KV cache size

A model has 32 layers, 8 key-value heads of dimension 128, and stores the cache in 16-bit. How large is the cache for one 8,000-token conversation?

Show solution
  1. Per token per layer: keys and values, 2 × 8 × 128 = 2,048 numbers.
  2. All layers: 2,048 × 32 = 65,536 numbers per token, at 2 bytes = 131,072 bytes.
  3. 8,000 tokens: about \(1.05 \times 10^9\) bytes.

Answer: About 1 GB per conversation; with 32 query heads and no grouping it would be four times larger.

Common mistakes

  • MistakeOmit the 1/√d_k scaling.

    InsteadScores then grow with dimension and the softmax saturates, starving gradients.

  • MistakeForget the causal mask when training a decoder.

    InsteadThe model sees future tokens and learns to copy them; validation loss looks great and generation fails.

  • MistakeAssume self-attention knows token order.

    InsteadIt is permutation-equivariant; positions must be supplied by encodings such as RoPE.

  • MistakeExpect attention cost to scale linearly with context.

    InsteadScores cost O(T²d) time and O(T²) memory per head without memory-efficient kernels.

  • MistakeTreat attention weights as complete explanations of model behavior.

    InsteadThey show where information is gathered in one layer, not why the model decides; use them with other interpretability tools.

Summary and key terms

  • Attention is a soft lookup: queries are compared with keys, softmax weights average the values.
  • Scaling scores by 1/√d_k keeps their variance near 1 and the softmax unsaturated.
  • A causal mask lets decoders train on all positions in parallel without seeing the future.
  • Multi-head attention runs several attention patterns in parallel at the cost of one; grouped-query attention shares keys and values to save memory.
  • Attention is order-blind; positional encodings (sinusoidal, learned, RoPE) supply order, and RoPE makes scores depend on relative position.
  • A transformer block is pre-norm attention plus a feedforward network, each with a residual connection.
  • Attention costs O(T²) in sequence length; generation uses a KV cache that grows with context.
Attention
A weighted average of values, with weights from a softmax over query-key similarities.
Self-attention
Attention in which queries, keys and values all come from the same sequence.
Scaled dot-product attention
softmax(QK^T/√d_k)V.
Causal mask
Setting scores for future positions to −∞ so each position attends only to the past.
Multi-head attention
Several attention operations in parallel on lower-dimensional projections, concatenated.
Positional encoding
Information added to tell an attention model the position of each token.
Rotary position embedding
Rotating query and key dimension pairs by position-dependent angles so scores depend on relative offset.
Cross-attention
Attention whose queries come from one sequence and keys and values from another.
KV cache
Stored keys and values of previous tokens reused during autoregressive generation.
Grouped-query attention
Sharing key and value projections across groups of query heads.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. What is the path length between any two positions in self-attention, and in an RNN?

    Show answer

    1 for self-attention; up to T for an RNN.

  2. Why is the softmax applied row by row?

    Show answer

    Each row is one query's distribution over keys.

  3. If d_k = 256 and q, k have unit-variance entries, what is the standard deviation of q·k?

    Show answer

    16.

  4. What does the causal mask do to the attention matrix?

    Show answer

    Sets entries above the diagonal to zero weight.

  5. Why does multi-head attention cost about the same as single-head attention?

    Show answer

    Each of h heads works in dimension d/h.

  6. Why does RoPE give relative-position behavior?

    Show answer

    Rotating q by mθ and k by nθ makes their dot product depend only on (m − n)θ.

  7. Where do most of a standard transformer's non-embedding parameters live?

    Show answer

    In the feedforward (MLP) layers, about two thirds.

  8. Which is BERT, encoder-only or decoder-only, and how is it trained?

    Show answer

    Encoder-only, trained to predict masked tokens.

  9. What problem does FlashAttention solve?

    Show answer

    The memory traffic of writing the full T × T score matrix, by computing exact attention in on-chip blocks.

  10. Why does the KV cache limit serving capacity?

    Show answer

    It grows with context length and number of users and can exceed the memory used by the weights.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz