常見問題¶
我的模型報告「cuda runtime error(2): out of memory」¶
正如錯誤訊息所示,您的 GPU 記憶體已用盡。由於我們經常在 PyTorch 中處理大量數據,因此小的錯誤可能會迅速導致您的程式用盡所有 GPU 資源;幸運的是,在這些情況下,修復通常很簡單。以下是一些常見的檢查事項
不要在訓練迴圈中累積歷史紀錄。 預設情況下,涉及需要梯度變數的計算會保留歷史紀錄。這表示您應該避免在訓練迴圈之外的計算中使用這些變數,例如追蹤統計數據時。相反地,您應該分離 (detach) 變數或存取其底層資料。
有時,可微分變數何時會出現可能不明顯。考慮以下訓練迴圈(摘錄自 來源)
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。
不要持有不需要的 tensors 和變數。 如果您將 Tensor 或 Variable 賦值給 local 變數,Python 不會在 local 變數超出範圍之前釋放記憶體。 您可以使用 del x
來釋放此參考。 同樣地,如果您將 Tensor 或 Variable 賦值給物件的成員變數,它也不會在物件超出範圍之前釋放記憶體。 如果您不持有不需要的暫存變數,您將獲得最佳的記憶體使用率。
locals 的範圍可能比您預期的更大。例如
for i in range(5):
intermediate = f(input[i])
result += g(intermediate)
output = h(result)
return output
在此,即使 h
正在執行,intermediate
仍然存在,因為它的範圍超出了迴圈的末端。 為了更早釋放它,您應該在使用完畢後 del intermediate
。
避免在過大的序列上執行 RNN。 透過 RNN 反向傳播所需的記憶體量與 RNN 輸入的長度成線性比例;因此,如果您嘗試將太長的序列輸入到 RNN 中,您將耗盡記憶體。
此現象的技術術語是 隨時間反向傳播,並且有大量的參考文獻介紹如何實作截斷的 BPTT,包括在 單字語言模型 範例中;截斷由 repackage
函數處理,如 此論壇帖子 中所述。
不要使用過大的線性層。 線性層 nn.Linear(m, n)
使用 記憶體:也就是說,權重的記憶體需求與特徵數量成二次方比例。 很容易透過這種方式 耗盡您的記憶體(請記住,您至少需要權重大小的兩倍,因為您還需要儲存梯度。)
考慮檢查點 (checkpointing)。 您可以使用 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 異常物件持有對引發錯誤的堆疊幀的引用。 這會阻止原始 tensor 物件被釋放。 解決方案是將您的 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
及 DataParallel
或 data_parallel()
的 pack sequence -> recurrent network -> unpack sequence
模式時,存在一個微妙之處。每個設備上 forward()
的輸入只會是整個輸入的一部分。由於預設情況下,unpack 操作 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
進行分散。因此需要額外的程式碼來操作張量的形狀。