常见问题解答¶
我的模型报告“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
是具有 autograd 历史记录的可微变量。您可以通过编写 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)
使用 内存:也就是说,权重的内存需求随特征数量的平方而增加。很容易 耗尽内存(请记住,您将需要至少是权重大小的两倍的内存,因为您还需要存储梯度)。
考虑使用检查点。您可以使用 检查点 将内存换取计算。
我的 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
启动的。请参阅 torch.utils.data.DataLoader
的文档,了解如何在工作程序中使用其 worker_init_fn
选项正确设置随机种子。
我的循环神经网络无法与数据并行运行¶
在 Module
中使用 pack sequence -> recurrent network -> unpack sequence
模式与 DataParallel
或 data_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)
此外,当批处理维度为 1
(即,batch_first=False
)并行使用数据时,需要格外小心。在这种情况下,pack_padded_sequence
的第一个参数 padding_input
的形状将为 [T x B x *]
并且应该沿着维度 1
分散,但是第二个参数 input_lengths
的形状将为 [B]
并且应该沿着维度 0
分散。将需要额外的代码来操作张量形状。