iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0

https://ithelp.ithome.com.tw/upload/images/20260910/20141816jpyD09N4VG.png

為什麼維運篇要放在倒數第三天

因為前面 27 天的素材已經夠了。這台機器在系列期間整台停擺兩次(一次斷電、一次只剩 ping 有回應),差點停擺一次(MemAvailable 掉到 2 GiB 被我手動救回),看門狗中止過一支跑了 404 秒的渲染,閘道因為一條死掉的別名被 agent 判定整個掛了兩天。每一件都在當天的文章裡出現過,今天則是將這些個人經驗談和觀察到的現象,把它們逐步寫成維運用的資料,讓類似事件之後不會再發生。

有沒有很熟悉?

沒錯,這就是 PDCA 循環,Plan-Do-Check-Act 循環式品質管理。XD

NVIDIA DXG Spark 的維運不是裝一套監控軟體或平台就收工了,而是得先知道這台機器會怎麼掛掉或當機,再決定維運時優先要看什麼,這在資料中心的 AI 伺服器維運也有異曲同工之妙。統一記憶體的桌面機跟資料中心 GPU 伺服器的死法畢竟不一樣,這篇講的主要是這系列桌面 AI 算力工作站的眉角,除了適用於 GB10 之外,凡是叫小機殼又有相當算力、GPU 熱、高速網卡晶片的同類堆疊設備,也是會有類似情況發生。

先講今天的方法論:這篇裡每一個數字都要能指回一個檔案。 為了寫這篇,我把雙機叢集拆掉、測試完五種狀態的功耗、再照 runbook 復原,這是本系列唯一一次為了寫文章而故意讓服務中斷。

過程中有幾個自己想特別分享的小事,比方說Day 24 那組起跑溫度的口徑、儲存真正的風險在哪、微調的功耗、重啟時間為什麼會漂、以及救了測試資料一命的那份紀錄在這台機器上居然只留七天? 等等,這些都寫在下面對應的段落裡。

GB10 維運用的程式,我同步放在 GitHub 上,成為 gb10-ops 這個專案,歡迎有興趣的人多利用。

散熱永遠是這類機器的問題,起跑溫度決定一切

Day 24 那組對照值得再貼一次,今天為了這份資料的說明,我把當天那份 3 秒一抽的原始紀錄(hw-leg1.jsonl,6,128 列)攤開來處理,並修正 Day 24 那張表的一個小問題。

https://ithelp.ithome.com.tw/upload/images/20260910/20141816uabYaezzH7.png

Day 24 寫的「起跑 55 度對 73 度」是決策當下的讀數,不是真正開跑那一刻的讀數。 把時間軸拉開來看,前一支在 13:30:57 收工,接下來幾秒的讀數是 75 度、73 度,閘門本來應該在這裡把下一支擋住,但閘門被誤殺,驅動腳本直接往下走,等容器真的開始算已經是 52 秒後的 13:31:49,GPU 已經自然掉到 62 度。對照組那支則是等閘門放行後才開跑,起跑是 56 度。

所以真正的差距是這樣:

night-market(走完閘門) coast(閘門被誤殺)
起跑 GPU 溫度 56 度 62 度
起跑 zone 溫度 58 度 66 度
最長連續 zone ≥ 88 225 秒 288 秒(看門狗以 5 秒抽樣累計到 330 秒)
累計 zone ≥ 88 291 秒 342 秒
結果 正常完成 418.8 秒 404.8 秒被中止

差 6 度的是 GPU,差 8 度的是機殼。 修正之後結論反而更乾淨,GPU 那款晶片的溫度掉得很快,可是呢,機殼的溫度降得很慢,而決定一支長渲染任務安不安全的是機殼溫度。這也是為什麼閘門只看 GPU 溫度其實是個折衷,它是那台機器上最容易讀到的代理指標,不是最適合的溫度指標。

負載下的峰值是 GPU 87 度、zone 95 度、89.45 W,而更早那次讓整台斷電的渲染是 zone 平均 91、連續浸透 417 秒。從這幾個數字,可以大致歸納出這台機器的熱模型:它的問題不是瞬時溫度,是浸透時間。GB10 系列機種的機殼小、風道短,連續高負載會讓機殼整體升溫,起跑時機殼有多熱,會決定它能撐多久不當掉。

今天把兩道機制的門檻值攤開:

機制 檔案 條件 抽樣 動作
冷卻閘門 thermal_guard.py --wait-cool 開跑前 GPU 高於 55 度就等(影片任務練是 50 度) 5 秒 等到降下來才啟動,--wait-timeout 300 逾時後印一行然後照跑
看門狗(vLLM-Omni) soak-watchdog.sh zone ≥ 88 連續 330 秒 5 秒 docker stop 中止容器
看門狗(ComfyUI) thermal_guard.py 預設模式 zone ≥ 88 連續 240 秒,瞬時上限 GPU 88、zone 96 5 秒 POST /interrupt 加清空佇列
被動版 hw-sample.py 同樣 88 度、240 秒 3 秒 兩個影片伺服器都不能中途插斷,所以只丟一個 HOT 旗標檔,驅動腳本在兩支之間檢查
斷電紅線(事後歸納) zone 連續浸透 417 秒 這是 9 月 1 日那次斷電的軌跡

三個門檻值的來歷都寫在程式的註解裡,這是我建議的方式,基於之前經驗來的,門檻值和真實故障的資料接近。

感測器路徑也值得寫明,因為這台 AI 工作站跟一般認知不同,GB10 沒有 GPU 的 hwmon,/sys/class/hwmon 底下只有 acpitz、NVMe、四張 mlx5 網卡與無線網卡。所以 GPU 溫度只能走 nvidia-smi --query-gpu=temperature.gpu,而「zone」是 /sys/class/thermal/thermal_zone*/temp 七個 zone 取最大值再除以 1000,七個全是 acpitz。閒置時這七個 zone 落在 45.8 到 47.4 度,也就是 88 度那條線在閒置之上約 41 度。

負責看溫度停服務的閘門腳本需要放進驅動腳本裡,讓它跟渲染是同一個呼叫的一部分(run-leg.py),人會忘記的步驟就不該是一個獨立步驟。

今天量功耗的時候撞到兩件計畫外的事,都跟這個門檻有關。

第一,純文字的 decode 滿載也會踩到 88 度。 雙機 TP=2 跑滿十分鐘,Spark1 的 zone 峰值 92 度,累計有 273 秒待在 88 度以上,但最長連續只有 135 秒,所以看門狗的 240 秒預算沒被觸發。這正好反過來印證浸透模型是對的:它算的是連續而不是累計,中間只要冷卻追得上,那台機器就活得下來。

第二,微調比推論熱得多,而我的閘門根本沒掛在它身上。 微調跑起來 zone 就穩在 92 到 94 度,連續浸透一路往上而且沒有回頭的跡象。這件事我在寫這篇之前完全沒意識到,細節放在功耗那一節,因為它是量功耗時才發現的。

記憶體:同一件事的兩個極端

Day 25 與 Day 26 各碰到一次統一記憶體的認知不一致,今天把它們放在一起看就清楚了。

一極是「以為不夠」。 page cache 讓 torch.cuda.mem_get_info() 只回報 13.4 GiB 可用,accelerate 據此把層卸到 CPU,bitsandbytes 拒絕啟動,錯誤訊息讓你以為記憶體真的不夠,實際上 103 GiB 卡在可回收的快取裡。

另一極是「以為夠」。 batch 推到需要 150 GiB 那格,機器沒有 OOM 例外,沒有被 OOM killer 收掉,整台停擺到只剩 ping 有回應,最後按電源鍵。

這張圖是這篇的核心。要坦白一件事:停擺當下沒有任何自寫的 sampler 在跑,Day 25 那份每 5 秒一抽的 mem-timeline.csv 是重開機之後那次成功訓練的紀錄,不是停擺那一段。真正把那晚留下來的是 sysstat,它每 10 分鐘被動收一次,檔案就躺在 /var/log/sysstat/sa06

https://ithelp.ithome.com.tw/upload/images/20260910/20141816hU6gUTcIlz.png

這張圖有三件事值得看:

第一,倒數計時是真的。 23:12:47 journald 開始印 Under memory pressure, flushing caches(那一晚印了 111 次),六秒後的 23:12:53 NVRM 噴出第一行 NV_ERR_NO_MEMORY,最後一行 journal 是 23:56:00,中間隔了 43 分鐘。之後只剩 ping 有回應,00:03:32 按電源鍵重開。43 分鐘足夠做很多事,包括自動處置。

第二,「快取回收不掉」是這台機器獨有的坑。 掉到底的時候 MemAvailable 只剩 0.16 GiB,而同一時刻 kbcached 還有 83 GB。在一般機器上這 83 GB 是可回收的,作業系統會回收它而不是把自己拖死,在統一記憶體上,那塊快取跟 GPU 的分配糾纏在一起,於是你看得到、拿不到。

第三,取樣器自己被拖慢也是訊號。 sysstat 排的是每 10 分鐘一次,那晚的取樣點卻是 23:10:00、23:22:44、23:33:02、23:50:31,間隔被拉成 12 分 44 秒與 17 分 29 秒。監控系統自己開始遲到,本身就是一個要告警的指標。

兩極的共同根源是統一記憶體下作業系統與 GPU 對「還剩多少」的答案不一樣,而且沒有安全網,獨顯給你 OOM 例外,這台給你一台當掉的電腦。所以規則有三條:

看 MemAvailable,不看 nvidia-smi。 GB10 上 NVML 對統一記憶體回報 N/A,nvtop 的記憶體欄是空的,唯一可信的來源是 /proc/meminfo。順帶一提,救了我的那份資料不是我自己收的,是 sysstat 這個裝好就在那裡的老東西,它的價值在於它一直在跑。自寫的 sampler 只有在你想到要開的時候才在跑,而故障不會等你想到。它的保存期限只有七天這件事,見下一節。

把 NVRM 的 NV_ERR_NO_MEMORY 當一級告警。 它出現到停擺有 43 分鐘,足夠自動處置。要注意這個訊號很吵:整個 journal 現在有 6,697 行,最早可以追到 7 月 27 日,所以「出現就告警」會被雜訊淹沒,要看的是速率,那一晚的特徵是 21 秒內連噴 24 行。

在整台停擺之前先殺掉單一行程。 我原本以為這件事用容器的記憶體上限就能做,動手之後發現 cgroup 根本看不到該看的那一塊,最後做成一支主機層的守護程式,細節在「三件當場補上的事」那節。

健康檢查:行程活著不等於功能活著

系列裡累積了三個反面教材,剛好覆蓋三種說謊方式。

vLLM-Omni 的 /health 回 200,但 result-pump 執行緒死了(Day 24 那個 30 秒逾時的上游 bug),行程級檢查對功能級死亡無感。FastVideo 的 /health 在模型根本沒載入時就回 ok(Day 24,lazy load),就緒檢查對「就緒」的定義跟你不同。LiteLLM 的 /health 因為一條死別名回報 unhealthy,探測它的 agent 判定整個閘道掛了(Day 26),聚合式健康檢查會把局部故障放大成全域故障。

今天叢集拆掉的時候,正好可以現場再驗一次第三個。後端全下線時打 :8100/health

healthy: 0  unhealthy: 3
探測請求: {"model": "ds4-flash-0731-dspark",
          "messages": [{"role": "user", "content": "Hey how's it going?"}],
          "max_tokens": 16}
錯誤首行: litellm.InternalServerError: OpenAIException - Connection error.

這裡有個我原本不知道的細節:LiteLLM 的 /health 本身就是拿一個真的推論請求去探測的,max_tokens 16,提示詞每次還不一樣(另一次跑出來是 What's 1 + 1?)。也就是說我在 Day 18 講「別用推論請求當健康檢查」的時候,我自己的閘道一直都在這樣做。

所以我的健康檢查分兩層,而且要修正 Day 18 那句「別用推論請求當健康檢查」的過度簡化:

探測什麼 頻率 判定
存活 /health/v1/models 每 10 秒 行程在不在
就緒 5 個 token 的最小 completion,逾時 30 秒 每 5 分鐘 功能在不在,回空字串視為失敗

Day 18 那句話的原意是「不要每 10 秒送一次推論請求」,那部分還是對的,成本與 slot 佔用都是真的。但 Day 24 之後的證據顯示,只有行程級檢查會漏掉最貴的一類故障:行程還在、埠還開、/health 回 200,功能已經死了。 兩層設計是折衷,不是升級:存活層維持秒級且便宜,就緒層付得起成本所以只能是分鐘級。

第二層要注意兩件事。第一,它會付推論成本也會佔一個 slot,模型載入期間會誤判,要在啟動流程裡用「首次就緒」而不是「立刻就緒」。第二,回空字串視為失敗,Day 27 那個「思考吃光預算所以回空」的假象在健康檢查上同樣會發生,探測請求要明確關思考或給足預算。

閘道層的規則只有一條:死掉的別名註解掉,不留著。LiteLLM 的聚合健康檢查沒有「忽略這一條」的選項,一條 unhealthy 就是整體 unhealthy。

儲存:ds4 磁碟 KV 的寫入帳

Day 17 埋的帳今天算,而結論跟我下筆前的預期相反:耐寫度完全不是問題,剩餘空間才是。

先講耐寫度。/dev/nvme0root:root 0600,帳號在 sudo 群組但不在 disk 群組,所以要 root 才讀得到(順帶一提,這也是為什麼我的 agent 讀不到它:沒有 tty 就沒辦法給 sudo 密碼,這種需要人類在場的檢查值得單獨列一類)。兩台都跑一次:

Spark 1 Spark 2
型號 ESL01TBTLCZ-27J2-TYN,1.00 TB 同型號
Percentage Used 0% 0%
Available Spare 100%(門檻 5%) 100%
Media and Data Integrity Errors 0 0
Data Units Written 6.29 TB 1.15 TB
Data Units Read 22.2 TB 724 GB
Power On Hours 6,231 小時(259.6 天) 987 小時(41.1 天)
換算日寫入 24.2 GB/開機日 28.0 GB/開機日
折算年寫入 8.84 TB/年 10.21 TB/年
Power Cycles 296 51
Unsafe Shutdowns 34 5

三件事值得看:

第一,耐寫度這個擔心可以直接放下。 開機 260 天、寫了 6.29 TB,Percentage Used 還是 0%,備援區塊 100%,媒體錯誤 0。這款 OEM 碟沒有公開的 TBW,但同級 1 TB TLC 的耐寫規格通常在數百 TB 的量級,照 8.84 TB/年的速度,寫穿它需要幾十年。磁碟 KV 的疑慮不在壽命。

第二,第二台的日寫入其實比第一台高。 24.2 對 28.0 GB/開機日。第二台只是開機時數少(41 天對 260 天),不是比較閒。做容量規劃時要看速率不要看總量。

第三,那 34 次不安全關機就是這個系列的傷疤。 296 次開機裡有 34 次沒有正常關機流程,11%。熱斷電、記憶體停擺按電源鍵、我今天為了量功耗做的那些事,全都記在這款碟自己的計數器裡。這是唯一一份不需要我自己維護、也不會過期的故障紀錄,跟只留七天的 sysstat 剛好是對照。順帶一提第一台讀寫比是 3.5 比 1,讀遠多於寫,那是反覆載入權重的形狀。

https://ithelp.ithome.com.tw/upload/images/20260910/20141816A7JQCAEop9.png

耐寫度沒事,那風險在哪?/var/lib/ds4-kv 現在是這樣:

項目 數字
目前佔用 49.2 GB(256 個 .kv 檔)
最舊的檔案 2026-08-13,26 天前,還在
有寫入的日子 9 天,平均每天 5.46 GB
攤到整個期間 每天 1.89 GB
單日最多 2026-08-14 寫了 20.9 GB(95 個檔)
根目錄剩餘空間 38.1 GB(已用 96%)
照攤平的日均寫入 再 20 天就滿

長對話跨天不用重算 prefill 的代價,不是磁碟壽命,是它沒有淘汰機制。八月十三號那批快取到今天還躺在那裡,而根目錄只剩 38 GB。這條規則因此要改寫:不是「注意寫入量」,是 KV 目錄要有大小上限或定期清理,而且要有人看剩餘空間。

另外兩條儲存規則來自系列裡的實際遭遇。autofs 掛載點的逾時要短,Day 26 那台不通的 NAS 讓 ls /mnt/models 卡六十秒,任何碰到那個路徑的腳本都跟著卡,這是可用性問題不是儲存問題。readahead 的設定要持久化,Day 13 那行 2.8 倍的加速重開機會還原,udev rule 才是正解。

功耗:這台機器的底價

這是今天唯一的新實測,也是 Day 29 成本表的輸入,有幾個地方要留意。

在 GB10 上,你唯一拿得到的功耗數字是 GPU 那一軌。 nvidia-smipower.draw 讀得到,但同一個 nvidia-smi -q -d POWER 裡,Current Power LimitDefault Power LimitModule Power ReadingsGPU Memory Power Readings 全部是 N/A。往下找也沒有別條路:/sys/class/hwmon 底下沒有任何 power*_inputcurr*_inputin*_input,沒有 ina2xxina3221 驅動掛上去,/sys/class/power_supply/ 是空的,tegrastatsjtop 都不存在(這是 DGX OS 不是 L4T,Jetson 那套 INA3221 加 tegrastats 完全沒有)。

所以下面這張表是 GPU 模組功耗,不是整機牆插功耗。明天文章會說明差的那些東西(CPU 叢集、記憶體、四張網卡、風扇、電源轉換損耗),是用智慧插座測試的實際數字。

https://ithelp.ithome.com.tw/upload/images/20260910/20141816eJFuBgSIRS.png

狀態 Spark 1(W) Spark 2(W) 說明
閒置(無任何服務) 5.91 4.36 只有作業系統
閒置(雙機服務常駐、無請求) 12.47 11.53 24 小時養模型的底價
單機 decode 滿載 37.62 不適用 Qwen3.8-27B-FP8,vLLM,12 並發
雙機 TP=2 decode 滿載 51.01 49.31 DeepSeek-V4-Flash,vLLM,12 並發
微調(LoRA 4-bit) 80.00 不適用 Qwen3.8-27B,bs 4、seq 1536,峰值 85.41
影片生成(Day 24 峰值) 89.45 不適用 9 月 5 日量的,本篇沒重跑

每一格都是 3 秒一抽、掐頭去尾之後的窗內平均,樣本數在 52 到 191 之間。滿載是真的滿載:雙機那格 GPU 使用率整段 93%,單機那格整段 96%,而單機那格的功率 p10 到 p90 只落在 36.5 到 37.7 W,穩到不需要爭論取哪個統計量。

簡單說明如下:

第一,把模型養著本身就要錢。 無服務閒置是 5.91 加 4.36 等於 10.3 W,權重載進去之後即使一個請求都沒有,變成 12.47 加 11.53 等於 24.0 W。光是「讓它隨時可用」這件事,就讓閒置功耗多了 2.3 倍。 這是「一次只養得起一款模型」之外,常駐服務的第二個代價。

第二,兩台合體不是免費的。 雙機 TP=2 兩台加起來 100.3 W,跑出 120.7 tok/s,單機 37.6 W 跑出 81.9 tok/s。換成每瓦的吞吐是 1.20 對 2.18 tok/s/W,單機的能源效率是雙機的 1.8 倍。要注意這兩列跑的不是同一款模型(雙機那款單機根本裝不下,這正是合體的理由),所以這個比值是「兩種可行配置的效率差」,不是同模型的對照。合體買到的是「跑得動」與絕對吞吐,代價是每一份吞吐更貴。

第三,最耗電的不是推論,是微調。 這是今天最意外的一格:LoRA 微調吃 80.00 W,是單機推論 37.62 W 的 2.1 倍,峰值 85.41 W 已經逼近影片生成的 89.45 W。推論是記憶體頻寬受限的,算術單元大半時間在等資料,微調要跑反向傳播,算術密度高得多,於是功耗與發熱都上一個檔次。如果你照推論的功耗去估微調的電費與散熱,會低估一倍。

這一格我沒有量完,而且理由本身就是這篇的主題。 微調跑到第 8 步的時候,zone 已經穩定在 92 到 94 度,連續浸透累積到 123 秒還在往上走。剩下的 16 步大約還要八分鐘,也就是連續浸透會直接衝過 417 秒,那正是 9 月 1 日那次整台斷電的軌跡。所以我按照自己訂的規則把量測停掉了。 80 W 這個數字來自 52 個穩態樣本,已經夠用,為了把表格填得更漂亮而去踩自己畫的紅線,那就完全搞錯了寫這篇的用意。

順帶一提,這也改了一件事的優先順序:,我原本以為冷卻閘門與看門狗是影片生成這種重度任務才需要的,今天才發現微調同樣會把機殼推到 94 度。那兩支程式當時只掛在影片鏈上,微調腳本沒有,今晚就同步補上了。

第四,這台的功耗動態範圍很小。 從 5.9 W 到 89.45 W,滿載只有閒置的 15 倍,而且絕對值低到不像一台能跑 27B 模型的機器。這是統一記憶體低功耗設計的另一面,它比較慢,但它相對較省電且整體成本較便宜。

換算電費

台電住宅非時間電價自 2025 年 10 月 1 日起實施,夏月(6 月 1 日到 9 月 30 日)與非夏月分開計價:

級距 非夏月(元/度) 夏月(元/度)
120 度以下 1.78 1.78
121 到 330 度 2.26 2.55
331 到 500 度 3.13 3.80
501 到 700 度 4.24 5.14
701 到 1000 度 5.27 6.44
1001 度以上 7.03 8.86

累進電價的重點是只有邊際級距才算數。一台 24 小時開著的機器是加在家戶用電的最上面那一層,所以要用邊際單價而不是平均單價。以一個月 730 小時算,1 W 連續運轉等於 0.73 度。

情境 月耗電 夏月電費(邊際 5.14) 夏月電費(邊際 6.44)
兩台常駐閒置(24.0 W) 17.5 度 90 元 113 元
兩台整月滿載(100.3 W) 73.2 度 376 元 471 元

這兩個數字是下限,不是估計值。 它們只算了 GPU 那一軌,整機牆插功耗一定更高,而我量不到。Day 29 的成本表會標明這個限制,並且用「下限」而不是「大約」來寫。即使是下限,結論的方向也很清楚:電費不是這套系統的成本瓶頸,硬體攤提才是。一個月三百多塊的電費,換成雲端 API 大概買不到多少 token。

重啟成本:40 分鐘教你的事

雙機叢集啟動要 38 到 40 分鐘,投機解碼要把 48 個 shard 載兩趟。今天為了拆機量功耗,我又付了一次,而且付得比以往都貴:從下指令到服務可用是 46 分 28 秒,其中 Model loading 佔 2588.8 秒,比我手上所有歷史紀錄都慢。這一輪的 KV 池是 2,499,656 tokens。

順帶記一件每次都會嚇到人的事:啟動腳本自己有 25 分鐘的逾時,所以它會在載到一半時 exit 1 退出,而容器還在好好地載。那個 exit 1 不是失敗,只是腳本的耐心比機器短。今天它又發生了一次,我的復原日誌因此停在 01:15,後半段要改看 docker logs

對照組 今天單機起 Qwen3.8-27B-FP8(28.43 GiB,本機 NVMe),Model loading took 28.43 GiB memory and 191.85 seconds,三分鐘出頭。同一台機器載 DeepSeek-V4-Flash 的 79.17 GiB 卻要二十到四十分鐘。差的不是 2.8 倍的體積,是投機解碼要載兩趟、以及兩台要對齊。

KV 的數字每次都不同。 將歷次啟動日誌撈出來對,同一份權重、同一個設定,Model loading 的秒數落在 1200.7 到 2588.8 秒之間,最快跟最慢差 2.2 倍。所以「重啟要 40 分鐘」這句話應該說成「重啟要 20 到 45 分鐘,而你不能預測是哪一種」。變更管理要照最壞的那個數字排。

今天總算查出它為什麼會漂。 復原的時候日誌裡有這麼一行:

WARNING [weight_utils.py:862] Network filesystem (NFS4) detected but checkpoint
total size (155.43 GiB) exceeds 90% of available RAM (34.71 GiB).
Skipping auto-prefetch.

兩件事同時成立:這款模型的權重放在 NAS 上(/mnt/nas/AIModels/hf-cache,走 NFS4,不是本機 NVMe),而且 vLLM 本來會為網路檔案系統做的自動預取被跳過了,因為 155.43 GiB 的檢查點比當下可用的記憶體大得多。於是載入速度直接取決於那一刻的網路與 NAS 狀況,以及 page cache 裡剛好還留著多少。20 到 40 分鐘的差距不是玄學,是它每次都要重新把上百 GiB 從網路上拉一遍,而快取幫不上忙。

這也解釋了為什麼本機 NVMe 上的 Qwen3.8-27B 只要 192 秒。如果哪天要把重啟時間壓下來,該動的是權重的位置,不是啟動腳本。

變更管理。 每一次重啟都是這麼一段服務中斷,所以設定變更要批次做,改一項重啟一次是奢侈。Day 26 發現 docker compose up -d 對 bind mount 的設定檔無感,要 restart 才生效,這種「以為改了其實沒改」的坑會讓你白付一次。

每次啟動都記錄關鍵數字。 KV 池的 token 數每次重啟都漂,加上今天這一輪總共七個值:2,468,694、2,469,284、2,478,130、2,480,342、2,481,521、2,499,656(今天)、2,702,825。最大跟最小差 9.5%。它不是模型的屬性,是那次啟動的屬性。啟動日誌要留檔,引用數字要標明是哪一輪。

關機很便宜,開機很貴。 今天實測拆掉雙機叢集只花了 11 秒(stop 腳本掃完兩台)。這個不對稱值得記住:你隨時可以停,但你不能隨時回來。

監控堆疊:看什麼,用什麼看

先誠實回答「你實際用什麼」這個問題:沒有堆疊。 這台機器上沒有 Grafana、沒有 Prometheus、沒有 node_exporter、沒有 netdata、沒有 telegraf,dpkg 查下去零筆,也沒有任何 metrics 埠在聽。

實際在跑的是這四樣:

  1. 逐次實驗自己寫的 sampler。 hw-sample.py 每 3 秒抽一次 nvidia-smi、七個 thermal zone 與 /proc/meminfo,寫成 JSONL,每一行都 fsync。這個設計不是龜毛,是因為這台機器的死法之一是硬斷電,沒 flush 的資料就是沒有。今天這篇的功耗數字全部出自它。

  2. sysstat。 裝好就在那裡,每 10 分鐘一次。它救回了上面那張圖,而且是險勝:/etc/sysstat/sysstatHISTORY=7,只留七天,sa06 這個檔案再過三天就會被自動刪掉。我今天把它複製了一份出來。事後才想查的證據通常已經在倒數,這是我今天學到最便宜的一課。

  3. 一支 cron 巡檢,每天 06:00 由一個唯讀的 agent 產一份維運報告。這裡有個值得看的數字:從 8 月 14 日到今天,該產 26 份,實際產出 17 份,失敗 9 次。九次的原因全部留在 dsh-daily-inspection.err 裡,而最後一次(09-04)的錯誤是這樣的:

    dsh: SERVER: litellm.InternalServerError: OpenAIException - Connection error..
         Received Model Group=deepseek-v4-flash
    

    巡檢是靠模型寫的,而模型正是它要巡檢的東西。 後端一掛,報告就不存在,於是最需要那份報告的日子剛好沒有。這不是設計失誤,是這種「用 agent 做維運」的架構內建的耦合,值得知道但不一定要修:至少它是明確失敗(.err 留下逐字錯誤),不是靜默跳過。

  4. 原廠的 DGX Dashboard,dgx-dashboard.service 一直開著,佔 24 MB 記憶體,聽在 127.0.0.1:11000,要帳密登入。

https://ithelp.ithome.com.tw/upload/images/20260910/201418163v3EjVOgZh.jpg

左邊是 System Memory 與 GPU Utilization 兩個錶頭加時間軸,右邊是 JupyterLab 的啟動器。沒有溫度、沒有功耗、沒有逐行程的分佈。而最關鍵的問題出在那個記憶體錶頭,它顯示的是「已用」。

截圖那一刻是 121.16 GB/130.60 GB,93%,而那正是雙機叢集正常服務、一切健康的狀態(MemAvailable 還有 7 GiB)。反過來看停擺那一晚,sar 留下的 kbmemused 是這樣:

時間 儀表板會顯示的「已用」 真正該看的 MemAvailable
23:10:00 39.20 GB(30.7%) 23.63 GiB
23:22:44 37.76 GB(29.6%) 0.20 GiB
23:33:02 37.76 GB(29.6%) 0.16 GiB
23:50:31 37.75 GB(29.6%) 0.16 GiB

機器死前那 43 分鐘,這個錶頭會穩穩地指在三成不到。 而它平常服務正常的時候指在九成三。也就是說在這台機器上,這個指標跟實際危險程度是反過來的:權重載進來會把「已用」推高但機器很健康,快取吃光可回收記憶體會讓「已用」看起來很低但機器正在死。

這不是 NVIDIA 做錯了什麼,是「已用」這個概念在統一記憶體加上不可回收的 page cache 之下就是失真的。但它確實說明了為什麼一個原廠儀表板擋在那裡兩個月,卻沒有在任何一次故障裡幫上忙。

DCGM 在 GB10 上能不能用? 一句話:跑得起來,但記憶體那一欄一樣是 Not Supported。 要說清楚這句話的來源:本機沒裝(dcgminv-hostengine 都不存在,sbsa 套件庫裡有候選版 1:3.3.9 但沒安裝),所以這是查 NVIDIA 開發者論壇與 k8s-device-plugin 的 issue 得到的說法,我沒有在這台驗證過。那邊的說明是統一記憶體沒有獨立 VRAM 可報,所以記憶體指標回 Not Supported 是預期行為,這跟 nvidia-smi 在這台回 memory.total = [N/A] 是同一個原因。

換句話說,就算裝了 DCGM,這篇裡最關鍵的那個指標(MemAvailable)它還是不會給你,你還是得回去讀 /proc/meminfo。這也是我到今天都沒裝它的原因。

把上面的規則收斂成一份最小的監控清單,每一行都對應這三十天裡一次真實的故障:

指標 來源 告警門檻 對應哪次故障
MemAvailable /proc/meminfosar -r 警戒 6 GiB、動手 3 GiB Day 25 停擺、Day 26 差點停擺
(不要看「已用」) 原廠儀表板的錶頭 不適用 停擺那晚它只指到 29.6%
NVRM 錯誤速率 journalctl -k 60 秒內出現 5 行以上 NV_ERR_NO_MEMORY Day 25,它提前 43 分鐘就在喊
誰在吃 GPU 記憶體 nvidia-smi --query-compute-apps 不告警,觸發後用它找兇手 cgroup 與 RSS 都看不到那 100 GiB
取樣器自己的延遲 兩次取樣的間隔 超過排定間隔 1.5 倍 Day 25,sysstat 那晚遲到 7 分鐘
GPU 與 zone 溫度 nvidia-smi/sys/class/thermal/thermal_zone*/temp 取最大 zone ≥ 88 連續 240 秒 9 月 1 日斷電、Day 24 中止
功耗 nvidia-smi --query-gpu=power.draw 不告警,只做成本追蹤 Day 29 的輸入
各引擎就緒 兩層健康檢查 就緒層連續兩次失敗 Day 24 兩個
閘道別名健康 LiteLLM /health 任一 unhealthy Day 26
根目錄剩餘空間 df 低於 60 GB 今天發現只剩 38 GB
NVMe 寫入量與不安全關機 smartctl(需 root) 每週趨勢 Day 17 埋的帳,Unsafe Shutdowns 順便當硬故障計數器
NCCL 直連線 ethtool 計數器 錯誤計數增加 Day 27

這份清單的重點不在工具,在於每一行都對應系列裡一次真實的故障。表格裡目前只剩一行是缺口,NVMe 那條沒有 root 就讀不到(NVRM 那條今晚接上了,見下一節)。我沒有裝監控堆疊不是因為不需要,是因為在兩台機器的規模下,上面那四樣覆蓋得到的部分 Grafana 也不會多告訴我什麼,而覆蓋不到的那一行,裝了 Grafana 一樣覆蓋不到。缺的是採集器,不是儀表板。 如果哪天多到第三台第四台,這個判斷就要重算。

三件維運用的補充

一、NVRM 告警與記憶體守護,合併成一支主機層的程式

原本規劃是兩件事:一支看 NVRM 錯誤的服務,加上容器記憶體上限。做下去才發現它們應該是同一支,而且容器記憶體上限這條路根本走不通。

為什麼走不通。 服務常駐的當下,主機用掉 113 GiB,而那個容器的 cgroup 是這樣:

memory.current = 11 GiB          容器內所有行程的 RSS 加總 = 4 GiB
memory.peak    = 37 GiB          同一時刻主機 used = 113 GiB

差的那 100 GiB 是 NVIDIA 驅動的統一記憶體配置,memory cgroup 看不到它,ps 的 RSS 也看不到它。所以 docker run --memory 限制得到的東西,不是會把這台機器撐爆的那一塊。我原本以為這是「要做一次實驗來定值」的問題,其實是方向錯了:這個工具量不到那個東西。

那誰看得到? 只有一個地方:

$ nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv
pid, process_name, used_gpu_memory [MiB]
2065247, VLLM::Worker_TP0, 102843 MiB

同一款 GPU,memory.total 回報 [N/A],per-process 的 used_memory 卻誠實給你 102843 MiB。在這台機器上,「誰在吃記憶體」這個問題只有 nvidia-smi --query-compute-apps 答得出來。

於是守護程式長這樣:跑在主機層而不是容器層,用 /proc/meminfo 的 MemAvailable 判斷危險程度,用 journalctl -k 的 NVRM 速率當第二個訊號,用 compute-apps 找出兇手,然後 SIGTERM 最大的那個非保護行程(正在服務的 vLLM worker 永遠不動)。

門檻是用整晚的實測資料校準的,而且順手抓到我自己表格裡的一個錯。 我最初設的門檻是「MemAvailable 低於 8 GiB 告警」,但正常雙機服務時它就只有 7.85 GiB,那條線一裝上去就會一直亮紅燈。改用實測:46 分鐘的叢集復原全程最低 6.91 GiB,整晚沒有任何一筆低於 6。所以警戒線 6 GiB、動手線 3 GiB,對照兩次真實事故(停擺當晚 0.20 GiB、Day 26 那次 2 GiB)都會觸發,而正常運作零誤報。

順帶一提,這台的 Linger=no,systemd 的 user unit 活不過登出,所以常駐是靠 crontab 的 @reboot,不需要 root。

二、冷卻閘門與看門狗掛到微調上

這一件是今天量功耗時才發現的缺口:微調吃 80 W、把機殼推到 zone 94 度,跟影片生成同一個等級,而那兩道機制當時只掛在影片鏈上。今天那次量測是我人盯著才手動停下來的,現在把那個判斷寫成程式。

跟影片那支的分工要說清楚:ComfyUI 與 vLLM-Omni 一旦開始生成就不能插斷,所以那支只能走 HTTP 的 /interrupt 或丟旗標檔。本機行程可以直接收訊號,所以新這支自己帶小孩,用 setsid 開新的行程群組,中止時 killpg 整組一起收。

兩條路徑都實測過:溫度正常時閘門直接放行、子行程跑完回 0。把浸透門檻壓到當下溫度以下時,看門狗在預算到期那一刻中止,子行程的 trap TERM 有收到、乾淨收尾,包裝層回退出碼 75,事後檢查沒有孤兒行程。

門檻沿用同一組來歷:預算 240 秒,因為 9 月 1 日那次斷電是連續浸透 417 秒,240 會在斷電前三分鐘開火。

三、把「不要用 pkill 的樣式比對」寫進兩支程式

這是今晚實地又踩了一次的坑,我要停掉那個微調行程,用 pgrep -f train-power.py 找 pid,結果樣式連我自己那支背景等待的 shell 一起命中,它跟著被收掉(exit 144)。

所以兩支新程式都刻意避開樣式比對,守護程式只殺 nvidia-smi 給的明確 pid,包裝層只對自己 setsid 出來的行程群組動手。會誤殺自己的工具不該出現在維運腳本裡,即使它在命令列上很順手。

一頁維運清單

  1. 長任務開跑前看機殼溫度。 冷卻閘門與看門狗不是裝飾品,而且閘門要放進驅動腳本裡,人會忘記的步驟不該是獨立步驟。長任務不只影片,微調一樣會把機殼推到 94 度
  2. 記憶體看 MemAvailable,不看 nvidia-smi,也不看「已用」。 停擺前那 43 分鐘「已用」只有 29.6%,而 MemAvailable 剩 0.16 GiB
  3. NVRM 的記憶體錯誤是停擺倒數計時。 看速率不看單行,它提前 43 分鐘就在喊,守護要跑在主機層,因為容器 cgroup 看不到那 100 GiB
  4. 監控要有一份「一直在跑」的,而且要知道它留多久。 救回那晚的是裝好就在那裡的 sysstat,不是我想到才開的 sampler,而它預設只留七天,證據會自己過期
  5. 告警門檻要用實測校準,不要挑整數。 我原本寫的 8 GiB 在正常服務時就一直亮紅燈,實測後改成 6 與 3
  6. 健康檢查兩層。 存活層秒級便宜,就緒層分鐘級用最小 completion,且空字串算失敗
  7. 死掉的閘道別名註解掉。 聚合式健康檢查沒有「忽略這一條」的選項
  8. 每次啟動記錄 KV 池與版本。 同一份權重載入時間會在 20 到 45 分鐘之間漂(權重在 NAS 上、自動預取被跳過),引用數字要標明輪次
  9. 設定變更批次做,而且要確認 restart 才生效
  10. 第二台機器要有自己的 runbook。 「有第二台」跟「第二台能獨立幹活」是兩件事
  11. 看剩餘空間,不是看耐寫度。 開機 260 天之後 Percentage Used 還是 0%,但磁碟 KV 沒有淘汰機制,再 20 天就把碟塞滿了
  12. 維運腳本裡不要用 pkill -f。 它會把你自己的 shell 一起殺掉

明天預告

機器怎麼養講完了,明天算它值不值。Day 29 把三十天的資料收成兩張表:成本效益(兩台 Spark 加上今天量到的電費下限,對上雲端 API 的損益兩平點,Day 22 那個 16GB 顯卡加 128GB 記憶體的入場券也一起算),以及資安治理(資料主權、稽核軌跡、agent 的權限邊界,從 Day 13 到 Day 21 累積的那些觀察,用 ISO 27001 的角度來看待)。

我們 Day 29 見囉。


(單位體例:記憶體沿用工具回報的 GiB,NVMe 寫入量與原廠儀表板的數字保留其原始顯示的十進位 GB,功耗為 nvidia-smi 回報的 GPU 模組軌,不含整機。本篇所有數字皆可指回對應的紀錄檔。)

Day 1|為什麼 2026 年是地端 AI 部署元年:系列規劃與硬體總覽
Day 2|DGX Spark GB10 深度解析:128GB 統一記憶體到底解決了什麼問題
Day 3|網路與儲存規劃:10GbE 骨幹、雙 Spark 直連與 NFS 集中模型庫
Day 4|開箱之後:DGX OS 初始環境建置與 CUDA、Docker 生態確認
Day 5|NFS 模型庫實戰:下載工具、權限設計與版本管理
Day 6|llama.cpp、vLLM、TensorRT-LLM 、Ollama、DS4 與 Unsloth 的定位與取捨
Day 7|第一個模型上線:gpt-oss-120b 從模型庫到 API 的完整流程
Day 8|llama.cpp 實測:先定量尺與 SOP
Day 9|vLLM 實測:官方容器、同時處理吞吐曲線,與剛出爐的新模型 Ornith 1.5
Day 10|TensorRT-LLM 實測:我花了一個下午,跟它的預設值搏感情
Day 11|地端 AI 三大推論引擎綜合評測,同場加映神秘嘉賓
Day 12|量化格式解析:「4-bit」兩個字,古今多少事 ? 都付笑談中
Day 13|Perplexity 把整套 Agent 搬上 DGX Spark:地端元年的官方背書
Day 14|Qwen3.8-27B 與 Flash-Next 加碼實測:地端模型世代對決與落地評估
Day 15|Ornith 1.5 實測:為 AI agent 而生的 35B-A3B
Day 16|模型選型方法論:五個問題幫你跳脫排行榜迷失,找到適合自己任務用的 AI 模型
Day 17|ds4 深度解析:Redis 之父只為一種模型造了一個引擎
Day 18|OpenAI 相容 API 層:地端 AI 多模型常駐好助手
Day 19|地端 agent 框架總覽:它們到底對你的端點做了什麼呢?
Day 20|DeepSeek Harness 實戰:21 萬款星的外掛式 harness 接上地端
Day 21|herdr 是地端 AI 同時跑多個 AI agent 的最佳解
Day 22|語意錨點對上 prefix cache,FreeToken VS vLLM,以及三個關於統一記憶體的教訓
Day 23|ComfyUI 部署與 NFS 模型庫整合:把集中管理的哲學帶進生圖世界
Day 24|MiniMax-H3 地端影片生成:h3-ui 與四步蒸餾版的三段對決
Day 25|Unsloth 微調實戰:教一款模型用繁體中文思考,並遵守台灣的編輯規範
Day 26|微調的最後一哩路:合併、轉檔、量化、上線
Day 27|雙 DGX Spark GB10 合體與分工合作的部署與實測
Day 28|NVIDIA DGX Spark GB10 維運篇


上一篇
Day 27|雙 DGX Spark GB10 合體與分工合作的部署與實測
系列文
128GB 統一記憶體的三十天:DGX Spark 地端 LLM 與生成式 AI 部署實戰28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言