外观
延迟、吞吐与并发
概念定义:推理系统的"不可能三角"
推理系统有三类用户关心的指标:延迟(latency)——单个请求要等多久;吞吐(throughput)——单位时间能处理多少请求;并发(concurrency)——同时能服务多少用户。三者之间存在固有权衡,盲目优化其中一个通常会让另一个变差,这正是批处理与调度与模型服务化要解决的核心矛盾。
理解三个指标的关键认知:
- 延迟和吞吐不是同一件事——一个系统可以"单请求很快但总吞吐很低"(小 batch),也可以"总吞吐很高但单请求慢"(大 batch);
- 并发是连接两者的桥梁——根据 Little's Law,并发 = 吞吐 × 延迟,三者知二推一;
- LLM 推理有两阶段延迟——prefill(计算密集)与 decode(访存密集)的瓶颈完全不同,必须分开看。
没有延迟和吞吐概念的推理优化,要么"刷了个高 QPS 但用户觉得卡",要么"延迟很低但 GPU 利用率不到 20%"——都是工程上的常见翻车。
一、延迟的三个时间点
LLM 推理不像传统分类模型那样"输入→输出"一次性返回,而是流式生成 token。所以延迟要拆成三个时间点:
| 指标 | 全称 | 含义 | 用户感知 |
|---|---|---|---|
| TTFT | Time To First Token | 从请求到达到首个 token 输出 | "响应多快"——交互式场景最敏感 |
| TPOT | Time Per Output Token | 后续每个 token 的平均生成时间 | "打字速度多快" |
| E2E Latency | End-to-End Latency | 从请求到完成全部输出的总时长 | 批处理场景的关心点 |
三者的关系:
text
E2E = TTFT + TPOT × (output_tokens - 1)为什么 TTFT 单独算
TTFT 对应 prefill 阶段——模型要并行处理整个 prompt(数百到数千 token),算力密集,单次开销大。之后每个 token 走 decode 阶段——自回归生成一个 token,访存密集,单次开销小但需要重复 N 次。两阶段的瓶颈特性截然不同(详见显存层次与带宽墙与Roofline 模型),所以延迟必须分开计量。
交互式 vs 批处理:指标取舍
不同场景对三个指标的敏感度不同:
| 场景 | 关键指标 | 可妥协指标 | 典型要求 |
|---|---|---|---|
| 对话/客服 | TTFT | E2E(用户容忍几秒) | 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 为什么重要
- 容量规划:知道单 GPU 能撑 200 并发、平均延迟 2s,就能反推单 GPU 吞吐 100 QPS,从而算出扛 1000 QPS 需要几块 GPU;
- 诊断瓶颈:如果实测吞吐远小于"理论吞吐",而延迟正常 → 系统有同步等待/排队;如果延迟暴涨而吞吐没怎么涨 → 已经到了并发上限(GPU 满了,请求在排队);
- 它对任何稳定系统都成立——不管你的调度策略多复杂,这个关系都跑不掉。
四、延迟-吞吐曲线:硬件的基本盘
任何推理硬件上都有一条延迟-吞吐曲线——它描述了"在某个并发度下,单请求延迟是多少"。典型形状:
text
延迟
│ ╱────── 饱和区
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱──── 线性区
│ ╱
│ ╱
│ ╱──── 低并发区
└────────────────────────────────────────→ 吞吐/并发曲线分三段:
- 低并发区(左下):GPU 没喂饱,吞吐随并发线性增长,延迟几乎不变——增加并发是免费的;
- 线性区(中段):吞吐继续随并发线性增长,但延迟开始上升(排队 + batch 变大);
- 饱和区(右上):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 sizeTPOT 几乎不随 batch 变化的工程意义
这意味着 decode 阶段堆并发几乎免费——把 batch 从 1 加到 32,TPOT 可能只增加 10%,但吞吐能涨 30 倍。这是 vLLM continuous batching 设计的根基:decode 阶段尽量塞请求,把带宽榨干。
六、并发度的设置:理论与实践的差距
理论上的最优并发度是"曲线拐点对应的并发"。实践上要考虑:
- 显存上限:每个并发请求要占 KV cache,估算公式见显存层次与带宽墙——显存不够,再大并发也跑不起来;
- 请求长度分布:长短请求混跑时,长请求会拖住并发槽位(continuous batching 的核心动机就是解决这个);
- 排队延迟:理论延迟只算服务时间,实际还有排队延迟;并发高了排队会变长,反过来又抬高 E2E 延迟;
- 尾部延迟:p50 看着没问题,p99 可能已经爆炸——生产系统必须看分位数,不只看平均。
七、测量方法:用什么工具
"我这个模型延迟多少、吞吐多少"——必须用标准化的 benchmark 工具来回答,不能凭感觉:
| 工具 | 用途 | 特点 |
|---|---|---|
| vLLM benchmark | LLM 服务实测 | 真实 HTTP 调用,给出 TTFT/TPOT/E2E/吞吐全套指标 |
| MLPerf Inference LLM | 跨厂商标准化对比 | 严格规则、固定数据集,可比性强但门槛高 |
| 自建 benchmark | 端到端业务场景 | 必须模拟真实请求分布(prompt 长度、QPS、并发) |
| PyTorch Profiler / Nsight | 算子级瓶颈诊断 | 不是给用户看的指标,是给优化工程师看的 |
benchmark 三大坑
- 请求分布要真实:用平均长度 256 的 prompt 测出来的吞吐,跟你线上平均 1200 的真实流量差 5 倍;
- 预热再测:第一次请求包含模型加载、CUDA kernel 编译,会虚高延迟——必须先跑几十个请求预热,再开始统计;
- 看分位数:只看平均延迟会被偶发长尾掩盖;生产系统看 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)能让吞吐翻倍,代价是少量精度损失——大部分业务场景都划算。
延伸阅读
- 批处理与请求调度——把并发度调到最优的工程方法
- 显存层次与带宽墙——为什么 decode 阶段堆并发几乎免费
- Roofline 模型与算力分析——判定算子是计算还是访存密集
- 模型服务化与编排——把延迟/吞吐指标接入监控闭环
- vLLM 案例研究——continuous batching 的工业实现
- benchmarking 实践——怎么搭一套可信的测量流程
- 常见陷阱与反模式——延迟/吞吐测量的常见翻车
参考资料
- Little. A Proof for the Queuing Formula L = λW(Operations Research, 1961) —— Little's Law 原始论文
- MLPerf Inference: Benchmark Suite for ML Inference —— 行业标准化推理基准
- vLLM: PagedAttention 官方文档与 benchmark 脚本 —— 工业级 LLM 服务与测量
- Gunho et al. Attention Is All You Need(NeurIPS 2017) —— prefill/decode 两阶段特性的源头
- Pope et al. Efficiently Scaling Transformer Inference(MLSys 2023) —— LLM 推理延迟-吞吐曲线的系统分析