Skip to content

延迟、吞吐与并发

本页速览 推理系统的三个核心指标——延迟(TTFT/TPOT/E2E)、吞吐(tokens/s、QPS)、并发数,以及它们之间此消彼长的关系。本文给出 Little's Law、prefill/decode 两阶段延迟特性差异、延迟-吞吐曲线,以及测量方法。

延迟、吞吐与并发

概念定义:推理系统的"不可能三角"

推理系统有三类用户关心的指标:延迟(latency)——单个请求要等多久;吞吐(throughput)——单位时间能处理多少请求;并发(concurrency)——同时能服务多少用户。三者之间存在固有权衡,盲目优化其中一个通常会让另一个变差,这正是批处理与调度模型服务化要解决的核心矛盾。

理解三个指标的关键认知:

  1. 延迟和吞吐不是同一件事——一个系统可以"单请求很快但总吞吐很低"(小 batch),也可以"总吞吐很高但单请求慢"(大 batch);
  2. 并发是连接两者的桥梁——根据 Little's Law,并发 = 吞吐 × 延迟,三者知二推一;
  3. LLM 推理有两阶段延迟——prefill(计算密集)与 decode(访存密集)的瓶颈完全不同,必须分开看。

没有延迟和吞吐概念的推理优化,要么"刷了个高 QPS 但用户觉得卡",要么"延迟很低但 GPU 利用率不到 20%"——都是工程上的常见翻车。

一、延迟的三个时间点

LLM 推理不像传统分类模型那样"输入→输出"一次性返回,而是流式生成 token。所以延迟要拆成三个时间点:

指标全称含义用户感知
TTFTTime To First Token从请求到达到首个 token 输出"响应多快"——交互式场景最敏感
TPOTTime Per Output Token后续每个 token 的平均生成时间"打字速度多快"
E2E LatencyEnd-to-End Latency从请求到完成全部输出的总时长批处理场景的关心点

三者的关系:

text
E2E = TTFT + TPOT × (output_tokens - 1)

为什么 TTFT 单独算

TTFT 对应 prefill 阶段——模型要并行处理整个 prompt(数百到数千 token),算力密集,单次开销大。之后每个 token 走 decode 阶段——自回归生成一个 token,访存密集,单次开销小但需要重复 N 次。两阶段的瓶颈特性截然不同(详见显存层次与带宽墙Roofline 模型),所以延迟必须分开计量。

交互式 vs 批处理:指标取舍

不同场景对三个指标的敏感度不同:

场景关键指标可妥协指标典型要求
对话/客服TTFTE2E(用户容忍几秒)TTFT < 500ms,TPOT < 50ms
代码补全TPOT吞吐TPOT < 30ms(跟得上打字速度)
批处理评分吞吐单请求延迟单条 E2E 几秒可接受
离线批量生成吞吐延迟完全不在乎单条延迟

选指标前先问"用户怎么用"

交互式场景的延迟优化(小 batch、连续批处理)和批处理场景的吞吐优化(大 batch、静态批处理)思路完全相反。给错指标调参,等于白干。详见批处理与调度

二、吞吐的多种度量

吞吐是"单位时间处理的量",但"量"有多种定义,必须分清:

度量公式适用陷阱
QPS请求数 / 秒短请求、固定输出长度长短请求混在一起会失真
tokens/s(输出)输出 token 总数 / 秒LLM 推理最常用只算输出还是算输入+输出?要统一口径
tokens/s(输入+输出)(输入+输出) / 秒反映总算力消耗与"用户感受的快慢"脱节
concurrent users同时在线用户数容量规划用户 ≠ 请求(用户可能空闲)

LLM 场景的事实标准是 output tokens/s——它直接对应"这套硬件每秒能产出多少有用内容"。但即便如此,也要同时报告输入 token 数平均输出长度,否则容易被"长输出刷高吞吐"的把戏骗到。

三、Little's Law:三指标的万能公式

来自排队论的利特尔法则(John Little, 1961)给出了三者最简洁的关系:

text
L = λ × W
即:并发数 = 吞吐 × 平均延迟
  • L:系统中平均同时存在的请求数(并发);
  • λ:到达率(吞吐,请求/秒);
  • W:每个请求在系统中平均停留时间(延迟,秒)。

举例:你的服务每秒处理 100 个请求(吞吐 100 QPS),每个请求平均逗留 2 秒(延迟 2s),那么系统中平均同时有 100 × 2 = 200 个并发请求。知二推一——只要测出任意两个,第三个就可以算。

Little's Law 为什么重要

  1. 容量规划:知道单 GPU 能撑 200 并发、平均延迟 2s,就能反推单 GPU 吞吐 100 QPS,从而算出扛 1000 QPS 需要几块 GPU;
  2. 诊断瓶颈:如果实测吞吐远小于"理论吞吐",而延迟正常 → 系统有同步等待/排队;如果延迟暴涨而吞吐没怎么涨 → 已经到了并发上限(GPU 满了,请求在排队);
  3. 它对任何稳定系统都成立——不管你的调度策略多复杂,这个关系都跑不掉。

四、延迟-吞吐曲线:硬件的基本盘

任何推理硬件上都有一条延迟-吞吐曲线——它描述了"在某个并发度下,单请求延迟是多少"。典型形状:

text
延迟
 │                                          ╱────── 饱和区
 │                                        ╱
 │                                      ╱
 │                                   ╱
 │                                ╱
 │                            ╱
 │                       ╱
 │                  ╱──── 线性区
 │            ╱
 │      ╱
 │ ╱──── 低并发区
 └────────────────────────────────────────→ 吞吐/并发

曲线分三段:

  1. 低并发区(左下):GPU 没喂饱,吞吐随并发线性增长,延迟几乎不变——增加并发是免费的;
  2. 线性区(中段):吞吐继续随并发线性增长,但延迟开始上升(排队 + batch 变大);
  3. 饱和区(右上):GPU 满了,吞吐不再增长,所有新增请求都转化为排队延迟——延迟暴涨。

不要在饱和区运营

很多团队"加了 GPU 还是不快",根因是已经在饱和区——再多的并发都变成延迟。正确做法是找到曲线拐点(吞吐接近峰值、延迟开始陡升的并发度),把生产负载维持在那个并发度附近,而不是无脑加并发。

为什么 LLM 的曲线更陡

传统 DNN 推理的曲线比较平缓——batch 从 1 到 32,延迟可能只涨 30%。但 LLM 推理的曲线陡得多,原因有两层:

  • prefill 阶段:prompt 越长,单步计算量越大;多个长 prompt 合批会撑爆显存和算力;
  • KV cache 显存:每个并发请求要持有自己的 KV cache(见显存层次与带宽墙),并发越多显存压力越大,触发 swapping 后延迟暴涨数倍。

所以 LLM 推理系统的调度器(如 vLLM)要做的核心事情,就是在显存预算内最大化并发,又不至于触发 swap。

五、prefill 与 decode:两阶段延迟特性

LLM 推理的延迟特性之所以特殊,是因为它有两个截然不同的阶段:

阶段计算/访存特性主要瓶颈延迟随 batch 的变化
Prefill(处理 prompt)计算密集(大矩阵 GEMM)GPU 算力(Tensor Core)几乎线性增长(算力被分摊)
Decode(生成 token)访存密集(每生成 1 token 都要 load 全部权重)HBM 带宽几乎不变(带宽够用,再多 batch 也只是多读 KV cache)

这是为什么 LLM 推理系统经常做 prefill/decode 分离调度——prefill 单独成批,把 GPU 算力喂饱;decode 大批合并,把带宽利用满。详见批处理与调度

text
延迟
 │     prefill(计算密集,随 batch 线性增长)
 │      ╱
 │     ╱
 │    ╱   decode(访存密集,几乎不随 batch 增长)
 │   ╱  ──────────────────────────
 │  ╱
 │ ╱
 └────────────────────────────→ batch size

TPOT 几乎不随 batch 变化的工程意义

这意味着 decode 阶段堆并发几乎免费——把 batch 从 1 加到 32,TPOT 可能只增加 10%,但吞吐能涨 30 倍。这是 vLLM continuous batching 设计的根基:decode 阶段尽量塞请求,把带宽榨干。

六、并发度的设置:理论与实践的差距

理论上的最优并发度是"曲线拐点对应的并发"。实践上要考虑:

  1. 显存上限:每个并发请求要占 KV cache,估算公式见显存层次与带宽墙——显存不够,再大并发也跑不起来;
  2. 请求长度分布:长短请求混跑时,长请求会拖住并发槽位(continuous batching 的核心动机就是解决这个);
  3. 排队延迟:理论延迟只算服务时间,实际还有排队延迟;并发高了排队会变长,反过来又抬高 E2E 延迟;
  4. 尾部延迟:p50 看着没问题,p99 可能已经爆炸——生产系统必须看分位数,不只看平均。

七、测量方法:用什么工具

"我这个模型延迟多少、吞吐多少"——必须用标准化的 benchmark 工具来回答,不能凭感觉

工具用途特点
vLLM benchmarkLLM 服务实测真实 HTTP 调用,给出 TTFT/TPOT/E2E/吞吐全套指标
MLPerf Inference LLM跨厂商标准化对比严格规则、固定数据集,可比性强但门槛高
自建 benchmark端到端业务场景必须模拟真实请求分布(prompt 长度、QPS、并发)
PyTorch Profiler / Nsight算子级瓶颈诊断不是给用户看的指标,是给优化工程师看的

benchmark 三大坑

  1. 请求分布要真实:用平均长度 256 的 prompt 测出来的吞吐,跟你线上平均 1200 的真实流量差 5 倍;
  2. 预热再测:第一次请求包含模型加载、CUDA kernel 编译,会虚高延迟——必须先跑几十个请求预热,再开始统计;
  3. 看分位数:只看平均延迟会被偶发长尾掩盖;生产系统看 p95/p99,关键业务看 p99.9。

更多测量实践见benchmarking 实践常见陷阱与反模式

八、权衡与取舍

  • 延迟 vs 吞吐:交互式场景优先延迟(小 batch + continuous batching);批处理场景优先吞吐(大 batch + 静态/动态 batching);
  • 并发 vs 显存:并发越高吞吐越好,但 KV cache 显存线性增长——要么加显存(H200 141GB),要么用量化压权重腾出空间,要么用 PagedAttention(vLLM)减少碎片;
  • TTFT vs TPOT:很多优化(如投机采样)会牺牲一点 TTFT 换 TPOT 大幅下降;交互式场景对 TTFT 敏感,要权衡;
  • 精度 vs 速度权重量化(W4A16)能让吞吐翻倍,代价是少量精度损失——大部分业务场景都划算。

延伸阅读

参考资料