torch.cuda.memory_allocated()更可靠,因它只统计当前python对象持有的未回收gpu内存,而nvidia-smi显示的是cuda上下文缓存的假象,包含已分配但未释放给系统的显存。

为什么torch.cuda.memory_allocated()比nvidia-smi更可靠?
nvidia-smi显示的显存占用常比实际模型训练时高得多,甚至在脚本退出后仍不释放——这是CUDA上下文缓存导致的假象。真正反映PyTorch张量内存压力的是torch.cuda.memory_allocated(),它只统计当前Python对象持有的、尚未被del或GC回收的GPU内存。
实操建议:
- 在关键步骤前后插入
print(torch.cuda.memory_allocated() / 1024**3),单位是GB,比看nvidia-smi更能定位哪行代码突然涨内存 - 避免用
torch.cuda.empty_cache()“清空”来掩盖问题——它不释放给系统的显存,只释放PyTorch缓存池,对OOM无实质缓解 - 注意:该值在多GPU时默认返回当前
current_device的分配量,跨卡需先torch.cuda.set_device(i)
如何快速识别是模型参数、梯度还是中间激活占满显存?
PyTorch中三类对象生命周期不同:模型参数(长期驻留)、梯度(反向传播后存在)、中间激活(前向传播中临时生成,通常最大头)。OOM往往由激活爆炸引发,尤其在RNN、Transformer长序列或未启用checkpointing时。
实操建议:
- 用
torch.utils.checkpoint.checkpoint包装子模块,用时间换空间,但注意它禁用部分autograd优化,可能影响精度 - 检查是否误将
.to('cuda')写在循环内——比如每次迭代都把同一份数据重复拷贝到GPU,形成N份副本 - 打印
model.parameters().__next__().size()和sum(p.numel() * p.element_size() for p in model.parameters()) / 1024**2估算参数本身占多少MB,若远小于报错时的memory_allocated,基本可排除参数问题
batch_size调小还不行?检查pin_memory=True和num_workers>0的副作用
看似CPU端的DataLoader配置,其实会间接加剧GPU内存压力:当pin_memory=True且num_workers>0时,PyTorch会在GPU显存之外,额外在系统内存中预分配 pinned memory 缓冲区;若worker进程异常退出或队列积压,这些缓冲区可能滞留并阻塞后续GPU内存申请,触发虚假OOM。
实操建议:
- 先设
num_workers=0和pin_memory=False跑通,确认是否为DataLoader侧问题 - 若必须用多worker,确保每个worker内不创建GPU张量(如自定义
collate_fn里调用.cuda()) - Linux下可配合
ps aux | grep python查是否有僵尸worker进程,kill后重试
为什么torch.no_grad()没生效?检查装饰器、上下文管理器与函数作用域
torch.no_grad()仅禁用当前作用域内的梯度计算,但极易因作用域嵌套或异步逻辑失效。典型场景:模型forward里写了with torch.no_grad():,但调用方仍在requires_grad=True的张量上运行,梯度图仍会被构建。
实操建议:
- 验证是否真进去了:
print(torch.is_grad_enabled())放在疑似no_grad块内部,输出False才算生效 - 避免混用装饰器和上下文管理器——比如函数加了
@torch.no_grad(),内部又写with torch.no_grad():,虽不报错但易误判 - 推理时务必保证输入张量也来自
torch.no_grad()上下文,否则即使模型不更新,输入梯度链也会撑爆显存
排查CUDA OOM最耗时的环节,往往不是找哪行代码alloc了内存,而是确认哪些内存“本不该还在”。PyTorch的显存管理是懒回收+引用计数驱动的,一个没被del的中间变量、一个意外保留的loss历史记录、甚至Jupyter里某个cell残留的out = model(x),都可能让显存居高不下。动手前,先关掉所有notebook kernel,从干净脚本开始测。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











