Navigation
🏋️ Training Systems· 12 min read

GPU Memory Anatomy

Where GPU memory goes during training — parameters, gradients, optimizer states, activations, and framework overhead, with exact accounting.

memorytrainingparametersgradientsoptimizeractivationsanatomy

Understanding where GPU memory goes is the foundation of all training infrastructure decisions. This chapter gives you the complete breakdown — every byte accounted for — so you can predict memory usage before launching a single training run.


1. The Four Categories of Training Memory

Training memory splits into static (constant regardless of input) and dynamic (varies with batch size and sequence length):

CategoryStatic/DynamicTypical shareDepends on
Model parametersStatic12-15%Parameter count Ψ
GradientsStatic12-15%Parameter count Ψ
Optimizer statesStatic55-75%Parameter count Ψ, optimizer type
ActivationsDynamic10-30%Batch size, sequence length, model width
Framework overheadStatic2-5%CUDA context, NCCL buffers, fragmentation

Key insight: Optimizer states dominate training memory. For Adam/AdamW, they consume 3× the memory of the parameters themselves.


2. Model Parameters

The working copy of the model used in forward and backward passes.

Mparams=Ψ×bytes_per_paramM_{\text{params}} = \Psi \times \text{bytes\_per\_param}
PrecisionBytes7B13B70B405B
FP32428 GB52 GB280 GB1,620 GB
BF16214 GB26 GB140 GB810 GB
FP817 GB13 GB70 GB405 GB

Modern training uses BF16 (or FP8 on Hopper+) for the working copy, with FP32 reserved for the optimizer’s master weights.

Counting Parameters Accurately

For a Transformer with LL layers, hidden dim hh, vocab VV, FFN dim dffd_{ff}:

Ψ≈V×h+L×(4h2+2h×dff+norms)\Psi \approx V \times h + L \times (4h^2 + 2h \times d_{ff} + \text{norms})
ComponentParameters per layer70B (h=8192)
QKV projection3h23h^2 (or h2+2hkv×hh^2 + 2h_{kv} \times h for GQA)~201M
Output projectionh2h^267M
FFN up + gate + down3h×dff3h \times d_{ff} (SwiGLU)707M
LayerNorm (×2)4h4h65K
Per-layer total~975M
80 layers~78B
Embedding + LM head2×V×h2 \times V \times h~2.6B (V=128K)

Counting Parameters: Worked Example (Llama-3.1 70B)

Architecture: h=8192h=8192, L=80L=80, V=128,256V=128{,}256, GQA with a=64a=64 query heads, akv=8a_{kv}=8 KV heads, dff=28,672d_{ff}=28{,}672 (SwiGLU), no bias terms.

ComponentFormulaCount
EmbeddingV×hV \times h128,256 × 8,192 = 1.050B
Per-layer QKV projectionh×(a×dh+2×akv×dh)h \times (a \times d_h + 2 \times a_{kv} \times d_h)8,192 × (64×128 + 2×8×128) = 75.5M
Per-layer output projectiona×dh×ha \times d_h \times h64 × 128 × 8,192 = 67.1M
Per-layer FFN (SwiGLU: gate + up + down)3×h×dff3 \times h \times d_{ff}3 × 8,192 × 28,672 = 704.6M
Per-layer RMSNorm (×2)2×h2 \times h2 × 8,192 = 16.4K
Per-layer total847.2M
80 layers67.78B
Final RMSNormhh8.2K
LM head (tied with embedding)0 (shared)0
Grand total~68.8B

The “70B” label is approximate — actual parameter counts vary by implementation details (bias terms, head dim rounding, tied/untied LM head).


3. Gradients

Same shape as parameters — one gradient value per parameter:

Mgradients=Ψ×bytes_per_gradM_{\text{gradients}} = \Psi \times \text{bytes\_per\_grad}

With BF16 training: 2 bytes per gradient. With FP32 gradient accumulation: 4 bytes.

Gradient accumulation doesn’t change peak gradient memory — you still hold one full set of gradients, just accumulated over multiple micro-batches before the optimizer step.


4. Optimizer States (The Dominant Cost)

Different optimizers require different additional state per parameter:

OptimizerStates per paramBytes/param (FP32)7B overheadNotes
SGDNone00 GBRarely used for LLMs
SGD + Momentumvelocity vv428 GBOne FP32 buffer
Adam/AdamWmm (1st moment) + vv (2nd moment)856 GBTwo FP32 buffers
Adam + FP32 mastermaster weights + mm + vv1284 GBStandard mixed-precision

The Adam 12Ψ Rule

In standard BF16 mixed-precision training with AdamW, the optimizer maintains:

  1. FP32 master weights (4Ψ bytes) — high-precision copy for accurate updates
  2. FP32 first moment mm (4Ψ bytes) — exponential moving average of gradients
  3. FP32 second moment vv (4Ψ bytes) — EMA of squared gradients

Total optimizer overhead: 12Ψ bytes.

Why FP32 for optimizer states? Small weight updates (lr×gradient≈10−4×10−3=10−7\text{lr} \times \text{gradient} \approx 10^{-4} \times 10^{-3} = 10^{-7}) fall below BF16’s precision floor (≈10−3\approx 10^{-3} relative). FP32 is necessary to accumulate these small changes accurately. Without it, weights simply don’t update — training stalls.

The Full Static Ledger

ComponentBytes/paramFormula7B70B
BF16 parameters22Ψ2\Psi14 GB140 GB
BF16 gradients22Ψ2\Psi14 GB140 GB
FP32 master weights44Ψ4\Psi28 GB280 GB
FP32 Adam mm44Ψ4\Psi28 GB280 GB
FP32 Adam vv44Ψ4\Psi28 GB280 GB
Total1616Ψ112 GB1,120 GB

For a 7B model: 112 GB of static memory — already exceeds a single 80 GB GPU. This is why even “small” models need distributed training.


5. Activations (The Dynamic Cost)

Activations are intermediate tensors saved during forward pass for the backward pass. They scale with batch size and sequence length.

For a Transformer layer:

Saved tensorShapePurpose
Input to attentionb×s×hb \times s \times hBackward through QKV projection
Q, K, V tensors3×b×s×h3 \times b \times s \times hBackward through attention scores
Attention weightsb×a×s×sb \times a \times s \times sBackward through softmax
Attention outputb×s×hb \times s \times hBackward through output projection
FFN inputb×s×hb \times s \times hBackward through FFN
FFN intermediateb×s×dffb \times s \times d_{ff}Backward through gate/activation
LayerNorm inputs2×b×s×h2 \times b \times s \times hBackward through normalization

Approximate total per layer (BF16):

Mact/layer≈2×(22bsh+2bas2) bytesM_{\text{act/layer}} \approx 2 \times (22bsh + 2bas^2) \text{ bytes}

For 70B (h=8192h=8192, a=64a=64, s=4096s=4096, bμ=1b_\mu=1):

  • Linear terms: 14×4096×8192+6×4096×28672≈1.214 \times 4096 \times 8192 + 6 \times 4096 \times 28672 \approx 1.2 GB
  • Attention terms: 3×64×40962≈3.23 \times 64 \times 4096^2 \approx 3.2 GB
  • Per layer: ~4.5 GB
  • 80 layers: ~357 GB without activation checkpointing

Activations scale as O(s2)O(s^2) for standard attention. This is one reason long-context training is so memory-intensive. FlashAttention removes this quadratic term. See Activation Memory Analysis for detailed formulas and mitigation strategies.


6. Framework Overhead

SourceTypical sizeNotes
CUDA context0.5-1.5 GBPer GPU, allocated on first CUDA call
NCCL buffers0.5-2 GBCommunication scratch space
PyTorch allocator fragmentation1-5 GBWasted space in memory pools
Temporary buffers0.5-2 GBIntermediate computation tensors

Total overhead: typically 3-8 GB per GPU — significant when memory is tight.

# Check actual memory usage vs theoretical
import torch
print(f"Allocated: {torch.cuda.memory_allocated()/1e9:.1f} GB")
print(f"Reserved:  {torch.cuda.memory_reserved()/1e9:.1f} GB")
print(f"Max allocated: {torch.cuda.max_memory_allocated()/1e9:.1f} GB")
# Reserved - Allocated = fragmentation + cached blocks

7. Complete Memory Picture

Memory Breakdown

For a 7B model on a single 80 GB H100 (BF16 + AdamW, bμ=4b_\mu=4, s=2048s=2048):

ComponentMemory
Parameters (BF16)14 GB
Gradients (BF16)14 GB
Optimizer (FP32 master + m + v)84 GB
Static subtotal112 GB — already OOM!

This is why distributed training exists. Even a 7B model can’t train on a single GPU with Adam. Solutions:

  1. FSDP/ZeRO: Shard static memory across GPUs → 112/8 = 14 GB per GPU
  2. CPU offloading: Move optimizer to system RAM → only 28 GB on GPU
  3. 8-bit Adam: Use INT8 optimizer states → 56 GB instead of 84 GB
  4. SGD instead of Adam: 0 optimizer overhead → 28 GB total (but worse convergence)

For sharding strategies, see ZeRO Stage 1/2/3.


Self-Check

  • Name the four categories of training memory and their approximate shares
  • Calculate the 16Ψ static memory for a 13B model with Adam
  • Explain why optimizer states consume 75% of static training memory
  • Explain why Adam needs FP32 (precision floor argument)
  • Calculate per-layer activation memory for a given architecture
  • Use torch.cuda.memory_allocated() to verify theoretical predictions

References