显存泄漏的典型表现是pytorch中torch.cuda.memory_allocated()持续上升,常见诱因包括循环中累积gpu tensor、未释放的hook或buffer、多进程dataloader引用等;可用torch.cuda.memory_summary()对比分配快照定位泄漏点,配合memory_history和引用计数辅助排查。

显存泄漏的典型表现和初步确认
PyTorch中显存持续增长、torch.cuda.memory_allocated() 不断上升,但模型训练/推理逻辑没变,GPU显存占用却越跑越高——这往往是显存泄漏的信号。注意:不是所有“显存没释放”都算泄漏,比如 torch.no_grad() 块里创建的 tensor 若被意外保留引用(如塞进全局 list),就可能阻断回收。
常见诱因包括:
- 在循环中不断
appendGPU tensor 到 Python list 或 dict - 使用
tensor.detach().cpu()后仍保留对原始 GPU tensor 的引用 - 自定义
nn.Module中缓存了未清空的中间结果(如 hooks、buffer) - 多进程 dataloader 里 worker 持有 tensor 引用(尤其用了
pin_memory=True但没及时 .item() 或 .cpu())
用 torch.cuda.memory_summary() 定位泄漏源头
这个函数不返回结构化数据,而是打印人类可读的显存分配快照,关键在对比「两次调用之间新增了哪些 persistent allocations」。
执行建议:
- 在疑似泄漏点前后各调一次:
torch.cuda.memory_summary(device=None, abbreviated=False) - 加
abbreviated=False确保显示每一块 allocation 的 size 和 stack trace(需编译时启用了 debug info,conda/pip 官方包默认支持) - 如果看到大量重复出现的 stack trace(尤其含
<strong>call</strong>、forward、collate_fn),基本就是泄漏发生处
示例片段:
# 在 epoch 开始前
print("Before loop:")
torch.cuda.memory_summary()
<p>for i, (x, y) in enumerate(dataloader):
out = model(x.cuda())</p><h1>❌ 错误:把 GPU tensor 直接存下来</h1><pre class="brush:python;toolbar:false;">history.append(out) # ← 这里会累积显存
# ✅ 正确:只存标量或 CPU 数据
loss_history.append(out.mean().item())print("After loop:") torch.cuda.memory_summary()
为什么 memory_summary() 有时看不到 stack trace?
不是所有 allocation 都带 traceback —— 只有通过 PyTorch C++ 分配器(如 at::empty, at::randn)创建的 tensor 才能关联 Python 调用栈;而底层 CUDA malloc(如自定义 kernel、第三方库调用)或某些 inplace 操作(tensor.add_(...))可能不记录。
排查时注意:
- 确保用的是 debug 版本 PyTorch(
torch.<strong>version</strong>含+cu但不含dev通常也够,pip install torch --force-reinstall --no-deps可重装带符号版本) - 关闭
torch.backends.cudnn.benchmark = True,避免 cudnn 缓存干扰分配模式 - 运行前加
torch.cuda.memory._record_memory_history(enabled=True, max_entries=100000),之后可用torch.cuda.memory._dump_snapshot("snapshot.pickle")导出供离线分析
比 memory_summary() 更细粒度的辅助手段
单靠打印 summary 容易漏掉“缓慢泄漏”,建议组合使用:
- 每轮迭代后检查:
torch.cuda.memory_allocated()和torch.cuda.memory_reserved()是否单调增长 - 用
gc.collect()+torch.cuda.empty_cache()强制触发清理,再看是否回落——不回落说明仍有活跃引用 - 对可疑对象做引用计数检查:
sys.getrefcount(obj)(注意该函数本身会+1,需对比基准值) - 在关键位置插入
torch.cuda.memory._get_memory_history()获取最近分配记录(需提前开启 history)
真正难缠的泄漏往往藏在跨函数边界的 tensor 生命周期管理里,比如一个 hook 函数里保存了 input tensor,而 hook 没被 remove;或者分布式训练中 torch.distributed.all_gather 返回的 list 里混入了未 .cpu() 的 tensor。这些地方 memory_summary() 能指方向,但得靠人工顺着 stack trace 查引用链。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











