前言¶
在 Windows 上用 DSH 跑訓練類任務時經常會遇到一種情況:你已經按了停止,agent 也確實退出了,但任務管理器裏某個 python.exe 還在佔着顯存,過一會兒 GPU 又被點滿。看起來像“停了又自己跑起來”,其實是兩件事疊在一起:Windows 不會級聯殺進程,停掉 subagent 只殺掉它直接 spawn 的那個 shell,shell 下面的 bash -> conda run -> python -> worker 全部變成孤兒,繼續持有 CUDA 上下文;而訓練腳本自帶的 for 循環或 --retries N 又會讓外層腳本再起一輪,於是顯存被反覆佔滿。
lvyue1314/dsh-gpu-guard 就是爲這個問題寫的 DSH 插件。它做的是兩件事:把“誰是孤兒根”算出來,並可選地讓一個看門狗一直盯着,在孤兒樹存活超過寬限期後從根上整樹結束。
下面介紹它的工作方式、安裝方法和典型用法。
這是什麼¶
一個社區維護的 DSH 插件,維護者是 lvyue1314,分類爲 admin-security,採用 MIT 許可證。它面向 Windows + Node >= 22 + DSH >= 0.2.1-rc.5 的環境。
定位一句話:掃描持有 GPU 計算上下文的進程,沿着祖先鏈找到那個“發起它的進程已經不存在”的孤兒根,然後從這個根開始用 taskkill /T /F 整樹結束。默認 dry-run,需要顯式確認或開啓看門狗才會真殺。
一個典型的泄漏鏈如下,進程名和層級是插件作者實測的形狀:
<launcher shell> <- agent 停止時被殺掉的就是這一層
└ bash run_train.sh <- 從這裏開始全部成爲孤兒
└ conda run -n myenv python -m pkg.train --retries 2
└ cmd.exe /d /c %TEMP%\tmpXXXXXXXX.bat
└ python -m pkg.train --worker
└ python train.py <- 持有 CUDA 上下文,顯存與利用率都被它佔着
命令行層面本來就能用 taskkill /T /F /PID <根> 解決,難點是你得先知道根是誰,還得在腳本拉起下一輪之前動手。這個插件把這兩步自動化了。
判定規則¶
對每個持有 GPU 計算上下文的 PID,插件會向上走一遍祖先鏈,按下面幾條規則歸類。
1、鏈上某個父 PID 已經不在進程表裏 → 判爲孤兒,根取鏈上最上面那個還活着的進程。
2、父進程存在,但它的啓動時間比子進程還晚 → 說明 PID 被複用了,原父進程實際已死 → 同樣判孤兒。
3、走到 0 或自身(System、服務宿主)→ 視爲正常,不動。
4、鏈上有環,或者長度超出合理深度 → 歸屬不明,一律不殺。
5、鏈上任意一環命中 protect 正則(默認 jupyter、nohup、code.exe、explorer.exe、services.exe、svchost.exe,大小寫不敏感,匹配進程名和完整命令行)→ 跳過。
真正依據的信號是“發起它的那個進程沒了”,不是“它跑了很久”。所以用 nohup 或計劃任務拉起來的常駐任務不受影響,除非把 nohup 從保護列表裏刪掉。
殺的時候殺的是整棵樹最上面的孤兒根,不是最深的那個 worker。這樣腳本里的重試循環和 supervisor 會被一起端掉,不會出現“殺了 worker、父進程又起一個”的情況。
核心功能¶
插件暴露三種使用面,共用同一套掃描和判定實現(lib/core.js 是純函數層,插件和命令行共用,沒有第二套邏輯)。
命令行工具 gpu-guard 提供幾個子命令:
node bin/gpu-guard.mjs status # 誰在佔 GPU、誰是孤兒
node bin/gpu-guard.mjs reap # 預覽:會殺哪些樹
node bin/gpu-guard.mjs reap --apply # 真殺
node bin/gpu-guard.mjs watch --apply # 常駐看門狗(默認 60s 掃一次、孤兒滿 60s 才動手)
node bin/gpu-guard.mjs status --json # 給腳本用
JSON 輸出結構爲 { at, gpu: { gpus, holders }, holders, roots, warnings }。命令行這份不依賴 DSH 運行,裝完隨時可用。
插件工具在對話裏提供三個:
gpu_status:每張卡的顯存/利用率/溫度 + 持有 GPU 的進程 + 祖先鏈 + 哪些是孤兒。gpu_reap:結束孤兒樹,默認 dry-run,傳confirm: true才真殺;可以只處理指定的pids。gpu_guard:看門狗狀態、開啓、關閉、改配置(間隔、寬限期、保護正則)。
看門狗通過配置文件開啓:
node bin/gpu-guard.mjs config --auto # 寫 ~/.dsh/gpu-guard.json: { "autoReap": true }
插件加載時就會啓動定時器。autoReap=false 時看門狗只把孤兒樹打到日誌和 stderr,不動手。孤兒樹要連續存活超過 graceSeconds(默認 60s)纔會被處理,短暫出現的孤兒(比如一個命令剛好結束)不會被誤殺。
安裝與啓用¶
把插件鏈接進 profile:
cd ~/.dsh/profiles/desktop
pnpm add link:/path/to/dsh-gpu-guard
然後二選一掛載。
方式 A,寫進 profile 的 package.json:
{
"dependencies": { "dsh-gpu-guard": "link:/path/to/dsh-gpu-guard" },
"dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-gpu-guard"] } }
}
方式 B,寫進 profile 的 cordis.patch.yml:
- insert:
- id: dsh-gpu-guard
name: file:///path/to/dsh-gpu-guard/lib/entry.js
如果 profile 配置了 patchReload: live,下一次模型調用就能看到三個工具;否則重啓 DSH。README 裏還給了一個用計劃任務替代常駐進程跑看門狗的示例 examples/scheduled-task.ps1。
配置¶
配置文件位於 ~/.dsh/gpu-guard.json,可以用環境變量 DSH_GPU_GUARD_CONFIG 改路徑:
| 鍵 | 默認 | 說明 |
|---|---|---|
autoReap |
false |
看門狗是否自動結束孤兒樹。默認關,因爲這是殺進程 |
intervalSeconds |
60 |
掃描間隔(一次完整掃描約 1.5s:nvidia-smi + WMI 進程表) |
graceSeconds |
60 |
孤兒樹要存活多久才動手 |
protect |
見上文 | 命中即跳過的正則 |
日誌追加到 ~/.dsh/gpu-guard.log(環境變量 DSH_GPU_GUARD_LOG)。插件狀態也可以直接查:GET http://127.0.0.1:<port>/dsh-gpu-guard/state。
適用場景與注意¶
適合使用場景:Windows 上通過 DSH 跑 CUDA 訓練或推理任務,曾經遇到過 agent 停止後顯存仍被佔用、或每隔幾分鐘 GPU 又被點滿的情況。插件只處理持有 GPU 計算上下文的孤兒樹,節點是 Windows,命令行版本不依賴 DSH 也能用。
幾個使用上需要注意的點:
1、插件以當前 DSH 進程的權限運行,殺進程用的是 taskkill /T /F,安裝前應當先讀一遍源碼確認行爲,並確認許可證爲 MIT。
2、接口層面默認不殺,gpu_reap 需要顯式 confirm: true,命令行 reap 需要 --apply。
3、WDDM 下 nvidia-smi 的 per-process 顯存顯示爲 N/A,而且只列計算上下文。插件改從 Windows 性能計數器讀(\GPU Process Memory(*)\Dedicated Usage),把圖形上下文也算上;極少數服務器 SKU 沒有這組計數器時會給出警告並退回 N/A。
4、圖形上下文(瀏覽器/聊天軟件的硬件加速、桌面合成)會被單獨標註爲 G,計算上下文標註爲 C,兩者都有標註 C+G。把性能計數器加總能和 nvidia-smi 報的總佔用對上,就說明沒有看不見的殘骸,顯存剩餘通常是別的常駐程序佔的。
5、PID 複用已用啓動時間校驗擋掉;極端情況下(同一秒內 PID 被複用)仍有窗口,killTree 會在殺之前再讀一次進程表確認。
6、不處理 DSH 的 background job:如果進程是以 run_in_background: true 啓動的,它本來就應該活過 subagent。插件不替你判斷語義,只按“祖先鏈斷沒斷”這條硬事實動手。
作者在 README 裏也說明這是止血手段,根治方案是在 dsh-subprocess-local 那一層用 Job Object(JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE)讓父進程死亡時整棵樹被內核帶走,相關 issue 指向 @deepseek-ai/dsh-win32-process 中已存在的 spawnCurrentTokenJobProcess 原語。
結語¶
停下來別不管的 GPU 守護進程問題,在 Windows + DSH 的組合裏很常見。dsh-gpu-guard 的價值在於把“找孤兒根”這件事從手工排查變成一條命令或一個工具調用,並且在默認配置下不會誤殺。對這類任務,它值得先試用 dry-run 模式看看判定結果是否準確,再決定是否開啓看門狗。
插件目錄頁:https://www.skillhub.cn/plugins/lvyue1314/dsh-gpu-guard
GitHub 倉庫:https://github.com/lvyue1314/dsh-gpu-guard