常见问题¶
我的模型报告“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: # 内存不足
for _ in range(batch_size):
run_model(1)
但发现当你确实耗尽内存时,你的恢复代码也无法分配内存。这是因为Python异常对象持有对引发错误时的栈帧的引用。这会阻止原始张量对象被释放。解决方案是将你的OOM恢复代码移到except
子句之外。
oom = False
try:
run_model(batch_size)
except RuntimeError: # 内存不足
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()
默认只填充到它所看到的最大输入长度,即该特定设备上的最长输入,当结果被汇总在一起时,会出现大小不匹配的情况。因此,你可以利用 pad_packed_sequence()
的 total_length
参数,以确保 forward()
调用返回相同长度的序列。例如,你可以这样写:
from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence
class MyModule(nn.Module):
# ... __init__, 其他方法等
# 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)
此外,当批量维度为dim 1
(即,batch_first=False
)时,需要特别注意数据并行性。在这种情况下,pack_padded_sequence的第一个参数
padding_input
的形状将为
[T x B x *]
并应沿dim 1
分散,但第二个参数
input_lengths
的形状将为 [B]
并应沿dim
0
分散。需要额外的代码来操作张量形状。