最近在跑一个图像分割的模型,用的DeepLabV3+,backbone是ResNet101。我把batch_size降到2了,输入图片也缩到256x256,但显存还是从开始的2G一直涨到12G,最后OOM。我查了网上说可能是梯度累积、或者变量没detach的问题,但我没开梯度累积,损失函数也是常用的CrossEntropy。想问问大家有没有什么成熟的debug思路?比如用torch.cuda.memory_summary()看哪里泄露,或者有没有工具能可视化每层的显存占用?另外,是不是我模型里有循环或者多次forward导致的?感谢各位大佬!
PyTorch训练时显存一直涨但batch_size已经很小了,咋排查?
全部回复
共 180 条看到你这个问题我太有共鸣了,之前也被DeepLabV3+的显存折磨过。其实batch_size小不代表不会爆,ResNet101的中间特征图特别大,即便输入256x256,每层输出的激活值累积起来也很可观。你可以试试用torch.cuda.memory_summary()抓一下具体哪个操作在涨,我遇到过是中间特征保存到了list里没释放。另外建议检查一下数据加载时是不是每个batch都创建了新的计算图,比如图像增强用了随机参数没固定seed。还有一个冷门的点:DeepLabV3+的ASPP模块里并行空洞卷积如果没处理好,可能会在反向传播时重复分配显存。你可以先挂一个memory_profiler在训练循环里每步打印,定位到具体是forward还是backward阶段涨的。如果发现是backward阶段涨,八成是某个中间变量被重复引用了,试试用del手动释放或者加torch.cuda.empty_cache()。可视化工具的话,pytorch的torchinfo可以看每层参数占存,但激活值还得靠nvidia-smi配合代码内排查。
试试把dataloader的num_workers设成0,有时候多进程加载数据也会有显存泄露的bug。另外检查下模型里有没有用类似for循环重复调用同一层,或者自定义的forward里有没有保存中间变量没释放。torch.cuda.memory_summary()确实能看出点端倪,尤其是allocated和cached的差值。还有就是看看有没有开sync_bn,这东西在某些版本里也会导致显存持续增长。
torch.cuda.memory_summary()确实值得先跑一下,能看清哪块分配没释放。我之前遇到过类似问题,最后发现是DataLoader的num_workers开太多,加上pin_memory=True,进程间共享显存没及时回收。另外可以试试把验证集的with torch.no_grad()包严实点,有时候验证循环里忘了关梯度也会悄悄吃显存。如果你有自定义的Module,检查下forward里有没有重复调用的op,比如多次使用同一个卷积层但没复用结果。
我遇到过类似的问题,建议先试试torch.cuda.memory_summary(),这个能直接看到哪个tensor占着显存没释放。另外检查一下有没有在循环里反复给网络输入赋值,或者用了list保存中间特征图没清空,这些都会让显存持续增长。也可以用gpustat实时监控显存变化,配合代码定位到具体哪一步开始暴涨。
试试用torch.cuda.reset_peak_memory_stats()记录峰值,再配合memory_summary逐层排查,大概率是中间变量没释放。
这种情况我遇到过,很可能是DataLoader的num_workers开太大或者pin_memory=True导致缓存没及时释放,试试把这俩调低或者关掉。另外你说的torch.cuda.memory_summary()确实好用,能直接看到每个tensor的分配情况,建议在训练循环里每隔几步打印一次,对比看哪些变量没被释放。还有个偏方:把optimizer的zero_grad改成set_to_none=True,有时候能省下不少碎片显存。最后检查下模型里有没有保存中间特征的list,比如for循环里不断append,那个很容易爆。
试试把dataloader的num_workers设成0,有时候多进程加载会累积显存碎片。
试试在每次batch后加上torch.cuda.empty_cache()看能不能缓解,不过治标不治本。更推荐用torch.autograd.set_detect_anomaly(True)来追踪梯度问题,我上次就是靠这个发现某层没用detach导致计算图一直膨胀。另外检查下数据加载有没有pin_memory=True时没及时释放,还有验证集里如果用了no_grad也要确认下是不是漏了。
这种情况我遇到过,大概率不是梯度累积的问题,而是模型内部某些操作在动态图里不断创建新的计算节点。你试过用torch.cuda.memory_summary()打印一下分配记录吗?那个能看到每次分配的大小和堆栈,我上次发现是数据增强里有个随机缩放用了interpolate,每次生成新的梯度图没释放。另外DeepLabV3+的ASPP模块里如果用了空洞卷积,不同rate的并行分支可能会在反向传播时累积中间变量,建议检查下有没有地方显式调用了retain_graph=True。还有个偏方:把optimizer.zero_grad()改成set_to_none=True,有时候能释放一些梯度缓存。你提到没有多次forward,但注意一下训练循环里有没有把loss.backward()写在with torch.no_grad()外面?或者数据加载时pin_memory=True配合num_workers>0也可能导致缓存堆积。如果实在找不出,可以先用torch.autograd.set_detect_anomaly(True)跑一个epoch,它会定位到具体哪行代码产生了异常梯度图。
用torch.cuda.memory_summary配合snapshot能定位到是哪个操作在持续申请显存,我之前这么找到过问题。
我最近也踩过类似的坑,尤其是用ResNet101这种大backbone,显存泄漏问题真的很头疼。你提到的torch.cuda.memory_summary()确实是最直接的排查工具,建议你在每个epoch或者每个iteration结束都打印一下,看看是哪个张量在累积。另外有个小技巧:用torch.cuda.reset_peak_memory_stats()配合torch.cuda.max_memory_allocated(),能看出显存峰值是阶段性涨的还是持续涨的。我自己的经验是,有时候问题不在模型本身,而是dataloader里num_workers开太多,数据预处理阶段会残留未释放的显存,你可以试试把workers设为0看看会不会缓解。还有一种可能是你用了torch.no_grad()没包裹住某些计算,或者模型里有无意中保存了中间变量的操作,比如在forward里把特征图append到一个list里。如果你用到了torchvision的DeepLabV3预训练模型,建议直接跑官方demo对比一下,排除掉模型结构本身的问题。
我最近也遇到过类似问题,后来发现是DataLoader的num_workers设太多导致缓存没释放,你可以试试把workers设成0看看显存还涨不涨。另外建议用torch.cuda.memory_summary()跟踪一下,如果发现是某个特定层持续增长,八成是forward时有不必要的变量被保留了。还有个小技巧:每轮训练后手动调一下torch.cuda.empty_cache(),虽然治标不治本但能帮你快速定位是不是代码逻辑问题。
试试在每次迭代后加个torch.cuda.empty_cache(),看是不是显存没及时释放。
这问题我碰到过,用torch.cuda.memory_summary()确实能帮上忙,重点看下是不是有些中间变量没释放,比如在循环里重复调用了模型或者把loss存成了list。另外你可以试试给dataloader加上pin_memory=False,有时候这个也会让显存偷偷涨。还有个小技巧,每跑几个batch手动调一下torch.cuda.empty_cache(),看能不能把占用压下来,虽然治标不治本但能缩小排查范围。
这种情况我遇到过几次,挺折磨人的。你说的 torch.cuda.memory_summary() 确实是个好起点,它能把所有分配但没释放的张量列出来,特别适合定位那些没被回收的中间变量。不过我觉得根源可能不在梯度累积,而是模型推理过程中有没有意外保留了一些计算图——比如你在循环里反复调用了同一个模型却没有用 with torch.no_grad(),或者 loss.backward() 之后忘了清空 optimizer.zero_grad(),虽然你说了没开梯度累积,但显存一直涨更像是计算图没被释放的典型症状。另外 DeepLabV3+ 的 ASPP 模块里有多尺度空洞卷积,如果输入尺寸不是 8 的倍数,可能触发内部 padding 的重复计算,也会偷偷吃显存。我建议你用 torch.no_grad() 包住验证阶段的 forward,同时检查 dataloader 的 num_workers 是不是开太多,有时候 DataLoader 的子进程会缓存一些张量导致显存不降。要是还不行,可以试试 torch.cuda.set_per_process_memory_fraction(0.9) 提前限制上限,至少不会直接崩掉。至于可视化每层显存,torchinfo 的 summary 能看参数占用量,但动态显存还是得靠 memory_summary 逐行盯。
这种情况我遇到过类似的,排查思路可以这样:先跑一个极简版本,把模型换成简单的卷积层看显存会不会涨,排除数据加载和loss计算的问题。然后重点关注一下你的DataLoader里num_workers是不是设得太高,还有验证/测试阶段有没有把梯度关掉。torch.cuda.memory_summary确实可以看分配细节,但更直接的可能是用torch.autograd.set_detect_anomaly(True)查一下梯度流里有没有异常累加。另外检查一下你的模型里是不是有像nn.LSTMCell这种会循环调用forward的模块。
这种显存持续上涨的情况我碰到过好几次,大概率不是batch_size的问题,而是有变量在计算图中累积了梯度或者引用没释放。你可以用torch.cuda.memory_summary()打印一下分配详情,重点看allocated和cached的区别,如果cached持续涨但allocated波动,那可能是缓存策略导致的,不用太担心;如果allocated一直往上走,那肯定有泄漏。另外检查一下你的数据加载部分,是不是每个epoch都重新创建了新的Tensor但没有回收,比如在DataLoader的worker里用了全局变量。还有一个小技巧:在训练循环里手动插torch.cuda.empty_cache()观察显存变化,虽然不治本但能帮你定位是哪个操作之后开始涨的。至于可视化每层显存,pytorch自带的torch.autograd.profiler可以按模块统计显存,或者用nvidia-smi配合python的pynvml库实时监控。最后确认一下你的模型里有没有类似for循环调用了同一个模块多次但没有用no_grad包裹的情况,这种很容易导致中间变量滞留。
试试在每次迭代后手动调下torch.cuda.empty_cache(),或者用torch.no_grad()包住验证阶段。
试试在每一步迭代后加torch.cuda.empty_cache(),或者检查下DataLoader的num_workers是不是设太高了。
torch.cuda.memory_summary()确实好用,另外检查下dataloader里有没有缓存数据没释放。