Module 07 · Part II · The Transformer
The Block and the Full Model
Positional Encodings, the Residual Stream, LayerNorm, the Feed-Forward Network, the Output Layer, and One Token Traced Through the Whole Model
By the end you can trace one token through a full transformer and name every tensor's shape along the way.
Check yourself ▸
Before you start, can you already…
- ☐compute attention by hand, prove why scores are divided by √d_k, and explain what multiple heads add. (Module 6)
If any is shaky, the linked module teaches it.
Attention is one part of a transformer. This module supplies the rest: position information so word order counts, residual connections and normalization so deep stacks train, the feed-forward network, and the output layer. Section 7.6 assembles them and follows one input through the whole model.
7.1 Positional EncodingsRun this section
The 2017 paper gives each position its own vector and adds it to the token's embedding before the first layer:
Both have width . The paper builds from waves, in pairs of features: pair uses feature (sine) and (cosine) at the same frequency:
runs from to and from to . Pair has frequency and so repeats every positions, its wavelength: about for pair 0, growing to almost for the last.
In words. Short waves separate nearby positions; long waves separate distant ones. Read together, like the hands of a clock, they give every position a distinct vector, with nearby positions similar.
The Linear Shift Property
Sines and cosines make moving positions forward the same operation at every starting position: one matrix per pair, from the angle-addition formulas:
For fixed , and are constants, so each shifted value is a fixed mix of the unshifted pair:
The matrix rotates each pair by the angle . It contains but not , so the same matrix works at every position. (Its minus sign sits bottom-left, unlike section 15.1's rotation, only because each pair is written sine first.)
Each pair of features is a hand turning at its own speed. Move the position and every hand turns; the gold arc from pos to pos + k stays the same size wherever you start.
Pick two positions far apart, say 3 and 40, and then 103 and 140. Before looking at the hands, predict whether the turn from the first position to the second is the same in both pairs.
In words. The encoding as a heatmap and as six clock hands, one per frequency pair. Moving k positions forward turns every hand by an angle depending only on k and the hand's speed, wherever you start.
The hope was that attention could then learn relative patterns such as "the word 3 places back" once, for every position. Nothing forces the model to use the structure. Section 15.1 covers RoPE (rotary position embedding), which builds the rotation into the query–key product itself.
Measured (lab 2a,
experiments/ab_positional.py). The property holds. Fitting the best linear map from to over all positions (by least squares) leaves an error of for and . That is the rounding floor of float32 arithmetic, not a flaw in the encoding. The fitted matrix matches the rotation matrix in this section to within .
Which Encoding the Reference Implementation Uses
The reference implementation uses learned absolute positional embeddings, as GPT-1 and GPT-2 did: a table with one trainable vector per position, up to the context length . This section derives the sinusoids because RoPE builds on their maths.
# src/transformer/positional_embedding.py
self.embedding = nn.Embedding(
num_embeddings=context_length,
embedding_dim=d_model,
)The choice shows up in three places:
| learned absolute | sinusoidal | |
|---|---|---|
| parameters at , | (a table) | |
| position beyond when the trained model is used | no vector exists | computed by the formula |
| with a KV cache (section 12.2: stored keys and values reused while generating) | the code must pass the true position, and it cannot exceed | the code must pass the true position, and any position works |
Measured (lab 2a). Over 600 training steps with the same seed and the same batches, the sinusoidal version reaches validation loss (the error on text held out from training; lower is better) and the learned table reaches . The sinusoidal version is better by and has 4,096 fewer parameters. Read this with care: it is two layers, 600 steps and one seed. It does not show that sinusoids beat learned tables in general, and both are used in production. It shows that at this scale the choice does not decide the result.
7.2 Residual Connections and the Residual StreamRun this section
A block has two sub-layers, attention then the feed-forward network (section 7.4), and each one's output is added to its input: , which the 2017 paper then normalizes (section 7.3). The is a (He et al., 2016). It does two jobs:
- Each sub-layer only learns a change. With nothing useful to add, it outputs near zero and the vector passes through untouched, so a deep stack starts out harmless rather than scrambling its input.
- The gradient gets a path with no multiplications. The derivative of is ; the identity passes the gradient straight through, curing the long-chain problem of section 2.6. Section 8.5 makes this exact for a whole stack.
Picture the running vector as a shared channel up through the model, which every sub-layer reads from and writes into: the residual stream. Section 14.1 shows how much this picture explains.
7.3 LayerNormRun this section
Adding to the stream again and again lets the numbers drift: growing, or lopsided with a few features dominating. LayerNorm (Ba et al., 2016) rescales each token's vector to mean 0 and variance 1, then applies a learned scale and shift , keeping values in a steady range so softmax stays out of saturation and gradients stay healthy. Normalizing after the add, as in 2017, is Post-LN; section 8.5 compares it with Pre-LN.
is a tiny constant that keeps the denominator away from zero. The mean and variance are over the features of one token, not across tokens or the batch.
Worked example. Take , with and . The mean is and the variance is , so the standard deviation is . The output is : mean 0, variance 1. Doubling to gives exactly the same output, because LayerNorm discards the overall scale.
One token’s eight features, before and after each stage of LayerNorm. Scale or shift the whole input and the normalized bars do not move; change one feature and they do.
Multiply the input vector by 10 using the scale slider. Before you look, predict what happens to LayerNorm's output, and what that implies about information carried in a vector's overall size.
In words. An eight-feature vector as bars: as input, after subtracting the mean, after dividing by the standard deviation, and after the learned scale and shift. Scaling or shifting the whole input leaves the normalized bars still; changing one feature moves them.
7.4 The Feed-Forward NetworkRun this section
Every attention sub-layer is followed by a feed-forward network (FFN): a one-hidden-layer MLP (section 4.2) applied to each token separately, with the same weights at every position.
Why? Attention moves information between tokens but barely changes it. Token 's output is a weighted average of values: the weights depend nonlinearly on the input through softmax, but the features themselves pass only through linear maps ( and ). Attention cannot compute something like "switch this feature on only if these two others are both large". The FFN supplies that per-token nonlinearity:
widens the vector fourfold (the paper uses for ), ReLU zeroes the negative entries, and brings it back to model width. GPT-2 and the reference implementation use GELU, a smooth ReLU that bends gently near zero instead of at a corner:
# src/transformer/feed_forward.py — the shipped activation
self.net = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(),
nn.Linear(d_ff, d_model),
)Section 14.2 reads the FFN another way, as a store of memorized associations.
7.5 The Output LayerRun this section
After the last block, each token's numbers must become one score per vocabulary entry. The output layer (unembedding, or language-model head) does this with one more matrix:
Logit is the final vector dotted with column , large when they point the same way. Softmax turns the logits into probabilities, and training applies cross-entropy to them.
In a small model this is usually the largest matrix: 38.6 million numbers at and . In training it runs at every position, since each predicts its own next token (section 8.4); in generation only the last position's scores are used.
Tied vs. Untied Embeddings
The input embedding and the output layer have transposed shapes. The original Transformer and GPT-2 tied them (), saving a whole block of parameters.
LLaMA and the larger Qwen models untie them, because the jobs differ: the embedding should place similar tokens close together, while the output layer is a classifier that must separate near-identical tokens such as "run", "runs" and "running". The extra parameters matter less in a large model, where the embedding is a small share. Small models often still tie; Gemma ties at every size because its 256,000-token vocabulary makes the table unusually large.
7.6 The Complete ModelRun this section
The whole 2017 model (Post-LN) for tokens, with each step's output shape. Module 15 modernizes it piece by piece.
- Token ids: integers.
- Embedding lookup (section 1.5): .
- Add position vectors (section 7.1): .
- Repeat for each of blocks:
- Multi-head attention (Module 6), whose heads each produce an table of weights; their joined outputs go through back to .
- Add to the input and normalize (sections 7.2 and 7.3): .
- Feed-forward network (section 7.4), widening each token to and back: .
- Add and normalize again: .
- Output layer (section 7.5): logits, then softmax.
From step 2 through step 4 every tensor is : the token vectors are refined in place. Only the first and last steps change the shape.
Counting the Parameters
With and :
- Attention: four matrices (, , , , with the heads together), so .
- Feed-forward: and , so .
- Norms and biases: a few times , negligible.
So each block holds about , and the model about
where the last term is the embedding table (shared with the output layer if they are tied). For GPT-2 Small, and : million in the blocks plus million in the embedding, about 124 million in total, which is its published size.
Follow One Input
The model from Module 0's loop, opened up stage by stage, with every stage explained.
Pick a stage to see the tensor it produces. Every number is computed in your browser by a two-block model trained on Shakespeare, so the final stage makes real guesses.
Open Block 1 · attention weights and look at the first row of each head. Before you do, predict what it must contain, whatever the weights of the model are.
In words. The two-block model trained on Shakespeare from Module 0 lists every stage: token ids, embedding, added positions, each block's attention weights and outputs, and the final next-character probabilities. Choosing a stage shows its tensor as a heatmap with its shape, the operation that produced it and the section that teaches it.
Exercises
Which steps of the full model change the shape of the tensor, and which keep it at ?
Apply LayerNorm (with , and negligible) to . What is the last entry of the output?
Using , how many parameters are in the blocks of a model with and , in millions?
Show that the shift matrix for the sinusoidal encoding does not depend on , by writing with the angle-addition formula.
Without the residual connection, what would the derivative of a sub-layer's output with respect to its input be, and why does the in matter for a 48-layer stack?
Core Academic References
- Vaswani et al. (2017): Attention Is All You Need. NeurIPS 2017. arXiv:1706.03762
- He et al. (2016): Deep Residual Learning for Image Recognition. CVPR 2016: 770–778.
- Ba, Kiros & Hinton (2016): Layer Normalization. arXiv:1607.06450
- Hendrycks & Gimpel (2016): Gaussian Error Linear Units (GELUs). arXiv:1606.08415
- Radford et al., OpenAI (2019): Language Models are Unsupervised Multitask Learners (GPT-2). Technical Report.
Measured labs
These labs test what this module says by running real code. Each card states the idea, what the experiment found, and what it does not prove. The numbers come from experiments/results.json, the output of running experiments/run_all.sh on the tiny-transformers reference code at version 137f7ef. The project checks fail if a measured number in the module text stops matching that file.
Lab 2a — sine-wave positions against a learned position table
What the module says
Section 7.1 builds position encodings from sine and cosine waves, . It shows that moving forward by a fixed number of positions is a rotation: for each sine/cosine pair, one 2×2 matrix turns into , whatever is.
What the experiment found
The rotation property holds. Predicting from with the best-fit matrix leaves an error of at most 2.9e-06, which is the rounding limit of 32-bit floats. The fitted matrix matches the formula from the module to within 6e-08. However, the repository's positional_embedding.py does not use sine waves: it is a learned table (nn.Embedding). Both approaches are valid, and both are used in real models. The module is right to teach the sine waves, but you should not assume the code uses them.
- rotation-fit error for
- 2.9e-06, 2.8e-06, 2.0e-06
- formula vs fitted matrix,
- 0.00000006
- learned table parameters
- 4,096
- sine-wave parameters
- 0
- validation loss, learned vs sine waves
- 2.0978 vs 1.9442
float32 rounding, not a flaw in the encoding
largest difference between any two entries
nothing to learn: the waves are fixed
lower is better; 600 steps, same seed and same batches
What this does not prove
The sine-wave model ends 0.154 lower in validation loss, with 4,096 fewer parameters. That is one short run (600 steps, two layers), so it does not show that sine waves beat learned tables in general. The point of this lab is the derivation, and which of the two the code actually uses.
Run it yourself
cd tiny-transformers && .venv/bin/python experiments/ab_positional.pyThe code being tested