快捷方式

常见问题解答

我的模型报告“cuda 运行时错误 (2): 内存不足”

正如错误消息所暗示的那样,您的 GPU 内存不足。由于我们在 PyTorch 中经常处理大量数据,因此小的错误会导致您的程序快速耗尽所有 GPU 内存;幸运的是,这些情况下的修复通常很简单。以下是一些常见的检查事项

不要在训练循环中累积历史记录。 默认情况下,涉及需要梯度的变量的计算将保留历史记录。这意味着您应该避免在超出训练循环的计算中使用此类变量,例如,在跟踪统计信息时。相反,您应该分离变量或访问其底层数据。

有时,可微变量何时出现可能并不明显。考虑以下训练循环(摘自 来源

total_loss = 0
for i in range(10000):
    optimizer.zero_grad()
    output = model(input)
    loss = criterion(output)
    loss.backward()
    optimizer.step()
    total_loss += loss

在这里,total_loss 在您的训练循环中累积历史记录,因为 loss 是具有自动梯度历史记录的可微变量。您可以通过编写 total_loss += float(loss) 来解决此问题。

其他出现此问题的情况:1

不要保留不需要的张量和变量。 如果你将张量或变量分配给局部变量,Python 不会在局部变量超出范围之前释放它。你可以使用 del x 来释放此引用。类似地,如果你将张量或变量分配给对象的成员变量,它不会在对象超出范围之前释放。如果你不保留不需要的临时变量,你将获得最佳的内存使用情况。

局部变量的范围可能比你预期的更大。例如

for i in range(5):
    intermediate = f(input[i])
    result += g(intermediate)
output = h(result)
return output

这里,intermediate 即使在 h 执行时仍然有效,因为它的范围超出循环的末尾。为了更早地释放它,你应该在完成使用它后 del intermediate

避免在过长的序列上运行 RNN。 通过 RNN 反向传播所需的内存量与 RNN 输入的长度成线性关系;因此,如果你尝试向 RNN 提供过长的序列,你将耗尽内存。

这种现象的专业术语是 时间反向传播,并且有许多关于如何实现截断 BPTT 的参考资料,包括在 词语语言模型 示例中;截断由 repackage 函数处理,如 此论坛帖子 中所述。

不要使用过大的线性层。 线性层 nn.Linear(m, n) 使用 O(nm)O(nm) 内存:也就是说,权重的内存需求与特征数量的平方成正比。很容易 耗尽你的内存(并且记住你需要至少两倍于权重的大小,因为你还需要存储梯度。)

考虑使用检查点。 你可以使用 检查点 来用计算换取内存。

我的 GPU 内存没有正确释放

PyTorch 使用缓存内存分配器来加速内存分配。因此,nvidia-smi 中显示的值通常不反映实际的内存使用情况。有关 GPU 内存管理的更多详细信息,请参阅 内存管理

如果您的 GPU 内存即使在 Python 退出后也没有释放,很可能是某些 Python 子进程仍然存活。您可以通过 ps -elf | grep python 找到它们,并使用 kill -9 [pid] 手动杀死它们。

我的内存不足异常处理程序无法分配内存

您可能有一些代码试图从内存不足错误中恢复。

try:
    run_model(batch_size)
except RuntimeError: # Out of memory
    for _ in range(batch_size):
        run_model(1)

但是发现当您确实内存不足时,您的恢复代码也无法分配内存。这是因为 python 异常对象持有对引发错误的堆栈帧的引用。这会阻止原始张量对象被释放。解决方法是将您的 OOM 恢复代码移到 except 子句之外。

oom = False
try:
    run_model(batch_size)
except RuntimeError: # Out of memory
    oom = True

if oom:
    for _ in range(batch_size):
        run_model(1)

我的数据加载器工作线程返回相同的随机数

您可能正在使用其他库在数据集和工作线程子进程中生成随机数,这些子进程是通过 fork 启动的。有关如何在工作线程中使用其 worker_init_fn 选项正确设置随机种子,请参阅 torch.utils.data.DataLoader 的文档。

我的循环神经网络无法与数据并行工作

在使用 pack sequence -> recurrent network -> unpack sequence 模式时,在带有 ModuleDataParalleldata_parallel() 中存在一个细微之处。每个设备上 forward() 的输入将仅是整个输入的一部分。由于解包操作 torch.nn.utils.rnn.pad_packed_sequence() 默认情况下只填充到它看到的最长输入,即特定设备上的最长输入,因此当结果被收集在一起时会发生大小不匹配。因此,您可以利用 pad_packed_sequence()total_length 参数来确保 forward() 调用返回相同长度的序列。例如,您可以编写

from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence

class MyModule(nn.Module):
    # ... __init__, other methods, etc.

    # padded_input is of shape [B x T x *] (batch_first mode) and contains
    # the sequences sorted by lengths
    #   B is the batch size
    #   T is max sequence length
    def forward(self, padded_input, input_lengths):
        total_length = padded_input.size(1)  # get the max sequence length
        packed_input = pack_padded_sequence(padded_input, input_lengths,
                                            batch_first=True)
        packed_output, _ = self.my_lstm(packed_input)
        output, _ = pad_packed_sequence(packed_output, batch_first=True,
                                        total_length=total_length)
        return output


m = MyModule().cuda()
dp_m = nn.DataParallel(m)

此外,当批次维度为 dim 1(即 batch_first=False)时,使用数据并行需要格外小心。在这种情况下,pack_padded_sequence 的第一个参数 padding_input 的形状为 [T x B x *],应该沿着 dim 1 散布,但第二个参数 input_lengths 的形状为 [B],应该沿着 dim 0 散布。将需要额外的代码来操作张量形状。

文档

訪問 PyTorch 的全面開發者文檔

查看文檔

教程

獲取針對初學者和高級開發人員的深入教程

查看教程

資源

查找開發資源並獲得問題解答

查看資源