最近在跑一个简单的ResNet50迁移学习项目,数据集大概2万张图片,每张resize到224x224。我设batch size=32,结果跑第一个epoch就报CUDA out of memory。我用的是RTX 3060 12G,按理说应该够用吧?是不是我DataLoader里开了太多num_workers?还是说模型里用了什么隐藏的显存泄露?尝试把batch size降到8倒是能跑了,但训练慢得离谱,而且准确率也不太行。
想请教下各位大佬,这种场景下一般怎么优化显存?混合精度、梯度累积这些方法真的能立竿见影吗?还是说我哪里写得不规范?求指点,孩子快被显存整自闭了。
用PyTorch做图像分类训练时显存炸了,是我代码写错还是batch size太大?
全部回复
共 167 条RTX 3060 12G跑ResNet50 batch size 32按理说确实不该炸,我拿3060跑过差不多的配置,显存占用大概也就6-7G的样子。你这个情况我第一反应是num_workers开太多确实会额外吃显存,因为每个worker都要copy一份数据到CUDA pinned memory里,尤其你resize到224之后单张图不算小。不过更可能的问题是你有没有把整个验证集或者测试集也放到GPU上了?或者训练循环里有没有不小心把loss或者输出累积在list里没释放?我之前就干过把每个batch的output都append到一个list里想最后算指标,结果显存直接爆掉。建议先用torch.cuda.memory_summary()看一眼到底是谁在占显存,比瞎猜强多了。混合精度基本是立竿见影的,AMP一开显存能省30%-40%,速度还能快一点,你batch size 32应该能轻松跑起来。梯度累积主要是解决batch size受限于显存的问题,但它不会帮你省显存,只是让你用小batch模拟大batch的效果。另外你说batch size降到8准确率不行,大概率是BN层在太小batch下统计量不准,可以试试冻结BN或者换GroupNorm。
RTX 3060 12G跑ResNet50 batch size 32按理说确实不该炸,我之前用3060 12G跑过类似的配置,224x224、batch 32是能撑住的,所以你这边大概率有别的东西在吃显存。先排查一下是不是在训练循环里把loss或者输出累加到了列表里没释放,比如把每个batch的output都append到一个list里,这种隐形的显存泄漏特别常见,跑着跑着就爆了。num_workers本身不占显存,它影响的是内存和CPU,开太多反而可能拖慢数据加载,但不会直接导致CUDA OOM。另外检查下是不是没有用torch.no_grad()包住验证部分,或者模型没有设成eval模式,验证集跑的时候梯度还在算,显存直接翻倍。混合精度是真的有用,AMP一开显存能省差不多三分之一到一半,速度也快,基本是立竿见影的。梯度累积适合你显存不够但又想要大batch效果的情况,batch 8累积4次等效batch 32,但训练时间不会变短,只是让你能跑起来。你可以先用torch.cuda.memory_allocated()打印一下每个阶段显存占用,定位到底是谁在吃,比瞎猜强多了。
RTX 3060 12G跑ResNet50 batch size 32理论上确实不至于炸,但你得看看是不是输入尺寸或者模型本身有额外开销。我怀疑你可能踩了几个坑:一是DataLoader的num_workers开太多确实会占显存,每个worker都会缓存数据,但更关键的可能是pin_memory加上没有及时释放中间变量。另一个常见问题是如果你用了预训练模型但没冻结前面的层,反向传播时所有参数的梯度都存着,显存直接翻倍。混合精度和梯度累积确实有用,但混合精度要配合GradScaler,不然容易出NaN,梯度累积则是用时间换空间,batch size降到8再累积4次效果差不多。建议先用torch.cuda.memory_summary看看峰值在哪,然后试试把模型前半部分freeze掉,再开amp,基本能救回来。
RTX 3060 12G跑ResNet50 batch size 32按理说不至于直接炸,但迁移学习有个坑很容易被忽略:如果你没冻结backbone,整个网络所有参数都在算梯度,显存占用会比只训fc层高不少。另外224x224这个尺寸下,中间激活值才是大头,尤其ResNet50的stage3、stage4特征图,batch 32累积起来很吓人。num_workers一般影响的是内存和CPU,跟显存关系不大,除非你pin_memory配合得不好反而拖慢。混合精度确实立竿见影,AMP能把激活和梯度占的显存砍掉将近一半,速度也能提一些,建议先加上torch.cuda.amp试试。梯度累积是拿时间换显存,batch 8累积4次等效batch 32,但BN统计会有点差异,小batch下可以换成GroupNorm或者冻BN。还有个小技巧是训练时用torch.utils.checkpoint,用计算换显存,对ResNet这种结构挺友好。你先把batch调回16配上AMP跑一下,大概率就能稳住,不用一上来就砍到8。
3060 12G跑ResNet50 bs=32按理说确实不该炸,224×224的输入算下来激活值也没那么夸张。你先别急着怀疑num_workers,那玩意儿主要吃CPU内存,跟显存关系不大。真正容易踩的坑是测试/验证阶段没加torch.no_grad(),或者训练循环里把loss、accuracy这些张量一直往list里塞导致计算图越积越多,报错往往就出在第一个epoch快结束的时候。另外迁移学习如果没冻结backbone,梯度得全存,显存直接翻倍,你可以先试试只训fc层看看还炸不炸。混合精度是真的立竿见影,AMP一开显存能省30%到40%,速度还能快一点,几乎是零成本。梯度累积适合你想用大batch但显存不够的情况,不过它只是模拟大batch,单步显存并不会降。建议先装个torch.cuda.memory_summary或者用nvidia-smi盯着看,确认到底是模型前向炸的还是数据加载炸的,别瞎调batch size。
3060跑ResNet50 bs32不该炸,八成是没关梯度或者忘了加no_grad,混合精度能省不少显存值得试。
3060跑ResNet50 bs=32按理说问题不大,但224×224加上数据增强和DataLoader的pin_memory确实容易顶到天花板。先别急着怀疑代码,用torch.cuda.memory_summary看看是模型本身占得多还是数据加载那块爆的。混合精度基本是立竿见影的,能省三成左右显存,梯度累积配合小batch也能凑出大batch的效果,就是训练时间会拉长。另外检查下有没有在训练循环里意外保留了计算图,比如loss没加.item()或者验证时忘了torch.no_grad(),这些坑挺常见的。