C.W.K.
Stream
Lesson 01 of 12 · published

Layer Normalization: Pre-LN vs Post-LN

~12 min · layer-norm, stability

Level 0Token
0 XP0/94 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

Layer normalization stabilizes training by normalizing activations within each layer to have zero mean and unit variance, then applying learned scale and shift. Where you place it in the block matters more than you'd think.

Two placements

Post-LN (original Transformer, 2017): normalize after the residual connection.

x = LayerNorm(x + Sublayer(x))

Trains poorly without learning-rate warmup. Gradient signals at depth tend to explode or vanish unless carefully tuned. Most papers from 2017-2019 use Post-LN; modern models avoid it.

Pre-LN (modern standard): normalize before the sublayer.

x = x + Sublayer(LayerNorm(x))

The residual stream remains un-normalized; gradients can flow back through the skip connection without ever being pushed through layer norms. Training is much more stable. GPT-2, GPT-3, BERT-base, every modern Llama, Mistral, and Claude variant uses Pre-LN.

RMSNorm

Modern Llama and Mistral use RMSNorm instead of LayerNorm. Same shape, but skips the mean-centering step — it normalizes by the root mean square of the activations rather than (activation − mean) / std. Slightly faster, slightly fewer parameters, empirically just as stable.

Code

Pre-LN block (modern)·python
class PreLNBlock(nn.Module):
    def __init__(self, d_model, attn, ffn, norm_cls=nn.LayerNorm):
        super().__init__()
        self.norm1 = norm_cls(d_model)
        self.attn = attn
        self.norm2 = norm_cls(d_model)
        self.ffn = ffn
    def forward(self, x):
        x = x + self.attn(self.norm1(x))    # norm BEFORE attention
        x = x + self.ffn(self.norm2(x))      # norm BEFORE FFN
        return x
RMSNorm in 6 lines·python
class RMSNorm(nn.Module):
    def __init__(self, d_model, eps=1e-6):
        super().__init__()
        self.weight = nn.Parameter(torch.ones(d_model))
        self.eps = eps
    def forward(self, x):
        # Skip mean centering; just normalize by RMS
        rms = x.pow(2).mean(-1, keepdim=True).rsqrt()
        return self.weight * (x * (rms + self.eps))

External links

Exercise

Implement Pre-LN and Post-LN blocks. Train a tiny Transformer (4 layers) on a copy task, both with and without learning-rate warmup, with each placement. Report which combinations actually train stably. (Spoiler: Pre-LN works without warmup; Post-LN often doesn't.)

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.