第2章:显存溢出诊断——如何用nvidia-smi和Nsight Systems定位OOM问题

显存溢出(OOM)这问题,我敢说每个做GPU编程的人都遇到过。而且往往是半夜跑实验时蹦出来,让人血压飙升。我自己就经历过一次——训练一个大型Transformer模型,跑了整整6个小时,在最后一个epoch突然报OOM。那种感觉,嗯,你懂的。

但别慌。OOM其实没那么玄乎。说白了就是显存不够用了。关键是怎么快速定位到问题根源。今天我就带你看看,怎么用两个最趁手的工具——nvidia-smi和Nsight Systems,把OOM问题揪出来。

2.1 先搞清楚:OOM到底是怎么发生的?

OOM的全称是Out of Memory。GPU在跑程序时,会向显存申请空间。如果申请的总量超过了显存上限,驱动就会直接杀掉进程。你看到的报错通常是:

RuntimeError: CUDA out of memory. Tried to allocate 2.00 GiB (GPU 0; 8.00 GiB total capacity; 6.50 GiB already allocated; 1.02 GiB free; 7.80 GiB reserved in total by PyTorch)

这个报错信息其实很有用。它告诉你了三件事:

  • 当前申请了多少:比如2.00 GiB
  • 已经用了多少:6.50 GiB
  • 总共还剩多少:1.02 GiB

但问题是——它只告诉你现在爆了,没告诉你之前是谁把显存吃掉的。这就需要我们借助工具来追溯。

我的经验:看到OOM报错后,第一件事不是改代码,而是先看显存使用曲线。很多时候问题出在某个中间变量没释放,而不是模型本身太大。

2.2 第一板斧:nvidia-smi实时监控

nvidia-smi是NVIDIA自带的命令行工具。它最常用的用法就是看显存占用:

nvidia-smi -l 1

这个命令会每秒刷新一次,显示所有GPU的实时状态。输出大概长这样:

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.86    Driver Version: 470.86    CUDA Version: 11.4           |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla T4            Off  | 00000000:00:1E.0 Off |                    0 |
|  N/A   45C    P0    28W /  70W|   7234MiB / 15109MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

关键看Memory-Usage这一列。如果显存占用持续增长,最后爆掉,那基本就是内存泄漏了。

但nvidia-smi有个硬伤——它只能看到当前时刻的快照。你没法回放过去几分钟的显存变化。而且它不告诉你每个进程内部到底在干什么。

注意:用nvidia-smi监控时,建议加上-l 1参数每秒刷新。我曾经见过有人只跑一次nvidia-smi,没看到问题就以为没事了——结果程序在下一秒钟就爆了。

2.3 第二板斧:Nsight Systems深度分析

如果你觉得nvidia-smi不够用,那就得上Nsight Systems了。这是NVIDIA官方提供的性能分析工具,能记录整个程序运行期间的显存分配和释放情况。

用法很简单:

nsys profile -o my_profile --stats=true python train.py

跑完之后会生成一个.nsys-rep文件。用Nsight Systems GUI打开它,你会看到类似这样的时间线:

核心功能:Nsight Systems能展示每个CUDA API调用的显存分配量、分配时间、释放时间。你可以一眼看出哪个操作在疯狂吃显存。

我个人习惯重点关注这几个指标:

  • cudaMalloc调用次数和大小——看谁在频繁申请大块显存
  • cudaFree调用时机——看显存有没有及时释放
  • 峰值显存使用量——看程序运行期间的最高占用

举个例子。有一次我跑一个图像分割模型,发现显存在某个循环里不断增长。用Nsight Systems一看,原来是每次迭代都创建了一个临时张量,但忘了.detach(),导致计算图一直保留。说白了就是Python的引用计数没归零,显存释放不掉。

2.4 实战:三步定位OOM

好了,理论说完了。咱们来点实际的。假设你现在遇到了OOM,按这个流程走:

  1. 第一步:用nvidia-smi看当前状态

    先跑nvidia-smi -l 1,确认是不是真的显存满了。有时候可能是别的进程占着显存,你的程序根本没启动。

  2. 第二步:用Nsight Systems记录完整运行

    如果确认是自己的程序有问题,就用nsys profile跑一次。注意,OOM程序可能跑不完,但Nsight Systems会记录到崩溃前那一刻的数据。

  3. 第三步:分析时间线,找到罪魁祸首

    打开.nsys-rep文件,看显存分配曲线。如果某个时刻显存突然飙升,点进去看是哪个API调用导致的。

避坑指南:我曾经遇到过一种情况——Nsight Systems记录显示显存使用很平稳,但程序还是OOM了。后来发现是PyTorch的缓存分配器在搞鬼。它预分配了一大块显存,但nvidia-smi显示的是「已分配」而不是「已使用」。这时候需要用torch.cuda.memory_summary()来查看内部细节。

2.5 一张图看懂诊断流程

下面这张SVG图,总结了从遇到OOM到定位问题的完整流程。你可以把它当作一个检查清单:

OOM诊断流程图 遇到CUDA OOM 步骤1:nvidia-smi -l 1 查看实时显存占用,确认是否真的满了 显存持续增长? 是 步骤2:Nsight Systems 记录完整运行,分析分配曲线 否 检查其他进程 用nvidia-smi看PID 步骤3:定位具体代码位置 查看API调用栈,找到未释放的变量

2.6 几个实用小技巧

最后分享几个我平时常用的技巧,帮你更快定位OOM:

  • 用torch.cuda.memory_allocated()打点——在代码关键位置打印当前显存使用量,看哪个区间增长最快
  • 开启PyTorch的显存调试模式——设置环境变量PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,可以更细粒度地控制显存分配
  • 检查torch.no_grad()上下文——推理时忘记加这个,会导致计算图一直保留,显存只增不减
重要提醒:不要在生产环境直接跑nsys profile。它会显著降低程序运行速度。我建议先在测试集上跑一个小规模版本,确认没问题了再上全量数据。

好了,这一章的内容就到这里。显存溢出诊断其实没那么复杂——用好nvidia-smi和Nsight Systems这两个工具,再配合一点耐心,大部分OOM问题都能在半小时内定位到根因。下一章我们会聊怎么从代码层面预防OOM,包括梯度检查点、混合精度这些实战技巧。

想要更多资料,联系可可