常见问题#
创建日期:2018 年 2 月 15 日 | 最后更新日期:2021 年 8 月 5 日
我的模型报告“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
在此,由于 loss 是一个具有 autograd 历史的可微分变量,total_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 输入的长度成线性关系;因此,如果您尝试向 RNN 输入过长的序列,您将耗尽内存。
这个现象的技术术语是 时间反向传播,并且有很多关于如何实现截断 BPTT 的参考资料,包括在 词语言模型 示例中;截断由 repackage 函数处理,具体请参阅 这篇论坛帖子。
不要使用过大的线性层。 线性层 nn.Linear(m, n) 使用 内存:也就是说,权重的内存需求随着特征数量的增加而平方增长。很容易 耗尽您的内存(请记住,您至少需要权重大小的两倍,因为您还需要存储梯度。)
考虑检查点。 您可以使用 检查点 来权衡内存和计算。
我的 GPU 内存没有被正确释放#
PyTorch 使用缓存内存分配器来加速内存分配。因此,nvidia-smi 中显示的值通常不能反映实际内存使用情况。有关 GPU 内存管理的更多详细信息,请参阅 内存管理。
如果即使在 Python 退出后 GPU 内存仍未释放,那很可能是因为一些 Python 子进程仍然存活。您可以通过 ps -elf | grep python 找到它们,并使用 kill -9 [pid] 手动终止它们。
我的 out of memory 异常处理程序无法分配内存#
您可能有一些代码试图从 out of memory 错误中恢复。
try:
run_model(batch_size)
except RuntimeError: # Out of memory
for _ in range(batch_size):
run_model(1)
但发现当您确实遇到 out of memory 时,您的恢复代码也无法分配。这是因为 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 选项在工作进程中正确设置随机种子。
我的循环神经网络与数据并行不兼容#
在使用 pack sequence -> recurrent network -> unpack sequence 模式并在 Module 中使用 DataParallel 或 data_parallel() 时存在一个细微之处。每个设备上的 forward() 的输入仅为整个输入的一部分。因为 unpack 操作 torch.nn.utils.rnn.pad_packed_sequence() 默认仅填充到它看到的 longest input(即该特定设备上的 longest input),当结果被收集在一起时会发生大小不匹配。因此,您可以利用 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 分散。需要额外的代码来操作张量形状。