← All tutorials

Normalization Layers

One recipe on different slices, and how BatchNorm changes at inference.

On this page

1 Normalization keeps each layer's inputs on one scale 2 The variants differ only in which numbers share a mean 3 BatchNorm behaves differently in training and inference 4 Each architecture picks the norm its batch allows 5 When normalization goes wrong 6 Normalization in a PyTorch model 7 References

A normalization layer computes a center and a spread over a slice of the numbers flowing through a network, centers them on 0 and rescales them to spread 1 (RMSNorm skips the centering and scales their typical size, the root mean square, to 1, Section 2.4), then lets the network pick its own scale back. The methods differ only in which numbers form the slice.

slice S 1 2 3 4 center μ of the slice spread σ of the slice x̂ = (x − μ) / spread y = γ x̂ + β Which numbers form the slice BN ① to ④ LN ① to ④ IN ① to ④ GN ① to ④ RMSNorm skips ① Rows are examples, columns are channels.
Figure 1. Every norm layer runs ① to ④; only the slice changes, and RMSNorm skips ①.

Every norm layer runs on every forward pass; only BatchNorm also stores averages for inference (Section 3).


1 Normalization keeps each layer's inputs on one scale

Each training step changes the weights, so the numbers the next layer receives drift in size from step to step. A layer that normalizes its inputs sees them on one scale at every step, which is often credited with easing vanishing and exploding gradients [2].

BatchNorm, the first of the layers on this page (Section 2.1), allowed much higher learning rates and less careful initialization, and reached the same accuracy in 14 times fewer training steps [1]. Its authors credited internal covariate shift, a layer's inputs changing as earlier weights change; Santurkar et al. found that link "tenuous" and measured a smoother loss surface instead, where the gradient says more about the loss a step away [2].

The running input X has two examples with three features each: two images with three channel values at one pixel, or two tokens.

Illustrative numbers
feature 1feature 2feature 3
example 1123
example 2468

Find a group's center and spread, express each number as spreads from the center, then let the network rescale.

The shared recipe, run on one slice S
μ = 1m Σk ∈ S xk
S is the slice that shares statistics, m its size, and k ∈ S runs over its numbers. μ is their average: 2 for row 1.
σ² = 1m Σk ∈ S (xk − μ)²
σ², the variance, is the average squared distance from μ, shared by every number in S: 0.667 here.
x̂i = xi − μ√σ² + ε
Subtracting μ centers the slice on 0 and dividing makes its spread 1. ε (epsilon), 0.00001 by default, keeps the division safe when σ² is 0. Rows 1 and 2 both become −1.22, 0, 1.22.
yi = γ x̂i + β
γ (the gain) and β (the learned shift) start at 1 and 0, so the layer starts as pure normalization and can learn any scale back, even the identity. This β is unrelated to Adam's.

Term by term, μ and σ² describe the group, x̂ is each number's place in it, γ and β are the only learned part, and Section 2 changes only S. Figure 1 draws the four lines as ① to ④.


2 The variants differ only in which numbers share a mean

A conv layer's output is an N × C × H × W tensor: images, channels, pixel rows and columns.

Four cubes with axes N, C and H, W; blue cells show the cells that share one mean and variance in Batch Norm, Layer Norm, Instance Norm and Group Norm
Figure 2. Wu and He (2018): "The pixels in blue are normalized by the same mean and variance, …"

2.1 BatchNorm averages one feature across the batch

BatchNorm came first, and in a CNN the batch gives many numbers per channel. Read X as two images: each column is one channel's value at one pixel.

Illustrative numbers
X, one column per channel BatchNorm output 123 468 −1−1−1 +1+1+1 column 1: μ = (1 + 4)/2 = 2.5 column 2: μ = (2 + 6)/2 = 4 column 3: μ = (3 + 8)/2 = 5.5 column 1: σ² = ((−1.5)² + 1.5²)/2 = 2.25
Figure 3. Each column gets its own mean and spread, so every output is −1 or +1.

Each feature is judged against the same feature in the other examples of this batch.

BatchNorm: S is one feature across the batch
μB,j = 1m Σn=1m xn,j
j is the feature (for images, the channel); n runs over the m examples in batch B. For feature 1, μB,1 = 2.5.
σ²B,j = 1m Σn (xn,j − μB,j)²
One variance per feature: 2.25, 4 and 6.25 for X's three features.
yn,j = γj xn,j − μB,j√σ²B,j + ε
  + βj
The recipe's lines 3 and 4, with one γ and β per feature. Every column of X becomes −1 and +1 (to 5 decimals with ε = 0.00001).

For a conv layer the slice is one channel over every image and pixel, so m is batch size × height × width [1]. Term by term, the subscript B says the statistics depend on which examples share the batch, which Section 3 deals with. LayerNorm flips the slice from columns to rows.

2.2 LayerNorm averages one example across its features

LayerNorm needs no batch, so it suits recurrent models [3] and, later, transformers. The Transformer Block shows the same layer from one token's side (section 2). Read X as two tokens with 3 features each; below them are row 1 made 10 times larger and row 1 with γ = (1, 0.5, 2), β = (0, 1, 0).

Illustrative numbers
tokeninputLayerNorm output
row 11, 2, 3−1.22, 0, 1.22
row 24, 6, 8−1.22, 0, 1.22
row 1 × 1010, 20, 30−1.22, 0, 1.22
row 1, with that γ, β1, 2, 3−1.22, 1, 2.45

Each token is judged against itself, so a batch of 1 works and training and inference compute the same thing.

LayerNorm: S is one example across its features
μn = 1D Σj=1D xn,j
D is the number of features per token (3 here). No other token enters. μ1 = 2, μ2 = 6.
σ²n = 1D Σj (xn,j − μn)²
One variance per token: 0.667 and 2.667 for the two rows.
yn,j = γj xn,j − μn√σ²n + ε
  + βj
Both rows, and the token 10 times larger, become −1.22, 0, 1.22; with the γ and β above, row 1 becomes −1.22, 1, 2.45.

Term by term, the subscript moved from B to n, removing the batch, and D is fixed by the architecture, so the statistics never get noisier with batch size. In PyTorch:

import torch
from torch import nn

X = torch.tensor([[1., 2., 3.],
                  [4., 6., 8.]])
ln = nn.LayerNorm(3)
y = ln(X)
mu = X.mean(-1, keepdim=True)
dev = X - mu
var = (dev ** 2).mean(-1, keepdim=True)
y_hand = dev / torch.sqrt(var + ln.eps)
  1. 1Imports and X, the table above.
  2. 2The layer, line 3: every row becomes −1.22, 0, 1.22.
  3. 3Lines 1 and 2 by hand; var is the biased variance (divided by D, not D − 1) that the LayerNorm doc uses, and y_hand equals y since γ = 1, β = 0 at init.

2.3 InstanceNorm and GroupNorm average inside one image

InstanceNorm removes each image's own contrast, which style transfer (repainting a photo in a painting's style) needs [4]. GroupNorm keeps the statistics inside one image, so it works at any batch size [5] (Section 3.1). Take tensor T: N = 2 images, C = 4 channels, 3 pixels each.

Illustrative numbers
ch 1ch 2ch 3ch 4
image 11, 2, 32, 4, 610, 11, 120, 5, 10
image 23, 3, 61, 1, 18, 9, 132, 2, 5

Pick the slice, then run the recipe on it; all four layers are one formula.

One formula, four slices: the set Si that cell i shares statistics with
SiBN = { k : kC = iC }
{ k : condition } is the set of cells k that meet the condition; kC is cell k's channel. BN pools one channel across both images: means 3, 2.5, 10.5, 4.
SiLN = { k : kN = iN }
LN pools every cell of one image: means 5.5 and 4.5.
SiIN = { k : kN = iN, kC = iC }
IN pools one channel of one image: image 1 gives 2, 4, 11, 5. Image 2 channel 2 (1, 1, 1) has variance 0, so ε turns a 0 / 0 into 0, 0, 0.
SiGN = { k : kN = iN,
  g(kC) = g(iC) }
g(c) is channel c's group: the C channels cut into G blocks of neighbors. With G = 2, channels 1, 2 and channels 3, 4 give image 1 means 3 and 8. G = 1 is LN; G = C is IN.

Term by term, each condition shrinks the slice: more numbers per mean gives steadier statistics but mixes more things that may differ. Figure 2 draws the four sets. In PyTorch, 1 or C groups give the LN and IN slices:

import torch
from torch import nn

x = torch.randn(2, 4, 8, 8)  # N, C, H, W
gn = nn.GroupNorm(2, 4)
y = gn(x)
like_in = nn.GroupNorm(4, 4)
like_ln = nn.GroupNorm(1, 4)
  1. 1An N × C × H × W batch.
  2. 2Line 4 with G = 2; C must be divisible by G.
  3. 3Lines 3 and 2. GroupNorm(1, C) shares LayerNorm's statistics, but its γ and β are per channel, not per element [11].

2.4 RMSNorm drops the mean and keeps only the scale

RMSNorm keeps only how large the numbers are and skips the centering, which saves compute [6]. RMS, the root mean square, measures size from 0.

Illustrative numbers
0 (1, 2, 3): √(1 + 4 + 9)/3 = √14/3 = 2.160 (4, 6, 8): √116/3 = 6.218 (−1, 0, 1): √2/3 = 0.816, and the mean is 0 green tick: mean red bar: RMS
Figure 4. RMS (red) is measured from 0, not from the mean (green).

Divide each number by the token's RMS instead of its spread, and never subtract the mean.

RMSNorm: LayerNorm without the mean
RMS(xn) = √1D Σj xn,j² + ε
How large the numbers are overall; no μ is computed. PyTorch puts ε inside the root; the paper has none. For row 1, RMS(x1) = 2.160.
yn,j = γj xn,jRMS(xn)
Unlike LN, the mean is not 0 and the rows stay different; for (−1, 0, 1) both give −1.22, 0, 1.22. Rows 1 and 2 become 0.463, 0.926, 1.389 and 0.643, 0.965, 1.287.

Term by term, LayerNorm's mean line is gone and the variance becomes a mean of squares around 0; at mean 0 the layers agree, so the paper hypothesizes the recentering can go. There is only a gain, no β. On a Transformer, RMSNorm took 231 s per 1,000 steps against LayerNorm's 248 s, 6.9% faster [6]. Press RMSNorm in Figure 5: the same slices as LN, but no cell goes negative because nothing is subtracted.

import torch
from torch import nn

class RMSNorm(nn.Module):
    def __init__(self, dim, eps=1e-6):
        super().__init__()
        self.eps = eps
        self.weight = nn.Parameter(torch.ones(dim))
    def forward(self, x):
        ms = (x * x).mean(-1, keepdim=True)
        return x / torch.sqrt(ms + self.eps) * self.weight

x = torch.tensor([1., 2., 3.])
mine, builtin = RMSNorm(3)(x), nn.RMSNorm(3)(x)
  1. 1super().__init__() must run before a Parameter is assigned; weight is γ.
  2. 2The root of ms plus eps is line 1; the return is line 2.
  3. 3Both give 0.463, 0.926, 1.389 for row 1. 1e-6 is this toy's value; nn.RMSNorm (PyTorch 2.4 or later) defaults to the machine epsilon [11].
Illustrative numbers
image 1 image 2 1 2 −2.5 0 +2.5

BN: 4 groups of 6 cells, one per channel across both images.

Figure 5. Each color is one slice sharing a mean and spread; ① is a flat channel, ② is 0.

3 BatchNorm behaves differently in training and inference

At inference there may be one image, so there is no batch to average. Like dropout, BatchNorm switches behavior in eval(): it uses averages saved during training. Take one BN channel as three batches pass, with means 2.4, 2.6 and 2.5.

Illustrative numbers
0 2 0123 batches seen 2.42.62.5 0.2400.4760.678 batch 1: 0.9 × 0 + 0.1 × 2.4 = 0.240 batch 2: 0.9 × 0.24 + 0.1 × 2.6 = 0.476 batch 3: 0.9 × 0.476 + 0.1 × 2.5 = 0.678 running_var starts at 1; X's feature 1 gives 2.25 × 2/1 = 4.5, so it becomes 0.9 × 1 + 0.1 × 4.5 = 1.35
Figure 6. μ (green) starts at 0 and moves 10% toward each batch mean (grey).

Training keeps a slow average of what the batches looked like; inference uses that average.

BatchNorm at inference: stored statistics replace the batch's
μ ← (1 − momentum) μ
  + momentum · μB
← replaces the stored value. μ is the running mean; momentum = 0.1 moves it 10% of the way to each batch mean. After the batches of Figure 6, μ = 0.678.
σ² ← (1 − momentum) σ²
  + momentum · mm − 1 σ²B
The update uses the unbiased variance: dividing by m − 1 instead of m corrects for the batch mean sitting closer to its numbers than the true mean. X's feature 1 is normalized with 2.25 but stored as 4.5. After one step, σ² = 1.35.
y = γ x − μ√σ² + ε + β
In eval() BN uses the stored μ and σ², so an output no longer depends on the rest of the batch; it is a fixed per-channel multiply and add.

Term by term, momentum is how fast the average forgets; optimizer momentum only reuses the word. PyTorch's momentum 0.1 is what the optimizer pages call a decay of 0.9, an average over about the last 10 batches. Its last line is the BatchNorm card's last line with the stored μ and σ² in place of the batch's, which lets Normalization and Quantization fold it into a conv. Over many steps the running mean warms up slowly:

share of the batch level reached 00.51 010223060 training steps n 1 2 0.198 65% 90% momentum 0.1 momentum 0.01
Figure 7. Momentum 0.1 reaches 65% at ① and 90% at ②; 0.01 is under 20% at step 22.

Only BatchNorm keeps state between steps [1, 3, 4, 5, 6, 11]:

MethodRuns whenRemembers between steps
BatchNormevery forward pass; batch statistics in train(), stored ones in eval()a running mean and variance per channel, plus a step counter
LayerNormevery forward pass, the same in both modesnothing
InstanceNormthe same, by defaultnothing by default
GroupNormthe same in both modesnothing
RMSNormthe same in both modesnothing

In PyTorch, train() moves the running statistics and eval() reads them:

import torch
from torch import nn

bn = nn.BatchNorm2d(4)
x = torch.randn(8, 4, 16, 16)  # N, C, H, W
bn.train()
for step in range(3):
    bn(x)  # moves running_mean and running_var
bn.eval()
with torch.no_grad():
    y = bn(x[:1])  # one image
  1. 1Defaults eps 1e-5, momentum 0.1 [11].
  2. 2Three passes move running_mean (line 1, Figure 7), and the step counter num_batches_tracked reaches 3.
  3. 3One image in eval: line 3.

3.1 Small batches make BatchNorm statistics noisy

With 2 images per GPU the batch mean is mostly noise, so the network learns on noise; when each image gives one number per feature, a batch of 2 can only output −1 and +1 (Figure 3); in a conv layer the pixels add numbers, but the mean and spread still come from just 2 images. Wu and He trained ResNet-50 on ImageNet on 8 GPUs with per-GPU BN statistics [5]: BN's error climbed from 23.6% at 32 images per GPU to 34.7% at 2, while GroupNorm stayed between 24.0% and 24.2%. The GroupNorm figure in Classic CNN Architectures: AlexNet to ResNet plots every batch size.

Illustrative simulation
BatchNorm: this pixel against its batch mates GroupNorm: this pixel against its own image's 8 values in this channel group 1 1.07 −1012

batch 32: 9 in 10 BN outputs fall between about 0.67 and 1.39

Figure 8. Smaller batches scatter BN's output around the dataset value ①; GN stays put.

GN's 1.07 sits off ① because 8 values give only a rough spread. SyncBatchNorm pools the statistics across all GPUs to get the big batch back, at the cost of one exchange per BN layer per step (code in Section 6) [5, 11].


4 Each architecture picks the norm its batch allows

The batch a model can afford picks its norm.

1 CNN classifiers (ResNet, ImageNet) BatchNorm [1, 5] 2 Detection, 1 or 2 images per GPU frozen BatchNorm or GroupNorm [5] 3 Style transfer InstanceNorm [4] 4 RNNs and transformers LayerNorm [3] 5 LLaMA RMSNorm [7]
Figure 9. Big batches ①, small batches ②, one image ③, one token ④ and ⑤.

Two other layers carry the name but normalize weights, not activations: weight norm learns each weight vector's length apart from its direction [9], and spectral norm divides a weight matrix by the most it can stretch any input, which steadies GAN (generative adversarial network) training [10].

4.1 Pre-LN puts the norm before each sublayer

A transformer block's sublayers (attention and the feed-forward network) each sit inside a residual add, x plus the sublayer's output.

Post-LN Pre-LN x sublayer + norm out x norm sublayer + out
Figure 10. Dashed: the residual path. Post-LN puts a norm on it; Pre-LN leaves it clean.

Xiong et al. showed that Post-LN Transformers need a learning-rate warmup, while Pre-LN trains without it in less time [8]; LLaMA uses Pre-LN with RMSNorm [7]. The Transformer Block compares the two (section 3).


5 When normalization goes wrong

SymptomCauseFixWhat to log
Validation far worse than training, or results change with the eval batcheval() not called, so BN uses batch statisticseval() before validation and exportmode flag at each eval
Accuracy collapses with 1 to 4 images per GPUnoisy batch statistics [5]GroupNorm, SyncBN or frozen BNper-GPU batch size
Good offline, worse on new datarunning statistics from another distribution [5]recompute the running statistics on representative datarunning_mean vs new batch means
Fine-tuning with tiny batches does worse than the pretrained modelBN statistics updated on tiny batches: about 6 AP (average precision, the detection score) worse than frozen BN at batch 2 [5]keep BN in eval (Section 6, code note 3) or use GNrunning_mean drift per epoch
NaN from a norm layera flat slice (Section 2.3) has variance 0, and a custom norm without ε divides 0 by 0keep an εsmallest variance per layer

6 Normalization in a PyTorch model

Knob (default)What it does
momentum (BN, 0.1)weight of the newest batch in the running average; higher is faster and noisier, lower smoother and slower to warm up (Figure 7); None gives a cumulative average
eps (BN, LN, GN 1e-5; RMSNorm None)added before the root; too small risks dividing by a near-0 spread, too large flattens low-spread slices; RMSNorm's None uses torch.finfo(torch.float32).eps, about 1.19e-7, for 16-bit float (fp16, bf16) and fp32 inputs
num_groups G (GroupNorm; paper default 32 [5])more groups, fewer channels per mean; must divide the channel count
affine / elementwise_affine (True; InstanceNorm2d False)turns γ and β on or off
track_running_stats (BN True; InstanceNorm2d False)False uses batch statistics in eval too; True makes InstanceNorm2d change in eval

A conv block with BN, converted to SyncBN, and frozen for fine-tuning:

from torch import nn

model = nn.Sequential(
    nn.Conv2d(3, 64, 3, bias=False),
    nn.BatchNorm2d(64),  # or GroupNorm(32, 64)
    nn.ReLU(),
)
model = nn.SyncBatchNorm.convert_sync_batchnorm(model)
bn_types = (nn.BatchNorm2d, nn.SyncBatchNorm)
model.train()
for m in model.modules():
    if isinstance(m, bn_types):
        m.eval()
  1. 1The model; the comment names the small-batch swap of Figure 9.
  2. 2SyncBN conversion, before wrapping in DistributedDataParallel (DDP, the multi-GPU wrapper), one GPU per process [11].
  3. 3Fine-tuning with BN frozen: the model trains, but every BN layer stays in eval() and keeps its stored statistics; converted layers are their own class: failure row 4.

What happens to these layers when a model is quantized is on Normalization and Quantization.


7 References

  1. Ioffe, S., and Szegedy, C. (2015). Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. ICML 2015. arXiv:1502.03167.
  2. Santurkar, S., et al. (2018). How Does Batch Normalization Help Optimization? NeurIPS 2018. arXiv:1805.11604.
  3. Ba, J. L., et al. (2016). Layer Normalization. arXiv:1607.06450.
  4. Ulyanov, D., et al. (2016). Instance Normalization: The Missing Ingredient for Fast Stylization. arXiv:1607.08022.
  5. Wu, Y., and He, K. (2018). Group Normalization. ECCV 2018. arXiv:1803.08494.
  6. Zhang, B., and Sennrich, R. (2019). Root Mean Square Layer Normalization. NeurIPS 2019. arXiv:1910.07467.
  7. Touvron, H., et al. (2023). LLaMA: Open and Efficient Foundation Language Models. arXiv:2302.13971.
  8. Xiong, R., et al. (2020). On Layer Normalization in the Transformer Architecture. ICML 2020. arXiv:2002.04745.
  9. Salimans, T., and Kingma, D. P. (2016). Weight Normalization: A Simple Reparameterization to Accelerate Training of Deep Neural Networks. NeurIPS 2016. arXiv:1602.07868.
  10. Miyato, T., et al. (2018). Spectral Normalization for Generative Adversarial Networks. ICLR 2018. arXiv:1802.05957.
  11. PyTorch 2.15 documentation: BatchNorm2d, LayerNorm, GroupNorm, InstanceNorm2d, RMSNorm, SyncBatchNorm.