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?