最近在调一个UNet做医学图像分割,输入patch是256x256,batch size设的8,显卡是3090(24G)。刚开始训练loss下降挺正常,结果跑到第20个epoch左右,突然报CUDA out of memory。但我用nvidia-smi看显存占用才60%左右,而且显存是慢慢涨上去的,不是一下子爆的。我怀疑是不是PyTorch的缓存机制在搞鬼,还是有内存碎片化的问题?试过torch.cuda.empty_cache()也没啥用。另外,我用了混合精度(autocast),但感觉反而更吃显存了?有没有大佬遇到过类似情况,求指点排查方向,或者有没有什么工具能可视化显存分配?谢谢了。
PyTorch训练到一半显存爆掉,但看占用率才60%,这正常吗?
全部回复
共 55 条大概率是缓存碎片化,试试给dataloader加prefetch_factor=2,再把batchsize降到4看稳不稳。
这情况大概率是PyTorch缓存碎片化,混合精度反而会放大碎片问题,试试max_split_size_mb参数或者换dataloader的pin_memory。
你试试用nvidia-smi看下进程的显存分配,碎片化时缓存块不连续,占用率和实际可用对不上很正常。
大概率是碎片化,allocator把显存切成小块后新的大块分配不到,试试max_split_size_mb调大点。
混合精度反而更吃显存可能是临时梯度没释放,查下loss.backward后有没有del变量。
大概率是缓存碎片化,PyTorch的缓存分配器会一直持有显存不还给驱动,nvidia-smi看到的是驱动层面的占用,跟你进程实际reserved的有差别。你可以试试设置PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb=64,或者用torch.cuda.memory_summary()看下分配细节,碎片多的话这个输出里能看出来。混合精度理论上应该省显存,你确认下是不是loss scaling或者gradient checkpointing没配合好,有些操作在fp16下反而会临时开更大buffer。我之前跑分割也遇到过类似的,最后是把batch size降到4加梯度累积解决的,虽然慢点但至少稳。
3090这情况太典型了,torch的缓存分配器确实会占着显存不释放,nvidia-smi看到的60%可能只是当前实际使用,但缓存已经预留了其他块。你可以试试在训练循环里用torch.cuda.reset_peak_memory_stats()看下真正的峰值,另外autocast配AMP时如果loss scaling没调好,反而会额外存梯度副本。我上次用pytorch的memory profiler(torch.cuda.memory._dump_snapshot)发现是中间变量没释放,你查下UNet的跳跃连接有没有在循环里重复创建图。
遇到过类似的,nvidia-smi看的其实是显存占用峰值,但PyTorch的缓存池是预分配的,实际可用显存可能已经见底了,所以60%占用不代表安全。混合精度理论上省显存,但autocast如果配合gradscaler,有时会额外缓存中间激活值,反而可能增加峰值占用,你可以试试把batch size调小一点看是否还涨。另外推荐用pytorch的memory_summary()或者torch.cuda.memory_snapshot()看下具体是哪些tensor在占空间,碎片化的话可以试试在dataloader里加non_blocking=True,或者干脆每个epoch后手动释放下缓存。我之前用UNet也遇到过类似问题,最后是通过减小patch size解决的,虽然损失点精度但至少能跑完。
遇到过类似的,你这大概率不是真爆显存,是PyTorch的缓存分配器没把显存还给CUDA,nvidia-smi看到的是占用但实际可复用。试试设置环境变量PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb=128,能缓解碎片化,另外autocast配GradScaler应该省显存才对,检查下是不是loss缩放因子出了问题。可视化的话可以用nvtop或者pytorch的memory_stats接口,能看缓存块分布。
大概率是缓存碎片化+峰值波动,试试把batch size降到4或者用gradient checkpointing,能明显缓解。
这题我熟,3090看着占用率低但OOM太经典了,大概率是PyTorch的缓存分配器把显存“囤”起来了,nvidia-smi显示的60%其实包含了缓存块,而实际需求峰值已经触顶了。empty_cache只是清空未使用的缓存,如果分配器里的碎片化严重,它也没法把大块连续内存腾出来。你试试设PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb=128或者更小的值,强制分配器多用小块内存,碎片问题能缓解不少。混合精度反而更吃显存,多半是autocast的gradient scaling或master weight在作祟,或者你某些层(比如BN)没走FP16,导致额外开销。排查的话,强烈推荐用torch.cuda.memory_snapshot()配合pytorch_memlab库,能按张量打印分配明细,比看nvidia-smi直观得多。另外注意一下,UNet的skip connection如果没及时释放中间特征,前向传播的激活值会像滚雪球一样涨,到第20个epoch才爆可能是数据分布变了导致某些层的输出size突变。最后建议把batch size降到4试试,如果稳定了基本就是峰值内存问题,再配合gradient accumulation找回训练效率。
我之前跑分割模型也遇到过这种显存慢慢涨然后突然爆的情况,大概率就是PyTorch的缓存碎片化在作祟,你那个60%是nvidia-smi显示的实时占用,但PyTorch自己预留的缓存块可能已经乱得不行了。empty_cache只是清空未使用的缓存,对已经分配出去的碎片没辙,可以试试在dataloader里加个num_workers=0或者关掉cudnn.benchmark看有没有改善。混合精度理论上应该省显存,但autocast配合gradscaler时如果某些层没对齐反而会多出临时变量,你检查下是不是所有卷积都包在autocast里了。可视化的话可以用torch.profiler或者pytorch_memlab,能直接看到每个tensor的分配点和峰值。
这情况太典型了,nvidia-smi看的是进程占用,不是torch实际持有的缓存,爆掉的时候往往看外面还有余量。你试试看用torch.cuda.memory_summary(),能直接看到缓存分配细节,大概率是碎片化加缓存累积。混合精度按理说省显存,但如果你loss缩放或者某些层没走fp16,反而可能因为额外张量更吃紧,检查下有没有漏掉哪些op。另外20个epoch才涨说明可能是验证阶段或者某个数据增强触发的,把batch先降到4跑一轮看看峰值涨不涨,能定位是不是缓存没释放。
这情况太典型了,不是玄学。nvidia-smi看到的是整个显卡的全局占用,而PyTorch的内存分配器是预取式的,它会在显存里囤着一大块缓存,所以你看60%其实是它占着但还没全用上,真正爆的是它内部给tensor预留的那部分。你那个显存慢慢涨,大概率是某个中间变量在反向传播时没被释放,或者是你某个操作在autocast下把梯度也转成了fp32,导致显存峰值比纯fp16还高。empty_cache只是清缓存,治标不治本,真正要查的是哪些op在产生大中间张量。建议你试试用torch.profiler跑几个step,看memory timeline,能精确到哪个层在爆。另外3090的24G跑UNet 256x256 batch8按理说绰绰有余,你检查下是不是dice loss里用到了什么全局操作,或者segmentation_models这个库自带了一些额外模块。我之前遇到过类似情况,最后发现是PyTorch版本bug,升级到2.1之后解决了,你也可以先看看自己的版本是不是太旧。
这现象我也撞见过,3090跑UNet按理说24G不该这么脆。你试试把batch size降到4跑几个epoch看显存曲线,如果还是缓慢爬升,大概率是PyTorch的缓存块没释放干净,跟碎片化关系不大。混合精度那事儿,autocast只在forward里生效,但loss和梯度反传还是FP32,显存反而可能多一份临时buffer,建议把scaler也配上再看看。工具的话可以用torch.cuda.memory_snapshot()或者pytorch的memory profiler,能直接看到每个tensor的分配情况,比nvidia-smi直观多了。
大概率是碎片化,PyTorch缓存分配器不会自动整理,试试max_split_size_mb参数。
试试用pytorch的memory_snapshot,能看得很清楚,大概率是碎片化加缓存没释放。