Navigation
🏋️ Training Systems· 10 min read

CPU & NVMe Offloading

ZeRO-Offload and ZeRO-Infinity — moving optimizer states and parameters to CPU RAM or SSD, with prefetching to hide latency.

offloadingzero-offloadzero-infinitycpunvmememory

When GPU memory isn’t enough even with ZeRO-3 and activation checkpointing, the next step is offloading cold state to CPU memory (DDR5) or NVMe SSDs. The key challenge is prefetching data back to GPU before it’s needed, hiding the latency of slower memory tiers.


1. Memory Hierarchy for Training

Offload Tiers

TierCapacityBandwidth (to GPU)LatencyCost/GB
HBM (GPU)80-192 GB3,350 GB/s (internal)~ns$$$$$
CPU RAM (DDR5)512-2048 GB64 GB/s (PCIe 5.0)~μs$$
NVMe SSD4-30 TB7-14 GB/s (PCIe 4.0×4)~10 μs$

Insight: CPU RAM is 8-32× larger than GPU memory, and NVMe is 100× larger. If we can hide the bandwidth gap with prefetching, we get access to vastly more memory.


2. ZeRO-Offload (CPU Offloading)

What Gets Offloaded

The optimizer step is the only phase that needs optimizer states. Offload them to CPU:

ComponentLocationWhen needed
BF16 parametersGPUForward + backward
BF16 gradientsGPU → CPU (after backward)Optimizer step (on CPU)
FP32 master weightsCPUOptimizer step (on CPU)
FP32 Adam m, vCPUOptimizer step (on CPU)

How It Works

Forward pass:   GPU (BF16 params on GPU)
Backward pass:  GPU (BF16 params + gradients on GPU)
                → Transfer gradients to CPU (PCIe)
Optimizer step: CPU (FP32 master weights + Adam states + gradients)
                → Compute Adam update on CPU
                → Transfer updated BF16 params back to GPU (PCIe)

Memory Savings

ConfigurationPer-GPU memory
Standard DP (7B)112 GB
ZeRO-3 (8 GPUs)14 GB
ZeRO-Offload (1 GPU)14 GB on GPU + 98 GB on CPU

ZeRO-Offload enables single-GPU training of models that normally need 8 GPUs — at the cost of slower optimizer steps.

Bandwidth Analysis

For a 7B model:

  • Gradients to CPU: 14 GB at 32 GB/s (PCIe 4.0 x16) = 0.44 seconds
  • Updated params to GPU: 14 GB at 32 GB/s = 0.44 seconds
  • CPU optimizer step: ~2-5 seconds (CPU is slow at FP32 math)

Total overhead: ~3-6 seconds per step. Backward pass on GPU takes ~0.3 seconds. Offloading adds 10-20× overhead to the optimizer step.

Optimization: Overlap Transfer with Compute

Layer L backward (GPU) → Transfer layer L grads to CPU (PCIe)
Layer L-1 backward (GPU) → Transfer layer L-1 grads to CPU
...
While GPU does backward, CPU starts optimizer step on received layers

With proper overlap, the CPU optimizer step can be partially hidden behind backward compute.


3. ZeRO-Infinity (NVMe Offloading)

When even CPU RAM isn’t enough (models >100B on limited hardware):

ComponentLocation
Parameters (sharded)NVMe → CPU → GPU (prefetched)
Optimizer statesNVMe → CPU (for optimizer step)
GradientsGPU → CPU → NVMe (offloaded after use)

NVMe Bandwidth Challenge

NVMe SSDs: ~7 GB/s read. For a 70B model (1,120 GB training state):

  • Full read from NVMe: 1,120 / 7 = 160 seconds — obviously too slow for per-step reads

Solution: Only prefetch the current layer’s data, and overlap with compute:

  • One layer of 70B: ~14 GB params + gradients
  • Prefetch time: 14 / 7 = 2 seconds (still slow, but overlappable)

When NVMe Offloading Makes Sense

ScenarioFeasible?Notes
Fine-tuning 70B on 1-2 GPUs⚠️ Very slowWorks but 50-100× slower than 8×H100
Research/experimentation✅Cost-effective for debugging, small runs
Production training❌Too slow for large-scale training

4. DeepSpeed Configuration

# ZeRO-Offload (CPU)
ds_config = {
    "zero_optimization": {
        "stage": 3,
        "offload_optimizer": {
            "device": "cpu",
            "pin_memory": True,       # faster CPU↔GPU transfer
        },
        "offload_param": {
            "device": "cpu",
            "pin_memory": True,
        },
    }
}

# ZeRO-Infinity (NVMe)
ds_config = {
    "zero_optimization": {
        "stage": 3,
        "offload_optimizer": {
            "device": "nvme",
            "nvme_path": "/local_nvme/offload",
            "buffer_count": 4,          # double-buffer for overlap
            "buffer_size": 1e9,
        },
        "offload_param": {
            "device": "nvme",
            "nvme_path": "/local_nvme/offload",
            "buffer_count": 5,
            "buffer_size": 1e8,
            "max_in_cpu": 1e9,          # keep some params in CPU for faster access
        },
    }
}

Pin memory (pin_memory=True): Uses page-locked (pinned) host memory for CPU↔GPU transfers. Pinned memory bypasses the OS page table, enabling DMA transfers at full PCIe bandwidth. Standard (pageable) memory requires an extra copy.


5. Decision Framework

Available resourcesRecommended approach
8+ GPUs with enough aggregate memoryZeRO-1/2 (no offloading needed)
1-4 GPUs, model fits with ZeRO-3ZeRO-3 (keep everything on GPU)
1-2 GPUs, model doesn’t fit with ZeRO-3ZeRO-Offload (CPU)
1 GPU, very large model, fine-tuning onlyZeRO-Infinity (NVMe)
Production pre-training at scaleNever use offloading — get more GPUs

For training launch mechanics and process management, see Distributed Launch Mechanics.


Self-Check

  • Compare the three memory tiers (HBM, CPU RAM, NVMe) by capacity, bandwidth, and latency
  • Explain ZeRO-Offload’s data flow: what stays on GPU vs what moves to CPU
  • Calculate the PCIe transfer overhead for offloading a 7B model’s gradients
  • Explain why NVMe offloading is only practical for fine-tuning, not pre-training
  • Configure DeepSpeed for CPU offloading with pinned memory

References