常見問題解答¶
我的模型報告“cuda runtime error(2): out of memory”¶
如錯誤訊息所示,您的 GPU 記憶體已不足。由於我們在 PyTorch 中經常處理大量資料,微小的錯誤可能迅速導致程式耗盡 GPU 所有記憶體;幸運的是,這些情況下的修復方法通常很簡單。這裡有一些常見的檢查項:
不要在訓練迴圈中累積歷史。預設情況下,涉及需要梯度的變數的計算會保留歷史。這意味著您應避免在超出訓練迴圈範圍的計算中使用此類變數,例如,在跟蹤統計資訊時。相反,您應該分離變數或訪問其底層資料。
有時,可微分變數何時出現可能不那麼明顯。考慮以下訓練迴圈(摘自 source)
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。
不要持有不需要的張量和變數。如果您將 Tensor 或 Variable 賦值給區域性變數,Python 在區域性變數超出作用域之前不會釋放記憶體。您可以使用 del x 釋放此引用。類似地,如果您將 Tensor 或 Variable 賦值給物件的成員變數,它在物件超出作用域之前不會釋放記憶體。如果您不持有不需要的臨時變數,將獲得最佳記憶體使用效果。
區域性變數的作用域可能比您預期的要大。例如
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 輸入過長的序列,將導致記憶體不足。
這種現象的技術術語是 backpropagation through time(隨時間反向傳播),關於如何實現截斷的 BPTT 有很多參考資料,包括在 word language model 示例中;截斷由 repackage 函式處理,如 this forum post 中所述。
不要使用過大的線性層。一個線性層 nn.Linear(m, n) 使用 記憶體:也就是說,權重的記憶體需求與特徵數量呈平方關係。透過這種方式很容易 耗盡記憶體(請記住,您至少需要權重的兩倍大小,因為您還需要儲存梯度)。
考慮檢查點。您可以透過使用 checkpoint 來權衡記憶體和計算資源。
我的 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 啟動的。有關如何使用 worker_init_fn 選項在工作程序中正確設定隨機種子的資訊,請參閱 torch.utils.data.DataLoader 的文件。
我的迴圈網路不適用於資料並行¶
在使用帶有 Module 的 pack sequence -> recurrent network -> unpack sequence 模式時,存在一個微妙之處。每個裝置上的 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)
此外,當批次維度是 dim 1(即 batch_first=False)並使用資料並行時,需要特別注意。在這種情況下,pack_padded_sequence 的第一個引數 padding_input 的形狀將是 [T x B x *],並且應該沿 dim 1 分散,但第二個引數 input_lengths 的形狀將是 [B],並且應該沿 dim 0 分散。這將需要額外的程式碼來操作張量形狀。