最近在跑一个图像分割的模型,用的DeepLabV3+,backbone是ResNet101。我把batch_size降到2了,输入图片也缩到256x256,但显存还是从开始的2G一直涨到12G,最后OOM。我查了网上说可能是梯度累积、或者变量没detach的问题,但我没开梯度累积,损失函数也是常用的CrossEntropy。想问问大家有没有什么成熟的debug思路?比如用torch.cuda.memory_summary()看哪里泄露,或者有没有工具能可视化每层的显存占用?另外,是不是我模型里有循环或者多次forward导致的?感谢各位大佬!
PyTorch训练时显存一直涨但batch_size已经很小了,咋排查?
全部回复
共 8 条这情况我熟,之前用DeepLabV3+跑语义分割也遇到过类似的显存泄漏。torch.cuda.memory_summary()确实值得一试,能直接看到哪些tensor没被释放,有时候是中间变量被意外保留了。另外你提到没开梯度累积,但可以检查下DataLoader的num_workers,worker进程如果太多,每个都会拷贝一份模型参数,显存会翻倍涨。还有个小细节:ResNet101的ASPP模块里如果用了空洞卷积,特征图尺寸没对齐的话,某些层会隐性创建大张量。建议你用torch.no_grad()包一下验证阶段的推理,或者试试在每轮迭代后显式调一下torch.cuda.empty_cache(),虽然治标不治本但能快速定位是不是缓存碎片的问题。如果怀疑是多次forward导致的,可以用torch.autograd.set_detect_anomaly(True)看看有没有梯度图没释放。
试试用torch.cuda.memory_summary()定位,顺便排查下数据加载有没有缓存没清。
我前两天也遇到过类似的情况,后来发现是DataLoader的num_workers设太高,子进程里的显存没及时释放导致的。另外你可以试试在优化器step之后手动调一下torch.cuda.empty_cache(),看看显存会不会回落。还有,检查一下模型里是不是用了类似torch.no_grad()包裹但实际还在计算图里的操作,这种隐蔽的坑我踩过好几次。
这种情况我遇到过几次,基本可以先把“循环多次forward”这个怀疑排除掉,除非你代码里显式写了for循环反复调用模型,否则正常的训练循环不会导致显存持续上涨。我觉得最有效的第一步就是跑一下torch.cuda.memory_summary(),它会直接把各个张量的分配情况列出来,配合snapshot功能能精确看到是哪一行代码分配了显存没释放。另外有个小技巧是每次迭代结束后手动调一下torch.cuda.empty_cache(),虽然不解决根本问题,但能快速验证是不是缓存碎片导致的假性上涨。我个人经验里最常见的坑其实是DataLoader的num_workers开太多,或者数据增强里某些操作(比如随机裁剪)没有释放中间变量,尤其是用到了albumentations这种库的时候。还有个冷门点——如果你用了SyncBN或者分布式采样,多卡训练时主进程可能会残留一些梯度变量。建议先单卡+最小模型跑10个iter,看显存变化曲线,如果每步都涨一点,那就用torch.autograd.set_detect_anomaly(True)定位梯度异常,虽然慢但确实能找到问题所在。
遇到过类似情况,建议你先用torch.cuda.memory_summary()看看是不是中间变量没释放,特别检查一下数据加载时有没有把图片转成多余副本。另外DeepLabV3+的ASPP模块里并行卷积多,显存占用本来就高,可以试试把backbone的pretrained参数冻住或者换成轻量级主干。如果训练循环里用了多个loss或者梯度回传时没清空计算图,也可能导致累积增长,记得在backward之后加个optimizer.zero_grad()。
我最近也遇到过类似问题,建议你先把torch.cuda.memory_summary()跑一下,看看是不是某个中间变量没释放,尤其是模型里有没有用到了类似torch.no_grad()或者detach()没加对的地方。另外检查下数据加载器有没有多线程缓存没清,有时候DataLoader的pin_memory或者num_workers设高了也会偷偷占用显存。还有个可能,就是DeepLabV3+的ASPP模块里并行卷积会吃不少显存,可以试试把dilation rates调小一点,或者换轻量级backbone验证下是不是模型结构问题。
这种情况我遇到过几次,最可能的原因其实是DataLoader的num_workers设得太高或者pin_memory=True导致内存碎片化,特别是图像分割这种输入输出都很大的任务,显存看起来在涨其实是碎片太多申请不到连续空间。你可以先试试把num_workers降到0或者2再跑一次,看显存曲线是不是就平了。另外torch.cuda.memory_summary()确实有用,重点看allocated和cached之间的差值,如果cached远大于allocated说明是碎片问题。还有个骚操作是每几个iteration手动调一下torch.cuda.empty_cache(),虽然治标不治本但能临时跑通debug。模型里如果有多次forward但梯度没清空,可以用retain_graph=False或者确保每次backward后optimizer.zero_grad()。顺便说下DeepLabV3+的ASPP模块里空洞卷积可能会产生临时变量没释放,我上次就是在那个位置发现有个中间变量没及时回收。
这问题我遇到过类似的,显存一直涨不回落几乎肯定是某个地方把计算图保留了下来。你可以先试试在训练循环的每个batch末尾显式调用 torch.cuda.empty_cache() 看有没有缓解,如果瞬间降下去说明是缓存没清,但更可能是代码里某个tensor被不小心挂在了loss上。我建议你用 torch.cuda.memory_summary() 打印出来,重点关注那个“Allocated”和“Cached”的差值,如果allocated持续增长而cached不变,那就是有变量没释放。另外检查一下模型里有没有类似 self.xxx = output 这种把中间特征存成成员变量的操作,或者自定义的forward里用了循环并在循环体内创建了新的tensor没有及时删引用。我自己之前用DeepLabV3+时踩过一个坑:辅助损失的那个分支如果没正确处理,它的梯度会一直累积。你可以试试把 torch.no_grad() 包在不参与梯度计算的推理部分,或者用 del 手动删除中间变量。工具方面,torchinfo 能看每层参数和中间显存,但动态图不太准,还是得靠打印各节点的 grad_fn 链来定位。