捷徑

常見問題

我的模型報告「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) 使用 O(nm)O(nm) 記憶體:也就是說,權重的記憶體需求與特徵數量成二次方比例。 很容易透過這種方式 耗盡您的記憶體(請記住,您至少需要權重大小的兩倍,因為您還需要儲存梯度。)

考慮檢查點 (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 的說明文件。

我的遞迴網路無法與資料平行處理一起工作

在使用具有 ModuleDataParalleldata_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 進行分散。因此需要額外的程式碼來操作張量的形狀。

文件

存取 PyTorch 的完整開發者文件

檢視文件

教學

取得適合初學者和進階開發者的深入教學

檢視教學

資源

尋找開發資源並獲得問題解答

檢視資源