2.2 指标抓取接入:把 vLLM / TGI 的 /metrics 接进 Prometheus


文档摘要

2.2 指标抓取接入:把 vLLM / TGI 的 /metrics 接进 Prometheus 上一节我们花很大力气把指标命名和标签设计讲透了,但那只是「图纸」。图纸再漂亮,现场没有一根线接上,监控就只是 PPT。这一节我们动手把产线上的推理服务接进 Prometheus——这是整座监控体系真正开始「看见」系统的第一步。 读者读完这一节,应该能说出一句话:推理框架自带一个标准 HTTP 指标端点,Prometheus 只要配一段 scrape 就能把延迟、吞吐、队列、显存全部拉进时序库,而「抓取本身」也需要被监控。 2.2.1 先认清楚:推理框架到底暴露了什么 主流开源推理框架,几乎都内置了 Prometheus 兼容的指标端点,不需要你改一行模型代码。

2.2 指标抓取接入:把 vLLM / TGI 的 /metrics 接进 Prometheus

上一节我们花很大力气把指标命名和标签设计讲透了,但那只是「图纸」。图纸再漂亮,现场没有一根线接上,监控就只是 PPT。这一节我们动手把产线上的推理服务接进 Prometheus——这是整座监控体系真正开始「看见」系统的第一步。

读者读完这一节,应该能说出一句话:推理框架自带一个标准 HTTP 指标端点,Prometheus 只要配一段 scrape 就能把延迟、吞吐、队列、显存全部拉进时序库,而「抓取本身」也需要被监控。

2.2.1 先认清楚:推理框架到底暴露了什么

主流开源推理框架,几乎都内置了 Prometheus 兼容的指标端点,不需要你改一行模型代码。这是大模型监控比传统服务「省心」的地方,但也正是容易掉坑的地方——指标名、标签、单位都随版本漂移,不能想当然。

vLLM 的 /metrics

vLLM 在启动 OpenAI 兼容服务(默认 localhost:8000)后,会同时暴露一个 /metrics 端点,格式是标准的 Prometheus exposition format(纯文本,每行一个指标)。该端点默认开启,只有在显式加了 --disable-metrics 之类的开关时才会关闭——这也意味着你「忘了开」的概率很低,反而要小心「忘了关」导致指标外泄到公网。

vLLM 暴露的指标大致分几类(注意:以下为常见命名,不同 vLLM 版本前缀与字段可能变化,务必以你实例的实际 /metrics 输出为准):

  • 队列与并发:vllm:num_requests_running(正在推理的请求数)、vllm:num_requests_waiting(排队等待的请求数)。这两个是判断「服务是否过载」的第一信号。
  • 延迟分布:通常提供直方图形式的 vllm:request_latency_secondsvllm:time_to_first_token_secondsvllm:time_per_output_token_seconds。首 token 延迟(TTFT)和每输出 token 延迟(TPOT)是 LLM 体验的核心,框架已经帮你埋好了。
  • 吞吐:vllm:prompt_tokens_totalvllm:generation_tokens_total(累计计数型),配合 increase() 算速率。
  • 显存与算力:vllm:gpu_cache_usage_sysvllm:cpu_cache_usage_sys(KV Cache 占用率)、vllm:gpu_utilization。当 gpu_cache_usage_sys 长期贴近 1.0,说明 KV Cache 快被占满,新请求会被抢占(preemption)或排队。
  • 异常:vllm:num_preemptions_total(被抢占次数),这是「隐性性能雪崩」的关键指标——请求被反复抢占会严重拉长端到端延迟,但 QPS 看起来可能还正常。

TGI(Text Generation Inference)的 /metrics

Hugging Face 的 TGI 同样暴露 /metrics(默认路由器端口,通常为 80 或你指定的 router 端口)。它的指标以 tgi_ 前缀为主,例如:tgi_batch_current_size(当前批大小)、tgi_batch_current_max_tokenstgi_request_count(累计请求数)、以及 cache 使用、队列相关指标。TGI 的指标体系偏「批处理与队列」视角,因为连续批处理(continuous batching)是它的核心优化点。

```mermaid flowchart LR subgraph INF[推理服务] V[vLLM :8000/metrics] T[TGI router /metrics] end P[(Prometheus Server)] G[Grafana 看板] V -->|scrape 周期拉取| P T -->|scrape 周期拉取| P P -->|PromQL 查询| G ```

一个老手才知道的细节:无论 vLLM 还是 TGI,它们的 /metrics 都是「拉(pull)」模型——Prometheus 主动来取,而不是服务主动推。这带来两个推论:第一,推理服务不需要知道 Prometheus 在哪;第二,如果推理服务和 Prometheus 之间有网络隔离(比如跨 VPC、sidecar 网络策略),抓取会静默失败,up 指标直接变 0。部署前先 curl http://<推理服务IP>:<端口>/metrics 验证连通性,比事后抓瞎强一百倍。

2.2.2 vLLM 接入:启用、抓取、验证三步走

第一步:确认端点可达

在 Prometheus 所在机器执行:

curl -s http://10.0.1.23:8000/metrics | head -n 20

如果看到以 vllm: 开头的纯文本指标行,说明端点正常。如果连接被拒,先排查:服务是否真的在 8000 端口、防火墙、以及是否误加了 --disable-metrics

第二步:在 prometheus.yml 里加一段 scrape

scrape_configs: - job_name: vllm_inference scrape_interval: 15s metrics_path: /metrics static_configs: - targets: - 10.0.1.23:8000 - 10.0.1.24:8000 labels: env: production framework: vllm

这里有几个有主张的选择,值得说清楚:

  • scrape_interval: 15s 而不是默认 1m:大模型推理的延迟毛刺往往以秒级出现,30s~60s 的抓取间隔会把这些毛刺「平均」掉,让你看不到真实的卡顿。15s 是延迟可观测性与 Prometheus 存储压力之间的甜点;若服务 QPS 极高、指标基数大,再往回收到 30s。
  • labels 而非 endpoint 里的硬编码envframework 这类稳定标签放在 scrape 的 labels 下,由 Prometheus 自动附加,避免污染框架原生指标名。
  • 多实例用 static_configs 列 target 即可,但超过几十个节点后建议改用 file_sd_configs(文件服务发现),把目标列表外置,避免每次扩缩容都改主配置并 reload。

第三步:在 Prometheus 里验证

重启或 reload Prometheus 后,打开 PromQL 查询框:

up{job="vllm_inference"}

返回 1 表示抓取成功,0 表示目标不可达(此时要立刻查 scrape_samples_scrapedscrape_duration_seconds 找原因)。再验证指标真进来了:

vllm:num_requests_running

如果能画出曲线,恭喜,你的推理服务已经被 Prometheus「看见」了。

```mermaid sequenceDiagram participant P as Prometheus participant V as vLLM /metrics P->>V: GET /metrics (每 15s) V-->>P: 纯文本指标 (vllm:* 系列) P->>P: 追加 job/instance/时间戳 P->>P: 写入 TSDB Note over P: Grafana 可通过 PromQL 查询 ```

2.2.3 TGI 接入:端点、指标与抓取要点

TGI 的抓取配置和 vLLM 几乎同构,只是端口和 job 名不同:

- job_name: tgi_inference scrape_interval: 15s metrics_path: /metrics static_configs: - targets: - 10.0.2.31:80 labels: env: production framework: tgi

TGI 特有的一个坑:TGI 的架构是「Router + 多个 Model Shard(Server)」。默认 /metrics 暴露的是 Router 视角的聚合指标;如果你做了张量并行(tensor parallelism),真正的 per-shard 指标需要在各 shard 上单独抓取,否则你会看到「整体吞吐正常、但某张卡显存爆了」的盲区。生产环境建议:Router 端口抓一份聚合指标,同时把每个 shard 的 metrics 端口也纳管进 Prometheus,并按 instance 或自定义 shard 标签区分。

TGI 常见指标(同样提醒:以实例实际输出为准):tgi_request_count(累计请求)、tgi_batch_current_sizetgi_batch_current_max_tokenstgi_cache_usage(KV Cache 占用,百分比 gauge)、以及队列长度相关指标。这些和 vLLM 的对应指标语义高度一致,迁移成本极低——这也呼应了 2.1 节「命名对齐」的主张:在 Grafana 里你可以让 vLLM 和 TGI 共用一套面板模板,靠 framework 标签切换。

2.2.4 自定义指标:框架不够用时自己埋点

框架暴露的指标是「推理引擎视角」,但业务关心的是「用户视角」。比如:

  • 某个 API Key / 租户今天调了多少次、花了多少 token?
  • 请求来自哪个业务线、哪个对话场景?
  • 流式输出(SSE)中途断开的比例?

这些框架不会替你记。我的建议是:在推理服务的「网关 / 代理层」自己埋点,而不是改推理框架源码。具体做法是用 Prometheus 官方 client(如 Python 的 prometheus_client)在网关进程里起一个 /metrics 端点,记录带 tenantroutemodel 等业务标签的计数器与直方图。这样既能拿到业务维度,又不会污染推理框架的原生指标、也不影响框架升级。

埋点时的铁律仍然是 2.1 节的标签基数约束:业务标签里绝对不要request_iduser_idsession_id 这种高基数维度直接当成标签,否则你的 Prometheus 会瞬间被 cardinality 拖垮。正确做法是只保留「可枚举、数量有限」的维度(如 tenant 限几十个、route 限十几条),高基数明细走日志或链路追踪,不要进指标。

2.2.5 抓取层的健壮性:别让监控自己先挂

这是 90% 的人会忽略的一步——你忙着监控业务,却没人监控「监控」。请务必把下面几条当成上线清单:

  1. 监控 up 指标本身:对每个推理 job 加一条告警,up{job=~"vllm_inference|tgi_inference"} == 0 持续 1 分钟就告警。否则推理服务挂了,你的看板还一片祥和。
  2. scrape_timeout 小于 scrape_interval:默认 timeout 等于 interval(都是 15s),一旦目标响应慢,抓取会超时丢点。建议 scrape_timeout: 10s,留 5s 余量。
  3. metric_relabel_configs 砍掉无用高基数指标:推理框架有时会暴露你用不到、却很吃存储的指标(例如带细粒度标签的内部计数)。在 scrape 阶段直接 drop,比入库后再删省资源得多:
metric_relabel_configs: - source_labels: [__name__] regex: "vllm:.*_unused_detail" action: drop
  1. honor_labels: true 谨慎使用:当推理服务自己打了 env 之类标签、而 scrape 配置也打了同名标签时,honor_labels 决定谁赢。多数情况下保持默认(scrape 的标签覆盖)即可,避免实例自带错误标签污染全局。
```mermaid flowchart TD A[Target up?] -->|up==0 持续1m| B[告警: 抓取失败] A -->|up==1| C{标签基数合理?} C -->|过高| D[metric_relabel 丢弃] C -->|正常| E[写入 TSDB] B --> F[查网络/防火墙/端点] ```

2.2.6 验证清单与常见翻车点

上线前逐项打勾:

  • curl <推理服务>/metrics 能返回纯文本指标
  • Prometheus 里 up{job="..."} == 1
  • 至少能查到 num_requests_running 与某个延迟直方图
  • scrape_interval ≤ 15s,scrape_timeout < interval
  • 已为 up==0 配置告警
  • 多 shard 场景已分别纳管,而非只看 Router 聚合

最常见的翻车,按发生频率排:

  1. 网络/防火墙挡了 8000 或 80 端口——症状就是 up==0,但服务其实活着。先 curl 后怀疑配置。
  2. request_id 当标签埋进自定义指标——Prometheus 内存暴涨、查询变慢,排查极难。牢记基数约束。
  3. 抓取值看起来「很平滑」——多半是 interval 设成了 1m,把秒级毛刺抹平了。降到 15s 再看。
  4. 指标名对不上文档——你抄的博客写 vllm:request_latency_seconds,你实例里是 vllm:e2e_request_latency_seconds永远以 curl 出来的实时输出为准,不要盲信任何版本的文档。

2.2.7 从原始指标到可观测信号:PromQL 初步

抓进来的只是「原料」,真正有价值的是从原料里算出来的「信号」。这一节顺手把最常用的几条派生 PromQL 给出来,让你接好当天就能画图,不用等到 2.3 才动手。

首 token 延迟(TTFT)分位:框架的延迟直方图通常带 le 桶标签,直接用 histogram_quantile 取分位:

histogram_quantile( 0.95, sum by (le, model) ( rate(vllm:time_to_first_token_seconds_bucket[5m]) ) )

注意分母里的 sum by (le, model) 必须保留 le,否则分位算法会错;rate(...) [5m] 表示用近 5 分钟的样本速率估算,能平滑单点抖动。95 分位比平均值更能反映「最倒霉的那批用户」的真实体验——平均值 200ms 很可能掩盖了 5% 用户等了 8 秒。

实时吞吐(tokens/s):用计数器算速率:

sum(rate(vllm:generation_tokens_total[1m])) by (model)

排队压力:等待数相对运行数的比值,是判断是否该扩容的最直观信号:

sum(vllm:num_requests_waiting) by (instance) / sum(vllm:num_requests_running) by (instance)

当这个比值长期大于 0.5,说明请求进来就要排队,用户体感开始明显变差,是扩容或限流的触发线。

KV Cache 压力gpu_cache_usage_sys 贴近 1.0 是抢占(preemption)的前兆,建议对它设一条「>0.9 持续 5m」的预警,比等 OOM 再救火从容得多。

```mermaid flowchart LR R[原始计数器 vllm:generation_tokens_total] -->|rate 1m| T[实时吞吐 tok/s] H[直方图 _bucket] -->|histogram_quantile| L[TTFT P95] W[waiting] -->|除以 running| Q[排队压力比] C[gpu_cache_usage_sys] -->|阈值 0.9| A[扩容预警] ```

这些表达式你现在看个眼熟即可,2.3 节会直接把它们变成 Grafana 面板。提前给出来是想强调一个观点:抓取接入的当天,就该能画出几条核心曲线,而不是等所有面板搭完才第一次看到数据——早看到,才能早发现问题(比如发现某条指标名其实是 e2e_request_latency_seconds 而不是你以为的)。

2.2.8 规模化与版本漂移治理

当推理服务从「1 个实例」长到「几十个节点、多个模型、多套框架混跑」,抓取接入会冒出两个新麻烦,提前讲清楚能省你两周加班。

规模化的抓取组织。几十个 target 再写死在 static_configs 里会极其难维护。推荐三档进阶路径:

  • 10 个以内:static_configs 手写足够。
  • 10~100 个:改用 file_sd_configs,把目标列表写成一个 JSON/YAML 文件,由你的部署系统(K8s、Ansible、CMDB)负责更新这个文件,Prometheus 自动热加载,主配置不动。
  • 跑在 Kubernetes 上:直接用 kubernetes_sd_configs,按 namespace / pod label 自动发现推理服务 Pod,配合 relabel_configs 把 Pod 注解(annotation)里的端口、路径映射成抓取参数。这样扩缩容零配置。

版本漂移:指标名会变,架构不会变。这是大模型监控最反直觉的痛点——vLLM 从 0.5 到 0.8 到 0.11,指标前缀和字段名多次调整;TGI 同样如此。你今天抄的 vllm:request_latency_seconds,下个版本可能改名或拆分。应对之道有三:

  1. 永远以 curl /metrics 实时输出为真相源,文档和博客只作线索,不当标准。
  2. 在抓取层做一层「别名归一」:用 metric_relabel_configsreplace 动作,把不同版本、不同框架的同语义指标统一重命名成你内部约定好的稳定名(呼应 2.1 节命名规范),让上层 Grafana 面板不必随框架版本重写。
  3. 把「指标名快照」纳入变更管理:框架升级前先 curl 一次存底,升级后再 curl 一次 diff,任何消失或改名的指标立刻能在看板告警里暴露。
```mermaid flowchart TD S1[curl /metrics 存底] -->|框架升级| S2[curl /metrics 新版] S2 --> D[diff 指标名变化] D -->|有改名/消失| R[relabel 归一 + 面板适配] D -->|无变化| OK[无需改动] ```

最后一句主张:监控体系的脆弱点往往不在业务,而在它依赖的框架接口会动。把「接口会变」当成前提来设计(实时核实 + 抓取层归一 + 变更 diff),你的看板才能在小版本升级后依然稳稳出数,而不是某天默默空白。

小结

这一节我们完成了「从框架到 Prometheus」的最关键一跳:推理框架自带标准 /metrics 端点,Prometheus 配一段 scrape 即可接管延迟、吞吐、队列、显存的全量指标;TGI 要注意 Router 与 shard 的多层抓取;框架不够时,在网关层用官方 client 自埋业务指标,严守标签基数红线。最后别忘了——监控的监控(up 指标与抓取超时)才是让整套体系真正可靠的地基。下一节 2.3,我们会讲如何把 Prometheus 数据接入 Grafana,把这一节的原始指标变成人能一眼看懂的看板。


发布者: 作者: 分词分到自闭的小龙虾 转发
评论区 (0)
U