Initialization and normalization
Keeping every layer's signal at the right scale, forward and backward
A network is a product of many layers, and products are unforgiving: a per-layer scale factor of 0.7 becomes after 50 layers. Initialization sets that factor at step zero; normalization layers keep it in range for the rest of training. These two ideas, plus residual connections, are what turned "deep" from a slur into a plan — they are why ResNets (5.3 Architectures: LeNet → AlexNet → VGG → Inception → ResNet → EfficientNet → ConvNeXt) go to 152 layers and why every transformer block carries a LayerNorm or RMSNorm (7.4 Inside the block: residuals, LayerNorm, FFN and embeddings).
You'll be able to
- Derive the variance-preservation condition for a linear layer and obtain LeCun, Xavier/Glorot and He initialization
- Write the BatchNorm forward pass for training and inference and explain where it fails
- Contrast BatchNorm, LayerNorm, RMSNorm and GroupNorm and choose the right one for an architecture
- Explain pre-norm vs post-norm placement and why residual connections keep gradients alive
- Diagnose bad initialization from the initial loss and activation statistics
Key ideas
- All-equal initialization makes hidden units identical forever; weights must be random, biases may be zero.
- Var(z) = n_in·Var(w)·Var(x): preserving scale needs Var(w) = 1/n_in (LeCun), 2/(n_in+n_out) (Xavier, for tanh/sigmoid) or 2/n_in (He, for ReLU).
- BatchNorm standardizes each feature over the batch, then rescales with γ, β; at inference it uses running statistics — remember model.eval().
Show 4 more key ideas
- BatchNorm helps by smoothing the loss landscape (higher LR, less init sensitivity), not by removing 'covariate shift'; it fails for small batches and sequences.
- LayerNorm normalizes each example across its features (no batch dependence); RMSNorm drops the centering and is the LLM default; GroupNorm serves small-batch vision.
- Pre-norm (x + Sub(LN(x))) leaves the residual stream unnormalized and trains stably; post-norm needs warmup and is fragile when deep.
- Residual connections give an identity term in the Jacobian product, so gradients always have an undiminished path to early layers.
Suggested path through this lesson
Two failure modes at step zero
Symmetry. Initialize every weight to the same value and every hidden unit in a layer computes the same function, receives the same gradient (4.4 Backpropagation, fully derived) and stays identical forever: the layer has one effective unit. Weights must be random. Biases can be zero.
Scale. Too small, and activations shrink layer by layer until the output is constant and gradients vanish; too large, and sigmoid/tanh units saturate (ReLU units explode). The question "how large should random weights be?" has an exact answer.
Variance propagation: deriving Xavier and He
Consider one pre-activation with weights and inputs independent, zero-mean. For independent zero-mean variables, (1.6 Probability I: random variables and distributions), and variances of independent terms add:
To keep the forward signal at constant scale we need , i.e. — LeCun initialization. The backward pass multiplies by (4.4 Backpropagation, fully derived), so by the same argument gradient variance is preserved when . Xavier/Glorot initialization compromises with the harmonic mean,
which is right for tanh/sigmoid units near their linear regime. ReLU zeroes half of every symmetric distribution, so and the signal halves per layer unless the weights compensate. He (Kaiming) initialization doubles the variance:
Math check. A 100-layer ReLU network with shrinks the activation variance by per layer: by the output. With He initialization the factor is per layer. The entire difference between "trains" and "does nothing" is a factor of 2 in a variance.
Three practical notes. PyTorch's default nn.Linear init is kaiming_uniform_ with a=√5, which works out to — conservative, and a historical quirk; set your initialization explicitly for anything deep. Residual networks add a further rule: scale the last layer of each residual branch down (GPT-2 multiplies the output projections by for blocks; many ResNets zero-initialize the last BatchNorm gain in each block) so that the sum of branches does not blow up the residual stream. And check your work: with correct initialization the initial loss of a -way classifier should be (4.8 Training dynamics and debugging).
Batch normalization
BatchNorm (Ioffe & Szegedy, 2015) standardizes each feature over the mini-batch and then lets the network undo it with learnable parameters. For a feature with batch values :
For convolutions the statistics are per channel, pooled over batch and spatial positions. At inference there is no batch to average over, so BatchNorm uses running estimates of and accumulated during training (momentum 0.1 in PyTorch) — which is what model.eval() switches on. The bias of a layer immediately before BatchNorm is redundant (it is subtracted off) and is normally omitted.
Worked example. Batch values : , , ; normalized ; with : .
Why it helps. The original story — reducing "internal covariate shift" — did not survive scrutiny. The better-supported explanation (Santurkar et al., 2018) is that BatchNorm makes the loss landscape smoother and the gradients more predictable, which allows learning rates 5–10× higher, makes training far less sensitive to initialization, and adds a mild regularizing noise because every example's normalization depends on its batch-mates.
Where it breaks. Small batches (noisy statistics; below 8–16 per device it degrades badly); sequence models with variable lengths and padding; train/test mismatch when the running statistics drift from the batch statistics; fine-tuning with frozen statistics; reinforcement learning with shifting data distributions; and distributed training, where per-device batches need synchronized BatchNorm to see the global statistics. Combining BatchNorm with dropout also interacts badly (variance shift). These failure cases are exactly why transformers do not use it.
LayerNorm, RMSNorm and GroupNorm
LayerNorm (Ba, Kiros & Hinton, 2016) normalizes across the features of a single example instead of across the batch:
No batch dependence, no running statistics, identical behaviour at training and inference, works for any sequence length — which is why it is the normalization inside transformers (7.4 Inside the block: residuals, LayerNorm, FFN and embeddings), applied to each token's -dimensional vector independently.
RMSNorm (Zhang & Sennrich, 2019) drops the mean subtraction and the :
It is cheaper (one reduction instead of two) and performs as well, so Llama, Mistral, Gemma, Qwen and DeepSeek all use it. Geometrically it projects each token vector onto a sphere of radius and lets rescale per dimension — the norm-division idea of 1.1 Vectors, dot products and norms.
GroupNorm splits channels into groups and normalizes within each group per example: the right choice for vision models trained with tiny batches (detection, segmentation; 5.5 Object detection, 5.6 Segmentation). InstanceNorm is GroupNorm with one channel per group and appears in style transfer.
| statistics over | batch-dependent | used in | |
|---|---|---|---|
| BatchNorm | batch (× space) per channel | yes | CNNs with large batches |
| LayerNorm | all features of one example | no | transformers, RNNs |
| RMSNorm | all features (no centering) | no | modern LLMs |
| GroupNorm | channel groups of one example | no | small-batch vision |
Pre-norm, post-norm and the residual stream
A transformer block adds a sub-layer's output to its input: . The original transformer applied normalization after the addition (post-norm, ), which normalizes the residual stream itself; deep post-norm stacks need careful warmup and still train unstably. Modern models use pre-norm, : the residual stream is never rescaled, each sub-layer sees a normalized input, and a final norm is applied before the output head (7.4 Inside the block: residuals, LayerNorm, FFN and embeddings).
The reason pre-norm is stable is the residual connection itself. With ,
so there is always an identity path along which the gradient reaches early layers undiminished, no matter how many layers sit in between — the Jacobian product of 4.2 Activation functions no longer has to stay near 1 on its own. Residual connections are the third pillar, with initialization and normalization, that makes depth work (5.3 Architectures: LeNet → AlexNet → VGG → Inception → ResNet → EfficientNet → ConvNeXt).
In code
import torch.nn as nn, math
def init_weights(m):
if isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight, nonlinearity="relu") # He: Var = 2/n_in
nn.init.zeros_(m.bias)
model.apply(init_weights)
# scale residual output projections in a GPT-style model with L blocks
for name, p in model.named_parameters():
if name.endswith("proj.weight"):
nn.init.normal_(p, mean=0.0, std=0.02 / math.sqrt(2 * L))
Where this shows up later
- Checking the initial loss and activation histograms is the first debugging step — 4.8 Training dynamics and debugging; regularization interacts with normalization (weight decay on scale-invariant layers) — 4.7 Regularization in deep nets.
- BatchNorm inside ResNets and GroupNorm in detectors — 5.3 Architectures: LeNet → AlexNet → VGG → Inception → ResNet → EfficientNet → ConvNeXt, 5.5 Object detection; LayerNorm/RMSNorm and pre-norm in the transformer block — 7.4 Inside the block: residuals, LayerNorm, FFN and embeddings; RMSNorm in the Llama recipe — 7.6 Decoder-only: GPT and autoregressive generation.
- Normalizing embeddings before cosine similarity — 9.2 Embeddings deep dive; the fixed-scale residual stream that interpretability research reads — 8.11 Interpretability: what is happening inside.
Common pitfalls (5)
- Forgetting model.eval() at test time, so BatchNorm keeps using batch statistics (and dropout stays on).
- Using BatchNorm with a per-device batch of 2–4 in detection/segmentation; switch to GroupNorm or synchronized BatchNorm.
- Relying on PyTorch's default Linear init for deep plain networks — it is 1/(3·n_in), too small for ReLU stacks.
- Keeping a bias in the layer right before BatchNorm (harmless but wasted) or dropping the bias after it (needed).
- Mixing dropout before BatchNorm: the variance shift between train and test hurts accuracy.
Recap: all 7 key points
- All-equal initialization makes hidden units identical forever; weights must be random, biases may be zero.
- Var(z) = n_in·Var(w)·Var(x): preserving scale needs Var(w) = 1/n_in (LeCun), 2/(n_in+n_out) (Xavier, for tanh/sigmoid) or 2/n_in (He, for ReLU).
- BatchNorm standardizes each feature over the batch, then rescales with γ, β; at inference it uses running statistics — remember model.eval().
- BatchNorm helps by smoothing the loss landscape (higher LR, less init sensitivity), not by removing 'covariate shift'; it fails for small batches and sequences.
- LayerNorm normalizes each example across its features (no batch dependence); RMSNorm drops the centering and is the LLM default; GroupNorm serves small-batch vision.
- Pre-norm (x + Sub(LN(x))) leaves the residual stream unnormalized and trains stably; post-norm needs warmup and is fragile when deep.
- Residual connections give an identity term in the Jacobian product, so gradients always have an undiminished path to early layers.
Watch
Derives Kaiming init empirically from activation histograms, then builds BatchNorm from scratch and shows its train/eval quirks. Essential.
Read
Exercises
- By hand: variance through three layers
Inputs have variance 1; a 3-layer ReLU network has widths 256, 256, 256. Compute the activation variance after each layer for . Expected: , , and respectively — only He init preserves scale.
- NumPy: activation histograms
Build a 10-layer tanh MLP (width 500) and a 10-layer ReLU MLP, each with Gaussian init of standard deviation 0.01, , and . Feed 1,000 random inputs and plot the histogram of activations at every layer (as in Karpathy's video). Expected: 0.01 collapses to zero; with ReLU shrinks; with ReLU stays healthy; tanh saturates when too large.
- PyTorch: BatchNorm from scratch
Implement a
BatchNorm1dmodule with running statistics and train/eval behaviour, and verify it matchesnn.BatchNorm1don random data in both modes (tolerance 1e-5). Then train a 6-layer MLP with and without it at learning rates 0.1 and 1.0. Expected: without BN, lr 1.0 diverges; with BN it trains. - Experiment: pre-norm vs post-norm
Using a tiny transformer (e.g. from 7.8 Build GPT from scratch (nanoGPT walkthrough) or nanoGPT) with 12 layers and no warmup, train for 500 steps with pre-norm and with post-norm. Expected: post-norm loss spikes or diverges while pre-norm trains smoothly; adding warmup rescues post-norm.
Quiz
Flashcards
These cards are now in your spaced-repetition queue. Review all due cards →