Fix torch.OutOfMemoryError: PyTorch CUDA OOM Triage Guide
Diagnose and resolve PyTorch CUDA out of memory exceptions with production memory profiling, allocator configuration tuning, and gradient checkpointing patterns.
Bottom Line Up Front
PyTorch CUDA out of memory errors occur when the caching allocator cannot satisfy a requested tensor allocation. This is frequently caused by VRAM fragmentation rather than true capacity exhaustion. Resolve root causes using expandable segments, activation checkpointing, and precise batch tuning.
Traceback (most recent call last):
File "train.py", line 142, in <module>
loss.backward()
File "site-packages/torch/autograd/__init__.py", line 204, in backward
Variable._execution_engine.run_backward(
torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.40 GiB (GPU 0; 23.69 GiB total capacity; 21.15 GiB already allocated; 1.02 GiB free in total; 1.18 GiB reserved in total by PyTorch)Try Denpex for 30 days. FREE.
No credit card. No automatic subscription. See whether Denpex helps with an incident you already understand.
Start your free 30-day Team evaluation
Up to 50 diagnoses a day for 30 days. Verify your work email to activate an eligible company evaluation, then replay a resolved incident and compare the evidence and next steps with your team's confirmed cause.
1. Reserved versus Allocated VRAM
The PyTorch caching allocator reserves large blocks of memory from the CUDA driver to avoid expensive kernel synchronization during frequent tensor creations. Total reserved VRAM often far exceeds actively allocated tensor memory.
import torch
# Inspect memory statistics
allocated = torch.cuda.memory_allocated(0) / (1024 ** 3)
reserved = torch.cuda.memory_reserved(0) / (1024 ** 3)
print(f"Allocated: {allocated:.2f} GB | Reserved: {reserved:.2f} GB")
# Print complete breakdown if near capacity
print(torch.cuda.memory_summary(device=0, abbreviated=False))Note that calling torch.cuda.empty_cache() does not solve training OOMs caused by large forward activations. It only returns unused cached blocks to the driver while introducing severe synchronization stalls.
2. Memory Fragmentation and expandable_segments
Standard caching allocators require contiguous physical memory blocks. Over time, variable tensor lifetimes create fragmentation where total free memory is high, but no single contiguous block satisfies the next allocation.
# Set environment variable before starting Python script export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True # Or configure programmatically at entry point import os os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True" import torch
Enabling expandable segments allows virtual memory segments to grow and merge without requiring contiguous physical underlying pages, virtually eliminating fragmentation crashes.
3. Activation Checkpointing
During the forward pass, intermediate activations are stored for gradient calculation during backpropagation. Activation checkpointing drops these activations and recomputes them on the fly during the backward pass.
import torch
from torch.utils.checkpoint import checkpoint
class CustomModel(torch.nn.Module):
def __init__(self, layer):
super().__init__()
self.layer = layer
def forward(self, x):
return checkpoint(self.layer, x, use_reentrant=False)
# For Hugging Face Transformers models
model.gradient_checkpointing_enable(
gradient_checkpointing_kwargs={"use_reentrant": False}
)
4. Micro-batch Sizing and Gradient Accumulation
When hardware constraints prevent large batch sizes, reduce the per-device micro-batch size and accumulate gradients across multiple steps to preserve effective batch size.
accumulation_steps = 4
optimizer.zero_grad()
for i, batch in enumerate(dataloader):
outputs = model(batch["input_ids"])
loss = outputs.loss / accumulation_steps
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
5. Common Memory Leak Traps
Subtle Python patterns can inadvertently retain computation graphs across iterations, leaking gigabytes of VRAM over time.
| Trap Pattern | Symptom | Correct Practice |
|---|---|---|
| total_loss += loss | Accumulates graph references in history tensor | total_loss += loss.item() |
| Eval without torch.no_grad | Builds autograd graph during validation evaluation | with torch.no_grad(): eval_loop() |
| DDP rank 0 logging accumulation | Storing tensors in lists across all iterations | Detach and extract scalar items immediately |
| Unpinned CPU tensors in dataloader | Slow asynchronous transfers locking pinned pools | pin_memory=True in DataLoader |
| CUDA graph capture overflow | Dynamic shapes exceeding static graph buffers | Pad inputs to fixed static dimensions |
Frequently Asked Questions
Why does torch.cuda.empty_cache() not fix training OOM errors?
torch.cuda.empty_cache releases unused cached memory back to the CUDA driver, but training OOMs happen when active allocation exceeds physical VRAM. It also introduces severe synchronization stalls.
What does expandable_segments do in PyTorch?
expandable_segments allows the PyTorch caching allocator to grow memory blocks virtually without requiring contiguous physical allocations, drastically reducing VRAM fragmentation.
When should I use activation checkpointing?
Use activation checkpointing when your model backbone is too large for the desired micro-batch size. It trades additional compute during the backward pass for exponential memory savings.
Related Diagnostic Tools
Calculate exact model weight, gradient, optimizer state, and activation memory requirements across GPU tiers.
Parse dmesg, kernel logs, and stack traces to identify hardware faults, ECC errors, and driver panics.
Comprehensive database of NVIDIA driver XID error codes with troubleshooting steps and recovery actions.
Detect out of bounds memory accesses and race conditions in custom CUDA kernels and extensions.