LayerNorm vs RMSNorm: How LLMs Normalize Each Token

Learn how LayerNorm and RMSNorm rescale each token vector, where post-norm, pre-norm and sandwich layouts place them, and why Qwen3 adds QK-Norm.

Oct 12, 202617 min readFollow

Topics You Will Master

Why a deep Transformer needs a norm on every token
How LayerNorm and RMSNorm rescale one token vector
Where post-norm, pre-norm and sandwich layouts put the norm
What QK-Norm adds inside attention in Qwen3

In Day 19, we met the full Transformer block. It had attention, an MLP, two shortcut additions, and a norm. The norm got only a few short paragraphs. But here's the real mystery: if we remove it, a deep model can fail to train. So what is this small layer doing?

A normalization layer rescales the numbers inside one token vector so they stay at a steady size. In simple words, it stops every layer's input from getting too big or too small. In this lesson, we will learn why LLMs need it, how LayerNorm and RMSNorm work, where the norm sits in the block, and what QK-Norm adds inside attention.

The Norms in One Qwen3 Decoder Block and After the Last Block

Every blue box is an RMSNorm. By the end of this lesson, we will know why each one sits where it does.

Bestseller

LLM Fine-Tuning with Hugging Face: LoRA, QLoRA, PEFT

Fine-tune BERT, T5, ViT, LLaMA-style models and Qwen3-TTS using Hugging Face Transformers, custom datasets, LoRA, QLoRA.

Enroll on Udemy →30 day refund, lifetime access

Why Does a Deep Transformer Need a Norm?

Let's start with the problem. In Day 19, we saw that each block adds its result back to its input. This running vector is called the residual stream. Every block reads it, works on it, and adds something to it.

Now, an LLM stacks many blocks. Qwen2.5-0.5B has 24 of them. If every block makes the numbers a little bigger, the growth piles up. Block 24 then sees numbers far larger than block 1 saw. Its weights were never tuned for inputs that big, so training becomes shaky.

Let's see this with a tiny toy. We build 24 random layers, each 512 numbers wide, with a shortcut around each one. We run the same input through two copies. One copy feeds each layer the raw stream. The other copy rescales the stream first. Let's see the code as below:

PYTHON
import torch

torch.manual_seed(0)
d, depth = 512, 24
layers = [torch.randn(d, d) / d ** 0.5 for _ in range(depth)]

def rms(v):
    return v.pow(2).mean().sqrt().item()

def norm(v):
    return v / v.pow(2).mean().sqrt()

x_plain = torch.randn(d)
x_norm = x_plain.clone()

for i, W in enumerate(layers, start=1):
    seen_plain = x_plain
    seen_norm = norm(x_norm)
    x_plain = x_plain + W @ seen_plain
    x_norm = x_norm + W @ seen_norm
    if i in (1, 6, 12, 24):
        print(f"layer {i:2d}: no norm sees {rms(seen_plain):8.1f}   with norm sees {rms(seen_norm):.1f}")
OUTPUT
layer  1: no norm sees      1.0   with norm sees 1.0
layer  6: no norm sees      5.6   with norm sees 1.0
layer 12: no norm sees     45.9   with norm sees 1.0
layer 24: no norm sees   2738.7   with norm sees 1.0

Here, we can see the problem clearly. Without a norm, layer 24 sees inputs of size 2,738.7. With a norm, every layer sees size 1.0.

A few choices in this code are worth a word:

  • We divide the random weights by d ** 0.5 so one layer keeps the size of its input about the same. Even so, the shortcut adds the input back, so the size still grows a little at every layer.
  • The rms helper measures the size of a vector. RMS stands for root mean square: square every number, take the mean, then take the square root.
  • The norm helper divides a vector by its own RMS. This is a bare RMSNorm, which we will meet properly in a moment.

Input Size Each Sublayer Sees Over 24 Random Layers

This is a toy with random weights, not a trained model. It only shows how growth piles up when nothing rescales the input.

So, here comes the normalization layer to the rescue. It sits in front of the work, and it hands every layer an input of a steady size.

Advertisement

Which Numbers Does a Norm Look At?

Before we see the formulas, we need to know which numbers get averaged together. Inside an LLM, the data is shaped like a box: sentences, then tokens, then the hidden features of each token.

You may have heard of Batch Norm from image models. It takes one feature and averages it across every token in the batch, from every sentence. That works well for images.

It works poorly for text, for three reasons:

  • Sentences have different lengths, so short ones are filled with padding tokens. Unless we mask them out, those fake tokens sneak into the average.
  • When a chatbot writes text, it often handles a single sentence at a time. One sentence gives a poor batch average.
  • During training, it uses the average of the current batch. At use time, it switches to a saved average, so the model can behave differently.

LLMs pick the other direction. LayerNorm and RMSNorm look at one token at a time, across that token's own features. In simple words, each token is rescaled using only its own numbers. Tokens never share an average, so the length of the sentence and the size of the batch do not matter.

Batch Norm vs LayerNorm: Which Numbers Share One Mean

Each blue group shares one average. In LLMs, that group is always one token's own features.

Advertisement

How Does LayerNorm Work?

LayerNorm was introduced by Ba, Kiros and Hinton in 2016. It does two things to a token vector. First, it moves the average of the numbers to 0. Then, it makes their spread equal to 1.

Let's say one token vector is [2, 4, 6, 8].

LayerNorm works on it in four steps:

  1. Find the mean: (2 + 4 + 6 + 8) / 4 = 5.
  2. Subtract the mean from every number: [-3, -1, 1, 3].
  3. Find the spread. We square each distance (9, 1, 1, 9) and take the mean, which is 5. This is the variance. Its square root, about 2.236, is the standard deviation.
  4. Divide every centered number by 2.236: [-1.342, -0.447, 0.447, 1.342].

There is one small detail in step 4. The code adds a tiny number, called epsilon, before the square root. It stops a division by zero when every number in the vector is the same.

Then LayerNorm adds two sets of learned numbers. A gain multiplies each feature, and a bias is added to each feature. Both are trained along with the rest of the model. They start at 1 and 0, so at first they change nothing. Later, they let the model pick its own scale for each feature.

The original Transformer, BERT and GPT-2 all use LayerNorm.

How Does RMSNorm Work?

In 2019, Biao Zhang and Rico Sennrich asked a simple question. Do we really need step 1 and step 2? Their guess was that centering is not needed. Only the rescaling matters. So, they proposed RMSNorm.

RMSNorm skips the mean completely. It divides each number by the RMS of the vector. In simple words, it shrinks or grows the vector to a steady size, and it leaves the average wherever it was.

Let's run the same vector [2, 4, 6, 8] through RMSNorm:

  1. Square each number: 4, 16, 36, 64.
  2. Take the mean of the squares: 120 / 4 = 30.
  3. Take the square root: about 5.477. This is the RMS.
  4. Divide every number by 5.477: [0.365, 0.730, 1.095, 1.461].

After that, RMSNorm multiplies by a learned gain, just like LayerNorm. But it has no bias. So it does less work: no mean to find, nothing to subtract, and nothing to add at the end.

LayerNorm vs RMSNorm Step by Step on (2, 4, 6, 8)

Both outputs end up with a steady size. Only LayerNorm also moves the average to 0.

Llama, Qwen, Mistral, Gemma and DeepSeek all use RMSNorm today.

Advertisement

Let's Check Both Norms in PyTorch

PyTorch ships both layers as nn.LayerNorm and nn.RMSNorm. We create each one for a vector of 4 features and pass in our token. We wrap the call in torch.no_grad() because we are not training, so we do not need PyTorch to track gradients. Let's see the code as below:

PYTHON
import torch
import torch.nn as nn

x = torch.tensor([[2.0, 4.0, 6.0, 8.0]])

layer_norm = nn.LayerNorm(4)
rms_norm = nn.RMSNorm(4)

with torch.no_grad():
    print(layer_norm(x))
    print(rms_norm(x))
OUTPUT
tensor([[-1.3416, -0.4472,  0.4472,  1.3416]])
tensor([[0.3651, 0.7303, 1.0954, 1.4606]])

Here, we can see the exact numbers we found by hand. LayerNorm gives values centered on 0. RMSNorm gives values that are all still positive, just smaller.

Now, let's do the same math ourselves, so nothing is hidden. Let's see the code as below:

PYTHON
import torch
x = torch.tensor([[2.0, 4.0, 6.0, 8.0]])

mean = x.mean(dim=-1, keepdim=True)
var = ((x - mean) ** 2).mean(dim=-1, keepdim=True)
print(mean, var)
print((x - mean) / torch.sqrt(var + 1e-5))

rms = torch.sqrt((x ** 2).mean(dim=-1, keepdim=True))
print(rms)
print(x / rms)
PYTHON
tensor([[5.]]) tensor([[5.]])
tensor([[-1.3416, -0.4472,  0.4472,  1.3416]])
tensor([[5.4772]])
tensor([[0.3651, 0.7303, 1.0954, 1.4606]])

Here, we can see the mean of 5, the variance of 5, and the RMS of 5.4772. Our hand-made results match the PyTorch layers.

Two small choices in this code matter:

  • dim=-1 means "work along the last axis", which is the feature axis. This is what makes the norm look at one token at a time.
  • 1e-5 is the epsilon. It is the same default that nn.LayerNorm uses.

When Do LayerNorm and RMSNorm Give the Same Answer?

Here is a nice surprise. Let's give both layers a vector whose mean is already 0. We use [-3, -1, 1, 3], the centered version of our token. Let's test it:

PYTHON
import torch
import torch.nn as nn
layer_norm = nn.LayerNorm(4)
rms_norm = nn.RMSNorm(4)

centered = torch.tensor([[-3.0, -1.0, 1.0, 3.0]])

with torch.no_grad():
    print(layer_norm(centered))
    print(rms_norm(centered))
OUTPUT
tensor([[-1.3416, -0.4472,  0.4472,  1.3416]])
tensor([[-1.3416, -0.4472,  0.4472,  1.3416]])

Here, we can see two identical rows. When the mean is already 0, subtracting it changes nothing. So the RMS and the standard deviation become the same number, and both layers divide by it.

This is why dropping the mean costs so little in practice. The rescaling does the important job. The centering is a small extra.

The two layers also differ in what they learn. Let's print their learned numbers:

PYTHON
import torch.nn as nn
layer_norm = nn.LayerNorm(4)
rms_norm = nn.RMSNorm(4)

for name, p in layer_norm.named_parameters():
    print("LayerNorm", name, p.data)
for name, p in rms_norm.named_parameters():
    print("RMSNorm  ", name, p.data)
OUTPUT
LayerNorm weight tensor([1., 1., 1., 1.])
LayerNorm bias tensor([0., 0., 0., 0.])
RMSNorm   weight tensor([1., 1., 1., 1.])

Here, we can see that LayerNorm learns a gain (called weight) and a bias. RMSNorm learns only the gain.

Let me tabulate the two norms for your better understanding.

LayerNorm RMSNorm
Subtracts the mean yes no
Divides by standard deviation RMS
Learned numbers per feature gain and bias gain only
Output average moved to 0 left as it was
Used in original Transformer, BERT, GPT-2 Llama, Qwen, Mistral, Gemma, DeepSeek
Advertisement

Where Does the Norm Sit in the Block?

Choosing the norm is only half of the story. The other half is where we put it. Let's call the attention or MLP step F. Then there are four common layouts.

Post-norm runs F, adds the shortcut, and then normalizes: x = Norm(x + F(x)). The original Transformer in 2017 used it, and so does BERT. The catch is that the norm sits on the main path, between blocks. In a deep stack, this made early training unstable. These models needed a slow start, called warm-up, where the learning rate begins tiny and grows step by step.

Pre-norm normalizes the input of F and leaves the shortcut untouched: x = x + F(Norm(x)). GPT-2 moved to this layout and added one final norm after the last block. In 2020, Xiong and others showed why it helps. Pre-norm models train well even without the warm-up stage. Llama and Qwen use pre-norm too.

Sandwich puts a norm on both sides of F: x = x + Norm(F(Norm(x))). Gemma 2 and Gemma 3 use it for both attention and the MLP. The second norm tames the size of what F adds back to the stream.

Output norm keeps only the second norm: x = x + Norm(F(x)). OLMo 2 uses this layout, so the input of F is not normalized at all.

Four Norm Placements: Post-Norm, Pre-Norm, Sandwich, Output Norm

In every layout except post-norm, the green shortcut path never passes through a norm.

Here, we can see the key idea. In pre-norm, sandwich and output norm, the shortcut path stays clean. The residual stream flows from the first block to the last with nothing in its way. That clean path is what makes deep stacks easier to train.

But pre-norm lets the stream itself grow from block to block. So pre-norm models add one final norm after the last block, right before the LM head turns vectors into next-token scores. We saw this final box in the first figure of this lesson.

Advertisement

What Is QK-Norm?

So far, every norm sat outside attention. Newer models add one more pair inside it.

Recall Day 14. An attention score is the dot product of a query and a key. If the queries and keys grow large during training, the scores grow too. Then softmax can put almost all of its weight on one token. When this happens, training can blow up.

QK-Norm rescales each query and each key with an RMSNorm before the dot product. In simple words, it keeps the inputs of the score at a steady size, so the scores cannot drift out of range as easily. The idea comes from Dehghani and others in 2023.

The Qwen3 report says it removes the QKV bias that Qwen2 used and introduces QK-Norm to keep training stable. In the Qwen3 code, q_norm and k_norm work per head, over each head's own features. They run right after the queries and keys are split into heads, and before RoPE adds position.

QK-Norm in Qwen3: RMSNorm on Each Head's Query and Key Before RoPE

Values skip both the norm and RoPE. Only the two vectors that form the score get rescaled.

Gemma 3 also has q_norm and k_norm. OLMo 2 uses QK-Norm too, but it normalizes all heads of the query together, before the split. The Qwen3 code even has a comment about this difference.

Advertisement

Which Norm Does Each Model Use?

Let me tabulate the models we met in this lesson for your better understanding. We checked every row against its model code in Hugging Face Transformers, except the original Transformer, which comes from the 2017 paper.

Model Norm Placement QK-Norm
Original Transformer LayerNorm post-norm no
BERT LayerNorm post-norm no
GPT-2 LayerNorm pre-norm + final norm no
Llama RMSNorm pre-norm + final norm no
Qwen2.5 RMSNorm pre-norm + final norm no (uses QKV bias)
Qwen3 RMSNorm pre-norm + final norm yes, per head
Gemma 2 RMSNorm sandwich no
Gemma 3 RMSNorm sandwich yes
OLMo 2 RMSNorm output norm yes, all heads together

Here, we can see the trend. The field moved from LayerNorm to RMSNorm, and from post-norm to pre-norm. Newer models then add QK-Norm, a second norm, or both.

Where Else Do Norms Show Up?

Once we know the pattern, we start to see norms in new places:

  • DeepSeek V3 squeezes its keys and values into a small vector before it caches them. It puts an RMSNorm on that small vector. We will meet this design in Day 27.
  • Qwen 3.8 Flash Next has no input_layernorm and no post_attention_layernorm at all. Its norms moved inside a new kind of shortcut with several lanes. We took it apart in our Qwen 3.8 Flash Next teardown.
  • For a real RMSNorm traced through every layer of a working model, see our Qwen2.5-0.5B teardown.

Recap

This is how normalization works in LLMs. We started with the problem: without a norm, the input of each layer keeps growing as blocks pile up. We saw why LLMs rescale one token at a time instead of using Batch Norm. Then we worked through LayerNorm, which centers and rescales, and RMSNorm, which only rescales. Finally, we placed the norm in the block. Pre-norm keeps the shortcut path clean, a final norm closes the stack, and QK-Norm keeps the attention scores in range.

On Day 20, we will look at the cost of left-to-right generation and see why a key-value cache makes each new token step cheaper.

Found this useful? Keep building with me.

New tutorials every week on YouTube: or go deeper with a full structured course.

Find this tutorial useful?

Subscribe to our YouTube channels for more practical production walk-throughs.

Discussion & Comments