可觀測性與監控:
看見你的 LLM
上一課你讓小模型先猜、大模型再驗,把生成加速了。速度調完之後,會冒出一個新問題: 它跑得對嗎?Agent 回傳 200、速度也不慢,答案卻可能整段是錯的; 一個卡住的重試迴圈可以在一夜之間把預算燒到三倍。狀態碼與日誌,看不見這些事。
下面三個案例都是從真實的 agent trace 紀錄改編的。按一個看看—— 沒有 trace,你只會知道「它做完了」;有 trace,你會知道它一路上做了什麼:
右邊的實驗場是真的 Python(在你的瀏覽器裡跑,不用安裝任何東西)。 首次載入約需 30–60 秒,正好夠你讀完第 1 節。你會在裡面轉告警的旋鈕、攤開 trace 的瀑布圖, 改壞了重新整理就復原——這是你的沙盒,盡量玩。
看不見推理過程,就無法 debug、無法優化、無法控成本
傳統服務的監控問句很單純:有沒有 500?P99 幾毫秒?到了 agent 時代,這些問句全部失靈, 因為 LLM 應用有四種傳統監控接不住的失敗:
| 失敗型態 | 為什麼傳統監控看不到 |
|---|---|
| 非確定性的失敗 | 回傳 200、速度也快,答案卻完全是錯的——狀態碼看不出品質問題 |
| 多步驟黑盒 | 一次請求扇出十幾個模型呼叫、檢索與工具調用,純日誌無法定位是哪一環斷掉 |
| 成本會失控 | token 依用量計費,一個卡住的重試迴圈可能一夜之間把預算燒到三倍 |
| 品質悄悄退化 | 模型升級或 prompt 改版後行為漂移,沒有評分機制就等客訴才發現 |
所以這個領域長出了專門的平台。不管你最後選哪一家,它們做的都是同樣四件事:
| 核心能力 | 回答什麼 |
|---|---|
| 追蹤 Tracing | 記錄每次呼叫的輸入、輸出、token、延遲、錯誤,把巢狀的模型/工具/檢索步驟組成 trace 樹——這是一切的基礎 |
| 成本追蹤 Cost | 把花費歸因到每位使用者、每個 prompt 模板、每個模型版本,才能找出正在流血的那條預算 |
| 評估 Evaluation | 用 LLM-as-a-judge、人工標註或使用者回饋衡量輸出品質,在客訴前抓到退化 |
| Prompt 管理 | 版本控制、A/B 測試、一鍵回滾,改 prompt 不用改程式碼、不用重新部署 |
選工具的方法很簡單:依「你最痛的那一項」來選——不同平台對這四項的側重差很多。
同一句「LLM 變慢了」,兩個視角缺一不可
這是整堂課最重要的一張表。可觀測性有兩層,它們回答的是完全不同的問題, 卻常常被當成同一件事:
| 基礎設施監控 | AI 應用觀測 | |
|---|---|---|
| 工具 | Prometheus + Grafana + Alertmanager | MLflow 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 秒,卻不知道為什麼要等。 兩層合起來,才叫可觀測性。
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:
autolog 只看得到「單次呼叫」。要看出慢在檢索還是生成,用 @mlflow.trace 把自己的函式包起來,巢狀結構會自動成形:
再往上疊三件事,這一層就完整了:
| 要回答 | 做法 |
|---|---|
| 「這個使用者的對話發生了什麼」 | 在 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 鎖死裁決只能二選一:
兩個容易翻車的地方: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 驗證沒變笨。
把 token 歸戶,流血的那條預算才會現形
trace 除了時間,還記了每次呼叫的 token 用量。有 token 就能算錢; 有 user/model/功能路由這些標籤,就能回答「錢到底花在誰身上」。
成本失控最典型的長相是 「請求數少、花費大」:一條夜間批次、一個超長 prompt、 一個沒人看見的重試迴圈。平均延遲、錯誤率、QPS 這些指標全都看不出它—— 它們是按請求數平均的,而肇事者的請求數很少。只有把 token 歸戶才抓得到。
右邊那批合成的 600 筆請求就是這個形狀:切換分組維度,同一筆錢會從三個角度指向同一個嫌犯。 看完你就知道為什麼「成本追蹤」會被列成核心能力之一。
誰產、誰收、誰看、誰叫
換另一半。機器層的架構是一條四段的鏈,每一段各司其職:
exporter 產(開一個 /metrics 端點等人來抓) → Prometheus 收+判斷(每 15 秒 pull 一輪、存時序、跑告警規則) → Grafana 看(儀表板) → Alertmanager 送(去重、分組、節流,推到 Telegram 之類的地方)
pull 模式的好處:被監控端什麼都不用知道,只要開一個端點。 新增一台機器 = 跑 exporter + 在 Prometheus 加一行 target。
| exporter | 提供什麼 |
|---|---|
| GPU exporter | 把 nvidia-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 一看「還剩 3GB,很安全」, 但那只是此刻;min_over_time 才會告訴你昨天半夜它曾經只剩 0.4GB。 容量規劃看的是最低水位,不是當下水位。
告警規則:門檻之外,for: 才是關鍵
一組實際在跑的規則長這樣(兩組七條):
| 組 | 規則 | 條件 | 嚴重度 |
|---|---|---|---|
| gpu | GPUFreeVRAMLow | 任一台剩 < 1.5GB 持續 5 分鐘 | warning |
| gpu | GPUExporterDown | GPU exporter 抓不到 3 分鐘 | critical |
| gpu | VLLMDown | 任一 vLLM 服務抓不到 3 分鐘 | critical |
| host | HostDown | 整台主機失聯 | critical |
| host | HostDisk / 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_by | alertname, host | 同機同規則合併成一則,不洗版 |
| group_wait | 30s | firing 後先等 30 秒,收集同組一起送 |
| repeat_interval | 4h | 一直沒修好,每 4 小時提醒一次 |
| send_resolved | true | 修好也通知(✅ 告警解除) |
沒有這幾個值,一次網路抖動就能讓手機半夜連震二十下,然後大家把通知關掉——監控自殺。 接到 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、排程任務), 不用為它多架一套監控。
看「剩多少」,不是看「用多少」
一次真實的容量事故(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 / waiting | waiting 持續大於 0 = 容量不夠 |
| vLLM KV cache 使用率 | 接近 1.0 就是排隊的原因 |
這些面板不是在 UI 上手點出來的:datasource 與 dashboard 的 JSON 全寫在 compose 的設定裡 (provisioning)。好處是重建容器 = 重建完整監控,零手工;代價是手改的儀表板重佈署會被蓋掉 ——要改就改 compose 裡的 JSON。告警規則也是同樣的道理。
沒有最好,只有最合適
先決定機器層那一半要多重。如果你只是要看幾台機器活不活著:
| 面向 | 輕量方案(如 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 平台 | 強(+優化) | 原生 |
| LangSmith | SaaS 優先 | LangChain 原生推理鏈 | 有限 | 經回調 |
| Arize Phoenix | 開源,可自托管 | 漂移偵測/LLM-judge | 強 | 原生 |
| Helicone | 開源,可自托管 | 網關式低侵入接入 | 基礎 | 支援 |
| AgentOps | SDK 開源,SaaS | 生產會話回放 | 生命週期指標 | 原生 |
| Braintrust | SaaS 優先 | 資料集評測 | 全面 | 標準 |
一句話版的選型建議:
- 要資料自主 + 功能全面(醫療/金融這類敏感場景)→ 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。
換你動手
右邊三個函式都可以直接呼叫:make_vram_series() 造時序、 evaluate_rule() 評一組規則、 self_times() 算 trace 的自身時間。
用 evaluate_rule 印出「for: = 0/2/5/13 分鐘」 四種設定的誤報數與偵測延遲,親手確認你在旋鈕上看到的行為。 注意 13 分鐘那一組發生了什麼事。
你的機器抖動比較長:把抖動改成 (2, 5, 8) 分鐘、真事件仍是 12 分鐘, 找出「誤報歸零又不漏報」的最小 for:。 然後想想這個數字對 on-call 的人意味著什麼。
把你自己遇過的一次慢請求寫成 spans 加進 TRACES, 算出 self time 最大的那一步。自我驗證的方法在 notebook 裡: 所有 span 的 self time 加總必須剛好等於根 span 的總時長,對不上就是你的層級或起訖寫錯了。
情境測驗
離開前試試看:下面的情境都真的會遇到。每題選一個你認為的最佳做法,選了馬上看得到解釋。
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 為了不拖慢你的應用,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 只證明「這個 /metrics 端點抓得到」,不證明端點裡的數字是新鮮的。GPU exporter 是靠呼叫 nvidia-smi 取值的,驅動更新後沒重開機這類狀況會讓它取值失敗——行程還在、端點還回得了 200,up 依然是 1,但 GPU 指標會無聲消失,沒有任何規則會叫。這正是為什麼要把 nvidia_smi_last_collect_success == 0 也寫成一條告警規則。A 對不上:保留期到了會整段消失,不會只剩一個 last_collect_success=0;C 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 服務一律排到沒有動態制服務的那台,並且替有動態制服務的那台永遠保留一塊餘裕。