第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
但问题是——它只告诉你现在爆了,没告诉你之前是谁把显存吃掉的。这就需要我们借助工具来追溯。
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,按这个流程走:
- 第一步:用nvidia-smi看当前状态
先跑
nvidia-smi -l 1,确认是不是真的显存满了。有时候可能是别的进程占着显存,你的程序根本没启动。 - 第二步:用Nsight Systems记录完整运行
如果确认是自己的程序有问题,就用
nsys profile跑一次。注意,OOM程序可能跑不完,但Nsight Systems会记录到崩溃前那一刻的数据。 - 第三步:分析时间线,找到罪魁祸首
打开
.nsys-rep文件,看显存分配曲线。如果某个时刻显存突然飙升,点进去看是哪个API调用导致的。
torch.cuda.memory_summary()来查看内部细节。
2.5 一张图看懂诊断流程
下面这张SVG图,总结了从遇到OOM到定位问题的完整流程。你可以把它当作一个检查清单:
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,包括梯度检查点、混合精度这些实战技巧。
想要更多资料,联系可可