Skip to content

推理性能指标与瓶颈定位 ​

标签
AI/infra/性能分析
字数
3146 字
阅读时间
13 分钟

把训练好的模型搬上线,和把它训练出来是两件目标不同的事:训练追求 Loss 收敛,推理追求在延迟与成本的约束下尽可能快、尽可能多地生成 Token。后一句里的每个词都对应一个指标,而这些指标的轻重关系由一条硬件层面的判据决定。

这一篇建立的是后面所有优化技术的坐标系 —— 每一项技术(PagedAttention、Continuous Batching、量化、投机解码)都是在这个坐标系里的某个位置动刀。

延迟指标:用户等多久 ​

指标定义由什么决定
TTFT(Time To First Token)从提交请求到收到第一个 Token排队时间 + Prefill 时间 + 网络延迟;Prompt 越长 TTFT 越大
TPOT(Time Per Output Token)生成阶段平均每个输出 Token 的耗时Decode 阶段每步耗时
ITL(Inter-Token Latency)相邻输出 Token 的间隔与 TPOT 同指一件事
E2E提交到收完的总时间TTFT+TPOT×(输出 Token 数−1)

TPOT 与 ITL 在多数语境下是同义词,差别只在是否把首 Token 那段算进去。NVIDIA GenAI-Perf 的口径是排除:

ITL=e2e_latency−TTFT输出 Token 数−1

E2E 那个公式看着平凡,但它决定优化方向。代入两个场景(设 TTFT = 500 ms、TPOT = 40 ms):

场景计算TTFT 占比
短输出(20 Token)500+40×19=1260 ms约 40% —— 首字慢就先炸
长输出(800 Token)500+40×799≈32460 ms约 1.5% —— 优化 TPOT 才是正事

同一套系统,业务形态不同,优化重心差得极远。「先测清自己的输入输出长度分布,再谈优化」这条纪律,正是这个公式的直接推论。

吞吐指标,与「平均值骗人」 ​

吞吐有两种口径,混用会得出相反结论:

口径含义用来看什么
Token/s(系统)全系统每秒生成的总 Token集群产能与单位成本
Token/s(单用户)单用户感受到的出字速度约等于 1/TPOT
RPS每秒成功完成的请求数并发承载能力

系统吞吐随并发上升而增长,直到 GPU 饱和后趋平甚至回落。吞吐和延迟是一对矛盾:把更多请求塞进一个 Batch 提高系统吞吐,但每个请求的排队与计算时间变长,单用户延迟变差。

平均延迟是最容易骗人的指标 —— 少数慢得离谱的请求会被平均值藏起来。生产环境看尾延迟:P50 / P95 / P99。

Goodput(有效吞吐) 比 Raw QPS 更贴近真实价值:它只统计满足 SLO 的那部分请求的吞吐(例如「TTFT < 1 s 且 TPOT < 50 ms」)。一个系统 QPS 很高但大部分请求超时,Goodput 就很低。

混淆的代价

「Raw QPS 涨了」和「用户体验变好了」是两件事。把 Goodput 作为优化目标,是因为它是唯一一个同时包含延迟约束的吞吐指标。

瓶颈定位:为什么 Decode 打不满算力 ​

这是整个推理优化的第一性原理。

算术强度 ​

GPU 干活要同时做两件事:从显存(HBM)搬数据,和用计算单元算数据。衡量一个任务偏计算还是偏访存:

算术强度=浮点运算次数 (FLOPs)访存字节数 (Bytes)

直白说就是每从显存搬 1 个字节,能顺带做多少次计算。

Roofline 与平衡点 ​

Roofline 模型把它画成一张图:横轴算术强度,纵轴实际可达算力。图上有一条「屋顶线」——左半段是被显存带宽压住的斜坡(Memory Bound 区),右半段是被峰值算力压住的水平线(Compute Bound 区),两段的交点就是这块卡的平衡点。

平衡点 = 峰值算力 ÷ 显存带宽。以 NVIDIA H100 SXM 为例,BF16 稠密算力约 990 TFLOPS(厂商标称的 1979 TFLOPS 是开启结构化稀疏后的数值,稠密约为一半),HBM3 带宽 3.35 TB/s:

平衡点=990×10123.35×1012≈295 FLOP/Byte

在这块卡上,一个任务每搬 1 字节得配上约 295 次浮点运算才刚好把算力和带宽同时喂饱。

不同 GPU 的平衡点都在同一量级(几百 FLOP/Byte)。带宽升级往往比算力升级更值钱,就是因为绝大多数 Decode 负载死死卡在斜坡左侧的带宽墙上。

把 Prefill 和 Decode 放上去 ​

阶段算术强度落在哪
Prefill高 —— 权重搬一次,batch 内所有 Token 共享,却要做海量矩阵乘Compute Bound 区
Decode极低 —— 每步只处理 1 个 Token,却要把整个模型的权重搬一遍Memory Bound 区

Decode 的算术强度可以手算。对一个权重量为 W(字节)的模型,FP16 下每步搬运约 W 字节,对应 W/2 个权重;每个权重参与约一次乘加,计 2 FLOPs,即约 W FLOP。于是单请求 Decode:

算术强度decode≈W FLOPW Byte≈1 FLOP/Byte

对比平衡点 295,低了两个数量级 —— 理论上算力利用率不到 1%。

把 B 个请求拼成一个 Batch,权重仍只搬一次却服务 B 个 Token,算术强度直接放大 B 倍,从而逼近平衡点。这就是 03-推理调度:Continuous Batching 与 Chunked Prefill 里 Batching 威力的来源。

Decode 慢在搬不过来,不在算不过来。 GPU 的计算单元大部分时间在空等权重从显存加载。

这一句话直接推出两个结论:

  1. Batch 能大幅提吞吐而几乎不增延迟 —— 权重只搬一次服务整个 Batch,把闲置算力利用起来。
  2. Weight-only 量化对 Decode 加速明显 —— 瓶颈在搬权重,FP16 压到 INT4 等于要搬的字节数减到 1/4。

两种负载落在同一张 Roofline 的两端:

   实际可达算力
      ▲
  990 ┤                      ┌──────────────────────  算力上限(Compute Bound 区)
TFLOPS│                    ╱   ● Prefill:权重搬一次,batch 内所有 Token 共享
      │                  ╱
      │                ╱
      │              ╱
      │            ╱
      │          ╱  ↑ 斜坡 = 带宽上限(Memory Bound 区)
      │        ╱
      │      ╱
      │    ╱
      │  ╱
      │╱ ● Decode:每步只处理 1 个 Token,却要把整个模型的权重搬一遍
      └──┬───────────────┬───────────────────────▶ 算术强度
         1              295(平衡点 = 990 TFLOPS ÷ 3.35 TB/s)
         ↑
    低了两个数量级 ⇒ 理论上算力利用率不到 1%

  ⇒ Decode 慢在「搬不过来」,不在「算不过来」
  ⇒ 两个直接推论:Batch 能大幅提吞吐而几乎不增延迟;
     Weight-only 量化对 Decode 加速明显(要搬的字节数减到 1/4)

全链路逐环节 ​

一个请求从进到出走这一条链,每个环节的计算特性和潜在瓶颈都不同:

Tokenize → Prefill → Decode Loop → Sampling → Detokenize
                        ↑______|
环节做什么计算特性潜在瓶颈
Tokenize文本切 Token IDCPU长文本或高并发时 CPU 成瓶颈
Prefill并行处理全部 Prompt Token,产出首 Token 与初始 KV CacheGPU,Compute BoundPrompt 长时拉高 TTFT
Decode Loop逐 Token 生成,每步读全量 KV、追加新 KVGPU,Memory Bound显存带宽,决定 TPOT
Sampling按 sampling 参数从分布取下一个 TokenGPU,轻量复杂采样或约束解码略增开销
DetokenizeToken ID 拼回文本,流式返回CPU与主循环争抢 CPU

Tokenize / Detokenize 这两个 CPU 环节最容易被忽略。GPU 优化到位后它们反而成为新瓶颈 —— 这是 vLLM V1 用多进程把它们与 GPU 核心循环重叠起来的原因。约束解码(结构化输出)也会把开销加在 Sampling 上。

一个请求从进到走(标出每个环节跑在谁身上):

   Tokenize ──▶ Prefill ──▶ Decode Loop ──▶ Sampling ──▶ Detokenize
    [CPU]       [GPU]         [GPU]          [GPU]        [CPU]
      │           │             ▲ │            │            │
      │           │             └─┘            │            │
      │           │         逐 Token 循环       │            │
      │           ▼                            │            │
      │      Compute Bound                     │            │
      │      (产出首 Token + 初始 KV Cache)   │            │
      │                                        │            │
      └─ 长文本 / 高并发时 CPU 成瓶颈 ──────────┴─ 与主循环争抢 CPU ─┘

  ⇒ 两个 CPU 环节最容易被忽略:GPU 优化到位后它们反而成为新瓶颈
     (vLLM V1 用多进程把它们与 GPU 核心循环重叠起来)

按症状选技术 ​

优化该先定位症状,再挑手段 —— 把最新的技术都开上解决不了问题:

症状该看哪些技术
TTFT 过高Chunked Prefill、Prefix Cache(命中可直接砍掉 Prefill 计算)、GEMM 优化、调小 max_num_batched_tokens 之外的排队因素
TPOT 过高FlashAttention 类后端、CUDA Graph、Batching、Speculative Decoding、量化(Weight-only)
显存不够 / 并发上不去PagedAttention、KV Cache 量化、量化权重、张量并行
尾延迟失控P/D 解耦、SLO 感知调度、限制批次规模

技术叠加不等于效果叠加。 已知的冲突有两处:投机解码与量化叠加会放大精度风险;投机解码与 Continuous Batching 叠加会抬高调度复杂度(一步内不同请求推进的 Token 数不再统一)。

推荐的优化顺序:

先解决 OOM → 再攻 TTFT → 再攻 TPOT/吞吐 → 最后处理尾延迟

理由是这个顺序里前一步会改变后一步的边界。显存不够时谈 TPOT 没有意义——你连请求都塞不进去。

压测与回归门禁 ​

优化效果必须量化验证,而「只报一次平均耗时」是没有意义的。一套可复现的推理压测至少要固定:

类别必须记录
环境GPU 型号与数量、驱动、CUDA 版本、引擎版本、模型版本与精度
负载输入/输出长度分布(不是平均值)、并发数、到达率
指标TTFT(P50/P95/P99)、TPOT(P50/P95/P99)、吞吐、显存峰值、GPU 利用率
配置全部引擎参数,尤其 max_num_batched_tokens、gpu_memory_utilization、并行度

工具侧:vLLM 自带 vllm bench serve / vllm bench latency / vllm bench throughput;跨引擎对比可用 GenAI-Perf、Triton Perf Analyzer。下钻用 torch.profiler(算子级)、Nsight Systems(全链路)、Nsight Compute(Kernel 级)。

性能回归门禁:把「TPOT P95 退化超过 5% 则阻断合并」这类规则写进 CI,退化时用 git bisect 配合 Nsight 对比定位。

**负载敏感性**:Continuous Batching 让延迟指标对负载高度敏感。低负载时批次小、延迟低;高负载时批次变大、排队变长,P95/P99 会明显上升。**容量规划必须在目标并发下压测,空载数据会给出过于乐观的结论。**

P/D 解耦的动机 ​

前面那张 Prefill/Decode 对照表藏着一个调度问题:两个阶段的瓶颈在相反的两侧(一个算力受限、一个带宽受限),放在同一批里会互相干扰 —— 长 Prefill 会把同批 Decode 的 TPOT 拉出尖刺。

由此推出服务系统的两个设计:

  1. 必须把多个并发 Decode 批在一起,把「一次性读权重」的开销摊到多个请求上。这是 batch size 在 Decode 阶段比在 Prefill 阶段重要得多的原因。
  2. 大部署会把两个阶段分到不同硬件上(disaggregated prefill/decode)—— 因为瓶颈在两边,混在一起调度会互相等待。

第 2 条也就是 PD 解耦架构的出发点。它在源材料里只有大纲(见 00-AI Infra 专栏导览 的待建清单),本库暂不展开。

这条和 18-Context Engineering 里 cold prefill / resume prefill / short decode 的分解是同一件事的两种拆法:那里按成本结构拆,这里按硬件瓶颈拆,指的是同两个阶段。

相关 ​

参考 ​

贡献者 ​

文件历史 ​