常见问题

我的模型报告“cuda runtime error(2): out of memory”

如错误信息所示,您的 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

在这里,即使在执行 h 时,intermediate 仍然处于活动状态,因为它的作用域超出了循环的范围。为了更早地释放它,你应该在不再需要时使用 del intermediate

避免在过长的序列上运行 RNN。 通过 RNN 反向传播所需的内存随着 RNN 输入的长度线性增长;因此,如果你尝试输入一个过长的序列,将会导致内存不足。

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

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

考虑使用检查点机制。 你可以通过使用 checkpoint 来权衡内存和计算。

我的 GPU 内存没有正确释放出来

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

如果你的 GPU 内存即使在 Python 退出后仍未释放,很可能是一些 Python 子进程仍在运行。你可以通过 ps -elf | grep python 查找它们,并使用 kill -9 [pid] 手动杀死这些子进程。

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

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

try:
    run_model(batch_size)
except RuntimeError: # 内存不足
    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 启动的。请参阅 torch.utils.data.DataLoader 的文档,了解如何使用其 worker_init_fn 选项正确设置工作进程中的随机种子。

我的循环神经网络无法使用数据并行

```

plain from torch.nn.utils.rnn import packpaddedsequence, padpackedsequence

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

# padded_input 的形状为 [B x T x *](batch_first 模式),并且包含按长度降序排列的序列
#   B 是批量大小
#   T 是最大序列长度
def forward(self, padded_input, input_lengths):
    total_length = padded_input.size(1)  # 获取最大序列长度
    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)

此外,当批量维度为 `1`(即 `batch_first=False`)时,在使用数据并行时需要特别小心。在这种情况下,`pack_padded_sequence` 的第一个参数 `padded_input` 的形状为 `[T x B x *]`,应沿维度 `1` 分散,而第二个参数 `input_lengths` 的形状为 `[B]`,应沿维度 `0` 分散。需要额外的代码来处理张量的形状。

```

本页目录