vLLM 推論服務監控實戰:Prometheus + Grafana + MLflow(附 5 個踩坑筆記)

用 Prometheus + Grafana + MLflow 監控自架的 vLLM 推論服務——從架構、該看哪些指標、PromQL 查詢,到我實際踩過的 5 個坑(指標改名、子路徑 datasource、Dokploy 不重建…)。

自架 LLM 推論(vLLM)把服務跑起來只是第一步。真正上線後你會想知道:服務忙不忙?會不會塞車?慢在哪?VRAM 還剩多少? 這些都得「看得見」才好維運。

這篇把我在兩張 RTX 4090(用 Dokploy 佈署一組 vLLM 叢集)上建的監控整理成一份實戰筆記:Prometheus 收數據、Grafana 畫圖、MLflow 記錄每一次呼叫的內容。最後是我實際踩過的 5 個坑——每個都讓我卡了一陣子,寫下來讓你少走冤枉路。

三個工具,各做一件事

工具 白話 看的是
Prometheus 時序資料庫 + 查詢引擎,每 15 秒抓一次數字、存 N 天,還能當場幫你算 數字(幾個請求、幾個 token…)
Grafana 漂亮的前端,把 Prometheus 的數字畫成圖 趨勢、儀表板
MLflow 記錄每一次 LLM 呼叫的完整內容 內容(問了什麼、答什麼、幾個 token)

一句話分工:Prometheus/Grafana 看「數字與趨勢」,MLflow 看「內容」。


一、Prometheus:直接抓 vLLM 的 /metrics

好消息:vLLM 原生就在 /metrics 吐 Prometheus 格式的指標,不用改任何程式。你只要讓 Prometheus 定時去抓(scrape)就好。

先確認 vLLM 有在吐:

curl -s http://<你的-vllm>:8000/metrics | grep '^vllm:' | head
Code language: HTML, XML (xml)

Prometheus 的 scrape 設定(我這裡抓 4 個 vLLM 服務):

# prometheus.yml
global:
  scrape_interval: 15s
scrape_configs:
  - job_name: vllm
    metrics_path: /metrics
    static_configs:
      - targets:
          - 10.131.28.119:18000   # LLM
          - 10.131.28.119:18003   # ASR
          - 10.131.28.113:18001   # OCR
          - 10.131.28.113:18002   # Embeddings
Code language: PHP (php)

進 Prometheus UI 的 Status → Targets,這 4 個都該是 up。若是 down,多半是網路/防火牆或 port 不對。

vLLM 的指標會自動帶 model_nameinstance 等 label,所以同一個 Prometheus 抓多個服務也分得清楚。


二、該看哪些指標(vLLM 專屬)

vLLM 的指標都是 vllm: 開頭。新手記這幾個最有感:

指標 白話 怎麼看好壞
vllm:num_requests_running 現在同時處理幾個請求 0=閒著;持續很高=很忙
vllm:num_requests_waiting 幾個請求在排隊 >0 就是塞車了,要降並發或加資源
vllm:kv_cache_usage_perc GPU 的 KV cache(短期記憶)用掉幾成(0~1) 越接近 1 越容易排隊/拒絕
vllm:generation_tokens_total 累積生成了多少 token rate() 看每秒吞吐
vllm:prompt_tokens_total 累積吃進多少 token 看輸入量
vllm:request_success_total 累積成功幾次 rate() 看 QPS
vllm:e2e_request_latency_seconds 端到端延遲(含排隊) 直方圖,用 p95 看
vllm:time_to_first_token_seconds 首 token 延遲(TTFT) 直方圖,影響「感覺快不快」

**p95** 的意思是「95% 的請求都比這個值快」。看 p95 比看平均值更能反映最慢那批人的體驗。


三、Prometheus 不只是「紀錄」——它會算

很多人以為 Prometheus 只是把數字存起來。其實它是時序資料庫 + 查詢引擎,用 PromQL 可以當場計算

# 累積型:每個模型總共生成多少 token
vllm:generation_tokens_total

# 算速率:最近 5 分鐘,每秒生成幾個 token(吞吐量)
rate(vllm:generation_tokens_total[5m])

# 算加總:所有模型加起來的總 token
sum(vllm:generation_tokens_total)

# 算百分位:E2E 延遲的 p95(直方圖必備)
histogram_quantile(
  0.95,
  sum(rate(vllm:e2e_request_latency_seconds_bucket[5m])) by (le, model_name)
)
Code language: CSS (css)

關鍵觀念:Grafana 其實只是個漂亮的前端。 它畫的每一張圖,背後都是送一句 PromQL 給 Prometheus、把回來的數字畫成線。所以「Grafana 看得到的,Prometheus 都查得到」。

用 Python 直接打 Prometheus HTTP API 也很方便(做健康檢查腳本很好用):

import requests
PROM = "http://10.131.28.113:19090"   # 若有子路徑記得帶(見踩坑 #2)

def promq(query):
    r = requests.get(f"{PROM}/api/v1/query", params={"query": query}, timeout=8)
    return r.json()["data"]["result"]

for m in promq("sum by (model_name) (vllm:generation_tokens_total)"):
    print(m["metric"].get("model_name"), "=", m["value"][1])
Code language: PHP (php)

四、Grafana:把數字畫成圖

Grafana 用「provisioning」自動掛 datasource,開箱即用:

# /etc/grafana/provisioning/datasources/ds.yml
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090      # ← 這行等下會踩坑(見 #2)
    isDefault: true
Code language: PHP (php)

儀表板不用手刻,可以用 API 直接匯入。核心面板就 6 個:running、waiting、KV cache %、token 速率、E2E p95、TTFT p95。用上面那些 PromQL 當每個 panel 的 query 即可。

看圖的訣竅:把時間範圍設 Last 15 minutes、自動刷新 10s,然後製造一點流量(跑幾十個請求)——你就會看到 running 跳起來、token 速率隆起、延遲出現數據點。閒置時本來就是平的,別以為壞了。


五、MLflow:看「每一次呼叫的內容」

Grafana 看數字/趨勢;MLflow 看內容。做 agent、除錯 prompt 時,你會想知道「這次到底問了什麼、模型回什麼、花幾個 token」。MLflow 3 的 tracing 一行就能開自動追蹤:

import mlflow
from openai import OpenAI

mlflow.set_tracking_uri("http://10.131.28.113:15000")   # 有子路徑記得帶 /mlflow
mlflow.set_experiment("my-agent")
mlflow.openai.autolog()          # ← 關鍵:自動追蹤所有 OpenAI 呼叫

client = OpenAI(base_url="http://10.131.28.119:18000/v1", api_key="none")
client.chat.completions.create(
    model="qwen3-4b",
    messages=[{"role": "user", "content": "hello"}],
)
# → 打開 MLflow UI 的 Traces 分頁,就能看到這次呼叫的完整輸入/輸出/token/耗時
Code language: PHP (php)

MLflow 幫你的:除錯(agent 為什麼答錯?點開那次 trace 看它實際收到/回什麼)、看成本(每次幾個 token)、比較(換 prompt 後開兩次 trace 對照)。而且它支援 LangChain / LlamaIndex / DSPy 等框架的 autolog()


六、踩坑筆記(重點來了)

以下是我實際卡住、查了半天才解決的 5 個坑。

坑 1:vLLM 指標改名了(gpu_cache_usage_perckv_cache_usage_perc

很多舊教學、舊儀表板用的是 vllm:gpu_cache_usage_perc。但在較新的 vLLM(我這是 0.24)它改名成 vllm:kv_cache_usage_perc。結果就是:Grafana 的「KV cache 使用率」面板一直是空的,我還以為是 datasource 壞了。

教訓:面板/查詢查無資料時,先去 Prometheus 確認指標名到底存不存在:

# 列出所有含 cache 的 vllm 指標名
curl -s http://<prom>/api/v1/label/__name__/values \
  | python3 -c "import json,sys; print([m for m in json.load(sys.stdin)['data'] if 'cache' in m])"
Code language: PHP (php)

坑 2:Prometheus 掛在子路徑,Grafana datasource 要一起改

為了對外(只開 443),我把 Prometheus 用 --web.external-url=https://host/prometheus 掛到 /prometheus 子路徑。結果 Grafana 整個查不到資料

原因:--web.external-url 會讓 Prometheus 的 API 只在 /prometheus/api/... 底下服務,根路徑 /api/... 會回 302。但 Grafana 的 datasource 還指著 http://prometheus:9090(沒帶子路徑)→ 查詢一律 302 → 拿不到 JSON。

修法:datasource 的 url 也要帶子路徑:

url: http://prometheus:9090/prometheus   # ← 要跟 Prometheus 的 route-prefix 一致
Code language: JavaScript (javascript)

同理,程式用 Python 打 API、MLflow 的 set_tracking_uri,只要服務掛了子路徑,全部都要帶上那段子路徑,否則不是 404 就是 302。

坑 3:Grafana 圖是「平的/空的」,但其實沒壞

兩個最常見原因:

  1. 時間範圍太大:跑一次流量就結束,你卻用「Last 6 hours」看,那個小凸起被壓成一條線。→ 改 Last 15 minutes 再看。
  2. 查的是即時 gauge:像 vllm:num_requests_running 這種,閒置時本來就是 0。想看「有發生過的事」要查累積型計數器..._total)或它的 rate()

坑 4:Prometheus UI 打開一片空白——它不是儀表板

新手最容易誤會:Prometheus 的 Graph/Query 頁預設是空白的,你得自己在框框裡貼一句 PromQL、按 Execute,再切 Table(看當前值)或 Graph(看趨勢)。它不像 Grafana 有現成的圖。想要一打開就有圖,看 Grafana。

坑 5:改了設定卻沒生效——Docker Compose 的 inline config 不會觸發重建

我用 compose 的內嵌 configs(inline content)放 Prometheus/Grafana 的設定。改完設定重新 docker compose up -d,卻發現容器根本沒更新

原因:up -d 只在「服務定義(image / command / env…)」變動時才重建容器;「只有 config 內容變」它偵測不到

修法:明確 force-recreate 那個服務:

docker compose up -d --force-recreate prometheus

(順帶一提:如果你像我一樣用 Dokploy 管 compose,它的部署一樣是 up -d,所以改內嵌 config 後也要手動 force-recreate 對應容器。)


小結

  • Prometheus 抓 vLLM 原生 /metrics,是資料庫也是計算引擎(rate/sum/histogram_quantile)。
  • Grafana 是前端,畫的都是 PromQL 的結果;看圖記得設短時間範圍 + 製造流量。
  • MLflow 補上「內容」層,autolog() 一行開追蹤,除錯 agent 神器。
  • 最容易踩的坑:指標改名子路徑要一路帶到底gauge 閒置=0 不是壞掉inline config 要 force-recreate

監控不是為了好看,是為了出事時你查得到、平常你安心。先把這幾個指標看懂、把這幾個坑避開,就能少很多半夜被叫起來的機會。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *