LayerNorm and RMSNorm
Section 5.1 left us with a problem. The residual stream is a running sum, and sums drift. After a few dozen layers a token's vector can be several times longer than it started, and different tokens can drift by different amounts. Every sublayer then reads its input at an unpredictable scale. That matters because the sublayers are scale-sensitive. Attention logits grow with the square of the input scale and push the softmax into saturation (the failure we analyzed in §4.3), and an activation function like GELU behaves like a linear map near zero and like a ReLU far from it.
The fix is to normalize what each sublayer reads. That sounds like a detail. It is not. Where you put the normalization decides whether a 48-layer Transformer trains at all, whether it needs a learning-rate warmup, and how its gradients are distributed over depth. This section derives LayerNorm and RMSNorm from scratch, forward and backward, then works through the argument for why modern models normalize before each sublayer rather than after.
Why not BatchNorm
You met BatchNorm in §0.7. It normalizes each feature using the mean and variance over the batch. For language models that is a poor fit, for three reasons.
It leaks across sequences and positions. Statistics over a batch of sequences mix tokens from different documents and, worse, from future positions of the same document. A causal language model is supposed to predict token from tokens up to , but a BatchNorm over all positions lets information from later tokens flow into earlier ones through the shared mean.
It behaves differently at test time. BatchNorm uses running averages at inference, so a model's output for a sequence depends on whether it is in training mode. Generation one token at a time would be normalized with statistics of batches it never sees.
Batches are small and ragged. Per-device batches in large-model training can be a handful of sequences, padded to different lengths, which makes batch statistics noisy and padding-dependent.
LayerNorm (Ba, Kiros & Hinton, 2016) sidesteps all three. It normalizes each token using statistics over that token's own features. No other token, no other sequence, and no running averages are involved, so training and inference compute exactly the same function.
LayerNorm normalizes each token by itself
Take one token's vector (a row of the stream ). LayerNorm computes its mean and variance over the features,
then standardizes and applies a learned per-feature gain and shift ,
Here is the all-ones vector, is the elementwise product, and (typically ) keeps the division safe for a constant vector. For a whole sequence, each row gets its own and , while and are shared by every token. A LayerNorm has parameters.
In plain words, shift the token's features to mean zero, scale them to unit variance, then let the model choose a new scale and offset for each feature.
A worked example. Let , so . The mean is . The deviations are , so and, ignoring , . Then . Notice two facts you can check by hand. The entries of sum to zero, and their squares sum to .
The geometry, a projection and then a sphere
Those two facts are the whole geometry of LayerNorm. Subtracting the mean is a linear projection,
which removes the component of along and lands in the -dimensional hyperplane of vectors whose entries sum to zero. Dividing by then rescales the result so that . So before the gain and shift, every token is mapped onto a sphere of radius inside that hyperplane. The gain stretches the sphere into an ellipsoid and the shift moves it.
In plain words, LayerNorm throws away exactly two numbers per token (the mean and the overall scale) and keeps the direction of what is left.
This explains its invariances. For any and any constant ,
A sublayer that adds the same constant to every coordinate of the stream, or rescales the whole stream, is invisible to the next LayerNorm. Play with this in the widget, where you can drag the bars of a 6-dimensional vector.
You wrote LayerNorm forward and backward, for any batch shape, in Exercise 0.7e of §0.7. Two details from that exercise matter in every implementation. The variance is the biased one, dividing by rather than (NumPy's default, while PyTorch's torch.var defaults to the unbiased one, a classic source of mismatches), and goes inside the square root. There we borrowed the backward formula from BatchNorm. Here we derive it from scratch, because its structure is what explains the pre-LN argument later in this section.
The backward pass, derived
Interviewers ask for this one constantly, because it tests whether you can differentiate through a function whose statistics depend on every input. Let for one token, and suppose the loss gradient arrives from above. The gain multiplies elementwise, so define , the gradient with respect to . We need where is the Jacobian. The result is
∂ The derivation, one partial derivative at a time
Start with the pieces. Since , we have .
For the variance, , so
because the deviations sum to zero. Then gives
Now differentiate with the quotient rule,
That is the Jacobian in matrix form. It is symmetric, so , and
which is the vector formula above. Note that never had to be dropped. The formula is exact with included, as long as and are the values used in the forward pass.
The parameter gradients are simpler. Since , summing over every token that shares the parameters gives and .
In plain words, the input gradient is the incoming gradient with two components removed, its average and its projection onto , all divided by the token's scale .
There is a cleaner way to see the whole formula. Let and . Both are unit vectors (ignoring , since ), and they are orthogonal, because the entries of sum to zero. The Jacobian is then
the orthogonal projection onto the -dimensional subspace perpendicular to both. In plain words, LayerNorm's backward pass projects the incoming gradient onto the directions LayerNorm does not throw away, then divides by the token's scale. The three properties below are this one statement, read three ways.
Each piece has a meaning worth saying out loud in an interview.
The gradient is orthogonal to . Summing the entries of gives , because . That is necessary. Moving along cannot change the output, so the loss cannot have a slope in that direction.
The gradient is orthogonal to , up to . The dot product with is , and is up to . Rescaling does not change the output either, so no gradient flows along it.
The gradient shrinks with the input's scale. Everything is divided by . A token whose stream has grown four times larger receives a gradient four times smaller through the norm. Keep this in mind, because it is the mechanism behind the pre-LN gradient argument below.
LayerNorm's Jacobian is a scaled projection
Write layer_norm_jacobian(x, eps=1e-5) for one token x of shape (d,), returning the (d, d) Jacobian of LayerNorm without gain and shift. Then write layer_norm_vjp(x, g, eps=1e-5) returning with the fast formula, without building the (d, d) matrix. The tests check the Jacobian against finite differences, check that is an orthogonal projection of rank when , and check that the two functions agree.
Here is a compact implementation we will reuse for the rest of the section, together with a check of the three properties.
RMSNorm drops the mean
Zhang & Sennrich (2019) asked which half of LayerNorm does the work, the re-centering or the re-scaling. Their hypothesis was that re-scaling is what matters, and they proposed RMSNorm, which skips the mean entirely,
In plain words, divide the token's vector by its root mean square, so that it lands on the sphere of radius , then apply a learned per-feature gain. There is no shift parameter, so RMSNorm has parameters.
The geometry is the sphere without the hyperplane. The mean direction survives, and RMSNorm is invariant to for but not to adding a constant. In the residual stream, is one direction among with no special meaning, so losing invariance to it costs little. In their experiments RMSNorm matched LayerNorm's quality while cutting running time by 7% to 64% depending on the model. It needs one reduction over the features instead of two, and no subtraction. T5, LLaMA, Mistral, Gemma, Qwen, and most recent open models use it.
The backward pass is the LayerNorm derivation with the mean terms deleted. With and ,
In plain words, remove from the incoming gradient its component along the normalized input and divide by the scale. No component along is removed, because RMSNorm does not ignore shifts.
RMSNorm forward and backward
Write rms_norm(x, gamma, eps=1e-6) and rms_norm_backward(dy, x, gamma, eps=1e-6) for inputs of shape (..., d). The backward returns (dx, dgamma), with dgamma of shape (d,).
Details that matter in real code
Compute the statistics in float32. bfloat16 carries only 8 bits of precision (§8.5), about two to three significant decimal digits per number. A running sum of 4,096 squares accumulated in it is far worse, because once the total is about 256 times larger than the next term, adding that term changes nothing and the sum stalls. LLaMA's reference RMSNorm upcasts to float32, normalizes, and casts back. Fused kernels do the same internally.
Know your parameterization. Gemma's RMSNorm multiplies by with initialized at zero, which is the same function with a different starting point for weight decay. OLMo's first release used a LayerNorm with no learned gain or shift at all. PaLM removed every bias from its norms and dense layers and reported better stability at scale.
Check . Values of and are both common, and putting it outside the square root () is a different function. When you port weights between frameworks, mismatched produces small but real differences in the logits.
Where does the norm go, pre-LN or post-LN
There are two ways to combine a normalization with a residual sublayer . The original Transformer (Vaswani et al., 2017) normalized after the residual add, which is called post-LN,
GPT-2 (Radford et al., 2019) moved the norm to the input of each sublayer and added one final norm before the unembedding, which is called pre-LN,
Here is the stream after all sublayers and is the unembedding. The difference looks cosmetic. It is not. In post-LN the residual stream itself is normalized at every layer, so the "identity path" of §5.1 passes through a LayerNorm Jacobian at every step. In pre-LN the identity path is clean all the way from the loss to the embeddings, and only the branches see normalized inputs.
The gradient-scale argument
Xiong et al. (2020) analyzed both architectures at initialization and found the answer. Here is the argument in a form you can reproduce at a whiteboard, using two facts we already have. Fact one, from the derivation above, is that the norm's Jacobian scales like (it divides by ). Fact two, from §5.1, is how the stream's norm grows.
Pre-LN. Each branch reads a normalized input, so each write has roughly the same expected size, a fixed multiple of in squared norm. The writes are close to uncorrelated at initialization, so their squared norms add,
linear growth in the layer index for some constant (Xiong et al. prove bounds of exactly this form, between and in their setup). The final norm divides by , so the gradient that enters the stream from the loss is scaled down by about . Every sublayer receives that gradient through the clean identity path. A branch's output weight gradient is its (order one, because its input was normalized) activation times that stream gradient, so every layer's weight gradient is of order .
Post-LN. The stream is renormalized after every sublayer, so its norm stays near forever. The last sublayer's gradient therefore does not shrink with depth at all. Going down one layer, the gradient passes through with . Since is the sum of two vectors of comparable size, is larger than and the Jacobian shrinks the gradient by a constant factor on every layer. The result is gradients that grow toward the output, with the top layers getting the largest ones.
Xiong et al. state the conclusion as a theorem about the last FFN layer's second weight matrix in a model with layers. With high probability at initialization,
In plain words, post-LN hands its top layers large gradients no matter how deep the model is, while pre-LN's gradients shrink like and are spread evenly across layers.
Why should you care? Large gradients at the top combined with a large learning rate make the early updates of the top layers too aggressive, and warmup is what keeps them in check. The argument is cleanest for SGD, where the step is proportional to the gradient. With Adam, which normalizes each coordinate's step (§5.7), the story also involves Adam's unreliable second-moment estimates in the first steps (Liu et al., 2019), but the conclusion is the same. Xiong et al. showed the effect directly. On IWSLT14 German to English, their post-LN Transformer trained with Adam reached a BLEU score of about 34 with warmup and only 8.45 without it, while a pre-LN Transformer trained without warmup reached comparable results in less training time. Warmup was a patch for post-LN's gradient profile.
Let's measure it. The cell below stacks residual MLP sublayers in both arrangements, backpropagates a random loss, and reports the gradient norm of each layer's output matrix.
The numbers tell the story. In post-LN the last layer's gradient stays near 30 whether the model has 8 or 128 layers, while the first layer's falls from about 14 to about 2, a fifteen-fold imbalance at 128 layers. In pre-LN the last layer's gradient falls from about 14 to about 4 as the model grows sixteen times deeper (close to the of the theorem), and the spread across layers is much smaller. The widget runs a smaller version of the same simulation live, averaged over four random initializations.
Pre-LN is not free
If pre-LN were strictly better, this would be a short section. It has two known costs.
Later layers matter less. The stream's norm grows like , and each pre-LN write has a fixed size, so a write at layer 40 is a much smaller fraction of the stream than a write at layer 4. Deep layers can only nudge the representation. Sun et al. (2025) connect this to the observation, which we met in §5.1, that many deep layers of open LLMs can be pruned, and propose scaling the output of each layer's norm by to compensate.
Post-LN can end up better, if it trains. Liu et al. (2020) argued that post-LN's instability comes less from gradient imbalance than from an amplification effect, where heavy reliance on the residual branches makes small parameter updates cause large output changes. They also found that the lighter reliance on the branches that pre-LN enforces limits the quality a model can reach, and proposed an initialization (Admin) that keeps post-LN stable early in training while leaving its full capacity for later. DeepNet (Wang et al., 2022) took the same direction to 1,000 layers by scaling the residual before a post-LN, a scheme we return to in §5.7.
Where modern models put their norms
| model | year | norm | placement |
|---|---|---|---|
| Transformer (Vaswani et al.) | 2017 | LayerNorm | post-LN |
| BERT | 2018 | LayerNorm | post-LN |
| GPT-2, GPT-3 | 2019, 2020 | LayerNorm | pre-LN plus a final LN |
| T5 | 2019 | RMS scaling, no bias | pre-norm plus a final norm |
| PaLM | 2022 | LayerNorm, no bias | pre-LN, attention and MLP in parallel from one norm |
| LLaMA 1 to 3 | 2023, 2024 | RMSNorm | pre-norm plus a final norm |
| Gemma 2 | 2024 | RMSNorm | before and after each sublayer |
| OLMo 2 | 2024 | RMSNorm | on each sublayer's output, , plus QK-norm |
Two newer patterns deserve a sentence each. The parallel block of GPT-J and PaLM computes , so the two input projections can be fused into one matrix multiplication. PaLM reports about 15% faster training at large scale, with a small quality loss at 8B parameters and none at 62B. Normalizing sublayer outputs (Gemma 2's post-norms, OLMo 2's reordered norm) bounds what each sublayer can write while keeping the identity path clean, which addresses the amplification concern while keeping pre-LN's gradient profile.
One residual sublayer, three placements
Write residual_sublayer(x, F, norm, mode) where x has shape (..., d), F is the sublayer function, and norm is a normalization function (both map (..., d) arrays to the same shape). mode is one of "pre" (), "post" (), or "output" (, the OLMo 2 ordering). Raise ValueError for anything else.
In PyTorch
PyTorch ships both norms. nn.RMSNorm arrived in version 2.4. Here we check them against our NumPy versions and let autograd confirm our backward formula.
| BatchNorm | LayerNorm | RMSNorm | |
|---|---|---|---|
| statistics over | the batch, per feature | one token's features | one token's features |
| removes | mean and scale per feature | the token's mean and scale | the token's scale only |
| train and test identical | no (running averages) | yes | yes |
| parameters | plus running stats | ||
| reductions per token | not applicable | two (mean, variance) | one (mean of squares) |
| typical use | CNNs | BERT, GPT-2, GPT-3 | T5, LLaMA, Mistral, Gemma |
Mean and variance in one stable pass
Fused LayerNorm kernels compute the statistics in a single pass over the features. The naive one-pass formula suffers catastrophic cancellation when . Implement Welford's algorithm, welford_mean_var(x), for a 1-D array, processing one element at a time and returning (mean, var) with the biased variance. The tests check accuracy on data with a large offset, where the naive formula fails. This is the reduction that GPU kernels parallelize (§11.4).
Read more
- Ba, Kiros & Hinton (2016). Layer Normalization. The original, motivated by recurrent networks.
- Zhang & Sennrich (2019). Root Mean Square Layer Normalization. RMSNorm, its invariances, and the speed results.
- Xiong et al. (2020). On Layer Normalization in the Transformer Architecture. The gradient-scale theorem and training pre-LN without warmup.
- Liu et al. (2020). Understanding the Difficulty of Training Transformers. The amplification effect and the Admin initialization.
- Sun et al. (2025). The Curse of Depth in Large Language Models. Why deep pre-LN layers are underused, and LayerNorm scaling.
- OLMo Team (2024). 2 OLMo 2 Furious. A careful modern account of norm placement, QK-norm, and stability, with ablations.
- Ioffe & Szegedy (2015). Batch Normalization. The predecessor, to see what LayerNorm changed.