
系列專案原始碼: ivanusto/gb10-ops (v0.1.0 / v0.2.0)
前情提要: Day 11 完成了雙節點 DGX Spark 的系統基線整平。今天來談談在長時間極限負載下,如何讓這兩台節點穩定活著,而不是無預警消失在叢集中。
在傳統分離式架構(獨立 CPU + 獨立 PCIe GPU)的伺服器上,記憶體爆掉頂多引發 Linux Kernel 的 OOM Killer 砍掉程序,溫度過高頂多觸發 GPU 降頻(Thermal Throttling)。但在配備統一記憶體架構的 GB10 上,遭遇過兩次常規監控完全抓不到的致命事故:
ping 之外,SSH 與所有服務完全癱瘓。核心日誌在 23:12:53 印下第一行 NV_ERR_NO_MEMORY,最後一筆系統紀錄停在 23:56:00——警訊其實比機器徹底失聯早到了足足 43 分鐘。這兩起事故的共同盲點是:標準維運監控工具在這裡全面失明。
--memory 限制,根本管不到填滿這台機器的隱形怪物。今天依據 gb10-ops v0.1.0(commit 5aa8b22)實作,拆解如何讀取底層數值、由事故量測反推門檻值、建立兩道守護機制,並透過最小權限的 systemd unit 將守護常駐化。
GB10 的硬體架構特殊,三個關鍵狀態的真實位置都不在傳統習慣的路徑上:
| 觀測指標 | 正確讀取路徑 | 為什麼傳統監控在此失效? |
|---|---|---|
| 記憶體餘量 | /proc/meminfo 的 MemAvailable |
NVML 對統一記憶體回報 N/A,nvidia-smi 查詢 memory.total 顯示 [N/A],nvtop 的記憶體欄位完全空白。傳統監控若看「已用百分比」會致命——在 Livelock 前的 43 分鐘,系統回報的「已用率」穩定在漂亮的 29.6%,但實際上 MemAvailable 只剩 0.16 GiB。 |
| 機箱溫度 | /sys/class/thermal/thermal_zone*/temp(7 個 thermal zone 取最大值) |
此平台沒有獨立的 GPU hwmon。雖然 GPU 晶片溫度能透過 nvidia-smi 讀取,但真正決定韌體是否硬斷電的是機箱溫度。 |
| 即時功耗 | nvidia-smi --query-gpu=power.draw |
系統只回報 GPU 核心功耗,模組、記憶體功耗與上限皆為 N/A。hwmon 下無 power*_input,無 INA 驅動,/sys/class/power_supply/ 為空。它僅代表 GPU 供電軌,整機真實功耗需依賴外掛瓦特計(Day 9 實測為 327.77 W)。 |
在一般伺服器(例如 Day 7 的 QNAP 實驗)上,透過 mem_limit 限制容器記憶體非常有效,因為 GPU 有獨立 VRAM,主機記憶體歸 cgroup 與系統管理。
但在 GB10 上,統一記憶體是由 NVIDIA 驅動直接配置。在 2026-09-09 量測到的記憶體分佈如下:
cgroup memory.current : 11 GiB
cgroup memory.peak : 37 GiB
容器內所有程序 RSS 加總 : 4 GiB
---------------------------------------------
主機實際已用記憶體 (Used) : 113 GiB
中間足足相差了 100 GiB!這 100 GiB 被 NVIDIA 驅動納為統一記憶體空間,cgroup 與程序 RSS 完全無法計算。
唯一能看見真相的只有這條指令:
nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv
它誠實回報了 VLLM::Worker_TP0 實佔 102,843 MiB。
結論:
守護機制不能建立在容器限制層(Container limit),必須下沉至主機層(Host level):以/proc/meminfo的MemAvailable判斷全局水位,再以nvidia-smi compute-apps鎖定具體消耗記憶體的目標 PID。
gb10-ops 中的六個核心閾值,每一個都是從硬體真實事故中精確倒推出來的,換機器時必須重新校準:
| 門檻項目 | 設定值 | 推導來源與依據 |
|---|---|---|
| 熱浸透預算 | 機箱區 ≥ 88 °C 連續 240 秒 | 致命斷電事故在 88 °C 以上浸透了 417 秒,設定 240 秒能在韌體硬斷電前 3 分鐘精準煞車。 |
| 受控中止極限 | 連續 330 秒 | 超過此上限,程式強制中止(丟棄任務)的成本,遠低於伺服器不受控斷電損壞硬體的風險。 |
| 冷卻閘門 | GPU 降至 ≤ 55 °C 才允許啟動 | 實測兩次相同重載任務,起始機箱溫度僅差 8 °C,就是「平安跑完」與「過熱被中止」的分水嶺。 |
| 記憶體警告線 | MemAvailable ≤ 6 GiB |
雙機生產服務穩態約 7.85 GiB,叢集重開機瞬態最低點為 6.91 GiB。設在 6 GiB 能做到整夜零誤報。 |
| 記憶體動作線 | MemAvailable ≤ 3 GiB |
Livelock 事故在失聯前水位降至 0.20 GiB,歷史驚險值曾觸及 2 GiB。設在 3 GiB 能確保及時挽救系統。 |
| NVRM 報錯頻率 | 60 秒內出現 5 行 | Livelock 事故發生時的頻率是 21 秒內爆出 24 行。單行警報太吵(歷史背景雜音有數千行),必須以短時間突發速率判定。 |
注意
校準經驗談:
原本直覺將記憶體警告線設在 8 GiB,結果正式服務一跑就持續常態觸發。正確的標定方法是:取樣一整夜正常服務的穩態,加上一次完整的重啟過程,將警告線劃在最低谷值之下,將動作線劃在事故歷史最高點之上。兩條線之間,就是「系統異常但還有挽救時間」的黃金介入區間。
Day 10 的模型實驗啟動後,可用記憶體穩態約在 1.5~2.0 GiB,暖機瞬間甚至會跌到 500 MB,完全落在 3 GiB 動作線之下。
但這時守護程式並不會殺掉它:
MemAvailable < 3 GiB,觸發處置流程。--protect 規則過濾白名單(預設保護名稱帶有 VLLM:: 的核心進程)。VLLM::Worker_TP0,符合保護條件,守護標記為 no_target,只記日誌、不動手。trigger 與 no_target。這反映出一個維運哲理:--protect 比對的是程序標籤,而不是工作角色。當實驗程序披著生產服務的名字時,全域守護會把它當成生產服務來保護。針對這種高壓實驗,必須自帶具備特定辨識能力的本機看門狗(如配方中低於 250 MB 就強制重啟容器的 mem-watchdog.sh)。
守護不是粗暴地寫個無窮迴圈去殺 Process。依照工作能否被打斷,gb10-ops 設計了兩種守護與三種介入機制。
+---------------------------------------------------+
| gb10-ops 守護矩陣 |
+---------------------------------------------------+
|
+-----------------------------------+-----------------------------------+
| |
[記憶體維度:全域常駐] [溫度維度:依任務型態]
| |
gb10-host-guard +------------------------+------------------------+
- 監控 MemAvailable | | |
- 追蹤 NVRM 錯誤速率 [信號中止型] [HTTP API 降載型] [段落標記型]
- SIGTERM -> SIGKILL thermal-run thermal-guard-http hw-sample
- 包裝批次腳本 - ComfyUI / vLLM - 放旗標檔 (/tmp/HOT)
- 獨立 Process Group - 呼叫 /interrupt API - 段落交接處檢查
- SIGKILL 終止 - 清空待處理 Queue - 散熱追上再啟動
gb10-host-guard.py此腳本每 10 秒讀取一次 MemAvailable,同時以非阻塞串流監聽 journalctl -k -f,統計過去 60 秒內的 NV_ERR_NO_MEMORY 次數。
觸發條件(滿足其一):
MemAvailable 低於動作線(3 GiB)連續 3 次取樣。MemAvailable 低於警告線(6 GiB)。滿足後,立即將現場的程序記憶體佔用寫入日誌,接著對消耗最大且未受保護的程序發送 SIGTERM,寬限 20 秒,若未退出則補上 SIGKILL,隨後進入 60 秒冷卻期以待記憶體釋放回穩。
工程師必須知道的 4 個實作細節:
- 每行日誌強制
fsync: 硬體瞬間斷電時,留在記憶體緩衝區(Buffer)裡的日誌等於沒寫。- 日誌追蹤程序自癒與半行拼接: 如果背景 tail journal 的子程序當掉,讀取會變為空值,會造成 NVRM 警報條件靜默失效。因此每輪必須檢查子程序狀態,且非阻塞讀取遇到的半行字元必須暫存,避免
NV_ERR_NO_MEMORY恰好被截斷在緩衝區邊界。- 嚴禁使用
pkill -f與pgrep -f: 這兩者會比對包含呼叫腳本在內的完整命令列,極容易誤殺守護進程自身所在的 Session。守護一律只對nvidia-smi查詢回傳的精確 PID 送出信號。0.0 GiB不得視為 falsy: 在 Python 邏輯中,avail = 0.0絕不能被視為空值而跳過,因為 0.0 GiB 是真實存在且極度危險的瀕死讀數。
thermal-run.pysetsid 將任務置於專屬 Process Group 中執行,每 5 秒讀取機箱溫度。若持續高於 88 °C,秒數開始累加(溫度回落則計數歸零,代表風扇散熱追上了)。累積達 240 秒或瞬時突破 96 °C,立即殺掉整個 Process Group,Exit code 設為 75。thermal-guard-http.py/interrupt 與 /queue API 發出 HTTP 請求,中止當前渲染並清空排隊佇列。犧牲單次生成,換取整台伺服器免於硬斷電。hw-sample.py/tmp/HOT 旗標檔。驅動批次處理的外部 Shell 腳本在每段任務完成後檢查該旗標,只要存在就暫停派工,直到機器冷卻降溫。早期的 README 採用 nohup setsid ... & 配上 crontab 的 @reboot 來啟動守護。這種作法在生產環境有兩大嚴重缺陷:
Operation not permitted,只留下毫無用處的 kill_failed 日誌。在 gb10-ops v0.2.0 中為其量身打造了系統級服務,建立專屬無登入權限的服務帳號 svc-guard,並利用 Linux Capabilities 嚴密限制其權限:
CAP_KILL:允許對系統中其他非 root / root 程序發送信號。systemd-journal 群組:允許讀取核心日誌 /dev/log 與 Journal 檔案。/etc/systemd/system/gb10-host-guard.service[Unit]
Description=GB10 Host Memory Guard
After=network.target
[Service]
Type=simple
User=svc-guard
SupplementaryGroups=systemd-journal
EnvironmentFile=/etc/default/gb10-host-guard
ExecStart=/usr/bin/python3 /usr/local/bin/gb10-host-guard.py \
--interval ${INTERVAL} --warn-gib ${WARN_GIB} --act-gib ${ACT_GIB} \
--sustain ${SUSTAIN} --nvrm-rate ${NVRM_RATE} --protect ${PROTECT} \
--log /var/log/gb10-ops/host-guard.jsonl ${DRY_RUN}
Restart=always
RestartSec=5s
# 最小權限沙箱配置
CapabilityBoundingSet=CAP_KILL
AmbientCapabilities=CAP_KILL
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/gb10-ops
[Install]
WantedBy=multi-user.target
備註
沙箱設定中的兩處刻意保留:
- 未設定
PrivateDevices=true: 因為nvidia-smi必須與/dev/nvidia*裝置節點進行 ioctl 通訊。- 未限制
ProtectProc: 守護必須解析/proc/meminfo,且需利用os.kill(pid, 0)探索進程是否存在。
在正式啟用殺程序功能前,務必先以 --dry-run 跑滿 24 小時,觀察其識別的穩態水位。
git clone https://github.com/ivanusto/gb10-ops.git
cd gb10-ops
git checkout v0.2.0
# 乾跑模式:只記錄 would_kill 事件,絕不動手
python3 guards/gb10-host-guard.py --dry-run
確認記錄中的 avail_gib 在一整夜的正常運作下,始終穩定高於 --warn-gib 設定值。
# 建立無登入權限的專屬系統帳號
sudo useradd --system --no-create-home --shell /usr/sbin/nologin svc-guard
# 安裝主程式與設定檔
sudo install -m755 -o root guards/gb10-host-guard.py /usr/local/bin/
sudo install -m644 systemd/gb10-host-guard.env /etc/default/gb10-host-guard
sudo install -m644 systemd/gb10-host-guard.service /etc/systemd/system/
# 建立專屬日誌目錄並指派權限
sudo install -d -o svc-guard -g svc-guard /var/log/gb10-ops
# 啟動並設定開機自啟
sudo systemctl daemon-reload
sudo systemctl enable --now gb10-host-guard
# 檢查狀態與即時日誌
systemctl status gb10-host-guard
tail -f /var/log/gb10-ops/host-guard.jsonl
確認跑滿一天、日誌沒有誤殺判定後,即可編輯 /etc/default/gb10-host-guard 將 DRY_RUN 設為空值,並執行 sudo systemctl restart gb10-host-guard 進入真實守護模式。
進行長達數小時的 LoRA 訓練或影像微調時,透過包裝器掛載取樣與溫度閘門:
# 1. 背景啟動硬體取樣器 (每 3 秒記錄一次資料,遇高溫產出 /tmp/HOT)
python3 samplers/hw-sample.py --out train_run.jsonl --hot-flag /tmp/HOT --interval 3 &
SAMPLER_PID=$!
# 2. 透過熱保護啟動任務:冷卻至 55°C 才開跑,超過 88°C 達 240 秒則受控退出
python3 guards/thermal-run.py \
--cool 55 \
--soak-zone 88 \
--soak-seconds 240 \
-- python3 train_lora.py --epochs 10
EXIT_CODE=$?
echo "Training finished with exit code: ${EXIT_CODE}" # 75 代表被守護程式安全阻斷
# 3. 停止取樣並輸出極值報表
kill -SIGINT ${SAMPLER_PID}
python3 bench/summarize-samples.py train_run.jsonl --skip-head 60 --skip-tail 15
輸出的統計指標(p10、p90 與平均功耗/溫度)將作為系統長期營運、電費核算與儀表板繪製的核心基準資料。
Day 13|NFS 集中模型庫:單寫多讀、身分校驗與動態掛載實戰
在解決了「NVIDIA 地端 AI 算力機器不會突然掛掉」的問題後,明天來處理 AI 算力叢集的儲存痛點:
pull 發生暫存檔互砍毀損。明天將統一定義集中式模型庫的權限架構、Manifest 驗證機制與多節點掛載規則,讓存放在地端的每一個模型權重,都能被清晰追溯。