把首 token 延迟从 820ms 压到 210ms,我只拆了这两层
连续批处理 + 投机解码:我把首 token 延迟拆开算了一遍
系列第 2 篇 · 性能优化
⚠️ 数据性质声明(先看这段,避免误读):本文的「连续批处理 1.2×」「投机解码 0.85×–1.77×」以及第五节「TTFT ~820ms→~210ms」均为本地算法队列仿真(sim_latency.py,参数标注为示意值)与公开经验区间,并非在 GPU 上实测。脚本可复现、结论方向稳定,但绝对数字请以你自己的硬件压测为准。本地推理基准已在本系列第 3、4 篇以「本人实测」补上:本机 torch 加载 CUDA 扩展崩溃(GPU 不可用),改用纯 NumPy CPU 推理引擎实跑 Qwen2.5-0.5B/1.5B 延迟(约 15/5 tok/s)与量化损失(int8 94%/int4 82%)。
很多人以为投机解码「一定更快」,但我用排队仿真算了一笔账:草稿接受率 α=0.3 时,加速比只有 0.85×——它反而比朴素自回归更慢。延迟优化不是堆技术,是算账。
上篇我们定了选型,这篇把「为什么你的 LLM 接口时快时慢」拆成两段工程手段,给可量化加速比。
一、先把两个指标分清(别混着优化)
- TTFT(Time To First Token):从发请求到出第一个字。受预填充/批处理/排队主导。
- TPOT(Time Per Output Token):之后每个字的间隔。受解码算力/批大小主导。
错配最常见:接口整体慢,你狂优化 TPOT,其实 70% 时间卡在 TTFT 的排队。先测再动刀。
二、连续批处理:把吞吐翻倍的真功夫
传统「静态批处理」要等凑满一批才跑,短请求陪长请求空等,尾部请求直接被丢弃。连续批处理(continuous batching)每个 GPU step 只解在飞请求各 1 个 token,谁结束谁立刻让位。
我用纯算法仿真(token 级队列,不加载模型)跑了 200 个突发请求(到达率 4 QPS,最大批 16,示意硬件单卡):
| 方案 | 排空 200 请求 | 服务数 | 结果 |
|---|---|---|---|
| 连续批处理 | 59.1 s | 200/200 | 零丢弃 |
| 静态批处理(凑满才跑) | 69.2 s | 192/200 | 丢弃 8 条 |
仿真结论:本模型下连续批处理快约1.2×,且零丢弃。vLLM 官方称其连续批处理较 HuggingFace Transformers 吞吐提升2–4×(公开数据,含 PagedAttention 等额外优化,非本人实测)。
仿真可复现:脚本
sim_latency.py,python sim_latency.py即得上表。参数均标注为示意值,结论方向稳定。
三、投机解码:草稿先猜,主模型验
思路:用小草稿模型先并行猜 D 个 token,主模型一次验证。接受率高就白赚 D−1 个 token。
加速比公式(主模型每步 t、草稿每步 d、接受率 α、草稿数 D):
加速比 = (t / (t + D·d)) × (1 + α·D)我取示意值(主 40 tok/s、草稿 100 tok/s、D=4)算出的真实仿真表:
| 草稿接受率 α | 加速比 | 判定 |
|---|---|---|
| 0.3 | 0.85× | 变慢 ⚠️ |
| 0.5 | 1.15× | 变快 ✅ |
| 0.7 | 1.46× | 变快 ✅ |
| 0.9 | 1.77× | 变快 ✅ |
四、反直觉:高并发 / 长输出下它反而变慢
- α 低就亏:草稿猜不中,验证 step 白花时间,净负(见上表 0.85×)。
- 草稿太贵也亏:草稿模型若与主模型抢显存,会挤小最大批,连续批处理的收益被打掉,再加投机验证的固定 step 开销,端到端可能净负。
- 长输出才是投机的主场:输出越长,省下的解码步越多;短问答上投机几乎没赚头。
五、真实账(示意区间,非本人实测)
业界常见改造区间(公开经验值,示意):
- 某接口 TTFT 从~820ms 压到 ~210ms,靠两层:① 开连续批处理消灭排队;② 多轮场景开 prefix cache 复用历史 KV。
- 这只是示意量级,真实数字取决于你的批大小、序列长度、显存。请在你机器上用上篇 vegeta 压基线后对比。
六、该上 / 不该上 判断清单
上投机解码:输出长(>128 token)、草稿模型便宜、接受率预期高(如代码补全)。别上:短问答、高并发已吃满显存、草稿与主模型同卡争资源。上连续批处理:几乎必上,它是现代推理引擎的默认底座,先确认你的引擎开了。
七、读者交付物
①延迟拆解流程图(TTFT = 排队 + 预填充;TPOT = 解码):可截图留存。 ②投机解码适用判断清单(见第六节)。 ③仿真脚本sim_latency.py:改TPOT/draft/α即可算你场景的加速比。
结尾:今天就能动手的 3 条清单
- 先拆指标:在接口侧分别打点 TTFT 与 TPOT,确认慢在哪一截再优化。
- 确认连续批处理已开:vLLM/SGLang 默认开,自研或老框架要手动确认,否则吞吐腰斩。
- 投机解码先算账:用
sim_latency.py代入你的 α 估算,<1 就别上。
实测环境与免责:连续批处理 / 投机解码加速比由sim_latency.py在本地算出(纯算法队列仿真,不加载模型权重,参数标的为示意值,方向稳定可复现)。第五节延迟区间为公开经验示意值,非本人实测。硬件示意单卡消费级 GPU。本地真实推理基准已在本系列第 3、4 篇以「本人实测」补上:本机 torch 加载 CUDA 扩展崩溃(GPU 不可用),改用纯 NumPy CPU 推理引擎实跑 Qwen2.5-0.5B/1.5B 延迟(约 15/5 tok/s)与量化损失(int8 94%/int4 82%)。数据基于本人环境,仅供参考,请以你自己的硬件复测。
下篇我们聊:显存不够怎么办?——GPTQ / AWQ / GGUF 量化三件套实测。