从0训练LLM · 现代大模型体系 阅读 + 实验 ≈ 35 分钟 页内实验 ×4
C13 推理:生成为什么慢、怎么变快 第 5 课

服务快不快,该看哪几个数?

服务指标:快有好几种
只看每秒多少 token 会一路把 batch 开满:按屋顶线推演,锚点 batch 从 1 开到 58,整机吞吐涨 35 倍,每个用户两个 token 之间却从 15.5 ms 拖到 25.2 ms;一个 32K 的长 prompt 整段塞进同一步,所有人都要等它 1.9 秒。所以要分开量:首 token 时间、之后每个 token 的间隔、整机吞吐,看分位数,按目标算 goodput;跨模型比速度,还要先把 token 换算成字。
❓

本课一问:前四课每一招都说自己「更快」:连续批处理与分页让整机吞吐翻几倍,投机解码让单个用户每步多拿几个 token,量化让每一步更短。可它们说的是同一个「快」吗?一个推理服务到底该看哪几个数?

这一课先不碰:质量(量化之后「好」还剩多少、分数能信几分,C14;投机解码按构造不改输出分布,第 3 课);多机多卡怎么切(C10);成本与定价。

从已有出发

从已有出发 · 一次请求在服务端走过的路 点任一格看锚点 Qwen3.8-27B 上的真实量
decode 循环:每个 token 走一圈
旁路:

站在用户那一侧看贯穿例句:他发出「小明把苹果给了」,先要等第一个字(「小王」)出现——这段等待包含排队和 prefill;之后每个字一个一个流出来,间隔是 decode 的一步。站在机房那一侧看:这张卡每秒一共吐出多少个 token,决定了一个 token 的成本。这两边的「快」,在第 1 课的屋顶线里是同一个式子的两面。

先自己设计:只看每秒多少 token

最常见的做法是只报一个数:吞吐,每秒生成多少个 token。这个数越大,每个 token 越便宜,于是一路把 batch 开大。按屋顶线推演(锚点、H100、每个请求上下文 4,096):

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案只看吞吐:batch 越大越好
batch B整机吞吐(token/s)每个用户两个 token 的间隔
16515.5 ms
848016.7 ms
271,35719.9 ms
58(80 GB 放得下的上限)2,30425.2 ms

只看第二列,答案永远是「开到最大」。

改进:把「快」拆开量

一个请求的端到端延迟
= + (n − 1) ·
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
整机吞吐 = 同时在解码的请求数 ÷ TPOT。三者都要看分位数(p50、p99),平均值会把少数很慢的请求藏起来。

用第 1 课的屋顶线把 batch 从 1 开到显存上限,每一个 batch 对应一个(TPOT,吞吐)点,连起来就是这张卡的延迟–吞吐前沿。给每个 token 的间隔定一个目标(SLO),能开到的 batch 就被这条线定住了:

推演 · 延迟–吞吐前沿与 TPOT 目标(锚点 + H100 屋顶线上界,不是实测)
推演 · 延迟与吞吐:batch 开多大

🎯

试三件事:① 把 TPOT 目标定在 20 ms:满足目标时最多开到 27 个请求、约 1,357 token/s,而不是 58 个、2,304 token/s;② 把目标放宽到 30 ms:能开满了,可每个用户每秒只拿到约 40 个 token;③ 把上下文拖到 32K:放得下的请求只剩约 10 个,同样的目标下吞吐掉一大截——长上下文的成本在这里。

「满足目标的吞吐」就是 goodput 的思路:DistServe 把它定义为「满足 TTFT 与 TPOT 目标的请求比例达到要求(比如 90%)时,每张卡能承受的最大请求率」,并报告在同样的目标下能多服务 7.4 倍请求,或把目标收紧 12.6 倍 。

长 prompt 来了:谁在等?

第 2 课留了一个问题:新请求进来要先 prefill,一整段长 prompt 塞进同一步,正在解码的人都得等。三种做法:整段塞进去;每步只 prefill 一小块(分块 prefill,Sarathi-Serve);干脆把 prefill 放到另一组卡上(P/D 分离,DistServe;DeepSeek-V3 的线上部署就是 prefill 32 卡、decode 320 卡分开的,第 1 课 Dig)。

推演 · 一个长 prompt 到来时的时间线(锚点 + H100 屋顶线上界)
推演 · 一个长 prompt 到来时,谁在等

🎯

试三件事:① 默认设置下读三条泳道:整段 prefill 让 57 个老用户的一个 token 等了约 1.91 s;分块后老用户每步最长约 36 ms,新请求的 TTFT 从约 1.91 s 变成约 2.11 s;② 把块拖到 8,192:老用户每步变长、新请求 TTFT 变短——块大小就是在这两者之间调;③ 把 prompt 拖到 131,072:整段 prefill 的那一步超过 10 秒,这时分离部署的好处最明显。

Kimi K3 的报告写到了线上的真实情形:同一个集群里,短请求不到 2K token、长请求到 1M,单个请求的成本相差约三个数量级;一阵长请求涌进来会占满算力,让之后到的短请求迟迟排不上,所有人的 TTFT 都变差。它的做法是按请求类别分预算做准入控制;而 1M 上下文的编码场景里,一个请求常有 400K 的前缀、只新增 4K,前缀缓存命中与否,成本差几个数量级 。

token/s 能跨模型比吗?

最后一个坑与机器无关,与 C1 有关:token 的长短在不同 tokenizer 里不一样。同一段 209 字的中文,Qwen3.8 切成 106 个 token,GPT-2 切成 461 个:

真 tokenizer · 同一段文字在两个词表下的 token 数与「字 / 秒」
token/s 能跨模型比吗

我们项目的实测

我们的 3.7M 模型在一台 Apple M5 Max 上、batch 1、贪心生成的真实速度(有 / 无 KV cache 各测 5 次取中位数):

项目实测 · 有 / 无 KV cache 的生成速度

这里只能看比值与趋势:有 cache 只比没 cache 快约 1.17–1.32 倍,远小于大模型上的差距。模型小到每一步的时间主要是 GPU 调度的固定开销,屋顶线在这个规模不成立——实验课里专门讨论。

⚠ 常见误解

⚠

「吞吐高,每个用户就快。」正好相反的方向也常见:batch 开到 58,吞吐 × 35,每个用户的 TPOT 却从 15.5 ms 变成 25.2 ms。

⚠

「平均延迟好,大家就满意。」一次长 prompt 的整段 prefill 能让几十个人卡住 1.9 秒,在平均值里只是一个小小的鼓包。看 p99,按目标算达标比例。

⚠

「token/s 可以跨模型直接比。」同一段中文,GPT-2 的 token 数是 Qwen3.8 的 4.3 倍。先换算成字 / 秒,或者用同一个 tokenizer 数。

⚠

「首 token 慢,是因为模型大。」还取决于 prompt 多长、前面排了多少人、前缀缓存有没有命中、prefill 和 decode 有没有在抢同一组卡。

在演进地图上的位置

推理加速的演进:每一代都被上一代的某个真数字逼出来(点名字去那一课;完整版在章首页)
  1. 每一步把整段重新喂进模型
  2. KV cache
  3. 批处理:一次解码多个请求
  4. 连续批处理(迭代级调度)
  5. PagedAttention:KV 分块按需分配
  6. 投机解码:草稿猜、目标一次验
  7. 让模型自己当草稿:MTP 与专用草稿器
  8. 量化:每个权重少读几位
  9. 按 TTFT / TPOT / goodput 调度:分块 prefill、P/D 分离
  10. 采样:温度 → top-k → top-p → min-p
  11. 少存 KV:GQA / MLA / 线性注意力
第七代按 TTFT / TPOT / goodput 调度:分块 prefill、P/D 分离Sarathi-Serve、DistServe 2024;DeepSeek-V3 部署你在这里
解决了同时看首 token 时间、每 token 间隔与满足 SLO 的请求率;DistServe 同 SLO 下多服务 7.4 倍请求(自报)。
卡在快的账算清了;量化、投机之后「好」还剩多少?分数能信吗?(C14)
数字口径:锚点 Qwen3.8-27B 的字节与时间是 H100 SXM 上的屋顶线上界(不是实测),由 anchor-model.json 现算;调度、投机、量化、采样的数字来自我们项目的 3 个小模型(章内 precomputed.json,脚本 gen_c13_precomputed.py,2026-09-26);vLLM、DeepSeek-V3 的数字出自本地 PDF,Orca、DistServe 出自论文原文,均为提出方自报。

证据三问

问本课的回答
① 它解决什么问题?把「快」拆成 TTFT、TPOT、吞吐三件事,按分位数与目标去量,避免只优化一个数而牺牲另两个
② 数学上为什么?端到端延迟的分解是定义 ;延迟–吞吐的取舍在屋顶线模型里可以算出来
③ 实验上呢?分块 prefill、P/D 分离、goodput 调度的收益都是系统论文的实测:Sarathi-Serve 对 vLLM 2.6–5.6 倍、DistServe 7.4 倍

一句话

📌

快有好几种:首 token 要快、之后每个 token 要稳、整机吞吐要高;它们互相拉扯,要看分位数、按 SLO 算 goodput,token/s 还要先对齐 tokenizer。

🚦

先停在这里:记住 TTFT、TPOT、吞吐三个数和它们的取舍,本章主线就走完了,去实验课把账在我们的模型上对一遍。
再深一层:读 How to Scale Your Model 的 Inference 一章和 DistServe 论文。

下一个缺口

快的账算清了。可量化压掉了几位、服务端改了调度——「好」还剩多少?报出来的分数能信吗?(投机解码按构造不改输出分布,第 3 课) → C14 评测:分数能信吗

先在我们自己的模型上把这一章的账对一遍 → 实验课

你可能会问

这一课还没有收到真实提问;你的提问会记到 05-问答记录/C13-L5.md。