# Pre-LN and Post-LN Transformer Architectures: Key Differences and Implementation Guide

> Understand the key differences between Pre-LN and Post-LN transformer architectures. Learn how Pre-LN improves stable training of deep models and explore implementation guides.

- Repository: [labml.ai/annotated_deep_learning_paper_implementations](https://github.com/labmlai/annotated_deep_learning_paper_implementations)
- Tags: deep-dive
- Published: 2026-03-04

---

**Pre-LN applies Layer Normalization before attention and feed-forward sub-layers to enable stable training of deep transformers, while Post-LN applies normalization after the residual connection following the original "Attention Is All You Need" design.**

The architectural choice between **Pre-LN and Post-LN transformer architectures** determines the placement of Layer Normalization relative to residual connections, directly impacting gradient flow and training stability in deep neural networks. This article examines the implementation differences using the **labmlai/annotated_deep_learning_paper_implementations** repository, which provides annotated PyTorch implementations demonstrating how these design patterns affect model scalability.

## Architectural Fundamentals: Pre-LN vs Post-LN

Transformer blocks consist of three core components: a multi-head self-attention sub-layer, a feed-forward sub-layer, and residual connections. The position of the Layer Normalization (LN) operation relative to these sub-layers defines the two major variants.

### Pre-LN (Pre-Normalization) Architecture

In **Pre-LN** transformers, Layer Normalization is applied **before** each sub-layer. The computation follows the pattern: `LN → Sub-layer → Add residual`.

- The residual connection is added **after** the normalized sub-layer output
- Normalizes the input to each sub-layer, keeping the residual path clean
- Enables stable training of very deep stacks (≥30 layers)
- Often eliminates the need for large learning-rate warm-ups

### Post-LN (Post-Normalization) Architecture

In **Post-LN** transformers, Layer Normalization is applied **after** each sub-layer. The computation follows the pattern: `Sub-layer → Add residual → LN`.

- The residual connection is added **before** the normalization
- Represents the original formulation from *"Attention Is All You Need"* (Vaswani et al., 2017)
- Works well for shallow models (≤12 layers)
- Can suffer from training instability for deeper networks due to gradient shrinking

## Training Dynamics and Gradient Flow

The placement of Layer Normalization significantly affects how gradients propagate through the network during backpropagation.

### Gradient Propagation in Deep Stacks

In **Post-LN** architectures, the residual path bypasses the LN operation. Consequently, gradients must pass through the Layer Normalization after the addition operation. With many layers, the LN can shrink gradient magnitudes, leading to vanishing-gradient problems that destabilize training.

In **Pre-LN** architectures, the normalization occurs before the sub-layer, preserving the clean residual path. This design maintains gradient magnitude throughout deep stacks, preventing the degradation that occurs in Post-LN variants.

### Learning Rate Schedule Requirements

**Post-LN** transformers typically require careful learning-rate warm-up phases and complex scheduling to prevent early-training instabilities. The **Pre-LN** architecture often converges faster and tolerates simpler, more aggressive learning-rate schedules due to its improved gradient flow characteristics.

### Model Depth Scalability

Empirical results demonstrate distinct scalability limits: **Pre-LN** versions (such as those in Transformer-XL and GPT-2) can be stacked to depths exceeding 30 layers without instability, whereas **Post-LN** models typically degrade in performance after approximately 12 layers.

## LabML Implementation Details

The **labmlai/annotated_deep_learning_paper_implementations** repository adopts **Pre-LN** for all transformer variants, providing clean, annotated PyTorch implementations.

### Core Transformer Block in models.py

In [`labml_nn/transformers/models.py`](https://github.com/labmlai/annotated_deep_learning_paper_implementations/blob/main/labml_nn/transformers/models.py), the `TransformerLayer` class implements the Pre-LN pattern at lines 66-92. Each attention and feed-forward sub-layer is wrapped by `nn.LayerNorm` **before** the sub-layer computation, with residuals added afterwards.

The implementation creates distinct normalization layers for each sub-path:

```python
class TransformerLayer(nn.Module):
    def __init__(self, d_model, n_heads, d_ff):
        super().__init__()
        self.norm_self_attn = nn.LayerNorm([d_model])   # Pre-norm for attention

        self.attn = MultiHeadAttention(d_model, n_heads)

        self.norm_ff = nn.LayerNorm([d_model])          # Pre-norm for feed-forward

        self.ff = FeedForward(d_model, d_ff)

    def forward(self, x, mask=None):
        # Self-attention sub-layer: LN first, then attention, then residual

        y = self.norm_self_attn(x)
        y = self.attn(y, y, y, mask)
        x = x + y

        # Feed-forward sub-layer: LN first, then FFN, then residual

        y = self.norm_ff(x)
        y = self.ff(y)
        x = x + y
        return x

```

### XL and Switch Transformer Variants

The same Pre-LN pattern appears in [`labml_nn/transformers/xl/__init__.py`](https://github.com/labmlai/annotated_deep_learning_paper_implementations/blob/main/labml_nn/transformers/xl/__init__.py) (lines 67-119) for the Transformer-XL implementation, where both self-attention and feed-forward sub-layers follow the normalization-first pattern plus a final LN after the last layer. The Switch Transformer implementation in [`labml_nn/transformers/switch/__init__.py`](https://github.com/labmlai/annotated_deep_learning_paper_implementations/blob/main/labml_nn/transformers/switch/__init__.py) also maintains this Pre-LN consistency across the codebase.

## Practical Code Examples

### Pre-LN Block Implementation

The LabML repository implements the modern Pre-LN design that powers deep transformer models:

```python
class TransformerLayer(nn.Module):
    def __init__(self, d_model, n_heads, d_ff):
        super().__init__()
        self.norm_self_attn = nn.LayerNorm([d_model])
        self.attn = MultiHeadAttention(d_model, n_heads)
        self.norm_ff = nn.LayerNorm([d_model])
        self.ff = FeedForward(d_model, d_ff)

    def forward(self, x, mask=None):
        # Self-attention with Pre-LN

        y = self.norm_self_attn(x)
        y = self.attn(y, y, y, mask)
        x = x + y  # Residual added after normalized sub-layer

        # Feed-forward with Pre-LN

        y = self.norm_ff(x)
        y = self.ff(y)
        x = x + y  # Residual added after normalized sub-layer

        return x

```

This implementation corresponds to the source at `labml_nn/transformers/models.py#L66-L92`.

### Post-LN Block Implementation

While not used in the LabML codebase, the original Post-LN formulation from the 2017 Transformer paper follows this pattern:

```python
class TransformerLayerPost(nn.Module):
    def __init__(self, d_model, n_heads, d_ff):
        super().__init__()
        self.attn = MultiHeadAttention(d_model, n_heads)
        self.ff = FeedForward(d_model, d_ff)
        self.norm = nn.LayerNorm([d_model])

    def forward(self, x, mask=None):
        # Sub-layer computation first

        y = self.attn(x, x, x, mask)
        x = x + y  # Residual added before normalization

        
        y = self.ff(x)
        x = x + y  # Residual added before normalization

        
        # Apply LN after the addition

        return self.norm(x)

```

This Post-LN style requires careful initialization and learning-rate management for models exceeding 12 layers.

## Summary

- **Pre-LN** places Layer Normalization **before** each sub-layer (attention and feed-forward), leading to better gradient flow, stable training of deep transformers (>30 layers), and simpler learning-rate schedules.
- **Post-LN** places Layer Normalization **after** each sub-layer (the original Vaswani et al. 2017 design), which works well for shallow models but can become unstable for deeper stacks due to gradient shrinking through the normalization layers.
- The **labmlai/annotated_deep_learning_paper_implementations** repository exclusively uses **Pre-LN** across its transformer implementations in [`labml_nn/transformers/models.py`](https://github.com/labmlai/annotated_deep_learning_paper_implementations/blob/main/labml_nn/transformers/models.py), [`labml_nn/transformers/xl/__init__.py`](https://github.com/labmlai/annotated_deep_learning_paper_implementations/blob/main/labml_nn/transformers/xl/__init__.py), and [`labml_nn/transformers/switch/__init__.py`](https://github.com/labmlai/annotated_deep_learning_paper_implementations/blob/main/labml_nn/transformers/switch/__init__.py).
- Pre-LN architectures preserve gradient magnitude through clean residual paths, while Post-LN architectures can suffer from vanishing gradients in deep networks.

## Frequently Asked Questions

### Why does Post-LN require learning rate warm-up?

Post-LN architectures place Layer Normalization after the residual addition, which can cause gradients to shrink as they propagate backward through many layers. According to the source analysis, this requires small initial learning rates that gradually warm up to prevent early-training instabilities and vanishing gradients. Pre-LN avoids this by normalizing inputs before sub-layers, keeping residual paths clean and allowing higher initial learning rates.

### Can I convert a Post-LN checkpoint to Pre-LN?

Converting between architectures is not straightforward because the normalization operations apply to different intermediate representations. Pre-LN normalizes inputs to sub-layers, while Post-LN normalizes outputs after residual addition. Retraining is typically required, though some research explores architecture-agnostic initialization techniques. The LabML implementations in [`labml_nn/transformers/models.py`](https://github.com/labmlai/annotated_deep_learning_paper_implementations/blob/main/labml_nn/transformers/models.py) follow Pre-LN exclusively to avoid these compatibility issues.

### Which architecture performs better for small models?

For shallow transformers (≤12 layers), both architectures can achieve comparable performance. Post-LN remains common in standard 6-12 layer encoder-decoder models following the original "Attention Is All You Need" specification. However, as shown in the LabML codebase, Pre-LN has become the default even for standard depths due to its superior training stability and reduced sensitivity to hyperparameter tuning.

### Does Pre-LN change the model capacity or just training dynamics?

Pre-LN primarily affects training dynamics and optimization rather than model capacity. Both architectures maintain the same parameter counts and representational capacity. However, Pre-LN enables effective utilization of deeper architectures (30+ layers) that Post-LN cannot train stably, indirectly allowing greater model capacity in practice. The feed-forward and attention mechanisms remain identical between variants, as seen in [`labml_nn/transformers/models.py`](https://github.com/labmlai/annotated_deep_learning_paper_implementations/blob/main/labml_nn/transformers/models.py).