Skip to content
AI360Xpert
Core ML

Pre-Norm vs Post-Norm

Normalizing a sub-layer's input before it runs keeps the residual stream itself untouched at every layer, which trains far more stably at depth than normalizing after adding the sub-layer's output.

Post-norm normalizes after adding the sub-layer's output, placing the residual stream through the normalizer every layer; pre-norm normalizes only the sub-layer's input, leaving the residual stream untouched
Post-norm normalizes after adding the sub-layer's output, placing the residual stream through the normalizer every layer; pre-norm normalizes only the sub-layer's input, leaving the residual stream untouched

Why Does This Exist?

The transformer block wraps both its sub-layers in a residual connection and a normalization step, but that description leaves out one detail that turns out to matter enormously: where exactly does the normalization go relative to the addition? The original transformer paper placed it after the residual addition — normalize the sum of the input and the sub-layer's output. That ordering, now called post-norm, is what the paper actually shipped, and for years it was simply "how transformers are normalized."

Then researchers training deeper transformer stacks kept hitting a specific wall: past a certain depth, post-norm transformers became difficult or impossible to train without a very careful, slow learning-rate warmup, and sometimes not even then. The fix wasn't a new normalization formula — it was moving the existing normalization to a different position in the block, which changed how gradients flow through the stack in a way the formula alone doesn't reveal.

Think of It Like This

Editing a shared master copy versus editing a private draft

Post-norm is like having a team where every contributor's edit gets folded directly into the one shared master document, and then the entire master document gets reformatted after every single edit — meaning every contributor's next edit has to work with a document that just changed shape underneath them. Pre-norm is like giving each contributor their own read-only, reformatted snapshot of the master to work from, while the actual master document itself is only ever added to, never reformatted in place.

In the second setup, the master stays a stable, growing sum that nothing ever distorts — contributors read a clean copy, contribute a correction, and that correction gets added to the untouched original. That stability is exactly what pre-norm buys a deep transformer stack.

How It Actually Works

Two orderings, one formula each

Post-norm, as shown on the left of the diagram, normalizes after the residual addition:

xpost=Norm(x+SubLayer(x))x_{\text{post}} = \text{Norm}\big(x + \text{SubLayer}(x)\big)

Pre-norm, on the right, normalizes only the sub-layer's input, leaving the addition itself untouched:

xpre=x+SubLayer(Norm(x))x_{\text{pre}} = x + \text{SubLayer}\big(\text{Norm}(x)\big)

The difference looks small — a normalization step moved from one side of an addition to the other — but it changes something structural: in pre-norm, the raw input xx skips straight through to the final addition, completely unmodified by any normalization. In post-norm, the entire running sum — the thing every earlier layer contributed to — gets renormalized at every single layer.

Why the raw residual stream matters

Recall from residual connections why the identity path was the whole point: it gives gradients a route back to early layers that doesn't have to pass through any nonlinearity or transformation, however deep the stack gets. Post-norm's placement of the normalization after the addition inserts a normalization operation directly into that identity path, at every layer — the "untouched" shortcut residual connections were supposed to provide isn't actually untouched anymore, because the sum it feeds into gets renormalized before continuing to the next layer.

Pre-norm avoids this precisely: the term that skips straight through to the next addition is the raw, unnormalized xx, exactly as residual connections intended. Normalization still happens, but only on the copy that feeds into the sub-layer computation, never on the running sum that carries gradient information down the stack. That preserved, untouched identity path is why pre-norm transformers train reliably at far greater depth, and with far less sensitivity to learning-rate warmup, than post-norm ones.

The cost, stated honestly

Pre-norm's stability isn't free. Because the residual stream in a pre-norm model can grow in magnitude across many layers — each layer adds its correction to an ever-growing sum without ever renormalizing that sum directly — very deep pre-norm stacks can eventually face their own numerical issues from that unbounded growth, and some architectures address this with an additional final normalization layer at the very end of the whole stack. Pre-norm trades post-norm's training instability for a different, generally more manageable problem.

A related technique, QK-normalization, normalizes the query and key vectors themselves — before the QKQK^\top dot product inside attention — rather than normalizing the block's input or output. It targets a different failure mode: attention logits growing unstably large as models scale up, independent of which pre-norm or post-norm convention the surrounding block uses. It's worth knowing as a sibling fix living at the same architectural layer, not a replacement for the pre-norm choice.

Show Me the Code

Both orderings, computed explicitly, to see exactly which term is normalized in each.

import numpy as np

def layer_norm(x: np.ndarray, eps: float = 1e-5) -> np.ndarray:    mean, var = x.mean(axis=-1, keepdims=True), x.var(axis=-1, keepdims=True)    return (x - mean) / np.sqrt(var + eps)

def post_norm_block(x: np.ndarray, sublayer_fn) -> np.ndarray:    return layer_norm(x + sublayer_fn(x))          # the WHOLE sum gets normalized

def pre_norm_block(x: np.ndarray, sublayer_fn) -> np.ndarray:    return x + sublayer_fn(layer_norm(x))           # only the sub-layer's INPUT is normalized

rng = np.random.default_rng(0)x = rng.normal(size=(4, 8))identity_sublayer = lambda z: 0.1 * z                # stand-in for attention or FFN
post = post_norm_block(x, identity_sublayer)pre = pre_norm_block(x, identity_sublayer)print(np.allclose(pre - x, 0.1 * layer_norm(x)))     # -> True — x survives untouched in pre-normprint(np.allclose(post, x))                          # -> False — x itself is gone after post-norm

pre - x isolates exactly what got added to the original input, and it's a function purely of the sub-layer's output — x itself is never inside a layer_norm call in the pre-norm path, which post_norm_block cannot say.

Watch Out For

Getting the ordering backwards when implementing from a paper

The two formulas differ by moving one function call from outside an addition to inside it, and it is a known, documented, easy mistake to write Norm(x) + SubLayer(Norm(x)) when the paper meant x + SubLayer(Norm(x)), or vice versa when reproducing post-norm. Both mistaken versions run without erroring, and both train — just noticeably worse, or with far more sensitivity to learning-rate warmup than intended. Cross-check the exact formula against the architecture you're reproducing rather than trusting memory.

Assuming pre-norm is strictly superior with no tradeoff

Pre-norm's training stability at depth is real and well-documented, but it isn't a strictly dominant choice — the unbounded residual-stream growth mentioned above is a genuine, measured property of very deep pre-norm stacks, not a hypothetical. Treating pre-norm as a costless upgrade, rather than a different failure mode traded for training stability, can lead to surprises when scaling a pre-norm architecture to extreme depths without the additional stabilizing techniques deep pre-norm models actually use.

The Quick Version

  • Post-norm normalizes after the residual addition, so the entire running sum gets renormalized at every layer.
  • Pre-norm normalizes only the sub-layer's input, leaving the raw residual stream to pass through untouched.
  • Post-norm's placement disrupts the clean identity path residual connections are meant to provide; pre-norm preserves it.
  • Pre-norm trains far more reliably at depth and needs less careful learning-rate warmup, which is why it became the modern default.
  • Pre-norm trades that stability for potentially unbounded residual-stream growth at extreme depth, a separate, generally more manageable issue.

Related concepts