Skip to the lesson
FixTheRobot Explore Robotics / LLM / LM4 Build a GPT Model from Scratch / LM4-T2

LLM · Theory · LM4

LM4-T2 — Layer Normalization and Residual Connections

Two cheap devices let a model be dozens of layers deep: rescale the activations, and let the input skip past each layer.

  • Lesson 2 of 5
  • Module 4: Build a GPT Model from Scratch
  • Theory
  • Difficulty: Intermediate
  • About 8 minutes to read
  • Prerequisite: LM4-T1

Stacking layers is easy; training a stack of dozens is not. Each layer rescales its input a little, and the effect compounds: after many layers signals vanish or explode, and so do the gradients. Transformers rely on two inexpensive devices to prevent this. Layer normalisation rescales each token’s vector to a fixed scale. Residual connections add the input of a sublayer to its output, so that information and gradients have a direct path through the whole stack. This lesson gives the formulas, works through numbers, compares the two placements of the norm and ties them to Lab LM4-L2.

Learning objectives

After this lesson, you should be able to:

  • Compute layer normalisation by hand, including scale, shift and the eps constant.
  • Explain how layer norm differs from batch norm and why Transformers use it.
  • Show with a derivative why a residual connection gives gradients a direct path.
  • Distinguish pre-LN from post-LN blocks and say which GPT-2 uses.

The idea: keep the scale steady, keep a shortcut open

Layer normalisation. Take the vector of d numbers for one token. Subtract its mean so that it is centred, divide by its standard deviation so that its typical size is 1. Then let the model undo some of this if it wants, with a learned scale and shift per feature. The next layer therefore sees inputs of a predictable size, however large the previous layers made them. Crucially each token is normalised on its own, using only its own d numbers.

Residual connections. Instead of computing y = f(x), compute y = x + f(x). The layer only has to learn a correction to its input. If a layer has nothing useful to add, it can learn f ≈ 0 and pass the input through unchanged. A block that starts out near the identity is easy to stack.

A residual connection and where the layer norm sitsTwo panels. Left, a pre-LN block: the input x goes both through a layer norm and a sublayer and is added back to its original self by a skip arrow. Right, a post-LN block: the input is added to the sublayer output first and the sum is normalised afterwards.Pre-LN (GPT-2)Post-LN (original Transformer) x LayerNorm sublayer f x + f(LN x) ↑→→skip path: no norm, no weights x sublayer f LayerNorm(x + f(x)) →↑→the norm sits on the main path
Where the norm goes. In the pre-LN design the skip path carries the raw input all the way up; in post-LN every skip passes through a norm.

Why they help: a signal view

Lab LM4-L2 stacks twelve random layers. Without help, the typical size of the activations is multiplied by a constant at every layer: a factor of 0.7 per layer leaves 0.014 of the signal after 12 layers, a factor 1.3 leaves 23 times too much. Layer norm removes the exponential drift. Residual connections keep the input visible in every later layer. Together they give a stack that can be made deep without the initial scale being tuned.

Why they help: a gradient view

For y = x + f(x) the derivative of the output with respect to the input is the identity matrix plus the derivative of f. When many layers are stacked, the gradient reaching an early layer is a sum of paths, and one of them is the pure identity path that crosses no layer at all. That path cannot vanish, so early layers always receive a learning signal.

Layer norm versus batch norm

Batch normalisation, common in image networks, computes statistics across the examples of a batch. For text that is awkward: sequence lengths vary, padding pollutes statistics, and generation runs with a batch of one. Layer norm computes statistics across the features of one token, so its result does not depend on the other examples or on the batch size, and it behaves identically in training and in generation.

The mathematics: normalise, scale, shift, add back

For one token with feature vector x = (x1, …, xd):

layer normalisation
μ=(1d) Σ xi\mu =(\frac{1}{d})\,\Sigma \,x_{i}
σ2=(1d) ∑(xi−μ)2\sigma ^{2}=(\frac{1}{d})\,\sum (x_{i}-\mu )^{2}
yi=γi⋅(xi−μ)σ2+ε+βiy_{i}=\frac{\gamma _{i}\cdot (x_{i}-\mu )}{\sqrt{\sigma ^{2}+\varepsilon }}+\beta _{i}
μ, σ²
mean and biased variance of the d entries of this one vector (divide by d, not d − 1).
ε
a small constant, typically 10⁻⁵, that prevents division by zero when all entries are equal.
γ, β
learned scale and shift, one pair per feature (2d parameters in total); initialised to 1 and 0.
y
the output, same shape as x. Before γ and β it has mean 0 and variance about 1.

For a tensor of shape (B, n, d) the statistics are taken over the last axis only, giving B · n separate normalisations.

Residual connection. For a sublayer f:

residual connection and its Jacobian
y=x+f(x)y=x+f(x)
∂y∂x=I+∂f∂x\frac{\partial y}{\partial x}=I+\frac{\partial f}{\partial x}

where I is the identity matrix. For N stacked residual blocks the end-to-end Jacobian is (I + JN) ⋯ (I + J1), which expands into the identity plus products of the individual Jacobians; the identity term is always there.

Two placements. With f an attention or feed-forward sublayer:

pre-LN
y=x+f(LN⁡(x))y=x+f(\operatorname{LN}(x))
post-LN
y=LN⁡(x+f(x))y=\operatorname{LN}(x+f(x))

GPT-2 and nearly all later GPT-style models use pre-LN, plus one more LN at the very end of the stack. In pre-LN the residual stream is never normalised inside the stack, so its scale grows with depth (roughly like the square root of the number of blocks at initialisation); the final LN restores a fixed scale before the output head. In post-LN the norm sits on the main path; very deep post-LN models are harder to start training and usually need a warm-up of the learning rate.

Parameter count. One layer norm has 2d parameters. A block has two (4d), and the model has one more at the end: for GPT-2 small, 12 · 3,072 + 1,536 = 38,400 parameters, 0.03% of the model.

A worked example: normalising four numbers

Let x = [2, 4, 6, 8], d = 4, ε = 10⁻⁵.

Mean: μ = (2 + 4 + 6 + 8)/4 = 5. Deviations: [−3, −1, 1, 3]. Biased variance: σ² = (9 + 1 + 1 + 9)/4 = 5. The standard deviation including ε is √5.00001 ≈ 2.23607. Normalised: [−3, −1, 1, 3]/2.23607 = [−1.3416, −0.4472, 0.4472, 1.3416]. Check: the mean is 0, and the average of the squares is (1.8 + 0.2 + 0.2 + 1.8)/4 = 1.

With learned γ = [1, 1, 2, 2] and β = [0, 0, 1, 1]: y = [−1.3416, −0.4472, 2·0.4472 + 1, 2·1.3416 + 1] = [−1.3416, −0.4472, 1.8944, 3.6833]. The model is free to give some features a larger scale or an offset.

Using the unbiased variance (divide by 3) would give σ² = 6.667 and a slightly different answer, [−1.162, −0.387, 0.387, 1.162]. This is the most common reimplementation error: PyTorch’s LayerNorm and GPT-2 use the biased version.

A residual block with numbers. If the input is x = [1, −1] and the sublayer outputs f(x) = [0.1, 0.2], then y = [1.1, −0.8]: the block nudges the input by a small amount instead of replacing it.

import torch
import torch.nn as nn
import torch.nn.functional as F

x = torch.tensor([[2.0, 4.0, 6.0, 8.0]])
mean = x.mean(-1, keepdim=True)
var = x.var(-1, keepdim=True, unbiased=False)                 # biased variance, as in GPT-2
y = (x - mean) / torch.sqrt(var + 1e-5)
print([round(v, 4) for v in y[0].tolist()])                   # [-1.3416, -0.4472, 0.4472, 1.3416]
print(torch.allclose(y, F.layer_norm(x, (4,), eps=1e-5)))     # True: same as PyTorch

# a residual block whose sublayer outputs zero returns its input exactly
lin = nn.Linear(4, 4)
nn.init.zeros_(lin.weight); nn.init.zeros_(lin.bias)
block_out = x + lin(F.layer_norm(x, (4,)))                    # pre-LN: x + f(LN(x))
print(torch.equal(block_out, x))                              # True

# gradient through a skip connection: d(x + f(x))/dx contains the identity
w = torch.tensor([[0.5, 0.0], [0.0, 0.5]])
v = torch.tensor([1.0, -1.0], requires_grad=True)
out = v + w @ v                                               # f(v) = W v
out.sum().backward()
print(v.grad.tolist())                                        # [1.5, 1.5] = 1 (skip) + 0.5 (through W)

The last lines show the Jacobian argument in miniature: the gradient is 1.5 = 1 (the skip path) + 0.5 (through W). If W were tiny, the gradient would still be about 1.

Engineering implications and limitations

Epsilon placement. The ε goes inside the square root, next to the variance. Putting it outside, or omitting it, changes results slightly and can divide by zero for constant vectors.

Normalise over the last axis. A frequent bug is dim=0 or dim=1. The check y.mean(-1) ≈ 0 and y.var(-1, unbiased=False) ≈ 1 catches it.

Use the built-in in production. nn.LayerNorm(d) is faster and numerically careful (it accumulates in higher precision when training in half precision). Writing your own is for learning, as in the notebook.

The final norm matters. In pre-LN models the residual stream has grown by the end of the stack; leaving out the final norm makes logits badly scaled.

Dropout on the branch. Dropout is applied to the sublayer output before it is added to the stream, not to the stream itself, so the skip path remains intact. In large pretraining runs dropout is often set to zero.

Zero-initialised branches. Setting the last projection of each sublayer to zero (or small) makes every block start as an identity, which makes the start of training stable; some recipes do this deliberately.

Variants. Newer models use RMSNorm, which drops the mean subtraction and the shift, saving a little compute (Module 10). The pre-LN structure stays the same.

Common misconceptions

“Layer norm normalises over the batch.”

It normalises over the features of one token; the other tokens and examples do not matter.

“The output of layer norm always has variance exactly 1.”

Only before the learned scale and shift, and slightly less than 1 because of ε.

“Residual connections add parameters.”

They are additions with no parameters.

“Post-LN and pre-LN are the same computation.”

They differ in what the skip path carries and in how the signal scale grows with depth; they train differently.

“The variance in layer norm is the sample variance (n − 1).”

It is the biased variance (divide by d).

Engineering takeaways

  1. Layer norm: y = γ (x − μ)/√(σ² + ε) + β, with μ and σ² over the d features of one token and σ² biased.
  2. Residual connections compute y = x + f(x), so ∂y/∂x = I + ∂f/∂x and gradients always have an identity path.
  3. Pre-LN, y = x + f(LN(x)), is used by GPT-2; post-LN, y = LN(x + f(x)), was the original design.
  4. The residual stream grows with depth in pre-LN, so a final layer norm precedes the output head.
  5. A layer norm has 2d parameters; the whole GPT-2 small has only 38,400 of them.

References and further reading

See Chapter 4 of the book (normalizing activations with layer normalization and adding shortcut connections) for the implementation used in this track.

Knowledge check

Three conceptual questions. Write an answer, then reveal the explanation. Your answers stay in your browser.

Question 1

Normalise x = [1, 3] with ε = 0 and γ = 1, β = 0.

Explanation

The mean is 2 and the biased variance is ((−1)² + 1²)/2 = 1, so y = [−1, 1].

Question 2

How many parameters do the layer norms of a model with 24 blocks and d = 1,024 have in total, with two per block plus a final one?

Explanation

Each has 2d = 2,048 parameters. There are 24 × 2 + 1 = 49 of them: 49 × 2,048 = 100,352 parameters.

Question 3

A sublayer has a very small Jacobian (about 0.01). What is the Jacobian of the residual output with respect to its input, and why is that helpful in a deep stack?

Explanation

About I + 0.01·(something small), close to the identity. The gradient therefore passes through the block almost unchanged instead of shrinking by a factor of about 100 per block, so early layers still receive a learning signal.

What’s next

Next: the feed-forward layer and its GELU nonlinearity, which hold most of a block’s parameters.

Practice this lesson

Layer Normalization and Residual Numerics

Implement layer norm and a residual update and see why activations stay stable.

Next lesson

LM4-T3 — Feed-Forward Layers and GELU

Explain the position-wise feed-forward network, its expansion factor and its nonlinearity.

Nothing below is required to finish this lesson.