有建议或反馈?欢迎邮件联系[email protected]

CUDA Out of Memory 完全排查指南

2026/01/17

"CUDA out of memory" 是深度学习中最令人头疼的错误。本文涵盖所有解决方案,从快速修复到高级技术。

理解这个错误

错误长什么样

PyTorch:

RuntimeError: CUDA out of memory. Tried to allocate 2.00 GiB
(GPU 0; 24.00 GiB total capacity; 21.58 GiB already allocated;
1.45 GiB free; 22.00 GiB reserved in total by PyTorch)

TensorFlow:

ResourceExhaustedError: OOM when allocating tensor with shape [32, 512, 512, 3]

Stable Diffusion:

torch.cuda.OutOfMemoryError: CUDA out of memory.

什么原因导致 CUDA OOM?

原因描述解决方案
模型太大模型参数超过显存使用更小的模型或量化
批次大小太高每个样本都占用显存减小 batch size
输入太大高分辨率图像/长序列减小输入尺寸
内存泄漏张量没有正确释放清理缓存,修复代码
梯度累积反向传播存储梯度使用梯度检查点
多个模型同时加载多个模型卸载不用的模型

快速诊断

第一步:检查当前 GPU 显存使用

# 实时监控
watch -n 1 nvidia-smi

# 单次检查
nvidia-smi

输出说明:

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 535.104.05   Driver Version: 535.104.05   CUDA Version: 12.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  NVIDIA RTX 4090     On   | 00000000:01:00.0 Off |                  Off |
| 30%   45C    P2    75W / 450W |  18432MiB / 24564MiB |     85%      Default |
+-------------------------------+----------------------+----------------------+

关键指标: 18432MiB / 24564MiB = 已使用 75% 显存

第二步:在 Python 中检查显存

import torch

# 检查 CUDA 是否可用
print(f"CUDA 可用: {torch.cuda.is_available()}")
print(f"设备数量: {torch.cuda.device_count()}")
print(f"当前设备: {torch.cuda.current_device()}")
print(f"设备名称: {torch.cuda.get_device_name(0)}")

# 显存信息
print(f"总显存: {torch.cuda.get_device_properties(0).total_memory / 1e9:.2f} GB")
print(f"已分配: {torch.cuda.memory_allocated(0) / 1e9:.2f} GB")
print(f"已缓存: {torch.cuda.memory_reserved(0) / 1e9:.2f} GB")

第三步:找出占用显存的操作

# 启用显存追踪
torch.cuda.memory._record_memory_history()

# 运行你的代码
# ...

# 获取显存快照
snapshot = torch.cuda.memory._snapshot()

# 使用 PyTorch 显存可视化工具分析

快速修复(先试这些)

修复 1:减小批次大小

最简单也最有效的解决方案:

# 修改前
train_loader = DataLoader(dataset, batch_size=32)  # OOM!

# 修改后
train_loader = DataLoader(dataset, batch_size=8)   # 成功!

经验法则: 如果 OOM,把 batch size 减半,直到能运行。

修复 2:清理 GPU 缓存

import torch
import gc

# 清理 PyTorch 缓存
torch.cuda.empty_cache()

# 强制垃圾回收
gc.collect()

使用时机: 加载/卸载模型后,训练运行之间。

修复 3:使用混合精度(FP16/BF16)

减少约 50% 显存占用,精度损失很小:

# PyTorch 原生 AMP
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

for batch in dataloader:
    optimizer.zero_grad()

    with autocast():  # FP16 前向传播
        outputs = model(batch)
        loss = criterion(outputs, targets)

    scaler.scale(loss).backward()  # 缩放后的反向传播
    scaler.step(optimizer)
    scaler.update()

Hugging Face Transformers:

from transformers import TrainingArguments

training_args = TrainingArguments(
    fp16=True,  # 启用 FP16
    # 或 bf16=True 用于新显卡 (RTX 3090+, A100, H100)
)

修复 4:启用梯度检查点

用计算换显存,通过重新计算激活值:

# PyTorch
model.gradient_checkpointing_enable()

# Hugging Face
from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b",
    gradient_checkpointing=True
)

显存节省: Transformer 模型可节省 50-70%

修复 5:使用更小的数据类型

# 以低精度加载模型
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b",
    torch_dtype=torch.float16,  # 半精度
    # 或 torch_dtype=torch.bfloat16 用于 A100/H100
)

高级解决方案

方案 1:8-bit 和 4-bit 量化

8-bit 量化 (bitsandbytes):

from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b",
    load_in_8bit=True,
    device_map="auto"
)

4-bit 量化 (QLoRA):

from transformers import BitsAndBytesConfig

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-70b",
    quantization_config=bnb_config,
    device_map="auto"
)

显存对比:

模型FP32FP168-bit4-bit
Llama 2 7B28 GB14 GB7 GB3.5 GB
Llama 2 13B52 GB26 GB13 GB6.5 GB
Llama 2 70B280 GB140 GB70 GB35 GB

方案 2:梯度累积

不增加显存模拟更大的批次:

accumulation_steps = 4
optimizer.zero_grad()

for i, batch in enumerate(dataloader):
    outputs = model(batch)
    loss = criterion(outputs, targets)
    loss = loss / accumulation_steps  # 归一化 loss
    loss.backward()

    if (i + 1) % accumulation_steps == 0:
        optimizer.step()
        optimizer.zero_grad()

等效批次大小: 实际批次大小 × 累积步数

方案 3:CPU 卸载

把模型部分放到 CPU:

from accelerate import Accelerator

accelerator = Accelerator(cpu_offload=True)
model, optimizer = accelerator.prepare(model, optimizer)

DeepSpeed ZeRO-Offload:

{
    "zero_optimization": {
        "stage": 3,
        "offload_optimizer": {
            "device": "cpu"
        },
        "offload_param": {
            "device": "cpu"
        }
    }
}

方案 4:模型并行

把模型分布到多个 GPU:

from accelerate import Accelerator

accelerator = Accelerator()
model = accelerator.prepare(model)

# 或使用 device_map 手动分配
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-70b",
    device_map="auto"  # 自动分布到多卡
)

方案 5:高效注意力(Flash Attention)

# 安装 flash-attn
# pip install flash-attn --no-build-isolation

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b",
    attn_implementation="flash_attention_2",
    torch_dtype=torch.bfloat16
)

显存节省: 长序列可节省 20-40%

各框架专用方案

PyTorch

# 1. 推理时禁用梯度
with torch.no_grad():
    outputs = model(inputs)

# 2. 用完后删除张量
del tensor
torch.cuda.empty_cache()

# 3. 使用推理模式(比 no_grad 更快)
with torch.inference_mode():
    outputs = model(inputs)

# 4. 限制显存碎片
import os
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"

TensorFlow

import tensorflow as tf

# 1. 启用显存增长
gpus = tf.config.list_physical_devices('GPU')
for gpu in gpus:
    tf.config.experimental.set_memory_growth(gpu, True)

# 2. 限制 GPU 显存
tf.config.set_logical_device_configuration(
    gpus[0],
    [tf.config.LogicalDeviceConfiguration(memory_limit=8192)]  # 8GB 限制
)

# 3. 清理会话
tf.keras.backend.clear_session()

Stable Diffusion / ComfyUI

# 1. 使用注意力切片
pipe.enable_attention_slicing()

# 2. 使用 VAE 切片处理大图
pipe.enable_vae_slicing()

# 3. 使用顺序 CPU 卸载
pipe.enable_sequential_cpu_offload()

# 4. 使用模型 CPU 卸载(更温和)
pipe.enable_model_cpu_offload()

# 5. 使用 xformers 高效注意力
pipe.enable_xformers_memory_efficient_attention()

Automatic1111 WebUI 启动参数:

python launch.py --medvram  # 中等显存优化
python launch.py --lowvram  # 低显存优化(更慢)
python launch.py --xformers # 启用 xformers

Hugging Face Transformers

from transformers import TrainingArguments

training_args = TrainingArguments(
    per_device_train_batch_size=4,
    gradient_accumulation_steps=8,
    gradient_checkpointing=True,
    fp16=True,
    optim="adamw_8bit",  # 8-bit 优化器
)

显存需求参考

LLM 训练显存公式

显存 ≈ 模型参数 × 4 字节 (FP32)
     + 优化器状态 × 每参数 8-12 字节 (Adam)
     + 梯度 × 每参数 4 字节
     + 激活值(取决于批次大小和序列长度)

大致所需显存:

模型推理 (FP16)微调 (FP16)完整训练
7B14 GB20 GB56 GB
13B26 GB40 GB104 GB
70B140 GB200 GB560 GB

图像生成显存

模型分辨率最低显存推荐显存
SD 1.5512×5124 GB8 GB
SD 1.5768×7686 GB10 GB
SDXL1024×10248 GB12 GB
SDXL + ControlNet1024×102412 GB16 GB
Flux.11024×102416 GB24 GB

排查内存泄漏

识别泄漏

import torch

def check_memory():
    print(f"已分配: {torch.cuda.memory_allocated(0) / 1e9:.2f} GB")
    print(f"已保留: {torch.cuda.memory_reserved(0) / 1e9:.2f} GB")

# 在不同位置检查
check_memory()  # 之前
output = model(input)
check_memory()  # 前向传播后
loss.backward()
check_memory()  # 反向传播后

常见泄漏模式

1. 在列表中存储张量:

# 错误 - 累积 GPU 显存
losses = []
for batch in dataloader:
    loss = model(batch)
    losses.append(loss)  # 张量留在 GPU 上!

# 正确 - 分离并移到 CPU
losses = []
for batch in dataloader:
    loss = model(batch)
    losses.append(loss.detach().cpu().item())

2. 没有清理梯度:

# 错误 - 梯度累积
for batch in dataloader:
    loss = model(batch)
    loss.backward()

# 正确 - 清零梯度
for batch in dataloader:
    optimizer.zero_grad()  # 清除之前的梯度
    loss = model(batch)
    loss.backward()
    optimizer.step()

3. 保留计算图:

# 错误 - 保留整个计算图
total_loss = 0
for batch in dataloader:
    loss = model(batch)
    total_loss += loss  # 保留了计算图!

# 正确 - 从计算图分离
total_loss = 0
for batch in dataloader:
    loss = model(batch)
    total_loss += loss.item()  # 只取数值

预防策略

1. 运行前估算显存

def estimate_model_memory(model, batch_size, precision='fp32'):
    """估算模型显存使用。"""
    param_size = sum(p.numel() for p in model.parameters())

    bytes_per_param = {'fp32': 4, 'fp16': 2, 'int8': 1}[precision]

    # 参数 + 梯度 + 优化器状态 (Adam ≈ 3x)
    training_memory = param_size * bytes_per_param * 4

    total = training_memory
    return total / 1e9  # GB

# 使用
memory_needed = estimate_model_memory(model, batch_size=8)
print(f"估计显存: {memory_needed:.2f} GB")

2. 从小开始,逐步扩大

# 从最小设置开始
batch_size = 1

# 逐步增加直到 OOM
for bs in [1, 2, 4, 8, 16, 32]:
    try:
        train_step(batch_size=bs)
        print(f"批次大小 {bs}: OK")
    except RuntimeError as e:
        if "out of memory" in str(e):
            print(f"批次大小 {bs}: OOM! 使用 {bs // 2}")
            break
        raise e

3. 训练时监控

from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter()

for step, batch in enumerate(dataloader):
    # ... 训练代码 ...

    # 记录显存使用
    writer.add_scalar('memory/allocated',
                      torch.cuda.memory_allocated(0) / 1e9, step)
    writer.add_scalar('memory/reserved',
                      torch.cuda.memory_reserved(0) / 1e9, step)

快速参考:各场景 OOM 解决方案

场景首先尝试如果还是 OOM最后手段
训练减小批次大小梯度检查点8-bit 优化器
微调使用 LoRA/QLoRA4-bit 量化CPU 卸载
推理FP16/BF164-bit 量化CPU 卸载
图像生成--medvram 参数--lowvram 参数降低分辨率
长序列Flash Attention减少序列长度分块处理

总结

  1. 先诊断: 使用 nvidia-smi 和 PyTorch 显存函数
  2. 快速修复: 减小批次大小、清理缓存、使用 FP16
  3. 高级方案: 量化、梯度检查点、CPU 卸载
  4. 预防: 提前估算显存、训练时监控
  5. 实在不行: 升级显存(升级显卡或使用云 GPU)

大多数 OOM 错误归根结底就是一个事实:工作负载装不进 GPU 显存。理解显存阈值对性能的决定性影响,能帮你在遇到错误之前就选对显卡。

记住:CUDA OOM 通常都能解决。从最简单的方案开始,逐步尝试。


需要更多显存?浏览 SynpixCloud 市场 获取 RTX 4090 (24GB)、A100 (80GB)、H100 (80GB) 实例。

推荐 GPU

SynpixCloud Team

SynpixCloud Team