最近在调一个简单的图像分类模型,ResNet18跑CIFAR-10。我batch size已经降到16了,但显存还是稳定上升,跑十几个epoch就OOM了。代码里用了DataLoader,shuffle=True,没开pin_memory。我怀疑是不是自己写的自定义Dataset里存了太多中间变量,或者是在训练循环里把loss和输出都保留了计算图?我试过在每个batch后手动del loss和output,也调用了torch.cuda.empty_cache(),但好像没啥用。有没有老哥遇到过类似情况?或者有没有什么工具能实时监控哪个变量占着显存不释放?感谢!
PyTorch训练时显存一直涨,但batch size已经很小了,是哪里漏了?
全部回复
共 169 条试试把loss.item()拿过来用,别直接加loss,大概率是计算图没断开。
检查下优化器里有没有把梯度累积的变量设成 retain_graph=True,或者试试 torch.no_grad() 包住验证阶段。
大概率是loss.backward()之后没清梯度,试试在每个batch开头加个optimizer.zero_grad()。
检查下有没有把loss.item()和output.detach()漏掉,或者是不是梯度累积时忘了清零。
试试把loss.item()再backward,别直接loss.backward(),可能是计算图没释放干净。
试试在loss.backward()后面加个optimizer.zero_grad(),我之前也这样,清了梯度就好了。
八成是loss.backward()后没清梯度,或者自定义Dataset里存了不该存的中间结果,建议用torch.no_grad()包一下验证环节试试。
这种情况我遇到过,十有八九是训练循环里没做梯度清零或者loss.backward()之后计算图没释放。建议你检查一下是不是每轮迭代都调了optimizer.zero_grad(),或者试试把loss.item()取出来再用,别直接保留loss变量。另外torch.cuda.empty_cache()其实治标不治本,你可以用nvidia-smi -l 1实时看显存变化,或者加一行print(torch.cuda.memory_summary())看看具体谁在占内存。
遇到过类似情况,多半是loss.backward()之后没清梯度或者计算图没释放干净。试试在optimizer.zero_grad()之前加一句model.zero_grad(),或者检查下自定义Dataset里是不是把每张图都存成了变量而不是Tensor。另外torch.cuda.empty_cache()只能清缓存不能解引用,建议用torch.cuda.memory_summary()看看具体哪块在涨,有时候是DataLoader的num_workers开太多也会累积显存碎片。
遇到过类似情况,你怀疑的计算图没释放大概率是元凶。试试在loss.backward()之后加上optimizer.zero_grad(),或者用with torch.no_grad()包住评估阶段的forward。另外检查下自定义Dataset的__getitem__里有没有重复加载图像没释放,用del img或者直接清下缓存。实在不行可以装个nvidia-smi dmon或者pytorch的torch.cuda.memory_summary()来实时盯一下。
断掉loss和output的计算图还不够,得看下是不是优化器step后梯度没清零,或者自定义Dataset里存了增强后的图。
大概率是loss.backward()之后optimizer.step()前没挂zero_grad,或者自定义Dataset的__getitem__里return了Tensor没转成numpy。试试用pytorch的profiler看下哪个op在累积。
大概率是loss.backward()之后optimizer.step()前没加optimizer.zero_grad(),梯度累积把计算图撑爆了。
大概率是loss.backward()之后optimizer.step()没做zero_grad,梯度累积把显存堆满了。用nvidia-smi看下进程占用曲线最直观。
试试在batch循环里加个torch.cuda.reset_peak_memory_stats(),配合显存快照能定位到具体是哪一行分配的张量。
我之前也踩过这坑,del和empty_cache其实治标不治本,大概率是优化器step之前梯度没清零,或者loss.backward()之后没把梯度归零,试试optimizer.zero_grad()放对位置没。另外自定义Dataset里如果存了图片的numpy副本,每次取数据都会额外占一份显存,建议检查下__getitem__里有没有把变量赋值给self。监控的话可以pip装个pytorch_memlab,能按行告诉你哪块显存没释放,比手动猜快多了。
我之前也踩过类似的坑,尤其是自定义Dataset里如果写了类似self.images.append(...)这种操作,整个训练集所有样本的中间特征都堆在内存里,显存自然就跟着涨,跟batch size关系不大。你试试把Dataset里的数据预处理结果直接return,别存成列表,或者用__getitem__里局部变量算完就丢,基本能解决。另外训练循环里除了del loss和output,还得把optimizer.zero_grad()放在forward之前,如果放在backward之后,有些版本的PyTorch会把上一轮的梯度计算图残留下来,虽然不常见但确实会导致显存缓慢累积。至于监控工具,推荐用nvidia-smi加pytorch的torch.cuda.memory_summary(),它会列出每个tensor的分配情况,但更直观的还是写个hook在每个step后打印最大显存占用,看是不是某个固定层在膨胀。还有个容易忽略的点是DataLoader的num_workers,如果设成0或者太大,会有一部分显存被数据加载的子进程占着不释放,你可以试试把num_workers调成2或者4,同时开pin_memory=True,反而可能让显存更稳定。如果这些都不行,就把模型换成torchvision自带的ResNet18,排除是不是自己改动模型结构时引入了什么持久化buffer。
试试在loss.backward()后面加optimizer.zero_grad(),位置不对的话梯度会一直累积,显存自然就爆了。
试试用nvidia-smi看PID和显存,再对比梯度累积前后,八成是loss.backward()后没清梯度。
用pytorch的torch.cuda.memory_summary()抓一下内存分配,通常都是优化器state或BN的running stats在偷偷长。
我之前也踩过这个坑,问题大概率不是batch size,而是你训练循环里每个step的loss.backward()之后没做optimizer.zero_grad(),或者反向传播后计算图没释放。可以试试在backward后面加一步optimizer.zero_grad(),再把loss.item()取出来用,别直接保留loss张量。另外自定义Dataset里如果返回了图像增强的中间Tensor,建议在__getitem__里转成numpy再返回,不然每个epoch都会累积新的引用。监控显存的话,pytorch有个torch.cuda.memory_summary()能看当前分配明细,或者用nvidia-smi配合py-spy看python栈,比瞎猜快。我上次就是这么找到是DataLoader的num_workers开太多导致缓存没释放,调成0就好了。
这问题我熟,之前调Unet也这样,你查一下backward之后optimizer.step()之前是不是有loss.item()没调,或者评估模式里还带着grad。另外自定义Dataset里如果做了数据增强的随机张量没释放,叠加几百个batch也会涨,建议用tracemalloc或者pytorch的memory_summary()看下每层的缓存。还有个小技巧,把dataloader的num_workers调成0试试,有时候多进程预取也会占显存。