如何在PyTorch中避免“CUDA内存不足”错误

156

我认为这是一个对于GPU内存较低的PyTorch用户来说非常普遍的信息:

RuntimeError: CUDA out of memory. Tried to allocate X MiB (GPU X; X GiB total capacity; X GiB already allocated; X MiB free; X cached)

我尝试通过将每个图层加载到GPU上并再次加载回来来处理图像:

for m in self.children():
    m.cuda()
    x = m(x)
    m.cpu()
    torch.cuda.empty_cache()

但似乎并不是非常有效。我在想是否有什么技巧可以在使用较少GPU内存的情况下训练大型深度学习模型。


4
笑脸表情怎么了?哈哈.. 另外,减小批次大小和/或对较小的图像进行训练。查看 Apex 库以进行混合精度训练。最后,当将批次大小降至1时,您可能希望在每次迭代后暂停将梯度设置为零,因为它只基于一张图片。 - sansa
6
我在使用 Kaggle 时遇到了同样的问题。当我使用 64 的 batch size 时一切正常,但是一旦尝试使用 128,就会出现错误,即使使用 64 的 batch size 也会出现相同的错误。我尝试过多次重置,但 torch.cuda.empty_cache() 没有起作用。相反,我首先禁用了 GPU,然后重新启动内核,并重新激活了 GPU。这对我起了作用。 - multitudes
3
减小向模型输入的数据批次大小。这对我有用。 - patrickpato
1
这是PyTorch的常见问题之一,您可以阅读该指南以帮助定位问题。 - Ynjxsjmh
22个回答

111

虽然

import torch
torch.cuda.empty_cache()

提供了清除已占用cuda内存的良好替代方法,我们也可以通过使用以下命令手动清除未使用的变量:

import gc
del variables
gc.collect()

但即使使用这些命令后,错误可能会再次出现,因为 PyTorch 实际上并没有清除内存,而是清除了变量所占用的内存的引用。

因此,在重启内核并找到最佳批处理大小后减少批处理大小是最好的选择(但有时不太可行)。

另一种更深入地了解 GPU 内存分配的方法是使用:

torch.cuda.memory_summary(device=None, abbreviated=False)

其中两个参数都是可选的。这提供了一个易于阅读的内存分配摘要,并使您能够找出CUDA内存耗尽的原因并重新启动内核以避免再次发生错误(就像我在我的情况下所做的那样)。

逐层传递数据可能有所帮助,但改变网络层的大小或将其分解也会证明是有效的(例如,在进行迁移学习时,有时模型占用了显著的内存)。


20
这段话的意思是,使用torch.cuda.memory_summary()函数可以得到一份易读的内存分配摘要,从而帮助你找出CUDA内存耗尽的原因。但是,打印出来的结果似乎没有任何详细信息可以解决问题。结果显示了分配的内存、活动内存、GPU保留内存等行。那么我应该看哪些内容,并采取什么措施呢? - stackoverflowuser2010
我有一台配备MX130和16GB内存的小型笔记本电脑。适当的batchsize为4。 - Gayan Kavirathne
2
@stackoverflowuser2010 你应该在函数调用之间打印出来,看看哪个导致了最大的内存增加。 - JobHunter69
6
执行 print(torch.cuda.memory_summary(device=None, abbreviated=False)) 以获得美观的信息。 - Elvin Aghammadzada

53

只需要减小批量大小(batch size),程序就可以运行了。在我的训练过程中,出现了以下错误:

CUDA内存不足。尝试分配20.00 MiB(GPU 0;10.76 GiB总容量;已分配4.29 GiB;剩余10.12 MiB;PyTorch总共保留4.46 GiB)

我使用的批量大小为32。因此,我将批量大小改为15,这样就可以正常运行。


5
这并不总是有效的。我已经将批次大小从16降低到2,但仍然“内存不足”。 - Danijel
这对我没用。我把批处理大小降到了1,但仍然出现了相同的内存错误。我尝试了上面发布的所有其他方法,包括清除CUDA缓存和垃圾回收,但都没有成功。 我在想Google Colab是否与其他同时使用Colab的用户共享TPU实例,而且我对内存分配没有任何控制权。 - undefined

29

将批次迭代地发送到CUDA,并使用较小的批次大小。不要一开始就将所有数据一次性发送到CUDA。相反,按照以下方式进行:

for e in range(epochs):
    for images, labels in train_loader:   
        if torch.cuda.is_available():
            images, labels = images.cuda(), labels.cuda()   
        # blablabla  

3
如果我在jupyter笔记本上运行一个启动训练的单元格超过一次,就会收到此错误消息。重新启动内核可解决此问题,但如果我们能够清除缓存的话那就更好了...例如,目前torch.cuda.empty_cache()无法帮助解决此问题,尽管它可能应该可以...:( - David

12

尽量不要拉开您的分数。

当我试图在所有批次中总结损失时,我遇到了相同的错误。

loss =  self.criterion(pred, label)

total_loss += loss

然后我使用了loss.item而不是需要grads的loss,从而解决了问题。

loss =  self.criterion(pred, label)

total_loss += loss.item()

以下解决方案归功于yuval reinakaggle question中。
这个错误与GPU内存有关,而不是一般的内存 => @cjinny的评论可能行不通。您使用TensorFlow/Keras还是Pytorch?尝试使用较小的批量大小。如果您使用Keras,请尝试减少一些隐藏层大小。如果您使用Pytorch:您是否始终将所有训练数据保留在GPU上?确保不要将grads拖得太远,检查您隐藏层的大小。

9

绝大部分内容都已覆盖,但还需添加一些细节。

如果 torch 显示错误信息 "Tried to allocate 2 MiB" 等,这实际上是一个误导性的信息。事实上,CUDA 运行时缺少训练模型所需的总内存。您可以减少批处理大小。例如,即使批处理大小为1也无法正常工作(当您使用海量序列训练NLP模型时可能会出现此问题),请尝试传递更少的数据,这将帮助您确定GPU没有足够的内存来训练模型。

另外,如果您要重新训练模型,则必须再次进行垃圾回收和清理缓存的操作。


1
我正在训练 NLP 模型,批量大小为 2。将其更改为 1 后,它可以正常工作了。 - Wojciech Jakubas
1
我训练了BERT和RoBERTa,并通过减少上下文单词窗口来解决了这个问题。 - TuringTux

4
如果你已经完成了训练,只想使用一个图像进行测试,请确保在开头添加"with torch.no_grad()"和"m.eval()":
with torch.no_grad():
  for m in self.children():
    m.cuda()
    m.eval()
    x = m(x)
    m.cpu()
    torch.cuda.empty_cache()

这可能看起来很显而易见,但它对我的情况奏效了。我试图使用BERT将句子转换为嵌入表示。由于BERT是预训练模型,我不需要保存所有梯度,并且它们会消耗所有GPU的内存。


4

按照以下步骤进行操作:

  1. 减少训练集、验证集和测试集的数据量
  2. 减小批次大小(例如16或32)
  3. 减少模型参数数量(例如少于一百万)

在我使用 Kaggle 内核来训练 Common Voice 数据集时,同样的错误也会出现。我采用了减少训练数据至20000,批次大小为16,模型参数为112K 的方法解决了这个问题。


2

有方法可以避免,但这肯定取决于您的GPU内存大小:

  1. 在迭代解包数据时将数据加载到GPU中,
features, labels in batch:
   features, labels = features.to(device), labels.to(device)
  1. 使用FP_16或单精度浮点数数据类型。
  2. 如果内存不足,请尝试减少批处理大小。
  3. 使用.detach()方法将不需要的张量从GPU中移除。

如果以上所有方法都得到了正确使用,那么PyTorch库已经高度优化和高效。


1

我看没人建议在垃圾收集后等待。如果没有帮助,可以尝试在垃圾被收集前等待。尝试这样做:

import torch
import time
import gc
from pynvml import nvmlInit, nvmlDeviceGetHandleByIndex, nvmlDeviceGetMemoryInfo

def clear_gpu_memory():
    torch.cuda.empty_cache()
    gc.collect()
    del variables

def wait_until_enough_gpu_memory(min_memory_available, max_retries=10, sleep_time=5):
    nvmlInit()
    handle = nvmlDeviceGetHandleByIndex(torch.cuda.current_device())

    for _ in range(max_retries):
        info = nvmlDeviceGetMemoryInfo(handle)
        if info.free >= min_memory_available:
            break
        print(f"Waiting for {min_memory_available} bytes of free GPU memory. Retrying in {sleep_time} seconds...")
        time.sleep(sleep_time)
    else:
        raise RuntimeError(f"Failed to acquire {min_memory_available} bytes of free GPU memory after {max_retries} retries.")

# Usage example
min_memory_available = 2 * 1024 * 1024 * 1024  # 2GB
clear_gpu_memory()
wait_until_enough_gpu_memory(min_memory_available)

1

可能看起来过于简单,但对我很有效;我只是关闭了我的VScode,然后重新打开它,然后重新启动并运行了所有的单元格。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接