捷徑

常見問題

我的模型回報「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 執行時仍然存在,因為它的範圍超出了迴圈的末尾。要提早釋放它,您應該在使用完 intermediate 後立即 del intermediate

**避免在過大的序列上運行 RNN。** 反向傳播 RNN 所需的記憶體量與 RNN 輸入的長度成線性關係;因此,如果您嘗試向 RNN 提供過長的序列,則會發生記憶體不足的錯誤。

這種現象的技術術語是 隨時間反向傳播,並且有很多關於如何實現截斷式 BPTT 的參考文獻,包括在 詞語模型 範例中;截斷由 repackage 函數處理,如 此論壇文章 中所述。

**不要使用過大的線性層。** 線性層 nn.Linear(m, n) 使用 O(nm)O(nm) 記憶體:也就是說,權重的記憶體需求與特徵數成二次方關係。很容易以這種方式 耗盡記憶體(請記住,您至少需要兩倍於權重大小的記憶體,因為您還需要儲存梯度。)

**考慮檢查點。** 您可以使用 檢查點 來權衡記憶體和計算量。

我的 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 選項在工作程式中正確設定隨機種子。

我的遞迴網路無法與資料平行處理搭配使用

在具有 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 的完整開發人員說明文件

檢視說明文件

教學課程

取得適用於初學者和進階開發人員的深入教學課程

檢視教學課程

資源

尋找開發資源並取得您的問題解答

檢視資源