1. 显存溢出初探:什么是显存溢出(OOM)

显存溢出,英文叫 Out of Memory,简称 OOM。说白了就是——你的 GPU 显存放不下你喂给它的数据了。

我刚开始做深度学习那会儿,第一次遇到 OOM 时整个人是懵的。明明代码跑得好好的,突然就崩了。屏幕上就一行红字:CUDA out of memory。嗯,那感觉就像你往杯子里倒水,水满了还继续倒,最后洒了一桌。

显存是 GPU 的“工作台”。模型参数、中间激活值、梯度、优化器状态……这些东西全都要挤在显存里。一旦超出容量,系统就会直接 kill 掉你的进程。没有商量余地。

显存溢出的典型表现

你遇到 OOM 时,通常会有下面这些症状:

  • 训练直接崩溃:跑着跑着,程序就挂了。控制台打印一堆 CUDA 错误堆栈。
  • 显存持续增长:用 nvidia-smi 一看,显存占用像坐火箭一样往上窜,直到爆掉。
  • 模型加载失败:刚想加载一个预训练模型,直接报 OOM。连开始的机会都不给。
  • 推理时突然卡死:推理到一半,GPU 没反应了。显存被一次性占满。
⚠️ 注意: 有时候 OOM 不会立刻报错。它会先卡住几秒,然后才崩溃。我遇到过最坑的一次,是训练了 12 个小时,在最后一个 epoch 爆了显存。那种感觉……你懂的。

显存溢出的常见原因概览

为什么会 OOM?我总结下来,无非是下面这几类原因:

原因类别 具体表现 我踩过的坑
batch size 太大 一次性塞进 GPU 的样本太多 我曾经把 batch size 设成 256,结果 24G 显存直接爆掉
模型太大 参数量远超显存容量 加载一个 7B 模型到 8G 显存的卡上,想都别想
序列过长 Transformer 的注意力矩阵是 O(n²) 的 处理 4096 长度的文本,显存消耗是 1024 的 16 倍
中间激活值爆炸 每一层的输出都占显存,层数一多就炸 训练深层 ResNet 时,激活值占的显存比模型参数还多
梯度累积未释放 代码里忘了 detach 或清空梯度 有一次写循环,忘了 zero_grad,显存直接线性增长
多进程显存共享 DataLoader 的 num_workers 开太多 num_workers=8 时,每个 worker 都复制一份数据到显存
核心要点: 显存溢出不是玄学。它背后一定有明确的数学原因。要么是数据量太大,要么是模型结构太复杂,要么是代码有 bug。找到根因,就能解决。

一张图看懂显存溢出

下面这张 SVG 图,帮你理清显存溢出的核心逻辑:

显存溢出(OOM)核心逻辑图 输入数据 GPU 显存(有限容量) 模型参数 + 激活值 + 梯度 + 优化器状态 已用显存 75% 显存容量检查 ✅ 正常训练 显存足够,继续运行 ❌ 显存溢出 进程被 kill,报 OOM batch size 太大 模型参数过多 序列长度过长
💡 个人小技巧: 我习惯在训练脚本开头加一行 torch.cuda.empty_cache(),虽然不能根治 OOM,但能清理掉一些碎片化的显存占用。至少能让你看到真实的显存使用情况。

避坑指南

我曾经犯过一个低级错误:在同一个脚本里同时加载了两个大模型,一个用来做 teacher,一个用来做 student。结果显存直接翻倍,8G 的卡根本扛不住。后来我才意识到,可以用 torch.no_grad() 把 teacher 模型设为 eval 模式,再配合 .half() 转成半精度,显存瞬间降了一半。

还有一次,我在 DataLoader 里把 num_workers 设成了 16。结果每个 worker 都复制了一份数据到显存里。16 个 worker 就是 16 份数据。嗯,那画面太美我不敢看。

⚠️ 避坑: 如果你用 nvidia-smi 看到显存占用很高,但你的模型明明不大——先检查一下是不是有多个进程在占用显存。用 fuser -v /dev/nvidia* 可以查到是哪些进程在吃显存。

显存溢出这件事,说白了就是资源管理的问题。你想想看,GPU 的显存就那么点,你要塞进去的东西却越来越多。要么你减少需求,要么你增加供给,要么你优化效率。没有别的路。

我个人习惯是,每次开始一个新项目,先用一个小 batch size 跑通流程,确认显存占用正常,再逐步放大 batch size。这样能避免一开始就 OOM 的尴尬。

可可精品资料库