Chapter 5 · Section 5.2 · 50 min

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 t+1t+1 from tokens up to tt, 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 x∈Rd\mathbf{x} \in \R^d (a row of the stream X∈RT×d\mathbf{X} \in \R^{T\times d}). LayerNorm computes its mean and variance over the dd features,

μ=1d∑j=1dxj,σ2=1d∑j=1d(xj−μ)2,s=σ2+ϵ,\mu = \frac{1}{d}\sum_{j=1}^{d} x_j, \qquad \sigma^2 = \frac{1}{d}\sum_{j=1}^{d} (x_j - \mu)^2, \qquad s = \sqrt{\sigma^2 + \epsilon},

then standardizes and applies a learned per-feature gain γ∈Rd\boldsymbol{\gamma} \in \R^d and shift β∈Rd\boldsymbol{\beta} \in \R^d,

x^=x−μ1s,LN⁡(x)=γ⊙x^+β.\hat{\mathbf{x}} = \frac{\mathbf{x} - \mu\mathbf{1}}{s}, \qquad \operatorname{LN}(\mathbf{x}) = \boldsymbol{\gamma} \odot \hat{\mathbf{x}} + \boldsymbol{\beta}.

Here 1\mathbf{1} is the all-ones vector, ⊙\odot is the elementwise product, and ϵ\epsilon (typically 10−510^{-5}) keeps the division safe for a constant vector. For a whole sequence, each row gets its own μ\mu and ss, while γ\boldsymbol{\gamma} and β\boldsymbol{\beta} are shared by every token. A LayerNorm has 2d2d 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 x=(2,4,6,8)\mathbf{x} = (2, 4, 6, 8), so d=4d = 4. The mean is μ=5\mu = 5. The deviations are (−3,−1,1,3)(-3, -1, 1, 3), so σ2=(9+1+1+9)/4=5\sigma^2 = (9 + 1 + 1 + 9)/4 = 5 and, ignoring ϵ\epsilon, s=5≈2.236s = \sqrt 5 \approx 2.236. Then x^≈(−1.342,−0.447,0.447,1.342)\hat{\mathbf{x}} \approx (-1.342, -0.447, 0.447, 1.342). Notice two facts you can check by hand. The entries of x^\hat{\mathbf{x}} sum to zero, and their squares sum to 4=d4 = d.

The geometry, a projection and then a sphere

Those two facts are the whole geometry of LayerNorm. Subtracting the mean is a linear projection,

x−μ1=(I−1d11⊤)x,\mathbf{x} - \mu\mathbf{1} = \Big(\mathbf{I} - \tfrac{1}{d}\mathbf{1}\mathbf{1}^\top\Big)\mathbf{x},

which removes the component of x\mathbf{x} along 1\mathbf{1} and lands in the (d−1)(d-1)-dimensional hyperplane of vectors whose entries sum to zero. Dividing by ss then rescales the result so that ∥x^∥2=d σ2/(σ2+ϵ)≈d\lVert\hat{\mathbf{x}}\rVert^2 = d\,\sigma^2/(\sigma^2 + \epsilon) \approx d. So before the gain and shift, every token is mapped onto a sphere of radius d\sqrt d 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 a>0a > 0 and any constant cc,

LN⁡(a x+c 1)=LN⁡(x)(ignoring ϵ).\operatorname{LN}(a\,\mathbf{x} + c\,\mathbf{1}) = \operatorname{LN}(\mathbf{x}) \quad (\text{ignoring } \epsilon).

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.

What do LayerNorm and RMSNorm throw away?
Drag the bars of the 6-dimensional input. Then rescale it or add a constant to every coordinate, and compare the two normalized outputs (gain 1, shift 0).
Loading interactive figure…

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 dd rather than d−1d - 1 (NumPy's default, while PyTorch's torch.var defaults to the unbiased one, a classic source of mismatches), and ϵ\epsilon 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 y=LN⁡(x)\mathbf{y} = \operatorname{LN}(\mathbf{x}) for one token, and suppose the loss gradient ∂L/∂y\partial\mathcal{L}/\partial\mathbf{y} arrives from above. The gain multiplies elementwise, so define g=γ⊙∂L/∂y\mathbf{g} = \boldsymbol{\gamma} \odot \partial\mathcal{L}/\partial\mathbf{y}, the gradient with respect to x^\hat{\mathbf{x}}. We need ∂L/∂x=J⊤g\partial\mathcal{L}/\partial\mathbf{x} = \mathbf{J}^\top\mathbf{g} where J=∂x^/∂x\mathbf{J} = \partial\hat{\mathbf{x}}/\partial\mathbf{x} is the Jacobian. The result is

∂x^∂x=1s(I−1d11⊤−1dx^x^⊤),∂L∂x=1s(g−mean⁡(g) 1−mean⁡(g⊙x^) x^).\frac{\partial \hat{\mathbf{x}}}{\partial \mathbf{x}} = \frac{1}{s}\Big(\mathbf{I} - \tfrac{1}{d}\mathbf{1}\mathbf{1}^\top - \tfrac{1}{d}\hat{\mathbf{x}}\hat{\mathbf{x}}^\top\Big), \qquad \frac{\partial \mathcal{L}}{\partial \mathbf{x}} = \frac{1}{s}\Big(\mathbf{g} - \operatorname{mean}(\mathbf{g})\,\mathbf{1} - \operatorname{mean}(\mathbf{g}\odot\hat{\mathbf{x}})\,\hat{\mathbf{x}}\Big).
∂ The derivation, one partial derivative at a time

Start with the pieces. Since μ=1d∑kxk\mu = \frac1d\sum_k x_k, we have ∂μ/∂xj=1/d\partial\mu/\partial x_j = 1/d.

For the variance, σ2=1d∑k(xk−μ)2\sigma^2 = \frac1d\sum_k (x_k - \mu)^2, so

∂σ2∂xj=2d∑k(xk−μ)(δkj−1d)=2d(xj−μ)−2d2∑k(xk−μ)=2d(xj−μ),\frac{\partial\sigma^2}{\partial x_j} = \frac{2}{d}\sum_k (x_k - \mu)\Big(\delta_{kj} - \frac1d\Big) = \frac{2}{d}(x_j - \mu) - \frac{2}{d^2}\sum_k (x_k - \mu) = \frac{2}{d}(x_j - \mu),

because the deviations sum to zero. Then s=σ2+ϵs = \sqrt{\sigma^2 + \epsilon} gives

∂s∂xj=12s⋅2d(xj−μ)=xj−μd s=x^jd.\frac{\partial s}{\partial x_j} = \frac{1}{2s}\cdot\frac{2}{d}(x_j - \mu) = \frac{x_j - \mu}{d\,s} = \frac{\hat{x}_j}{d}.

Now differentiate x^i=(xi−μ)/s\hat{x}_i = (x_i - \mu)/s with the quotient rule,

∂x^i∂xj=1s(δij−1d)−xi−μs2⋅x^jd=1s(δij−1d−x^ix^jd).\frac{\partial \hat{x}_i}{\partial x_j} = \frac{1}{s}\Big(\delta_{ij} - \frac1d\Big) - \frac{x_i - \mu}{s^2}\cdot\frac{\hat{x}_j}{d} = \frac{1}{s}\Big(\delta_{ij} - \frac1d - \frac{\hat{x}_i\hat{x}_j}{d}\Big).

That is the Jacobian in matrix form. It is symmetric, so J⊤g=Jg\mathbf{J}^\top\mathbf{g} = \mathbf{J}\mathbf{g}, and

(Jg)j=1s(gj−1d∑igi−x^jd∑igix^i),(\mathbf{J}\mathbf{g})_j = \frac1s\Big(g_j - \frac1d\sum_i g_i - \frac{\hat{x}_j}{d}\sum_i g_i\hat{x}_i\Big),

which is the vector formula above. Note that ϵ\epsilon never had to be dropped. The formula is exact with ϵ\epsilon included, as long as x^\hat{\mathbf{x}} and ss are the values used in the forward pass.

The parameter gradients are simpler. Since yj=γjx^j+βjy_j = \gamma_j\hat{x}_j + \beta_j, summing over every token tt that shares the parameters gives ∂L/∂γj=∑t(∂L/∂yt,j) x^t,j\partial\mathcal{L}/\partial\gamma_j = \sum_t (\partial\mathcal{L}/\partial y_{t,j})\,\hat{x}_{t,j} and ∂L/∂βj=∑t∂L/∂yt,j\partial\mathcal{L}/\partial\beta_j = \sum_t \partial\mathcal{L}/\partial y_{t,j}.

In plain words, the input gradient is the incoming gradient with two components removed, its average and its projection onto x^\hat{\mathbf{x}}, all divided by the token's scale ss.

There is a cleaner way to see the whole formula. Let u=1/d\mathbf{u} = \mathbf{1}/\sqrt d and w=x^/d\mathbf{w} = \hat{\mathbf{x}}/\sqrt d. Both are unit vectors (ignoring ϵ\epsilon, since ∥x^∥2=d\lVert\hat{\mathbf{x}}\rVert^2 = d), and they are orthogonal, because the entries of x^\hat{\mathbf{x}} sum to zero. The Jacobian is then

s ∂x^∂x=I−uu⊤−ww⊤,s\,\frac{\partial\hat{\mathbf{x}}}{\partial\mathbf{x}} = \mathbf{I} - \mathbf{u}\mathbf{u}^\top - \mathbf{w}\mathbf{w}^\top,

the orthogonal projection onto the (d−2)(d-2)-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 1\mathbf{1}. Summing the entries of ∂L/∂x\partial\mathcal{L}/\partial\mathbf{x} gives 1s(∑gj−dmean⁡(g)−mean⁡(g⊙x^)∑jx^j)=0\frac1s(\sum g_j - d\operatorname{mean}(\mathbf{g}) - \operatorname{mean}(\mathbf{g}\odot\hat{\mathbf{x}})\sum_j\hat{x}_j) = 0, because ∑jx^j=0\sum_j\hat{x}_j = 0. That is necessary. Moving x\mathbf{x} along 1\mathbf{1} cannot change the output, so the loss cannot have a slope in that direction.

The gradient is orthogonal to x^\hat{\mathbf{x}}, up to ϵ\epsilon. The dot product with x^\hat{\mathbf{x}} is 1smean⁡(g⊙x^)(d−∥x^∥2)\frac1s\operatorname{mean}(\mathbf{g}\odot\hat{\mathbf{x}})(d - \lVert\hat{\mathbf{x}}\rVert^2), and ∥x^∥2=dσ2/(σ2+ϵ)\lVert\hat{\mathbf{x}}\rVert^2 = d\sigma^2/(\sigma^2+\epsilon) is dd up to ϵ\epsilon. Rescaling x\mathbf{x} does not change the output either, so no gradient flows along it.

The gradient shrinks with the input's scale. Everything is divided by ss. 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.

Core

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 ∂x^/∂x\partial\hat{\mathbf{x}}/\partial\mathbf{x} of LayerNorm without gain and shift. Then write layer_norm_vjp(x, g, eps=1e-5) returning J⊤g\mathbf{J}^\top\mathbf{g} with the fast formula, without building the (d, d) matrix. The tests check the Jacobian against finite differences, check that sJs\mathbf{J} is an orthogonal projection of rank d−2d - 2 when ϵ=0\epsilon = 0, and check that the two functions agree.

Hidden tests, hints, and AI help, from $1.99 a month.

Here is a compact implementation we will reuse for the rest of the section, together with a check of the three properties.

Python · cell 1

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,

r=1d∑j=1dxj2+ϵ,RMSNorm⁡(x)=γ⊙xr.r = \sqrt{\frac{1}{d}\sum_{j=1}^{d} x_j^2 + \epsilon}, \qquad \operatorname{RMSNorm}(\mathbf{x}) = \boldsymbol{\gamma} \odot \frac{\mathbf{x}}{r}.

In plain words, divide the token's vector by its root mean square, so that it lands on the sphere of radius d\sqrt d, then apply a learned per-feature gain. There is no shift parameter, so RMSNorm has dd parameters.

The geometry is the sphere without the hyperplane. The mean direction 1\mathbf{1} survives, and RMSNorm is invariant to x↦ax\mathbf{x} \mapsto a\mathbf{x} for a>0a > 0 but not to adding a constant. In the residual stream, 1\mathbf{1} is one direction among dd 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 x^=x/r\hat{\mathbf{x}} = \mathbf{x}/r and g=γ⊙∂L/∂y\mathbf{g} = \boldsymbol{\gamma}\odot\partial\mathcal{L}/\partial\mathbf{y},

∂r∂xj=xjd r,∂x^i∂xj=1r(δij−x^ix^jd),∂L∂x=1r(g−mean⁡(g⊙x^) x^).\frac{\partial r}{\partial x_j} = \frac{x_j}{d\,r}, \qquad \frac{\partial \hat{x}_i}{\partial x_j} = \frac{1}{r}\Big(\delta_{ij} - \frac{\hat{x}_i\hat{x}_j}{d}\Big), \qquad \frac{\partial\mathcal{L}}{\partial\mathbf{x}} = \frac{1}{r}\Big(\mathbf{g} - \operatorname{mean}(\mathbf{g}\odot\hat{\mathbf{x}})\,\hat{\mathbf{x}}\Big).

In plain words, remove from the incoming gradient its component along the normalized input and divide by the scale. No component along 1\mathbf{1} is removed, because RMSNorm does not ignore shifts.

Interview

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,).

Hidden tests, hints, and AI help, from $1.99 a month.

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 (1+γ)(1 + \boldsymbol{\gamma}) with γ\boldsymbol{\gamma} 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 ϵ\epsilon. Values of 10−510^{-5} and 10−610^{-6} are both common, and putting it outside the square root (σ+ϵ\sigma + \epsilon) is a different function. When you port weights between frameworks, mismatched ϵ\epsilon 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 FF. The original Transformer (Vaswani et al., 2017) normalized after the residual add, which is called post-LN,

post-LN:x←LN⁡(x+F(x)).\text{post-LN:}\quad \mathbf{x} \leftarrow \operatorname{LN}\big(\mathbf{x} + F(\mathbf{x})\big).

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,

pre-LN:x←x+F(LN⁡(x)),logits=LN⁡f(x(S)) WU.\text{pre-LN:}\quad \mathbf{x} \leftarrow \mathbf{x} + F\big(\operatorname{LN}(\mathbf{x})\big), \qquad \text{logits} = \operatorname{LN}_f\big(\mathbf{x}^{(S)}\big)\,\mathbf{W}_U.

Here x(S)\mathbf{x}^{(S)} is the stream after all SS sublayers and WU∈Rd×V\mathbf{W}_U \in \R^{d \times V} 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 d/∥x∥\sqrt{d}/\lVert\mathbf{x}\rVert (it divides by s=∥x−μ1∥/ds = \lVert\mathbf{x} - \mu\mathbf{1}\rVert/\sqrt d). 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 dd in squared norm. The writes are close to uncorrelated at initialization, so their squared norms add,

E ∥x(ℓ)∥2≈∥x(0)∥2+ℓ c d,\mathbb{E}\,\lVert\mathbf{x}^{(\ell)}\rVert^2 \approx \lVert\mathbf{x}^{(0)}\rVert^2 + \ell\,c\,d,

linear growth in the layer index ℓ\ell for some constant cc (Xiong et al. prove bounds of exactly this form, between (1+ℓ/2)d(1 + \ell/2)d and (1+3ℓ/2)d(1 + 3\ell/2)d in their setup). The final norm divides by ∥x(S)∥/d∼S\lVert\mathbf{x}^{(S)}\rVert/\sqrt d \sim \sqrt{S}, so the gradient that enters the stream from the loss is scaled down by about 1/S1/\sqrt{S}. 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 1/S1/\sqrt{S}.

Post-LN. The stream is renormalized after every sublayer, so its norm stays near d\sqrt d forever. The last sublayer's gradient therefore does not shrink with depth at all. Going down one layer, the gradient passes through ∂LN⁡(z)/∂z\partial\operatorname{LN}(\mathbf{z})/\partial\mathbf{z} with z=x+F(x)\mathbf{z} = \mathbf{x} + F(\mathbf{x}). Since z\mathbf{z} is the sum of two vectors of comparable size, ∥z∥\lVert\mathbf{z}\rVert is larger than d\sqrt d 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 W2,L\mathbf{W}^{2,L} in a model with LL layers. With high probability at initialization,

post-LN:  ∥∂L∂W2,L∥F≤O(dln⁡d),pre-LN:  ∥∂L∂W2,L∥F≤O(dln⁡dL).\text{post-LN:}\ \ \Big\lVert\frac{\partial\mathcal{L}}{\partial\mathbf{W}^{2,L}}\Big\rVert_F \le \mathcal{O}\big(d\sqrt{\ln d}\big), \qquad \text{pre-LN:}\ \ \Big\lVert\frac{\partial\mathcal{L}}{\partial\mathbf{W}^{2,L}}\Big\rVert_F \le \mathcal{O}\Big(d\sqrt{\tfrac{\ln d}{L}}\Big).

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 1/L1/\sqrt{L} 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.

Python · cell 2

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 1/161/\sqrt{16} 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.

Where do gradients go in a deep pre-LN versus post-LN stack?
A live simulation at initialization, averaged over four random initializations. Each layer is a residual MLP sublayer (width 16, hidden 32) and the loss is a random linear readout. Left, the stream size after each layer. Right, the gradient norm of each layer's output matrix.
Loading interactive figure…

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 ℓ\sqrt{\ell}, 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 1/ℓ1/\sqrt{\ell} 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

modelyearnormplacement
Transformer (Vaswani et al.)2017LayerNormpost-LN
BERT2018LayerNormpost-LN
GPT-2, GPT-32019, 2020LayerNormpre-LN plus a final LN
T52019RMS scaling, no biaspre-norm plus a final norm
PaLM2022LayerNorm, no biaspre-LN, attention and MLP in parallel from one norm
LLaMA 1 to 32023, 2024RMSNormpre-norm plus a final norm
Gemma 22024RMSNormbefore and after each sublayer
OLMo 22024RMSNormon each sublayer's output, x+Norm⁡(F(x))\mathbf{x} + \operatorname{Norm}(F(\mathbf{x})), plus QK-norm

Two newer patterns deserve a sentence each. The parallel block of GPT-J and PaLM computes x+Attn⁡(LN⁡(x))+MLP⁡(LN⁡(x))\mathbf{x} + \operatorname{Attn}(\operatorname{LN}(\mathbf{x})) + \operatorname{MLP}(\operatorname{LN}(\mathbf{x})), 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.

Core

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" (x+F(norm⁡(x))\mathbf{x} + F(\operatorname{norm}(\mathbf{x}))), "post" (norm⁡(x+F(x))\operatorname{norm}(\mathbf{x} + F(\mathbf{x}))), or "output" (x+norm⁡(F(x))\mathbf{x} + \operatorname{norm}(F(\mathbf{x})), the OLMo 2 ordering). Raise ValueError for anything else.

Hidden tests, hints, and AI help, from $1.99 a month.

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.

🔥 In PyTorchfor a machine with PyTorch
BatchNormLayerNormRMSNorm
statistics overthe batch, per featureone token's featuresone token's features
removesmean and scale per featurethe token's mean and scalethe token's scale only
train and test identicalno (running averages)yesyes
parameters2d2d plus running stats2d2ddd
reductions per tokennot applicabletwo (mean, variance)one (mean of squares)
typical useCNNsBERT, GPT-2, GPT-3T5, LLaMA, Mistral, Gemma
Research

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 σ2=mean⁡(x2)−μ2\sigma^2 = \operatorname{mean}(x^2) - \mu^2 suffers catastrophic cancellation when ∣μ∣≫σ|\mu| \gg \sigma. 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).

Hidden tests, hints, and AI help, from $1.99 a month.

Read more

Watch next

🎬 Let's build GPT: from scratch, in code, spelled out. · Andrej Karpathy · starts at 92:51LayerNorm built from BatchNorm in a few lines, and why the pre-norm placement is used.
🎬 LLaMA explained: KV-Cache, Rotary Positional Embedding, RMS Norm, Grouped Query Attention, SwiGLU · Umar Jamil · starts at 8:52A careful walk from LayerNorm to RMSNorm as used in LLaMA.