最近在做一个小项目,想用ResNet50在自定义数据集上做迁移学习,训练图像分类模型。结果每次跑到第二个epoch,显存就飙到20G左右(我只有24G),然后直接OOM挂掉。数据集每张图是224x224,batchsize已经降到8了,还用了混合精度训练(torch.cuda.amp),也试了梯度累积,但感觉治标不治本。
我怀疑是不是模型本身太大,或者我的数据加载方式有问题?但看网上很多人同样配置都能跑,是不是我哪里写的不对?
想请教一下大家:除了换更小的模型,还有没有其他能稳定跑完训练的方法?或者有没有什么检查工具,能帮我定位显存占用到底在哪个层?谢谢各位大佬了。
用PyTorch跑ResNet50做迁移学习,显存总爆掉,求优化思路
全部回复
共 7 条24G显存跑ResNet50还OOM,batchsize=8加混合精度还崩,这不太正常。你检查下dataloader的num_workers是不是设太高了,或者pin_memory=True导致内存泄漏?另外可以用torch.cuda.memory_summary()打印分配细节,或者挂nvidia-smi看看是不是有残留进程占着显存。还有检查下你的预处理是不是无意中把图像尺寸放大了,或者训练时同时开了验证集的梯度计算。
这情况我也遇到过,ResNet50吃显存确实猛,尤其backbone没冻结的时候。建议先检查下是不是开了梯度checkpointing,PyTorch自带torch.utils.checkpoint能省不少显存,就是会慢点。另外可以试试用torch.cuda.memory_summary()打印下具体哪块爆的,说不定是DataLoader里pin_memory和num_workers开太高了。
这个问题我前阵子也遇到过,其实ResNet50本身还好,问题大概率出在DataLoader的num_workers开太多或者缓存了中间变量。可以试试torch.cuda.empty_cache()在每个epoch结束后清一下缓存,另外检查下是不是用了太大的pin_memory,或者把梯度检查点(activation checkpointing)打开,能省不少显存。至于定位工具,推荐用torch.cuda.memory_summary()或者nvidia-smi盯着看,有时候是数据增强那一步偷偷存了图没释放。
同感,我之前用ResNet50跑224的图,batchsize开到16都能跑,你这8就爆了确实奇怪。会不会是DataLoader的num_workers设太高,或者预处理里无意中做了多余的数据拷贝?可以先跑一个batch看看显存占用,用torch.cuda.memory_summary()能打印出每个tensor的分配情况。另外检查一下是不是把验证集的梯度也存了,关掉requires_grad能省不少。
这问题我碰到过好几次,ResNet50其实不算特别大,但迁移学习的时候梯度回传和中间激活确实能把显存吃满。你batchsize降到8还爆,我猜可能是数据加载那边无意中把图像尺寸搞大了,或者预处理里插入了什么额外的tensor没释放。建议先跑一个简单的profile:用torch.cuda.memory_summary()或者nvidia-smi盯着看,哪个step之后显存突然跳变。另外检查一下你的DataLoader,是不是num_workers开太多导致内存复制开销,或者pin_memory=True在部分显卡上反而有bug。
还有一个容易忽略的点——你是不是把整个ResNet50的backward梯度都保留了?迁移学习通常只需要更新最后几层,前面层可以设requires_grad=False,这样反向传播时中间激活图就不会全量保留,显存能省将近一半。用torch.no_grad()包住特征提取部分,或者先把backbone的权重冻结跑一遍,等收敛得差不多了再解冻微调。
如果还是不行,可以试试activation checkpointing(梯度检查点),PyTorch直接有torch.utils.checkpoint,它用计算换显存,训练速度会慢一点点,但24G卡跑224x224的ResNet50,batchsize 8应该稳如老狗。还有,检查下你混合精度里是不是忘了用GradScaler,有时候amp自动混精没生效,float32跑起来显存直接翻倍。
最后说个坑:自定义数据集路径里如果有中文或者特殊字符,PIL加载图像时可能会反复申请内存不释放,我就被这个坑过一天……
你这情况我太熟了,ResNet50按理说224的图batch 8不至于24G撑不住,除非你输入尺寸不是224x224,或者中间有什么奇怪的预处理把图变大了。建议先检查一下transform里有没有不小心resize到更大的尺寸,或者dataloader的num_workers设得太高导致内存碎片。
不过我更怀疑是优化器状态和中间激活值的问题。ResNet50虽然不算轻量,但batch 8的激活值撑死也就几个G,关键是Adam优化器会额外存一阶二阶动量,加上混合精度下的master weights和梯度,这些叠加起来很容易超。你可以试试把优化器换成SGD(momentum=0.9,weight_decay设小点),这样显存能省下3-4G。如果你的任务对收敛速度不太敏感,效果差不了太多。
另外推荐用torch.cuda.memory_summary()直接打印当前分配和预留的显存块,能看出来到底是模型参数、激活值还是优化器状态在吃显存。更细的可以用torch.cuda.set_per_process_memory_fraction(0.9)提前限制最大使用量,避免突然OOM。如果还想省,可以考虑gradient checkpointing,PyTorch有现成接口,对ResNet50能省30%左右的激活显存,代价就是慢一点。
最后提醒一下,混合精度训练下loss scaling可能会把某些层炸掉,检查一下是不是用了torch.cuda.amp.GradScaler,并且确认没在forward里手动对某些层做fp32强制转换。有时候一个小细节没注意,显存就翻倍。
同感,ResNet50在24G卡上跑迁移学习还爆显存确实挺头疼的。我上周也遇到过类似情况,batchsize降到4才勉强稳住,但训练速度直接崩了。
先帮你排查几个常见坑:第一,检查一下是不是把整个ResNet50的梯度都打开了?迁移学习通常只训练最后一层或最后几层全连接层,前面特征提取层要冻结掉(requires_grad=False)。这样反向传播时只更新少量参数,显存占用会直线下降。第二,数据加载里有没有做额外的数据增强?有些增强操作(比如RandomResizedCrop)在GPU上做会额外占用显存,建议全放在CPU上预处理。
关于定位工具,推荐用torch.cuda.memory_summary()打印详细分配,或者装个pytorch_memlab库,可以在每个iteration前后输出当前显存峰值。还有个骚操作:在你怀疑的层前后加torch.cuda.reset_peak_memory_stats()和torch.cuda.max_memory_allocated()对比,能精确定位哪块儿最吃显存。
另外你提到混合精度和梯度累积都试了还不行,会不会是DataLoader的num_workers开太多?我之前设成8直接多吃了2G显存,降到4就正常了。还有就是图像尺寸虽然是224x224,但ResNet50第一个卷积层输出通道数很大,可以试试把模型的stem部分替换成更轻量的结构(比如用3x3卷积替代7x7),网上有现成的ResNet50变体。
最后检查一下是不是把验证集的梯度也打开了?或者有没有在训练循环里意外保留了中间变量?比如计算loss时没有用loss.item()而是直接保留tensor,这种小细节经常被忽略。如果还不行,可以考虑用DeepSpeed的ZeRO优化器,它对显存占用优化非常暴力。