MLOps Infrastructure & Engineering 11 Aug 2026

Breaking the Memory Wall: A Deep Dive into ZeRO (Zero Redundancy Optimizer)

#Distributed Training #Memory Optimization #Large Language Models #Data Parallelism #Model Parallelism #Deep Learning #ZeRO Optimizer #Scalability

Breaking the Memory Wall: A Deep Dive into ZeRO (Zero Redundancy Optimizer)

In the era of Large Language Models (LLMs), we are facing a brutal reality: the "Memory Wall." As models scale to billions of parameters, they no longer fit on a single GPU. While Data Parallelism (DP) allows us to scale training across multiple GPUs, it introduces a massive inefficiency—every single GPU stores a complete copy of the model states.

Enter ZeRO (Zero Redundancy Optimizer). ZeRO is the architectural breakthrough that allows us to train massive models by eliminating memory redundancies without the communication overhead typically associated with Model Parallelism.


The Core Intuition: From Replication to Partitioning

In standard Data Parallelism, if you have 8 GPUs, you have 8 identical copies of the optimizer states, gradients, and parameters. This is highly redundant.

ZeRO’s fundamental thesis is simple: Why replicate when you can partition?

Instead of every GPU owning everything, ZeRO partitions the model states across the available GPUs. Each GPU becomes the "owner" of a specific shard. During the forward and backward passes, GPUs communicate just-in-time to fetch the shards they need, effectively treating the aggregate memory of the entire cluster as one giant, unified pool.

The ZeRO Hierarchy: Three Stages of Optimization

ZeRO doesn't apply a one-size-fits-all approach. It introduces three stages of increasing memory efficiency:

Stage What is Partitioned? Memory Reduction Key Benefit
Stage 1 Optimizer States ($P_{os}$) $\approx 4\times$ Removes redundant Adam states (momentum/variance).
Stage 2 Optimizer States + Gradients ($P_{os} + g$) $\approx 8\times$ Eliminates redundant gradient storage.
Stage 3 States + Gradients + Parameters ($P_{os} + g + p$) $\propto N_d$ Linear scaling; memory usage drops as you add GPUs.

Architectural Workflow

To understand how ZeRO operates during a training step, let's visualize the pipeline from the forward pass to the weight update.

flowchart TD subgraph Input_Phase ["Input & Forward Pass"] Data["Input Batch (X, y)"] --> Model["Model (Full Parameters)"] Model --> Loss["Loss Calculation"] end subgraph Backward_Phase ["Backward Pass & Gradient Generation"] Loss --> GradGen["Gradient Computation (Autograd)"] GradGen --> FlatGrads["Flattened Gradients (Full Vector)"] end subgraph ZeRO_Pipeline ["ZeRO Optimizer Pipeline (Partitioned Execution)"] direction TB FlatGrads --> RS["Reduce-Scatter (Simulated)"] subgraph Shards ["Virtual GPU Shards (1 to N)"] direction LR subgraph Shard_i ["Shard i"] S1["Stage 1: Optimizer State Partitioning\n(Momentum & Variance Shards)"] S2["Stage 2: Gradient Partitioning\n(Gradient Shard)"] S3["Stage 3: Parameter Partitioning\n(Parameter Shard)"] S2 --> S1 S1 --> S3 end end RS --> S2 S3 --> AG["All-Gather (Simulated)"] end subgraph Update_Phase ["Model Update"] AG --> Reconstruct["Reconstruct Full Parameter Vector"] Reconstruct --> UpdateModel["Update Model Weights (p.data.copy_)"] UpdateModel --> Model end %% Styling style ZeRO_Pipeline fill:#f9f,stroke:#333,stroke-width:2px style Shards fill:#e1f5fe,stroke:#01579b style Shard_i fill:#fff,stroke:#01579b,stroke-dasharray: 5 5 style Input_Phase fill:#f5f5f5 style Update_Phase fill:#f5f5f5

The Mathematical Impact

The memory reduction is not just theoretical; it is quantifiable. If $N_d$ is the number of GPUs in the data-parallel group:

  • Stage 1: Memory for optimizer states is reduced by $1/N_d$.
  • Stage 2: Memory for gradients is also reduced by $1/N_d$.
  • Stage 3: Memory for parameters is reduced by $1/N_d$.
  • Hybrid: When combined with Model Parallelism ($N_m$), the total reduction is $N_d \times N_m$.

Implementation: Simulating ZeRO in PyTorch

While production ZeRO (like in Microsoft's DeepSpeed) uses NCCL for high-speed GPU communication, we can simulate the logic using "Virtual Shards" in a single-process environment to understand the mechanics.

PYTHON
import torch
import torch.nn as nn
from typing import List

class ZeROOptimizer:
    """
    Simulated ZeRO Optimizer partitioning model states across 'virtual devices'.
    """
    def __init__(self, model: nn.Module, lr: float = 1e-3, num_shards: int = 4):
        self.model = model
        self.lr = lr
        self.num_shards = num_shards
        
        # Flatten parameters for easy partitioning
        self.params = [p for p in model.parameters() if p.requires_grad]
        self.total_params = sum(p.numel() for p in self.params)
        self.shard_size = self.total_params // num_shards
        
        # Stage 1 & 2: Partitioning Optimizer States and Gradients
        self.sharded_momentum = [torch.zeros(self.shard_size) for _ in range(num_shards)]
        self.sharded_variance = [torch.zeros(self.shard_size) for _ in range(num_shards)]
        self.sharded_grads = [torch.zeros(self.shard_size) for _ in range(num_shards)]
        
        # Stage 3: Parameter Partitioning
        self.sharded_params = [torch.zeros(self.shard_size) for _ in range(num_shards)]
        self._init_sharded_params()

    def _init_sharded_params(self):
        flat_params = torch.cat([p.view(-1) for p in self.params])
        for i in range(self.num_shards):
            start = i * self.shard_size
            end = (i + 1) * self.shard_size if i != self.num_shards - 1 else self.total_params
            self.sharded_params[i] = flat_params[start:end].clone()

    def step(self):
        # 1. Simulate Gradient Partitioning (Reduce-Scatter)
        flat_grads = torch.cat([p.grad.view(-1) for p in self.params])
        
        for i in range(self.num_shards):
            start = i * self.shard_size
            end = (i + 1) * self.shard_size if i != self.num_shards - 1 else self.total_params
            grad_shard = flat_grads[start:end]
            
            # 2. Update Optimizer States (Stage 1) - Adam Logic
            self.sharded_momentum[i] = 0.9 * self.sharded_momentum[i] + 0.1 * grad_shard
            self.sharded_variance[i] = 0.999 * self.sharded_variance[i] + 0.001 * (grad_shard**2)
            
            # Update the sharded parameter (Stage 3)
            denom = torch.sqrt(self.sharded_variance[i]) + 1e-8
            update = self.lr * self.sharded_momentum[i] / denom
            self.sharded_params[i] -= update

        # 3. Simulate Parameter All-Gather
        updated_flat_params = torch.cat(self.sharded_params)
        offset = 0
        for p in self.params:
            numel = p.numel()
            p.data.copy_(updated_flat_params[offset : offset + numel].view_as(p))
            offset += numel

Key Implementation Takeaways:

  1. Flattening: To partition tensors of different shapes, we flatten all parameters into a single 1D vector.
  2. Ownership: Each "shard" is responsible for a specific slice of the vector.
  3. The Cycle: The process follows a strict sequence: Compute Gradients $\rightarrow$ Reduce-Scatter (Partition) $\rightarrow$ Local Update $\rightarrow$ All-Gather (Reconstruct).

Beyond the Basics: ZeRO-R and Hybrid Parallelism

For the most extreme models, ZeRO introduces ZeRO-R (Residual Memory Optimization). This addresses memory that isn't part of the model states:

  • Activation Partitioning: Removing replication of activations in Model Parallelism.
  • Buffer Optimization: Sizing temporary buffers dynamically to prevent "Out of Memory" (OOM) spikes.
  • Fragmentation Control: Proactive memory management to keep the memory contiguous.

When ZeRO is layered on top of Model Parallelism (MP), it creates a powerhouse combination: MP handles the vertical partitioning of layers, while ZeRO handles the redundancy of the data-parallel replicas.

Final Verdict

ZeRO transforms the economics of LLM training. By shifting the paradigm from replication to partitioning, it allows researchers to train models that are orders of magnitude larger on the same hardware. Whether you are using DeepSpeed or Megatron-LM, ZeRO is the engine under the hood making the current AI revolution computationally possible.