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 图,帮你理清显存溢出的核心逻辑:
💡 个人小技巧: 我习惯在训练脚本开头加一行
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 的尴尬。