自架 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_name、instance等 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_perc → kv_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 圖是「平的/空的」,但其實沒壞
兩個最常見原因:
- 時間範圍太大:跑一次流量就結束,你卻用「Last 6 hours」看,那個小凸起被壓成一條線。→ 改 Last 15 minutes 再看。
- 查的是即時 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。
監控不是為了好看,是為了出事時你查得到、平常你安心。先把這幾個指標看懂、把這幾個坑避開,就能少很多半夜被叫起來的機會。