1. 程式人生 > 其它 >Pytorch 訓練停止,輸出顯示 died with <Signals,SIGKILL.9> 問題定位過程記錄

Pytorch 訓練停止,輸出顯示 died with <Signals,SIGKILL.9> 問題定位過程記錄

  最近使用 Pytorch 進行模型訓練時,模型在訓練到一小部分後程序均被停止。第一次以為是由於機器上其他人的誤操作,故而直接重新拉起訓練。但第二次程式終止時,發現基本與第一次訓練停止的訓練 iteration 一致,故而嘗試對問題進行定位。

  

  問題描述

  具體而言,在使用 Pytorch 訓練時的錯誤資訊類似如下所示:

  File "/usr/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.7/runpy.py", line 85, in
_run_code exec(code, run_globals) File "/usr/local/lib/python3.7/dist-packages/torch/distributed/launch.py", line 340, in <module> main() File "/usr/local/lib/python3.7/dist-packages/torch/distributed/launch.py", line 326, in main sigkill_handler(signal.SIGTERM, None) # not coming back File "
/usr/local/lib/python3.7/dist-packages/torch/distributed/launch.py", line 301, in sigkill_handler raise subprocess.CalledProcessError(returncode=last_return_code, cmd=cmd) subprocess.CalledProcessError: Command '['/usr/bin/python3', '-u', xxx]' died with <Signals.SIGKILL: 9>.```

  從中可以獲得了資訊是程式由於收到了對應的訊號 SIGTERM,從而對應的訊號處理 handler 被呼叫,最終使得程式執行的程序被終止。 SIGKILL 與 SIGTERM 均為傳遞終止資訊的訊號,也就是說有存在行為向執行中的程式傳送了終止訊號,所以問題在於定位傳送訊號的原因。

  問題定位

  首先由於輸出顯示程式收到了 SIGTERM 訊號,所以嘗試尋找發出對應訊號的位置。由於傳送訊號的行為需要經過作業系統 kernel,這裡可以通過 dmesg 命令檢視最近的核心操作(實際是輸出了核心中記錄行為的 kernel ring buffer 的內容)來檢視是否存在相關的資訊。具體命令如下所示,其中 dmesg 的 -T 選項表示顯示命令的操作時間。grep 的 -E 引數表示使用拓展的正則表示式進行匹配。 -i 引數表示忽略匹配的大小寫問題,-B num 引數表示在匹配行之前另外輸出 num 行的資料,主要可以用來看是否有上下文的相關資訊。命令來自What killed my process and why?

    dmesg -T | grep -E -i -B100 'killed process'

  如在筆者機器上,出現的問題資訊如下所示:

  

  這裡顯示使用者 uid = 1002 的程序由於 OOM 即 out of memory 的問題被 kill 了,且給出了在終止時整體的虛擬地址的使用量。這裡可以通過 id 命令獲取個人使用者的 uid / gid 等內容。

    id        // 獲取當前使用者的 uid 等資訊

  通過 id 命令確認了 uid = 1002 即為當前筆者的使用者 id。故而可以確定程式終止的原因是程式執行過程中發生了 OOM 導致系統向程式傳送了 SIGTERM 訊號。結合之前筆者發現程式總是在訓練到一定的 iteration 時即結束,即可猜測是由於程式存在記憶體洩露問題,導致隨著訓練的進行,記憶體佔用越來越大,從而最終出現 OOM。

  

  問題復現

  在獲得問題的猜測之後,可以通過重新執行程式來複現,看猜測是否正確。想要確定程式在執行時的記憶體使用情況,可以使用 ps 或者 top,兩個命令均可以顯示程序的資源使用情況,其中前者輸出命令執行時的系統的結果,而 top 則會顯示系統動態的情況。在執行程式後,使用 top 命令獲取當前執行程式的程序 id。ps 和 top 命令的結果展示如下所示,會顯示程序執行時的許多資訊。

    PID     //程序 PID
    USER    //程序所屬的使用者
    PR      //程序優先順序 
    NI      //Nice值,與程序排程相關
    VIRT     //程序使用的虛擬地址空間大小
    RES      //程序使用的不可交換實體地址的大小,resident memory size,如使用的 hugepage 等
    SHR      //使用的 shared memory 的大小
    S      //程序所處的狀態,包括 R:running(可執行或這在執行),S:sleeping, T:traced or stopped, Z:zombie, D:uninterruptiable sleep
    %CPU    //CPU使用率,以時間比例計算
    %MEM    //使用的實體記憶體比例
    TIME        //程式開始至目前的時間
    COMMAND    //執行程式名

  一個 top 執行時的例項如下所示,這裡我們可以通過 COMMAND 找到正在執行的程式確定 PID,同時通過 VIRT 檢視程式的記憶體使用情況。

  

  由於 top 時動態更新的結果展示,其中會隨著程序的排程等進行更新,這裡主要通過 top 命令獲得執行的程式的 PID,之後通過該 PID 使用 ps 命令檢視程式記憶體的使用情況。主要藉助 watch + ps + grep 的命令組合。

    watch -n 1 "ps aux | grep PID"    // watch 命令指定間隔 1s 執行命令,ps 命令顯示程序資訊,grep 用於篩選出目標程序所在的行

  在筆者實驗中,通過上述方式確定了程式確實存在記憶體佔用逐漸變大的問題,需要進行調整。

  具體問題

  由於筆者對於程式碼的修改部分很小,所以比較容易的發現問題。筆者這裡的原因是想要統計訓練過程中的平均 loss,所以在程式碼中加入了類似如下的程式碼:

    loss = L2( GT, output )
    loss.backward()
    total_loss += loss    //統計 loss 的情況

  而問題的主要原因即在於加入的求 total_loss 的程式碼部分。Pytorch 中計算產生的 tensor 也就是這裡的 loss 預設會納入導數計算的計算圖中,後續所有使用了 loss 的計算式也會被納入計算圖中(也就時 total loss 的計算也被加入了計算圖),所以關於 total_loss 部分的計算圖會逐漸積累變大。在 Pytorch 中,計算圖的儲存位於記憶體中,故而也就會產生記憶體佔用越來越大的問題。解決的辦法即將 total_loss 部分從計算圖中摘除,或者明確說明 total_loss 不需要進行導數運算即可,可通過 item 函式實現。Pytorch 中 item 函式會直接返回 tensor 具體的資料值,此時的 total_loss 計算過程即與 tensor 計算不相關,不需要進行梯度計算,運算過程就不會被納入計算圖的建立過程中了。

    total_loss += loss.item()

  參考

  Pytorch model training CPU Memory leak issue

  How Computational Graphs are Constructed in PyTorch

  Difference between "detach()" and "with torch.nograd()" in PyTorch?

  How can i process multi loss in pytorch?