Transformers and Self-Attention
Queries, keys, values, and why attention is all you need. Build a self-attention layer from scratch, then see how GPT and BERT use it.
LSTMs process tokens one at a time — step 1, then step 2, then step 3. A 512-token sequence takes 512 sequential steps. Self-attention processes every token in relation to every other token simultaneously. That is the entire revolution.
Module 47 showed the LSTM's fundamental limitation: sequential processing. To understand token 512 in a document you must first process tokens 1 through 511. You cannot parallelise across the sequence. Modern GPUs have thousands of cores that sit idle while the LSTM plods forward one step at a time. Training a large LSTM on a billion tokens takes weeks.
Self-attention removes sequential dependency entirely. For each token it asks: which other tokens in this sequence are relevant to understanding me? It computes a relevance score between every pair of tokens simultaneously — all in one matrix multiplication. The entire sequence is processed in parallel. GPT-3 was trained on 300 billion tokens in a few weeks. An LSTM of equivalent capacity would have taken years.
Beyond speed, attention solves the core weakness of LSTMs — long-range dependencies. "The bank on the river bank was steep." An LSTM processing this sentence might forget "river" by the time it reaches the second "bank." Self-attention directly connects "bank" to "river" regardless of distance. Every token directly attends to every other token in a single layer.
Imagine a meeting with 10 people. An LSTM-style meeting: person 1 speaks, whispers to person 2, person 2 whispers to person 3 — by person 10, the original message is distorted. A self-attention meeting: every person simultaneously reads every other person's written statement and decides how much to pay attention to each one when forming their own response. No information degrades. No sequential bottleneck.
The attention score between two tokens is their relevance — how much should token A look at token B when computing its contextual meaning? "Bank" should look at "river" with high attention weight. "Bank" should look at "steep" with lower weight. These weights are learned during training.
Scaled dot-product attention — queries, keys, and values
Self-attention projects each token into three vectors: a Query (Q), a Key (K), and a Value (V). Think of it like a library system. The query is your search request. The keys are the index cards for every book. The values are the actual book contents. Attention computes how well your query matches each key, converts those match scores to weights (softmax), and returns a weighted sum of values.
Multi-head attention — h parallel attention heads, concatenated
A single attention head learns one type of relationship between tokens. But a sentence has many simultaneous relationships — syntactic dependencies, coreference, semantic similarity, positional proximity. Multi-head attention runs h attention heads in parallel, each with its own Q, K, V projection matrices. Each head can specialise in a different relationship type. The outputs are concatenated and projected back to d_model.
Transformer encoder block — attention + feedforward + residual + LayerNorm
A single Transformer encoder block combines four components. Multi-head self-attention computes contextual representations. A position-wise feedforward network applies the same two-layer MLP to each token independently — adding non-linearity and capacity. Residual connections add the input to the output of each sub-layer — preventing vanishing gradients and enabling very deep stacking. Layer Normalisation stabilises training — applied before each sub-layer in the modern "Pre-LN" variant used by GPT.
BERT vs GPT — encoder vs decoder, bidirectional vs causal
The original Transformer had both an encoder and a decoder. Modern LLMs use just one half. BERT uses encoder-only — every token can attend to every other token (bidirectional). This makes it excellent for understanding tasks: classification, NER, question answering. GPT uses decoder-only — each token can only attend to previous tokens (causal masking). This makes it excellent for generation: complete this sentence, write this email.
Fine-tuning a pretrained Transformer — Stripe payment dispute classification
In production, nobody trains a Transformer from scratch for NLP tasks. You take a pretrained model (BERT, RoBERTa, DistilBERT) that has already learned language from billions of tokens, add a small task-specific head, and fine-tune on your labelled data. Stripe classifies payment dispute reasons — fraudulent charge, service not received, wrong amount — from customer-submitted text. A fine-tuned DistilBERT achieves near-human accuracy with 1,000 labelled examples in minutes of fine-tuning.
Every common Transformer mistake — explained and fixed
How attention's cost curve turns into an actual infrastructure decision
Everything this module derives mathematically — attention's quadratic cost in sequence length, the memory that a full attention matrix consumes — shows up in production not as theory but as a line item in a capacity-planning spreadsheet. When a team building a support-ticket classifier or a chat product picks a maximum context length, they are not setting an abstract hyperparameter. They are choosing a GPU memory budget, a cost-per-request, and a latency target, all at once, because doubling the context length roughly quadruples the attention compute and memory for every layer that processes it.
This is also why almost no production team trains a Transformer from scratch. Public estimates put the compute cost of pretraining a GPT-3-scale model in the range of several million dollars, before counting the curated training corpus and the distributed training infrastructure required to run it at all. Almost no company needs to make that investment: the standard production pattern is to start from an open pretrained model — Llama, Mistral, or a hosted model behind an API — and adapt it with prompting, parameter-efficient fine-tuning such as LoRA, or full fine-tuning on a much smaller labelled dataset, for a tiny fraction of the cost and in days rather than months. Full pretraining from scratch is realistically reserved for the handful of labs whose entire business is building foundation models.
Try prompting a hosted pretrained model first — no training at all. If accuracy or format control is not good enough, fine-tune a small open pretrained model on task-specific labelled data, often with LoRA to keep the cost and turnaround low. If long documents need to be handled, evaluate retrieval-augmented generation against simply widening the context window before assuming more context is the answer — retrieval is frequently both cheaper and more accurate, since it avoids paying attention's quadratic cost for context the model will not actually need on most requests. Training a Transformer from scratch is essentially never the first option evaluated.
Five things people get wrong about attention and Transformers
Self-attention computes Q·Kᵀ for every pair of tokens using only their content vectors — there is nothing in the raw operation that depends on where a token sits in the sequence. Shuffle "the cat sat on the mat" into "mat the on sat cat the" and, without positional encoding, self-attention produces the exact same set of pairwise scores rearranged — it is permutation-equivariant, not order-aware. That is precisely why every Transformer adds a positional signal (sinusoidal in the original paper, learned in BERT and GPT) into the token embedding before the first attention layer. "Sees everything" is not the same as "knows the order of what it sees" — attention treats the input as a set until position is injected explicitly.
Multi-head attention with h heads and single-head attention with one d_model-sized head use the same total number of parameters — the projection matrices are simply split into h smaller pieces (d_k = d_model / h per head) instead of one large piece. Nothing about the head count adds parameters or FLOPs to the QKV projections themselves. What multiple heads buy you is not more capacity but more independent subspaces: head 1 might learn to track subject-verb agreement while head 4 tracks coreference, each computing its own attention pattern over the same tokens. A single head is mathematically forced to average all these relationship types into one attention distribution per token — multi-head lets them coexist.
The scaling solves a specific, provable problem: when Q and K entries are drawn from a distribution with unit variance, the dot product Q·K summed over d_k dimensions has variance proportional to d_k itself — so as d_k grows, raw attention scores grow with it. Large scores push softmax into its saturated region, where the largest score gets a weight near 1 and every other gets a weight near 0, and the gradient of softmax there is close to zero everywhere. Dividing by √d_k exactly cancels the variance growth — a sum of d_k independent unit-variance terms, divided by √d_k, has variance 1 regardless of d_k — keeping softmax in a well-conditioned regime no matter how large the head dimension gets. It is a derived correction, not an arbitrary constant.
An attention weight of 0.61 from "bank" to "river" tells you the softmax-normalised dot product between those two vectors at that layer — it does not tell you that "river" caused the model's downstream prediction, nor does it account for the value vectors those weights multiply, the residual stream carrying information around attention entirely, or the many other layers and heads contributing to the final output. Research on attention-as-explanation has repeatedly found you can often edit attention weights substantially without changing the model's output, and find alternative attention patterns that produce the same prediction — the opposite of what a true causal explanation should allow. Treat attention weights as a debugging signal and a useful visualisation, not a certified explanation of model behaviour.
Parallelisable and cheap are different properties. An RNN does O(n) sequential steps, each O(1) relative to sequence length, for O(n) total compute and O(1) memory per step. Self-attention computes a full (seq_len × seq_len) score matrix, which is O(n²) in both compute and memory — for n=4096 that is 16 million score pairs before a single head's output is even computed, and it scales quadratically from there. Attention wins on wall-clock time because O(n²) work spread across thousands of GPU cores in parallel finishes faster than O(n) work done one step at a time — but for long enough sequences, the quadratic memory footprint of the attention matrix becomes the actual bottleneck, which is exactly why Flash Attention, sparse attention, and linear-attention variants exist.
Transformers and attention — 5 questions interviewers actually ask
An RNN processes tokens strictly in sequence — token 3's hidden state is computed from token 2's hidden state, so the order tokens arrive in is baked into the computation itself; you cannot get the same hidden state from a shuffled input. Self-attention has no such mechanism: it computes Q·Kᵀ between every pair of tokens using only their content, independent of position, so it is mathematically permutation-equivariant — reorder the input tokens and you get the same set of pairwise scores in a different order, not a different computation. To recover any notion of sequence order, the Transformer has to inject it explicitly, adding a positional vector (sinusoidal, or a learned embedding per position) to each token's representation before attention ever runs. Without it, "the dog bit the man" and "the man bit the dog" would look identical to self-attention.
Self-attention computes a full pairwise score matrix of shape (seq_len, seq_len), so both compute and memory scale as O(n²) in sequence length — double the sequence and you quadruple the cost, not double it. For short sequences (a few hundred tokens) this is a non-issue and the massive parallelism across GPU cores makes attention far faster in wall-clock time than an RNN's O(n) sequential steps. But for long-context use cases — long documents, long codebases, long conversations — the quadratic term dominates: at 32k tokens the score matrix alone has a billion entries per head. That's the direct motivation behind Flash Attention (same math, tiled to avoid materialising the full matrix in memory), sparse/local attention patterns, and linear-attention approximations — all attempts to get attention's quality without paying the full O(n²) bill.
A single attention head computes exactly one attention distribution per query token — one weighted average over all the other tokens' values. But a sentence carries multiple simultaneous relationship types at once: which word is the grammatical subject of which verb, which pronoun refers to which noun, which words are simply near each other. Forcing all of that into one softmax distribution means the model has to compromise — blend several different relevance signals into a single weighting, which loses information. Multi-head attention splits the d_model dimension into h smaller subspaces (d_k = d_model/h each) and runs attention independently in each — same total parameter count and compute as one big head, just partitioned — so different heads are free to specialise on different relationship types, and their outputs are concatenated and linearly projected back to d_model at the end.
As the key/query dimension d_k grows, the dot product Q·K — a sum of d_k terms — grows in variance proportionally to d_k, assuming roughly unit-variance inputs. Without correction, larger d_k produces larger-magnitude raw scores, and large scores push softmax toward a near-one-hot output where one token gets almost all the weight and the rest get almost none. In that saturated regime the softmax gradient is close to zero almost everywhere, which stalls learning — the model can't smoothly adjust which tokens to attend to because the loss landscape is nearly flat. Dividing by √d_k exactly compensates for the variance growth from summing d_k terms, keeping the score distribution's variance roughly constant regardless of head dimension, so softmax stays in a well-behaved, differentiable range no matter how wide the attention heads are.
Both are built from the identical Transformer block — multi-head self-attention plus a feedforward network, wrapped in residual connections and LayerNorm. The entire difference is the attention mask and the resulting pretraining objective. BERT applies no mask, so every token attends to every token in both directions — bidirectional context, ideal for understanding tasks like classification or NER where you want the fullest possible context before making a judgment. GPT applies a causal (upper-triangular) mask, so token i can only attend to tokens ≤ i — a hard requirement for autoregressive generation, since predicting token t+1 from tokens 1..t would leak the answer if the model could see token t+1 while training. That single masking choice cascades into everything else: BERT is pretrained with masked language modelling (fill in the blank, needs both directions), GPT with next-token prediction (needs only the past) — the mask is the cause, the pretraining objective and downstream use case are the effects.
The Deep Learning section is complete. Section 8 — NLP — begins next.
You have now completed the full Deep Learning section: neural networks from scratch, backpropagation, activation and loss functions, optimisers, batch normalisation and dropout, CNNs, RNNs and LSTMs, and Transformers. You can build, train, and debug any standard deep learning architecture from first principles.
Section 8 — NLP — goes deeper into language-specific techniques: tokenisation, embeddings, fine-tuning large pretrained models with HuggingFace, retrieval-augmented generation, and building production NLP pipelines. Everything builds on the Transformer architecture you just learned.
BPE, WordPiece, SentencePiece — how text becomes numbers. Word2Vec, GloVe, and contextual embeddings from BERT.
🎯 Key Takeaways
- ✓Self-attention processes every token in relation to every other token simultaneously — no sequential bottleneck. For each token it computes Q (what am I looking for?), K (what do I contain?), and V (what do I provide?). Attention(Q,K,V) = softmax(QKᵀ/√dₖ)V. The result is a weighted sum of values where weights reflect token relevance.
- ✓Scaling by √dₖ is essential. Without it, large d_k produces large dot products that push softmax into saturation — attention weights become one-hot and gradients vanish. Dividing by √dₖ keeps variance stable regardless of d_k.
- ✓Multi-head attention runs h attention heads in parallel, each with separate Wq, Wk, Wv projections. Each head specialises in a different relationship type — syntactic, semantic, positional. Outputs are concatenated and projected back to d_model. Total parameters are the same as one large head.
- ✓A Transformer encoder block: LayerNorm → Multi-head self-attention → residual → LayerNorm → Feed-forward (Linear→GELU→Linear) → residual. Residual connections allow gradients to flow through very deep stacks. Pre-LN (normalise before sub-layer) is more stable than the original Post-LN.
- ✓BERT (encoder-only): bidirectional attention, pretrained with masked language modelling, fine-tuned for understanding tasks. GPT (decoder-only): causal attention mask prevents attending to future tokens, pretrained with next-token prediction, used for generation. The causal mask is the only architectural difference.
- ✓In production never train a Transformer from scratch for NLP. Use HuggingFace pretrained models (DistilBERT, RoBERTa, LLaMA). Add a task-specific head, fine-tune with AdamW at lr=2e-5, warmup for 6% of steps. Self-attention memory scales as O(seq_len²) — use gradient checkpointing or Flash Attention for long sequences.
Discussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.