LLM 服务可观测性:吞吐、时延、token 与成本监控

LLM 推理服务没有可观测性就像盲人开车:TTFT、TPOT、吞吐、token 计量、GPU 利用率、链路追踪与成本核算缺一不可。本文给出从指标定义到看板、压测、告警的完整方法论。

LLM 推理服务与传统 Web 服务的可观测性有本质差异:延迟是「分阶段」的(TTFT/TPOT)、负载是按「token」计的、成本是按「GPU 小时」算的。一套只有 CPU 利用率和 5xx 的监控系统,对 LLM 服务基本等于盲人开车。本文从核心指标定义出发,讲透 token 计量、GPU 监控、链路追踪、成本核算,以及监控看板、压测方法论与告警 SLO 的完整闭环。

前置:/ai-inference-benchmark/(压测与指标)、/ai-llm-inference-architecture/(推理服务架构)、/ai-inference-engine-comparison/(引擎对比)、/ai-vllm-system/(引擎调度指标)。

目录

1. 核心指标:TTFT、TPOT、TPS 与并发

先把 LLM 服务的「延迟坐标系」定义清楚,它是所有监控的基石。

延迟类指标:
□ TTFT(Time To First Token):请求发出到首个 token 返回
  - 主要受 prefill + 排队影响 → 用户感知「响应快不快」
□ TPOT(Time Per Output Token):每个输出 token 的平均时间
  - 受 decode 带宽 + 并发影响 → 用户感知「生成快不快」
□ E2E Latency:完整请求总时延 ≈ TTFT + TPOT × 输出长度

吞吐类指标:
□ TPS(Tokens Per Second):系统每秒生成的 token 数
□ Requests Per Second:每秒请求数
□ 并发(Concurrency):同时在跑的请求数

饱和度指标:
□ 队列深度:等待中的请求数(过载的第一信号)
□ 每轮迭代 batched tokens:调度器每轮处理量
□ 抢占/换出次数:显存压力的信号
指标分层:
业务层:TTFT、TPOT、E2E、错误率、命中率
调度层:并发、队列深度、batched tokens、抢占次数
资源层:GPU 利用率、显存水位、带宽
→ 三层联动:业务层恶化时逐层下钻定位

工程要点:LLM 延迟必须按 TTFT/TPOT 拆开看——TTFT 反映 prefill 与排队,TPOT 反映 decode 与带宽。吞吐用 TPS 而不是纯 QPS,因为输出长度差异让 QPS 完全失真。三层指标(业务/调度/资源)联动,才能定位问题在哪一层。

2. Token 计量:输入、输出、缓存与生成速率

Token 是 LLM 世界的「货币」,计量口径必须统一。

Token 计量维度:
□ 输入 token(prompt tokens):每请求的输入长度
□ 输出 token(generated tokens):每请求的生成长度
□ 缓存 token:前缀缓存命中的部分(不计入计算成本)
□ 生成速率:tokens/s(单请求与系统级两个口径)

为什么重要:
□ 计费:按 token 收费(输入/输出单价不同)
□ 容量规划:输入+输出长度分布决定显存与带宽需求
□ 调度评估:每轮 batched tokens 反映调度效率

关键比例:
□ 缓存命中率:命中部分不花 prefill 算力 → 省钱指标
□ 平均输入/输出长度:决定负载模型
□ token 效率:同样请求,输出越短越省
# 从 vLLM metrics 里看 token 计量
metrics = {
  "vllm_prompt_tokens_total": 12_000_000,   # 累计输入 token
  "vllm_generation_tokens_total": 5_000_000, # 累计输出 token
  "vllm_preemption_total": 42,              # 抢占次数
}
# 缓存命中(前缀缓存场景):
#   vllm:num_cached_tokens / vllm:num_total_tokens
cache_hit = 1_200_000 / 12_000_000          # 10% 命中
# → prefill 算力省了约 10%(命中部分不算)

工程要点:Token 计量要「输入/输出/缓存/速率」四个口径全记录——计费按 token、容量规划看长度分布、省钱看缓存命中率。缓存命中率是「白赚 prefill 算力」的晴雨表,值得单独做成看板指标。

3. GPU 利用率与显存监控

GPU 是推理服务最贵的资源,它的监控逻辑和 CPU 完全不同。

GPU 利用率(关键坑):
□ SM 利用率(nvidia-smi 的 util):接近算力利用
  - LLM decode 阶段 SM 利用率通常很低(<30%)
  - 低不等于「坏了」——decode 就是带宽受限
□ 内存带宽利用率:decode 阶段真正的瓶颈
  - 用 DCGM 的 dram_activity 观测
□ 不要用「GPU 利用率高」当健康标准,要看「卡在哪个资源」

显存监控:
□ 显存使用量/总量:权重 + KV 池 + 激活
□ KV 池水位:free blocks / used blocks
□ 显存碎片与预留:gpu-memory-utilization 的余量
□ OOM 风险:接近峰值时要告警

每卡 vs 每集群:
□ 单卡利用率分布:负载均衡是否均匀(MoE/TP 尤其重要)
□ 集群 GPU 空闲率:成本黑洞的直接来源
监控组合建议:
□ SM 利用率(算力视角)
□ DRAM 带宽利用率(decode 瓶颈视角)
□ KV 池 free blocks(容量视角)
□ GPU 温度/功耗(硬件健康视角)
→ 四个数字一起看,才能判断「健康 vs 浪费」

工程要点:GPU 监控不能用单一利用率——decode 阶段 SM 利用率低是「正常的带宽受限」,要加看 DRAM 带宽利用率和 KV 池水位。集群级看「单卡利用率分布」抓负载不均和空闲卡,这是最大的成本黑洞。

4. 链路追踪:请求级别的 trace 与 span

单看聚合指标会漏掉「哪条请求慢」。请求级链路追踪是下钻工具。

一个 LLM 请求的生命周期 span:
□ gateway:路由/鉴权/限流
□ queue:排队等待
□ prefill:输入处理(TTFT 大头)
□ decode:逐 token 生成(含多次 forward)
□ streaming:返回与网络传输

关键附加字段:
□ prompt_tokens / output_tokens
□ 缓存命中数
□ 采样参数(temperature 等)
□ 模型版本 / LoRA 标识
□ 租户 / 用户 / session_id

价值:
□ 定位慢请求:是排队、prefill 还是 decode
□ 关联业务:哪些 prompt 类型最慢/最贵
□ 采样:按 x% 全量采样避免追踪开销
trace 结构示意:
request ─┬─ gateway:5ms
         ├─ queue:120ms(排队过长 → 过载信号)
         ├─ prefill:800ms(prompt 长 → TTFT 高)
         ├─ decode:1500ms(TPOT 高 × 输出 50)
         └─ streaming:30ms
→ 一眼看出:卡在排队还是卡在 prefill

工程要点:链路追踪把「聚合指标」拆成「单请求的 span 时间线」——queue/prefill/decode 各占多少毫秒,一眼定位瓶颈层。关键是要带上 token 数、缓存命中、采样参数等上下文,否则 span 看不出业务含义。

5. 日志与错误分类:从 5xx 到模型错误

LLM 服务的错误形态比传统服务丰富得多,需要专门分类。

错误分类体系:
□ 协议/网关错误:4xx(鉴权、限流)、5xx(网关超时)
□ 排队超时:请求在队列里超时被拒(过载信号)
□ prefill 失败:输入超长、显存不足(OOM)
□ 生成中断:输出达到 max_tokens 被截断
□ 内容过滤:命中安全策略被拦(不是故障,但要计量)
□ 模型错误:生成空、流中断、半截输出

日志要点:
□ 结构化日志(JSON),带上 request_id 关联 trace
□ 关键字段:token 数、耗时、错误类型、模型版本
□ 采样式全量:普通日志抽样,错误日志必全量

内容安全日志:
□ 命中过滤的 prompt/response 分级记录
□ 用于审计与误拦率评估
错误率语义(重要):
□ 5xx 只是「网关层错误」
□ LLM 特有的「语义错误」:
  - 空响应、截断、内容被过滤
  - 这类不产生 5xx,但要按错误统计
□ 区分「系统错误」(要告警)与「业务拒绝」(要计量)

工程要点:LLM 错误分类要区分「系统故障」(5xx、排队超时、OOM——该告警)与「业务拒绝」(内容过滤、截断——该计量)。结构化日志带 request_id 关联 trace,错误日志全量、普通日志采样。

6. 成本核算:GPU 小时、token 与单位成本

推理成本核算要把「资源成本」换算成「token 成本」,才能指导优化。

成本分层:
□ 资源层:GPU 小时(含 idle 时间)→ 硬件账单
□ 单位层:每百万 token 成本($/M tokens)
□ 业务层:每次请求成本 / 每用户成本

关键指标:
□ GPU 利用率(时间维度):闲置率 × 账单 = 浪费金额
□ token 产出:总 token / GPU 小时 → 单位效率
□ 缓存命中节省:命中的 prefill 算力折成钱
□ 批大小效率:同样的 GPU 小时产出多少 token

成本优化映射:
□ 提高 batch → 单位 token 成本下降
□ 量化/蒸馏 → 权重小、吞吐高 → 成本下降
□ 前缀缓存 → 省 prefill 算力
□ 削峰填谷:spot 实例跑离线批处理
成本核算示意:
月 GPU 账单 = 8 卡 × 720 小时 × $2/卡时 = $11,520
月生成 token = 5 亿 → 生成成本 ≈ $23/百万 token
月输入 token = 12 亿 → 输入成本 ≈ $9.6/百万 token
→ 与云厂商 API 定价对比,判断自建 vs API 的经济性

工程要点:成本核算的链路是「GPU 小时 → 每百万 token 成本 → 每请求成本」——先算闲置 GPU 的浪费金额,再算每百万 token 产出成本,最后与云厂商 API 定价对比。缓存命中、批大小、量化每一项优化都能映射成具体金额。

7. 监控看板搭建:Prometheus 与 Grafana 实践

把指标落成可看的看板,是团队协作的接口。

技术栈:
□ 采集:Prometheus + node_exporter + DCGM exporter
□ 引擎指标:vLLM 自带 /metrics(vllm_* 前缀)
□ 展示:Grafana 多面板
□ 追踪:OpenTelemetry + Jaeger/Tempo
□ 日志:Loki / ELK

看板分层(一个核心看板 + 分层下钻):
□ 概览:RPS、TPS、TTFT/TPOT P50/P99、错误率、成本
□ 调度:并发、队列深度、batched tokens、抢占
□ 资源:GPU 利用率、带宽、显存水位、KV 池
□ 业务:缓存命中、token 分布、按租户细分

看板设计原则:
□ 每个面板一个问题,别堆砌
□ 全局 P50/P99 与分位线一起展示
□ 时间范围可下钻(1h/1d/7d)
看板面板清单(概览层):
1. TTFT P50/P99(折线)
2. TPOT P50/P99(折线)
3. TPS 与 RPS(折线)
4. 队列深度 + 抢占次数(柱状)
5. 缓存命中率(百分条)
6. 每百万 token 成本(时序)
7. GPU SM 利用率 + DRAM 带宽(热力图)

工程要点:看板用「概览 + 分层下钻」组织——顶层放业务 SLO(TTFT/TPOT/TPS/成本),往下是调度层与资源层。引擎的 /metrics 直接暴露 vllm_* 指标,DCGM 负责 GPU 硬件指标;每个面板对应一个问题,避免装饰性堆砌。

8. 压测方法论:负载模型与基准测试

压测不是「发一堆请求看吞吐」,而是按真实负载模型测。

负载模型的三个输入:
□ 输入长度分布:短聊天 vs 长文档(差异巨大)
□ 输出长度分布:决定 decode 时长
□ 到达模式:均匀 / 突发(burst)

压测要素:
□ 负载生成:用真实流量回放 or 合成按分布采样
□ 渐增负载:找「吞吐拐点」与「延迟恶化点」
□ 混合场景:长+短请求混合(chunked prefill 才有意义)
□ 持续时长:跑够时间,覆盖 KV 池水位上升过程

测量口径:
□ 稳态吞吐:非瞬时峰值
□ P50/P95/P99 延迟:完整分布
□ 队列饱和表现:过载后是优雅排队还是雪崩
压测脚本要点(vLLM benchmark):
python benchmarks/benchmark_throughput.py \
    --input-len 512 --output-len 256 \
    --num-prompts 3000
# 但真实负载是长短混合 → 用 sharegpt 类真实数据
# 或用 --request-rate 控制到达率,测延迟-吞吐曲线

工程要点:压测的第一原则是「负载模型必须贴近真实」——输入/输出长度分布和到达模式决定一切结论。用渐增负载找吞吐拐点与延迟恶化点,测完整分位数而非均值,跑够时间覆盖 KV 池水位上升的过程。

9. 告警与 SLO:可观测性闭环

可观测性的终点是「可行动的告警与 SLO 闭环」。

SLO 定义(示例):
□ 目标:TTFT P99 < 1s 达 99.5%;TPOT P99 < 3s 达 99.5%
□ 错误预算:每月允许的超 SLO 时间
□ 预算耗尽 → 冻结新特性,优先修稳定性

告警设计:
□ 队列深度:最先告警的信号(过载前置)
  - 阈值按压测拐点标定,而非拍脑袋
□ KV 池 free blocks:低于 N 块告警(OOM 前兆)
□ 抢占次数飙升:显存压力信号
□ 缓存命中率骤降:前缀布局被破坏
□ 错误率:区分「系统错误」(告警)与「业务拒绝」(计量)

告警分级:
□ P0:5xx 飙升、GPU 宕机、队列雪崩
□ P1:延迟超 SLO、KV 池告急、抢占频繁
□ P2:缓存命中下降、成本异常
闭环流程:
压测定基线 → 设阈值 → 告警 → 下钻 trace/日志定位
→ 修引擎/调参数 → 重测基线 → 更新阈值
→ 每个告警都应有「对应行动」,否则就是噪音

工程要点:告警的第一信号是「队列深度」——GPU 弹性差,队列一旦堆积恢复极慢,必须在压测拐点处设阈值。SLO 用错误预算约束,告警区分 P0/P1/P2,每个告警都必须映射到具体行动,否则变成噪音。

10. 速查表与一句话记忆

问题一句话答案
TTFT 反映什么prefill + 排队,用户感知响应快慢
TPOT 反映什么decode 带宽与并发,用户感知生成快慢
为什么不用 QPS输出长度差异大,QPS 失真,用 TPS
Token 计量四口径输入、输出、缓存命中、生成速率
GPU 利用率怎么看SM 利用率 + DRAM 带宽 + KV 池水位一起看
慢请求怎么定位trace 的 queue/prefill/decode span 时间线
错误分几类系统故障(告警)与业务拒绝(计量)
成本怎么算GPU 小时 → 每百万 token → 每请求
压测最关键负载模型贴近真实(长度分布 + 到达模式)
最先告警什么队列深度(过载前置信号)

一句话记忆:LLM 可观测性 = 三层指标(TTFT/TPOT/TPS 业务层、队列/batched tokens 调度层、GPU/KV 池资源层)+ 四口径 token 计量 + trace 下钻慢请求 + GPU 小时成本核算 → 压测定基线 → 队列深度先告警 → 每个告警对应行动,形成闭环。

延伸阅读

  • /ai-inference-benchmark/ — 吞吐与延迟的压测方法论
  • /ai-llm-inference-architecture/ — 推理服务架构与调度
  • /ai-inference-engine-comparison/ — 引擎指标与差异
  • /ai-vllm-system/ — 引擎调度与内存指标
  • /ai-kv-cache-paged-attention/ — KV 池与显存指标
  • 分布式系统专题 — 分布式监控与链路追踪

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. AI 推理安全:内容安全、提示注入与模型防护
  2. FlashAttention 与高效注意力内核:IO 感知与分块计算
  3. MoE 混合专家推理:路由、负载均衡与显存策略