常见问题
我的模型报告“cuda 运行时错误(2):内存不足”。
如错误信息所示,您的 GPU 内存已耗尽。由于我们在 PyTorch 中经常处理大量数据,一些小错误会迅速导致您的程序耗尽所有 GPU 内存;幸运的是,在这种情况下,解决方案通常很简单。以下是一些常见检查事项:
不要在训练循环中累积历史数据。默认情况下,涉及需要梯度的变量的计算会保留历史数据。这意味着您应该避免在将超出训练循环的生命周期的计算中使用此类变量,例如,在跟踪统计数据时。相反,您应该断开该变量的连接或访问其底层数据。
有时,不同 iable 变量何时出现可能并不明显。考虑以下训练循环(摘自源代码):
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)
使用 内存:也就是说,权重的内存需求与特征数量成平方关系。这样很容易耗尽内存(并且记住,你需要至少两倍于权重的空间,因为你还需要存储梯度。)
考虑进行检查点保存。您可以通过使用检查点来以内存换取计算。
我的 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
选项正确设置随机种子。
我的循环神经网络不支持数据并行性
在使用 pack sequence -> recurrent network -> unpack sequence
模式时,存在一个细微差别,特别是在 Module
中使用 DataParallel
或 data_parallel()
。每个设备上的输入将仅是整个输入的一部分。因为默认情况下,解包操作 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
上分散。需要额外的代码来操作张量形状。