AI 互動教室 ‹ 個人地端大語言模型實作
下載 .py 單獨開啟實驗場 ↗ 留言回報
OBSERVE · 07 · 兩層監控

可觀測性與監控:
看見你的 LLM

上一課你讓小模型先猜、大模型再驗,把生成加速了。速度調完之後,會冒出一個新問題: 它跑得對嗎?Agent 回傳 200、速度也不慢,答案卻可能整段是錯的; 一個卡住的重試迴圈可以在一夜之間把預算燒到三倍。狀態碼與日誌,看不見這些事。

下面三個案例都是從真實的 agent trace 紀錄改編的。按一個看看—— 沒有 trace,你只會知道「它做完了」;有 trace,你會知道它一路上做了什麼

右邊的實驗場是真的 Python(在你的瀏覽器裡跑,不用安裝任何東西)。 首次載入約需 30–60 秒,正好夠你讀完第 1 節。你會在裡面轉告警的旋鈕、攤開 trace 的瀑布圖, 改壞了重新整理就復原——這是你的沙盒,盡量玩。

01 · 為什麼

看不見推理過程,就無法 debug、無法優化、無法控成本

傳統服務的監控問句很單純:有沒有 500?P99 幾毫秒?到了 agent 時代,這些問句全部失靈, 因為 LLM 應用有四種傳統監控接不住的失敗:

失敗型態為什麼傳統監控看不到
非確定性的失敗回傳 200、速度也快,答案卻完全是錯的——狀態碼看不出品質問題
多步驟黑盒一次請求扇出十幾個模型呼叫、檢索與工具調用,純日誌無法定位是哪一環斷掉
成本會失控token 依用量計費,一個卡住的重試迴圈可能一夜之間把預算燒到三倍
品質悄悄退化模型升級或 prompt 改版後行為漂移,沒有評分機制就等客訴才發現

所以這個領域長出了專門的平台。不管你最後選哪一家,它們做的都是同樣四件事:

核心能力回答什麼
追蹤 Tracing記錄每次呼叫的輸入、輸出、token、延遲、錯誤,把巢狀的模型/工具/檢索步驟組成 trace 樹——這是一切的基礎
成本追蹤 Cost把花費歸因到每位使用者、每個 prompt 模板、每個模型版本,才能找出正在流血的那條預算
評估 Evaluation用 LLM-as-a-judge、人工標註或使用者回饋衡量輸出品質,在客訴前抓到退化
Prompt 管理版本控制、A/B 測試、一鍵回滾,改 prompt 不用改程式碼、不用重新部署

選工具的方法很簡單:依「你最痛的那一項」來選——不同平台對這四項的側重差很多。

02 · 核心

同一句「LLM 變慢了」,兩個視角缺一不可

這是整堂課最重要的一張表。可觀測性有兩層,它們回答的是完全不同的問題, 卻常常被當成同一件事:

基礎設施監控AI 應用觀測
工具Prometheus + Grafana + AlertmanagerMLflow Tracing/Langfuse/Phoenix…
回答的問題機器活著嗎?VRAM 剩多少?服務掛了沒?這次呼叫問了什麼、回了什麼、慢在哪一步?
資料形態時序指標(每 15 秒被拉一次)trace / span(應用程式主動送)
粒度主機級、服務級單一請求級
出事時先於使用者發現,推播到你手機撈出那條 trace 除錯、跑評估防退步

把它們接起來的,就是那句「LLM 回覆變慢了」:

機器層
Grafana 上看見 vLLM 的 waiting 排隊數持續大於 0、KV cache 使用率貼近 1.0—— 這告訴你「為什麼慢」:容量不夠了。

請求層
trace 上看見這一筆請求的 9.2 秒裡有 7.2 秒卡在 queue_wait, 檢索與解碼都很正常——這告訴你「慢在哪一步」:不是你的 RAG 寫爛了。

只有機器層:你知道排隊很長,但不知道哪些請求受害、使用者實際等了多久。 只有請求層:你知道某一步等了 7.2 秒,卻不知道為什麼要等。 兩層合起來,才叫可觀測性。

03 · 請求層

trace 判讀:總時長沒有資訊量,self time 才有

一次「請求」在 agent 時代不是一件事,是一棵樹。trace 把這棵樹連同每一步的起訖時間記下來。 判讀第一課是分清楚兩種時間:

  • 總時長(duration):這個 span 從開始到結束的牆鐘時間,包含所有子步驟
  • 自身時間(self time):總時長扣掉直接子步驟的時間,才是「這一層自己花掉的」

只看總時長,最上層那個包住全部的 span 永遠最慢,這句話等於沒說。 self time 最大的那一步,才是兇手。右邊四條 trace 換著看, 你會看到同樣是「變慢了」,兇手可能在檢索、在排隊、也可能是被拼錯的工具名拖進重試迴圈。

MLflow Tracing:從一行到一棵樹

以 MLflow 為例(Apache 2.0、Linux Foundation、原生相容 OpenTelemetry), 最小的接法真的只有一行——所有經過 openai SDK 的呼叫都會自動變成 trace, 包含打你自己 GPU 機上的 vLLM:

import mlflow # 要在任何呼叫之前執行 mlflow.set_tracking_uri("http://localhost:5000") mlflow.openai.autolog() # ← 就這一行 # llm 的 base_url = http://localhost:8000/v1 r = llm.chat.completions.create( model="qwen3-4b", messages=[{"role": "user", "content": "什麼是 KV cache?"}], max_tokens=128, temperature=0, ) # 自動記下:prompt、完整回覆、token 用量、 # 延遲、span 名稱與類型

autolog 只看得到「單次呼叫」。要看出慢在檢索還是生成,用 @mlflow.trace 把自己的函式包起來,巢狀結構會自動成形:

@mlflow.trace(span_type=SpanType.RETRIEVER) def retrieve(query, top_k=2): ... # 算 embedding、取 cosine 相似度 top_k @mlflow.trace(name="rag_answer", span_type=SpanType.CHAIN) def rag_answer(question): context = retrieve(question) return llm.chat.completions.create(...) # UI 裡看到的 span 樹: # rag_answer CHAIN # retrieve RETRIEVER # Embeddings ← autolog 自動補上的 # Completions ← autolog 自動補上的

再往上疊三件事,這一層就完整了:

要回答做法
「這個使用者的對話發生了什麼」 在 trace 上掛標準 metadata(mlflow.trace.user / mlflow.trace.session),之後用 search_traces(filter_string=...) 篩:user 跨 session 追同一個人, session 還原一整段多輪對話
「使用者覺得好不好」 前端把該次回覆的 trace_id 一起送回來,倒讚就 log_feedback(trace_id, value=False, rationale=...) 記回同一條 trace。 之後撈出所有被倒讚的對話除錯,或直接當評估資料集
「改了 prompt 有沒有變笨」 mlflow.genai.evaluate() 對同一組測資跑前後兩版, 裁判就用你自己那顆本地模型——免外部 API、資料不出門

最後一項是這一層的殺手鐧,值得展開。讓小模型當裁判有個經典地雷:它會回一整段散文, 你解析不出來。解法是用 structured outputs 鎖死裁決只能二選一

@scorer def judge_correct(inputs, outputs, expectations): verdict = llm.chat.completions.create( model="qwen3-4b", messages=[...問題 / 標準答案 / 考生答案...], extra_body={"structured_outputs": {"choice": ["正確", "錯誤"]}}, # ← 鎖二選一 ).choices[0].message.content return verdict == "正確" res = mlflow.genai.evaluate( data=data, predict_fn=ask, scorers=[judge_correct], )

兩個容易翻車的地方:predict_fn 的參數名要對齊測資 inputs 的 key; 裁判是照 expectations 打分的——測資裡如果寫了錯的標準答案(例如 1+1=3), 它就會照那個錯的判,分數自然掉下來。這其實是好事:它證明裁判真的在比對,而不是在誇獎。

常見坑速查

症狀原因與解法
剛跑完就查,一條 trace 都查不到上傳是背景非同步的:先 mlflow.flush_trace_async_logging()search_traces()
trace 全跑去本地 ./mlruns/ set_tracking_uri() 沒有在任何呼叫之前執行
tracking URI 回 404反向代理底下的路徑前綴(例如 /mlflow)忘了帶
正式環境流量太大取樣(MLFLOW_TRACE_SAMPLING_RATIO=0.1) 或改用輕量的 tracing-only SDK,別全量記
舊資料佔空間delete_traces(experiment_id, max_timestamp_millis=...) 定期清

有了 trace id 還可以更懶:把 trace id 和你的疑問一起貼給 AI 助理 (MLflow 官方有 MCP 工具與 skills),讓它讀完整條 trace 直接告訴你 prompt 哪裡出問題。 除錯迴圈就閉起來了:記 trace → 貼 id 給 AI → 改 prompt → evaluate 驗證沒變笨

04 · 成本

把 token 歸戶,流血的那條預算才會現形

trace 除了時間,還記了每次呼叫的 token 用量。有 token 就能算錢; 有 user/model/功能路由這些標籤,就能回答「錢到底花在誰身上」。

成本失控最典型的長相是 「請求數少、花費大」:一條夜間批次、一個超長 prompt、 一個沒人看見的重試迴圈。平均延遲、錯誤率、QPS 這些指標全都看不出它—— 它們是按請求數平均的,而肇事者的請求數很少。只有把 token 歸戶才抓得到。

右邊那批合成的 600 筆請求就是這個形狀:切換分組維度,同一筆錢會從三個角度指向同一個嫌犯。 看完你就知道為什麼「成本追蹤」會被列成核心能力之一。

05 · 機器層

誰產、誰收、誰看、誰叫

換另一半。機器層的架構是一條四段的鏈,每一段各司其職:

exporter 產(開一個 /metrics 端點等人來抓) → Prometheus 收+判斷(每 15 秒 pull 一輪、存時序、跑告警規則) → Grafana 看(儀表板) → Alertmanager 送(去重、分組、節流,推到 Telegram 之類的地方)

pull 模式的好處:被監控端什麼都不用知道,只要開一個端點。 新增一台機器 = 跑 exporter + 在 Prometheus 加一行 target。

exporter提供什麼
GPU exporternvidia-smi 翻成指標:VRAM、使用率、溫度、功耗
node exporter主機本體:CPU、記憶體、磁碟
cAdvisor每個容器的資源用量
vLLM 內建排隊數 num_requests_waiting、KV cache 使用率

兩個常被問的問題。exporter 會吃 VRAM 嗎?不會——它只透過 NVML 查數字, 不建 CUDA context,監控本身零 VRAM 成本服務死掉要另外寫健康檢查嗎?不用——Prometheus 對每個 target 都免費附贈一個 up 指標:抓得到 = 1、抓不到 = 0。

⚠️ 一個安靜的陷阱:nvidia_smi_last_collect_success = 0 代表 exporter 活著,但它跑的 nvidia-smi 失敗了(例如驅動更新後沒重開機)。 這時 up 還是 1、沒有任何東西會叫,GPU 指標卻會無聲消失。 要把這個指標也放進告警規則。

PromQL:先會這三句就夠用

nvidia_smi_memory_free_bytes{host="gpu-a"} → 這台現在剩多少 VRAM min_over_time(nvidia_smi_memory_free_bytes[24h]) → 過去 24 小時的「最低水位」:昨天的峰值把餘裕吃到多低? 這是動態制服務做容量規劃最需要、而 nvidia-smi 當下一瞥永遠看不到的數字 rate(vllm:request_success_total[5m]) → vLLM 每秒完成幾個請求

第二句值得畫重點。你隨手 nvidia-smi 一看「還剩 3GB,很安全」, 但那只是此刻;min_over_time 才會告訴你昨天半夜它曾經只剩 0.4GB。 容量規劃看的是最低水位,不是當下水位。

告警規則:門檻之外,for: 才是關鍵

一組實際在跑的規則長這樣(兩組七條):

規則條件嚴重度
gpuGPUFreeVRAMLow任一台剩 < 1.5GB 持續 5 分鐘warning
gpuGPUExporterDownGPU exporter 抓不到 3 分鐘critical
gpuVLLMDown任一 vLLM 服務抓不到 3 分鐘critical
hostHostDown整台主機失聯critical
hostHostDisk / Mem / CPU磁碟快滿/記憶體吃緊/CPU 高檔(3 條)warning
  • for: 是告警品質的關鍵:條件要「持續多久才算數」。 沒有它,VRAM 抖一下就叫;叫了幾次沒事之後大家就開始忽略告警——這叫 告警疲乏,是監控自殺的第一步。
  • 1.5GB 這個門檻怎麼來的:那台上面的動態制服務,長文本峰值增量估 1~2GB。 所以 1.5GB 是「該去看一眼」的水位,不是「已經死了」的水位——告警要留反應時間
  • 規則寫在 rules.yml、跟著 compose 走,不是在 UI 手點的:重建即重現

for: 該設多久?這是可以算的。右邊的模擬器藏了 3 次無害抖動 (1/2/4 分鐘)和 1 次真事件(連續 12 分鐘),轉旋鈕就會看見取捨的形狀: 誤報數是階梯狀下降的,每一階都是某次抖動的長度;偵測延遲則是直線上升。 實務上的定法因此很具體——量一下你系統真實的抖動有多長,取比它稍長的值

告警禮儀:叫,但要叫得有禮貌

Prometheus 只負責「判斷」,送出去是 Alertmanager 的事。這四個參數是禮儀:

參數示例值意思
group_byalertname, host同機同規則合併成一則,不洗版
group_wait30sfiring 後先等 30 秒,收集同組一起送
repeat_interval4h一直沒修好,每 4 小時提醒一次
send_resolvedtrue修好也通知(✅ 告警解除)

沒有這幾個值,一次網路抖動就能讓手機半夜連震二十下,然後大家把通知關掉——監控自殺。 接到 Telegram 只要兩步:跟 @BotFather/newbot 拿 token;對 bot 按一次 Start 後用 getUpdates 挖出 chat_id(token 由環境變數注入,不進 git)。 驗證鏈路不用真把 GPU 塞爆:對 Alertmanager 的 REST 端點塞一則假告警, 30 秒後手機就該響。

番外,但很好用的一個模式:心跳檔 + 定時 push。 佇列輪詢那種行程沒有 /metrics 可拉,就反過來——用計時器每 3 分鐘體檢, 抓兩種死法(① 服務單元不是 active ② 行程活著但主迴圈卡死:worker 每圈碰一下心跳檔, 超過 5 分鐘沒動就算死),異常直接推 Alertmanager REST,借同一條通知出口。 告警帶一個 10 分鐘後的到期時間,問題持續就每 3 分鐘續火、恢復了就自動解除。 這個模式適用任何非 HTTP 的長跑行程(批次工作、輪詢 worker、排程任務), 不用為它多架一套監控

06 · 判讀

看「剩多少」,不是看「用多少」

一次真實的容量事故(RTX 4090 24GB、2026-07):某天 A 機的 VRAM 只剩 862 MiB,上面最重要的語音合成服務隨時可能 CUDA OOM; 而同時間 B 機「看起來也快滿了」,卻其實非常安全。差別不在用了多少,在要 VRAM 的方式

類型代表行為風險
預留制vLLM啟動時鎖一塊,之後不再長跑起來就穩
動態制PyTorch 類服務(TTS、影像)用多少要多少,長文本突然要更多餘裕不夠 → 隨時 OOM

判讀規則:卡上有動態制服務時,看「剩多少」而不是「用多少」。 B 機用量更兇但全是預留制,啟動時就鎖好了,之後不會再長; A 機看起來只是「有點滿」,但它的餘裕必須撐得住動態制服務的峰值增量。

當天的解法是把向量嵌入服務搬到 B 機,A 機的餘裕從 0.86 GB 變成 4.10 GB。 由此長出一條配置鐵律:新的 GPU 服務一律排到沒有動態制服務的那台。 右邊有這兩台機器的對照圖,拉一下峰值增量、打開那個搬遷開關看看。

而監控要做的事,就是把這段人肉分析變成 24 小時自動的: 剩餘 VRAM 隨時看得到,低於門檻持續一段時間就推播到手機。

儀表板即程式碼

儀表板該放哪些面板,也是有講究的——每一面板都要能回答一個問句:

面板回答什麼
VRAM 剩餘(數字+變色)第一眼:綠 >3GB/黃 1.5–3/紅 <1.5,門檻和告警規則一致
VRAM 使用量(時序)趨勢:誰在長?什麼時候長?
GPU 使用率忙不忙(注意:VRAM 滿 ≠ 算力忙)
溫度/功耗散熱與供電體檢
vLLM running / waitingwaiting 持續大於 0 = 容量不夠
vLLM KV cache 使用率接近 1.0 就是排隊的原因

這些面板不是在 UI 上手點出來的:datasource 與 dashboard 的 JSON 全寫在 compose 的設定裡 (provisioning)。好處是重建容器 = 重建完整監控,零手工;代價是手改的儀表板重佈署會被蓋掉 ——要改就改 compose 裡的 JSON。告警規則也是同樣的道理。

07 · 選型

沒有最好,只有最合適

先決定機器層那一半要多重。如果你只是要看幾台機器活不活著:

面向輕量方案(如 Beszel)專業堆疊(Prometheus + Grafana)
安裝一份 compose,開箱即用多元件,要寫設定、學 PromQL
資源極小(agent 約 3–14 MB)較高,要規劃時序資料庫資源
監控範圍CPU/記憶體/磁碟/溫度/GPU/Docker幾乎不限,有 exporter 就能收
查詢客製固定圖表,無查詢語言PromQL + Grafana 高度客製
告警內建門檻告警Alertmanager,規則細膩
維護幾乎零維運需要人顧

1~20 台主機、要快速上線、低維運 → 選輕量的。 要看應用/K8s 指標、要自訂查詢與細膩告警 → 選 Prometheus + Grafana。 本課走的是後者——因為我們還要看 GPU 與 vLLM 的指標,那是輕量方案接不到的。

請求層那一半:七家怎麼挑

工具開源/自托管核心強項內建評測OTel
Langfuse開源,可自托管prompt 層深度追蹤內建評分器兼容
MLflow開源,可自托管端到端 ML 平台強(+優化)原生
LangSmithSaaS 優先LangChain 原生推理鏈有限經回調
Arize Phoenix開源,可自托管漂移偵測/LLM-judge原生
Helicone開源,可自托管網關式低侵入接入基礎支援
AgentOpsSDK 開源,SaaS生產會話回放生命週期指標原生
BraintrustSaaS 優先資料集評測全面標準

一句話版的選型建議:

  • 資料自主 + 功能全面(醫療/金融這類敏感場景)→ Langfuse,一鍵自托管
  • 追蹤+評估+模型管理一站到位、避免廠商鎖定 → MLflow
  • 技術棧深綁 LangChain / LangGraph → LangSmith,零摩擦
  • 關心長期品質與模型漂移 → Arize Phoenix
  • 最小改動快速接入 → Helicone,網關式代理幾分鐘上線
  • 評測當開發核心 → Braintrust,用資料集量化迭代

小撇步:所有平台都有免費檔——先把真實流量灌進去跑一週,比看任何評測都準。

底層標準:OpenTelemetry GenAI

選工具之前先認識這個正在統一領域的標準,因為它決定你未來換工具、 或接進既有 APM 的成本:按 GenAI 語意約定埋點一次,統一 「LLM 呼叫/agent 步驟/工具調用」的欄位;OTel 作為廠商中立的傳輸層, 同一份資料可以送到任何支援它的後端,甚至同時送多個。 所以「是否原生支援 OTel」是選工具的重要加分項——埋點一次、到處可送 = 不被鎖定

閉環才叫 Ops

把整堂課裝回一張表,一個像樣的 AgentOps 有六個模塊:

模塊回答什麼本課對應
① Tracing 可觀測每次呼叫發生了什麼、慢在哪autolog + 巢狀 span(第 3 節、1️⃣)
② Evaluation 評估改了 prompt/模型,有沒有變笨genai.evaluate + 本地模型當裁判(第 3 節)
③ Feedback 回饋使用者覺得好不好讚/倒讚記回 trace(第 3 節)
④ Metrics 資源監控GPU/服務還健康嗎Prometheus + Grafana(第 5 節)
⑤ Alerting 告警出事誰來叫人Alertmanager → 手機推播(第 5 節、3️⃣4️⃣)
⑥ Cost/容量token 花多少、VRAM 水位夠不夠trace token 統計(2️⃣)+ PromQL 最低水位(5️⃣)

觀測(①④)→ 發現問題(⑤③)→ 改進 → 評估驗證(②)→ 再觀測。 少了任何一段,它就只是儀表板,不是 Ops。

08 · 實戰

換你動手

右邊三個函式都可以直接呼叫:make_vram_series() 造時序、 evaluate_rule() 評一組規則、 self_times() 算 trace 的自身時間。

LEVEL 1

evaluate_rule 印出「for: = 0/2/5/13 分鐘」 四種設定的誤報數與偵測延遲,親手確認你在旋鈕上看到的行為。 注意 13 分鐘那一組發生了什麼事。

LEVEL 2

你的機器抖動比較長:把抖動改成 (2, 5, 8) 分鐘、真事件仍是 12 分鐘, 找出「誤報歸零又不漏報」的最小 for:。 然後想想這個數字對 on-call 的人意味著什麼。

LEVEL 3

把你自己遇過的一次慢請求寫成 spans 加進 TRACES, 算出 self time 最大的那一步。自我驗證的方法在 notebook 裡: 所有 span 的 self time 加總必須剛好等於根 span 的總時長,對不上就是你的層級或起訖寫錯了。

09 · 驗收

情境測驗

離開前試試看:下面的情境都真的會遇到。每題選一個你認為的最佳做法,選了馬上看得到解釋。

Q1 情境題

使用者回報「你們的 AI 助理今天特別慢」。你手上兩套東西都有:Grafana 儀表板,以及每次呼叫的 trace。要最快找到原因,怎麼查?

解釋:兩層監控回答的是不同問句——trace 告訴你「慢在哪一步」,機器指標告訴你「為什麼慢」。實務上的順序是先用 trace 定位(例如 self time 最大的是 queue_wait 7.2 秒),再帶著這個線索回機器層找對應指標(vLLM 的 waiting 排隊數、KV cache 使用率),一趟就到位。A 漏掉了「哪些請求受害、使用者實際等了多久」;B 會停在「等了 7.2 秒」卻永遠不知道為什麼要等——那不是你的 RAG 寫爛了,是容量不夠;D 是多步驟黑盒最沒效率的查法,純日誌無法定位扇出的十幾個呼叫是哪一環斷掉,而且不見得重現得出來。

Q2 錯誤診斷

剛跑完一輪對話,馬上查自己的 trace,卻是空的。程式沒有拋任何例外。最可能的原因是?

mlflow.openai.autolog() r = llm.chat.completions.create(model="qwen3-4b", messages=[...]) traces = mlflow.search_traces(locations=[exp.experiment_id]) print(len(traces)) # → 0

解釋:MLflow 為了不拖慢你的應用,trace 是背景非同步上傳的——呼叫回來的當下它還在路上,馬上查當然查不到。修正:查詢前先 mlflow.flush_trace_async_logging()。這也提醒你正式環境不該同步等上傳。A 反了:autolog 攔的是 openai SDK 這一層,打自架的相容端點照樣記得到。C 不存在這種預設。D 症狀相似但原因不同——取樣要自己設 MLFLOW_TRACE_SAMPLING_RATIO,預設是全記;而且如果真的是取樣問題,等一下再查也不會出現,flush 之後就會出現的才是非同步問題。另外兩個同家族的坑一起記:trace 全跑去本地 ./mlruns/set_tracking_uri() 沒在任何呼叫之前執行;URI 回 404 = 反向代理的路徑前綴忘了帶。

Q3 情境題

你的 GPU 剩餘 VRAM 告警一天叫七、八次,每次去看都沒事,同事已經把通知靜音了。量測後發現:無害的短暫抖動最長 4 分鐘,而真正需要處理的低水位事件通常持續十幾分鐘以上。最好的調整是?

解釋:誤報的來源是「抖一下就算數」,所以要動的是 for:(持續多久才算數),而且它的定法是可以量出來的——取比你系統最長的無害抖動稍長的值。右邊模擬器裡,抖動是 1/2/4 分鐘,誤報歸零的第一個值就落在 4.25 分鐘,代價是偵測延遲同步變成 4 分鐘。A 是最常見的誤解:把門檻壓到 0.3GB 等於把告警設在「已經死了」的水位,動態制服務的峰值增量隨便就 1~2GB,你連反應時間都沒有了——告警要留反應時間B 只是讓同一則吵得少一點,誤報照樣進來,信噪比沒改善。C 方向對但過頭:for: 一旦超過事件本身的長度,這條規則就等於不存在(漏報),而且每多一分鐘你就晚一分鐘知道出事。

Q4 錯誤診斷

Grafana 上的 GPU 面板從昨天下午開始就是「No data」,但 Prometheus 的 target 頁面顯示這個 exporter 一切正常,也沒有任何告警。查了一下指標:

up{job="gpu-exporter", host="gpu-a"} → 1 nvidia_smi_last_collect_success{host="gpu-a"} → 0 nvidia_smi_memory_free_bytes{host="gpu-a"} → (查無資料)

解釋:up 只證明「這個 /metrics 端點抓得到」,不證明端點裡的數字是新鮮的。GPU exporter 是靠呼叫 nvidia-smi 取值的,驅動更新後沒重開機這類狀況會讓它取值失敗——行程還在、端點還回得了 200,up 依然是 1,但 GPU 指標會無聲消失,沒有任何規則會叫。這正是為什麼要把 nvidia_smi_last_collect_success == 0 也寫成一條告警規則。A 對不上:保留期到了會整段消失,不會只剩一個 last_collect_success=0C datasource 掛了的話所有面板都會壞,不會只有 GPU 那幾個;D 症狀相似但原因不同——GPU 指標來自 exporter 查驅動,跟上面有沒有跑服務無關,服務全停你會看到使用率 0、VRAM 幾乎全空,而不是查無資料。

Q5 情境題

你有兩張 24GB 的卡要塞一個新服務。A 卡:用了 14GB,上面跑著一個 PyTorch 的語音合成服務(長文本時 VRAM 會臨時長 1~2GB)。B 卡:用了 18GB,上面三個服務全是 vLLM。新服務該放哪?

解釋:卡上有動態制服務時,要看「剩多少」而不是「用多少」。A 卡帳面上用得少(剩 10GB),但那 10GB 裡有一部分是留給語音合成服務峰值增量的保命餘裕,塞滿了就會在某個長文本請求進來時 CUDA OOM——而且掛掉的是原本跑得好好的那個服務。B 卡雖然用了 18GB,但三個服務全是預留制:啟動時就鎖好了,之後不再長,剩下的 6GB 是確定可用的。B 正是這一題要破除的直覺(看用多少)。C 沒有解決問題,只是把風險同時放到兩張卡上,A 卡照樣會 OOM。D 代價被低估了:OOM 打死的通常不只是新服務,重啟期間所有依賴它的功能都在掉請求。由此長出的配置鐵律是——新的 GPU 服務一律排到沒有動態制服務的那台,並且替有動態制服務的那台永遠保留一塊餘裕。

Python 環境載入中(首次約 30–60 秒)…讀完第 1 節它就好了