常见问题¶
我的模型报告“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中,你将会耗尽内存。
这种现象的技术术语是时间反向传播,
关于如何实现截断的时间反向传播有很多参考资料,
包括在词语言模型示例中;截断由
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() 一起使用。每个设备上的 forward() 的输入将只是整个输入的一部分。因为默认情况下,解包操作
torch.nn.utils.rnn.pad_packed_sequence() 只会填充到它看到的最长输入,即特定设备上最长的那个输入,当结果被收集在一起时会发生大小不匹配。因此,你可以利用 total_length 参数
pad_packed_sequence() 来确保
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分散。需要额外的代码来操作张量形状。