
因為前面 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 那張表的一個小問題。

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。

這張圖有三件事值得看:
第一,倒數計時是真的。 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。
Day 17 埋的帳今天算,而結論跟我下筆前的預期相反:耐寫度完全不是問題,剩餘空間才是。
先講耐寫度。/dev/nvme0 是 root: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,讀遠多於寫,那是反覆載入權重的形狀。

耐寫度沒事,那風險在哪?/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-smi 的 power.draw 讀得到,但同一個 nvidia-smi -q -d POWER 裡,Current Power Limit、Default Power Limit、Module Power Readings、GPU Memory Power Readings 全部是 N/A。往下找也沒有別條路:/sys/class/hwmon 底下沒有任何 power*_input、curr*_input 或 in*_input,沒有 ina2xx 或 ina3221 驅動掛上去,/sys/class/power_supply/ 是空的,tegrastats 與 jtop 都不存在(這是 DGX OS 不是 L4T,Jetson 那套 INA3221 加 tegrastats 完全沒有)。
所以下面這張表是 GPU 模組功耗,不是整機牆插功耗。明天文章會說明差的那些東西(CPU 叢集、記憶體、四張網卡、風扇、電源轉換損耗),是用智慧插座測試的實際數字。

| 狀態 | 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。
雙機叢集啟動要 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 埠在聽。
實際在跑的是這四樣:
逐次實驗自己寫的 sampler。 hw-sample.py 每 3 秒抽一次 nvidia-smi、七個 thermal zone 與 /proc/meminfo,寫成 JSONL,每一行都 fsync。這個設計不是龜毛,是因為這台機器的死法之一是硬斷電,沒 flush 的資料就是沒有。今天這篇的功耗數字全部出自它。
sysstat。 裝好就在那裡,每 10 分鐘一次。它救回了上面那張圖,而且是險勝:/etc/sysstat/sysstat 裡 HISTORY=7,只留七天,sa06 這個檔案再過三天就會被自動刪掉。我今天把它複製了一份出來。事後才想查的證據通常已經在倒數,這是我今天學到最便宜的一課。
一支 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 留下逐字錯誤),不是靜默跳過。
原廠的 DGX Dashboard,dgx-dashboard.service 一直開著,佔 24 MB 記憶體,聽在 127.0.0.1:11000,要帳密登入。

左邊是 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。 要說清楚這句話的來源:本機沒裝(dcgmi 與 nv-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/meminfo、sar -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 錯誤的服務,加上容器記憶體上限。做下去才發現它們應該是同一支,而且容器記憶體上限這條路根本走不通。
為什麼走不通。 服務常駐的當下,主機用掉 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 會在斷電前三分鐘開火。
這是今晚實地又踩了一次的坑,我要停掉那個微調行程,用 pgrep -f train-power.py 找 pid,結果樣式連我自己那支背景等待的 shell 一起命中,它跟著被收掉(exit 144)。
所以兩支新程式都刻意避開樣式比對,守護程式只殺 nvidia-smi 給的明確 pid,包裝層只對自己 setsid 出來的行程群組動手。會誤殺自己的工具不該出現在維運腳本裡,即使它在命令列上很順手。
Percentage Used 還是 0%,但磁碟 KV 沒有淘汰機制,再 20 天就把碟塞滿了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 維運篇