← All Posts
ML · Transformers · Building Blocks · Part 6 of 8

Layer Normalization

Why Normalization Matters

Deep neural networks are stacks of transformations. Each layer takes its input, multiplies by a weight matrix, adds a bias, and applies a non-linearity. The output of one layer becomes the input of the next. When the network has 12, 24, or 96 layers, the composition of all these transformations creates an acute problem: activation drift.

Consider what happens without normalization. If the weights of a layer tend to amplify their input slightly - say, by a factor of 1.05 on average - then after 50 layers the activations have grown by 1.0550 ≈ 11.5×. After 100 layers, 1.05100 ≈ 131×. The values explode. Conversely, if each layer shrinks activations by a factor of 0.95, after 100 layers you have 0.95100 ≈ 0.006 - the signal effectively vanishes.

This phenomenon is sometimes called internal covariate shift (the term from the original Batch Normalization paper, Ioffe & Szegedy, 2015). The idea is that the distribution of inputs to each layer changes during training as the parameters of all preceding layers are updated. Each layer is trying to learn a mapping, but the input distribution it is learning from is constantly shifting under its feet. The optimizer has to chase a moving target.

What Normalization Does

Normalization layers act as stabilizers. They take the activations at a given point in the network, compute their statistics (mean, variance), and re-center and re-scale them to a standard distribution. This provides three concrete benefits:

Without normalization, training very deep networks (beyond 10-20 layers) is extremely fragile. You need meticulous weight initialization, tiny learning rates, and gradient clipping. With normalization, you can stack hundreds of layers and train with standard hyperparameters.

Core intuition: Normalization is not about making the model more expressive. It is about making the optimization landscape more navigable. The model can learn the same functions without normalization - it is just much, much harder for the optimizer to find them.

Batch Normalization: The Predecessor

Before Layer Normalization, the dominant approach was Batch Normalization (BN), introduced by Ioffe and Szegedy in 2015. BN was a breakthrough that made training deep convolutional networks practical and helped enable architectures like ResNet, Inception-v2, and many others.

How Batch Normalization Works

BN normalizes across the batch dimension for each feature independently. Suppose you have a mini-batch of 32 training samples, each represented as a vector of d features. BN takes each feature j (j = 0, 1, ..., d-1), looks at the 32 values that feature takes across all samples in the batch, and normalizes those 32 values to zero mean and unit variance.

For feature j, across batch of B samples: μ_j = (1/B) ∑_{i=1}^{B} x_{i,j} (mean of feature j across batch) σ_j² = (1/B) ∑_{i=1}^{B} (x_{i,j} - μ_j)² (variance of feature j) y_{i,j} = γ_j · (x_{i,j} - μ_j) / √(σ_j² + ε) + β_j

Here γj and βj are learned per-feature scale and shift parameters. The key word is across the batch: the statistics depend on what other samples happen to be in the same mini-batch.

Training vs Inference

During training, BN uses the mini-batch statistics (μbatch, σ2batch). During inference, you don’t have a batch - you might be processing a single input. So BN maintains running averages of the mean and variance computed during training, and uses those fixed statistics at inference time.

This works well for CNNs because the batch statistics are stable: each feature in a convolutional layer corresponds to a specific spatial filter response, and the distribution of that response is relatively consistent across different batches of images.

Why BN Works for CNNs

Why BatchNorm Fails for Transformers

When researchers attempted to apply Batch Normalization to sequence models - first RNNs, then Transformers - they found that it performed poorly. There are three fundamental reasons why BN is a bad fit for the Transformer architecture:

Problem 1: Variable Sequence Lengths

In a batch of text data, sentences have different lengths. "I agree" has 2 tokens. "The quick brown fox jumps over the lazy dog" has 9 tokens. In practice, shorter sequences are padded with special [PAD] tokens to match the length of the longest sequence in the batch.

Now consider what happens when BN tries to compute batch statistics for, say, position 8. Only a few sequences in the batch actually have meaningful content at position 8 - the rest are padding. The batch mean and variance at that position are dominated by padding tokens, which carry no linguistic information. The statistics are noisy and meaningless.

For CNNs, this isn’t a problem because every image has the same resolution. For text, variable length is the norm, not the exception.

Problem 2: Small Batch Sizes

Transformers are memory-hungry. A single forward pass through a large Transformer with long sequences can consume tens of gigabytes of GPU memory. This forces practitioners to use small batch sizes - often 1, 2, 4, or 8 sequences per GPU.

Batch Normalization computes statistics over the batch dimension. With a batch size of 4, you are estimating the mean and variance of a distribution from just 4 samples. The estimates are extremely noisy. Training becomes unstable because the normalization statistics fluctuate wildly from one mini-batch to the next. The gradients oscillate, the loss spikes, and the model may diverge entirely.

For BN to work well, you need batch sizes of at least 16-32. Transformer training frequently operates below this threshold.

Problem 3: Autoregressive Decoding

At inference time, autoregressive models (like GPT) generate text one token at a time. Each forward pass processes just the new token (with KV-cache for previous tokens). The effective batch size is 1.

BN handles this by using running averages computed during training. But these running averages were computed over training batches - they reflect the average statistics across many different sequences. At inference time, the actual activation statistics for the specific sequence being generated may differ substantially from these averages. The mismatch introduces systematic errors that accumulate over long generated sequences.

Furthermore, BN’s running averages create a train-test discrepancy: the normalization behaves differently during training (batch stats) and inference (running averages). This asymmetry is especially harmful for language models, where the quality of each token generation directly depends on the accuracy of the preceding computations.

The core insight: Batch Normalization couples the normalization of one sample to the other samples in the batch. For Transformers, we need a normalization that treats each sample independently. This is exactly what Layer Normalization provides.

Layer Normalization: The Formula

Layer Normalization (Ba et al., 2016) takes a fundamentally different approach from Batch Normalization. Instead of normalizing across the batch dimension for each feature, it normalizes across the feature dimension for each sample independently. No batch statistics, no running averages, no dependence on other samples.

Setup

Consider a single token’s representation at some point in the network: a vector x of dimension d (the model dimension, d_model). In a Transformer with d_model = 768, this is a 768-dimensional vector. Layer Normalization operates on this single vector.

Step 1: Compute the Mean

μ = (1/d) ∑_{i=1}^{d} x_i

Sum all d elements of the vector and divide by d. This gives the average activation value across the feature dimension. For a 768-dimensional vector, you are averaging 768 numbers.

Step 2: Compute the Variance

σ² = (1/d) ∑_{i=1}^{d} (x_i - μ)²

Subtract the mean from each element, square the result, and average. This measures how spread out the activations are. A large variance means the values are widely dispersed; a small variance means they are clustered near the mean.

Step 3: Normalize

x̂_i = (x_i - μ) / √(σ² + ε)

Subtract the mean (centering: makes the mean zero) and divide by the standard deviation (scaling: makes the variance one). The ε term (typically 10−5) prevents division by zero when all elements happen to be identical (variance = 0).

After this step, the normalized vector has mean ≈ 0 and variance ≈ 1 across its features.

Step 4: Scale and Shift (Affine Transform)

y_i = γ_i · x̂_i + β_i

The final output is an element-wise affine transformation using learned parameters γ (scale) and β (shift), both vectors of dimension d. These allow the network to undo the normalization if it is beneficial - we will discuss why this matters in the Learned Parameters section.

The Complete Formula

LayerNorm(x)_i = γ_i · (x_i - μ) / √(σ² + ε) + β_i where μ = (1/d) ∑ x_i σ² = (1/d) ∑ (x_i - μ)² γ, β ∈ ℝ^d (learned) ε = 1e-5 (numerical stability)

Key Properties

Numerical Walkthrough

Let’s work through a concrete example to make the formula tangible. We’ll use a small 4-dimensional vector (in practice d_model is 768 or larger, but the math is identical).

Input Vector

x = [2.0,  4.0,  −1.0,  3.0]   (d = 4)

Step 1: Mean

μ = (2.0 + 4.0 + (−1.0) + 3.0) / 4 = 8.0 / 4 = 2.0

Step 2: Variance

Deviations from the mean: [2.0−2.0,  4.0−2.0,  −1.0−2.0,  3.0−2.0] = [0.0,  2.0,  −3.0,  1.0]

Squared deviations: [0.0,  4.0,  9.0,  1.0]

σ² = (0.0 + 4.0 + 9.0 + 1.0) / 4 = 14.0 / 4 = 3.5

σ = √3.5 ≈ 1.8708

Step 3: Normalize (with ε = 1e−5)

0 = (2.0 − 2.0) / √(3.5 + 1e−5) = 0.0 / 1.8708 ≈ 0.0000

1 = (4.0 − 2.0) / 1.8708 = 2.0 / 1.8708 ≈ 1.0690

2 = (−1.0 − 2.0) / 1.8708 = −3.0 / 1.8708 ≈ −1.6036

3 = (3.0 − 2.0) / 1.8708 = 1.0 / 1.8708 ≈ 0.5345

Step 4: Scale and Shift

Assume γ = [1.0, 1.0, 1.0, 1.0] and β = [0.0, 0.0, 0.0, 0.0] (initial values):

y = γ ⊙ x̂ + β = [0.0000,  1.0690,  −1.6036,  0.5345]

With γ = [0.5, 2.0, 1.5, 0.8] and β = [0.1, −0.3, 0.0, 0.2]:

y = [0.5×0.0+0.1,  2.0×1.069−0.3,  1.5×(−1.604)+0.0,  0.8×0.535+0.2]

y = [0.1000,  1.8381,  −2.4053,  0.6276]

Full Computation Table

Feature index x_i x_i − μ (x_i − μ)² x̂_i (normalized) y_i (γ=1, β=0)
0 2.0 0.0 0.0 0.0000 0.0000
1 4.0 2.0 4.0 1.0690 1.0690
2 -1.0 -3.0 9.0 -1.6036 -1.6036
3 3.0 1.0 1.0 0.5345 0.5345
Sum / Stats μ = 2.0 σ² = 3.5 mean ≈ 0.0 var ≈ 1.0

Verify: the normalized values sum to approximately 0 (mean = 0) and their variance is approximately 1. This is the invariant that Layer Normalization enforces at every layer, for every token, at every training step.

BatchNorm vs LayerNorm: Visual Comparison

The key difference between BatchNorm and LayerNorm is the axis of normalization. This SVG diagram makes it crystal clear. Imagine a matrix where rows are samples (or tokens) and columns are features:

Normalization Axis Comparison Batch Normalization Layer Normalization Sample 1 Sample 2 Sample 3 Sample 4 normalize this column f1 f2 f3 f4 f5 Sample 1 Sample 2 Sample 3 Sample 4 normalize this row f1 f2 f3 f4 f5 Stats computed across BATCH (one μ, σ² per feature) Stats computed across FEATURES (one μ, σ² per sample) Depends on batch → noisy with small B Independent per sample → always stable

BatchNorm (left): For each feature column, compute the mean and variance over all samples in the batch. The highlighted red column shows feature f1 being normalized across all 4 samples. This couples every sample to every other sample in the batch.

LayerNorm (right): For each sample row, compute the mean and variance over all features. The highlighted blue row shows sample 1 being normalized across all 5 features. Each sample is self-contained - no coupling to other samples.

Property BatchNorm LayerNorm
Normalize across Batch dimension Feature dimension
Statistics per Feature (d params) Sample (computed on the fly)
Batch dependency Yes (coupled) No (independent)
Running averages Yes (for inference) No
Train/test behavior Different Identical
Works with batch=1 Poorly Perfectly
Variable-length sequences Problematic No issue
Learned parameters γ, β ∈ ℝ^d γ, β ∈ ℝ^d

Interactive Animation: LayerNorm Step by Step

The animation below visualizes Layer Normalization applied to a 6-element vector. Each step of the computation is shown as a transformation of the bar chart: the bars shift, compress, and scale as the normalization proceeds. Use the Step button to advance through each stage and the Reset button to start over.

LayerNorm Visualization

Watch how raw activations get centered, scaled, and transformed through each normalization step.

Step 0 / 5

The Learned Parameters γ and β

After normalizing to zero mean and unit variance, why do we immediately apply a learned scale (γ) and shift (β)? Doesn’t this just undo the normalization?

Why They Are Necessary

Normalization constrains the output to have mean 0 and variance 1. But the optimal representation at a given layer might not have mean 0 and variance 1. Perhaps some features should be larger than others. Perhaps the mean should be shifted away from zero for a particular dimension that represents a bias-like signal.

Without γ and β, the network loses expressiveness. The normalization forces all layers to produce outputs in a very narrow band, which can limit the functions the network can represent. In the worst case, it could prevent the network from learning the identity function (outputting its input unchanged), which is important for residual connections.

The Identity Initialization Trick

γ is initialized to all 1s and β is initialized to all 0s. This means that at initialization:

y_i = 1.0 · x̂_i + 0.0 = x̂_i

The affine transform is the identity - it does nothing. The output is just the normalized vector. During training, the optimizer adjusts γ and β to find the optimal scale and shift for each feature dimension. Some dimensions might end up with γ > 1 (amplified), others with γ < 1 (suppressed). Some might have positive β, others negative.

Parameter Count

Each LayerNorm has 2 × d_model learnable parameters (γ and β, each of size d_model). For a Transformer with d_model = 768, that’s 1,536 parameters per LayerNorm. A standard GPT-2 Small has about 25 LayerNorm layers (2 per Transformer block, plus 1 final), so the total LayerNorm parameters are roughly 25 × 1,536 = 38,400 - a tiny fraction of the model’s 117M total parameters.

Key insight: The learned γ and β give the normalization a “pressure release valve.” The normalization provides stable training dynamics. The learned parameters let the model recover any representation it needs. Together, they achieve both stability and expressiveness.

What Does the Network Learn?

In practice, after training, the γ values tend to cluster around 1 (many features remain at roughly unit scale) but with meaningful deviations. Some features consistently get amplified (γ ≈ 1.5–2.0), indicating they carry important signals. Others get suppressed (γ ≈ 0.3–0.5). The β values remain near 0 on average but develop per-feature biases that reflect the learned data distribution.

Researchers have even used the learned γ values as a form of feature importance analysis: features with large γ are features the model considers important.

RMSNorm: A Simpler Alternative

In 2019, Zhang and Sennrich proposed Root Mean Square Layer Normalization (RMSNorm), a simplified version of LayerNorm that has since been adopted by many high-profile models including LLaMA, LLaMA 2, T5, PaLM, and Gemma.

The Key Insight

Standard LayerNorm performs two operations: re-centering (subtracting the mean) and re-scaling (dividing by the standard deviation). Zhang and Sennrich hypothesized that the re-centering step is less important than the re-scaling step. Their experiments confirmed this: removing mean subtraction had minimal impact on model quality, but removing the variance normalization was catastrophic.

The RMSNorm Formula

RMS(x) = √( (1/d) ∑_{i=1}^{d} x_i² ) RMSNorm(x)_i = γ_i · x_i / RMS(x)

That’s it. No mean subtraction, no β parameter. Just divide each element by the root-mean-square of the vector, then scale by γ.

Why It’s Faster

RMSNorm skips two operations that LayerNorm must perform:

  1. Mean computation: LayerNorm computes μ = (1/d)∑xi, which requires a full reduction over the feature dimension.
  2. Mean subtraction: LayerNorm subtracts μ from every element, which requires a full pass over the vector.

RMSNorm replaces both with a single computation: ∑xi², which is just the sum of squares - one reduction, no subtraction pass. In practice, this yields a 10–15% speedup in the normalization operation, which matters at scale because LayerNorm is called many times per forward pass.

RMSNorm Numerical Example

Using the same vector x = [2.0, 4.0, −1.0, 3.0] with γ = [1, 1, 1, 1]:

RMSNorm Walkthrough

Sum of squares: 2² + 4² + (−1)² + 3² = 4 + 16 + 1 + 9 = 30

RMS = √(30/4) = √7.5 ≈ 2.7386

Normalized: [2.0/2.739, 4.0/2.739, −1.0/2.739, 3.0/2.739] = [0.7303, 1.4606, −0.3651, 1.0954]

LayerNorm vs RMSNorm Comparison

LayerNorm

  • Compute mean μ
  • Subtract mean (re-center)
  • Compute variance σ²
  • Divide by √(σ²+ε) (re-scale)
  • Apply γ and β
  • Parameters: 2 × d (both γ and β)

RMSNorm

  • Compute sum of squares
  • Divide by RMS (re-scale)
  • Apply γ only
  • No mean subtraction
  • No β parameter
  • Parameters: 1 × d (only γ)
Feature LayerNorm output RMSNorm output Difference
x_0 = 2.0 0.0000 0.7303 0.7303
x_1 = 4.0 1.0690 1.4606 0.3916
x_2 = -1.0 -1.6036 -0.3651 1.2385
x_3 = 3.0 0.5345 1.0954 0.5609

Notice that the RMSNorm outputs are not zero-centered - the mean of the RMSNorm output is 0.7303 rather than 0. This is exactly what we expect, since RMSNorm does not subtract the mean. In practice, the learned γ parameters and subsequent layers compensate for this shift, and the model performs equally well.

Adoption trend: RMSNorm has become the default normalization in most modern large language models. LLaMA (all versions), Mistral, Gemma, T5 v1.1, and PaLM all use RMSNorm. The combination of equivalent quality with reduced computation makes it a strict improvement over standard LayerNorm for most practical purposes.

Pre-Norm vs Post-Norm

Where you place the LayerNorm in relation to the sublayer (attention or FFN) and the residual connection has a profound impact on training stability and final model quality. There are two main configurations:

Post-Norm (Original Transformer)

output = LayerNorm(x + Sublayer(x))

The original “Attention Is All You Need” paper (Vaswani et al., 2017) used Post-Norm. The residual and the sublayer output are summed first, and then the sum is normalized. The data flow is:

  1. Input x passes through the sublayer (e.g., multi-head attention): produces Sublayer(x).
  2. Add the residual: x + Sublayer(x).
  3. Normalize the sum: LayerNorm(x + Sublayer(x)).

The problem: every path from input to output passes through LayerNorm. During backpropagation, the gradient must flow through the LayerNorm Jacobian at every layer. The Jacobian of LayerNorm is a dense matrix that modifies gradient direction and magnitude. Across many layers, these Jacobians compound, and the gradient signal can degrade.

Pre-Norm (Modern Standard)

output = x + Sublayer(LayerNorm(x))

Pre-Norm applies the normalization before the sublayer. The residual connection bypasses both the normalization and the sublayer. The data flow is:

  1. Normalize the input: LayerNorm(x).
  2. Pass the normalized input through the sublayer: Sublayer(LayerNorm(x)).
  3. Add the residual (the original, unnormalized input): x + Sublayer(LayerNorm(x)).

The crucial difference: the residual path is a direct, unmodified connection from input to output. During backpropagation, gradients can flow straight through the residual path without passing through any LayerNorm or sublayer Jacobian. This is similar to how skip connections in ResNets provide a gradient highway.

Why Pre-Norm Is More Stable

Post-Norm: Gradient Path

Gradient must pass through:

∂LN/∂z · (∂Sublayer/∂x + I)
at every layer.

The LN Jacobian modifies the gradient at each layer, creating potential for degradation over many layers.

Pre-Norm: Gradient Path

Gradient has a clean path:

I + ∂Sublayer/∂(LN(x)) · ∂LN/∂x

The identity (I) term provides a gradient highway. Even if the sublayer gradient vanishes, the identity term persists.

For shallow models (6–12 layers), both Post-Norm and Pre-Norm work fine. For deep models (32+ layers), Pre-Norm is significantly more stable and often the only configuration that converges without extensive warmup and careful learning rate tuning. GPT-2, GPT-3, LLaMA, and most modern LLMs use Pre-Norm.

The Performance Trade-off

There is a subtle trade-off. Some research (Xiong et al., 2020) has found that Post-Norm can achieve slightly better final performance when training is stable. The hypothesis is that passing gradients through LayerNorm at every layer provides a form of implicit regularization. Pre-Norm’s “clean” gradient highway makes optimization easier but may allow the model to overfit slightly more.

In practice, the stability advantage of Pre-Norm far outweighs the marginal quality difference, especially at scale. When training a 175B parameter model, you cannot afford to have training diverge after days of computation.

Aspect Post-Norm Pre-Norm
Formula LN(x + Sublayer(x)) x + Sublayer(LN(x))
Gradient highway No (gradient passes through LN) Yes (identity path)
Training stability Requires warmup, careful tuning Robust, easy to train
Deep networks (32+ layers) Often diverges Converges reliably
Final quality (matched configs) Slightly better (if stable) Very slightly worse
Used in practice Original Transformer, BERT GPT-2/3, LLaMA, Mistral, PaLM

Gradient Flow Analysis

Understanding how LayerNorm affects gradient flow is important for understanding why it stabilizes training. The gradient dynamics are more nuanced than simply “keeping activations bounded.”

The LayerNorm Jacobian

For a vector x of dimension d, the Jacobian of the normalization step (ignoring γ and β) is:

∂x̂ / ∂x = (1/σ) (I - (1/d) 11ᵀ - x̂ x̂ᵀ / d) where I = identity matrix 1 = vector of all ones 11ᵀ = outer product (matrix of all 1/d) x̂ = normalized vector

This Jacobian has three components:

Why This Prevents Explosion and Vanishing

The 1/σ scaling factor is the key. Consider two scenarios:

Combined with residual connections, this creates a powerful stabilization loop. The residual connection provides a gradient highway (gradients can bypass sublayers entirely). LayerNorm ensures that gradients passing through sublayers are neither too large nor too small. Together, gradient magnitude stays well-bounded across dozens or hundreds of layers.

Empirical Evidence

Measurements on trained Transformers confirm this analysis. The gradient norm per layer remains remarkably flat across all layers in a Pre-Norm Transformer, varying by less than a factor of 2 from the first layer to the last. Without LayerNorm, the gradient norm can vary by orders of magnitude, with later layers receiving vanishingly small gradients.

Summary of gradient effects: LayerNorm acts as an adaptive gradient normalizer. It compresses large gradients, amplifies small ones, centers them, and removes redundant components. This creates a stable gradient signal that enables training of very deep networks with standard optimizers and learning rates.

Implementation

Clean pseudo-code for both LayerNorm and RMSNorm, following the conventions used in production frameworks.

Layer Normalization

class LayerNorm:
    """Layer Normalization (Ba et al., 2016)"""

    def __init__(self, d_model, eps=1e-5):
        self.gamma = ones(d_model)      # learnable scale, shape (d,)
        self.beta  = zeros(d_model)     # learnable shift, shape (d,)
        self.eps   = eps

    def forward(self, x):
        # x shape: (..., d_model)
        # Compute statistics over the LAST dimension

        mu    = mean(x, dim=-1, keepdim=True)    # mean across features
        var   = var(x, dim=-1, keepdim=True)     # variance across features

        # Normalize: zero mean, unit variance
        x_hat = (x - mu) / sqrt(var + self.eps)

        # Affine transform: learned scale and shift
        return self.gamma * x_hat + self.beta

The key detail: the mean and variance are computed over dim=-1, which is the feature (d_model) dimension. The keepdim=True ensures broadcasting works correctly when subtracting the mean and dividing by the standard deviation.

RMS Normalization

class RMSNorm:
    """Root Mean Square Layer Normalization (Zhang & Sennrich, 2019)
    Used in LLaMA, T5, PaLM, Gemma, Mistral"""

    def __init__(self, d_model, eps=1e-5):
        self.gamma = ones(d_model)      # learnable scale, shape (d,)
        self.eps   = eps               # no beta parameter!

    def forward(self, x):
        # x shape: (..., d_model)

        # Root mean square: sqrt(mean(x^2))
        rms = sqrt(mean(x ** 2, dim=-1, keepdim=True) + self.eps)

        # Normalize and scale (no shift)
        return self.gamma * (x / rms)

Notice how much simpler RMSNorm is. One reduction (mean of squares), one sqrt, one division, one element-wise multiply. No mean subtraction, no separate variance computation, no β addition. This simplicity is why it’s faster and why it’s becoming the default.

Where LayerNorm Appears in a Transformer Block

class TransformerBlock:
    """Pre-Norm Transformer block (GPT-2 style)"""

    def __init__(self, d_model, n_heads, d_ff):
        self.ln1  = LayerNorm(d_model)
        self.attn = MultiHeadAttention(d_model, n_heads)
        self.ln2  = LayerNorm(d_model)
        self.ffn  = FeedForward(d_model, d_ff)

    def forward(self, x):
        # Pre-Norm: normalize BEFORE sublayer
        # Residual: add ORIGINAL input (not normalized)

        x = x + self.attn(self.ln1(x))   # attention sub-block
        x = x + self.ffn(self.ln2(x))    # FFN sub-block
        return x

Each Transformer block has two LayerNorm instances: one before attention, one before the feed-forward network. In a 12-layer Transformer, that’s 24 LayerNorm operations plus typically one final LayerNorm after the last block, for a total of 25.

Summary

Bottom line: Layer Normalization is a small operation (one mean, one variance, one affine transform) with an outsized impact. Without it, training deep Transformers is impractical. With it, we can stack 96 layers and train billion-parameter models stably. It is one of the essential building blocks that makes the Transformer architecture work.