状态:草案(2026-10-13),部分实测。 论文和官方文档已于 2026-10-13 直接打开并确认。实测值引用自同站点《托斯 ML/AI Infra 准备》第 14 篇,使用单张 8GB 级 GPU、Qwen3-1.7B、vLLM 0.31.0 进行测量的结果。大模型及多 GPU 下的数值未经实测。 编写规则:以实测数据为基础,未能测量的标注为“未实测”,未能确认的标注为“未确认”。
目录
- 1. 吞吐量归根结底是批处理大小的问题
- 2. PagedAttention:将 KV 划分为区块
- 3. 连续批处理:在每个步骤中填补空缺
- 4. 前缀缓存:相同的前半部分只需计算一次
- 5. 当 KV 空间不足时
- 6. 总结:以及 GPU 内存的高墙
- 参考
1. 吞吐量归根结底是批处理大小的问题
重申第 1 篇的结论。解码时每生成一个 token,都需要从内存中完整读取一次权重,因此如果只运行单个请求,GPU 的计算单元大部分时间处于闲置状态。如果在读取一次权重的同时间并解码多个请求,就可以通过同一次读取生成多个 token。因此, 增大批处理大小可以提高吞吐量。
批处理中的每个请求都需要在 GPU 内存中为其 KV 留出空间。由于 KV 共享了加载权重后剩余的内存,因此, 在相同内存中能将 KV 塞得多么紧凑,决定了批处理大小,进而决定了吞吐量。 服务引擎的大多数 KV 管理技术,本质上都是为了解决这一句话的问题。
| 技术 | 节省的内容 | 通往吞吐量的途径 |
|---|---|---|
| PagedAttention | 因预留和碎片化而浪费的 KV 内存 | 在相同内存中容纳更多请求 → 更大的批处理 |
| 连续批处理 | 提早结束的请求所留下的空位 | 每个步骤都将批处理填满 |
| 分块 Prefill | 长 Prefill 暂停解码的时间 | 在遵守 token 间隔约束的同时扩大批处理 |
| 前缀缓存 | 相同前半部分的 KV 计算与内存 | 跳过 Prefill 计算并共享区块 |
2. PagedAttention:将 KV 划分为区块
旧方式的浪费
在 vLLM 出现之前,服务系统会针对每个请求 按最大输出长度预先分配一块完整的 KV 空间。 由于不知道输出会有多少个 token,因此只能预留充裕的空间。vLLM 论文(Kwon 等人,SOSP 2023)将由此产生的浪费分为三种:
- 预留:预先留出未来可能用到的空间
- 内部碎片:请求结束时未被使用完的空间
- 外部碎片:区块之间残留的、谁也无法使用的空隙
论文测量显示,传统系统 仅将 KV 内存的 20.4%~38.2% 用于实际存储 token。 根据同一篇论文的图表,在 A100 上服务 13B 模型时,大约 65% 的内存用于权重,近 30% 用于 KV。KV 的份额原本就很小,其中却有三分之二以上被白白浪费了。
区块与区块表
PagedAttention 的解决方法类似于操作系统的虚拟内存。它将 KV 划分为 固定大小的区块(默认 16 个 token),并且为每个请求维护一个从“逻辑区块”到“物理区块”的 区块表。区块可以存放在 GPU 内存的任意空闲位置,当一个区块填满 token 时,系统才会为其分配一个新区块。
- 由于不再有连续大块的限制, 外部碎片消失了。
- 由于不再进行预先预留, 也没有了预留浪费。
- 剩下的浪费只有每个请求 最后一个区块的空白处(少于 16 个 token)。
注意力算子会顺着区块表去读取分散在各处的 K 和 V。即使它们不是连续的数组,计算结果也是相同的。
共享区块
采用区块化管理后, 多个请求可以指向同一个区块。 在从单个提示词生成多个回答的并行采样或束搜索(Beam Search)中,提示词部分的 KV 是完全相同的,因此系统会为区块记录引用计数并进行共享。只有当某个请求需要向该区块追加写入时才会进行复制(写时复制,copy-on-write)。论文测量显示,由此节省的 KV 内存如下:[原文表格无具体数据]
| 数据集 | 并行采样 | 束搜索 |
|---|---|---|
| Alpaca | 6.1~9.8% | 37.6~55.2% |
| ShareGPT | 16.2~30.5% | 44.3~66.3% |
结果
论文报告称,在相同的延迟水平下,其吞吐量比 FasterTransformer 和 Orca 高出 2~4 倍 (测试环境涵盖 OPT-13B/66B/175B 与 LLaMA-13B,单张 A100 40GB 至 8 张 A100 80GB)。在 ShareGPT 负载下,系统支持的请求吞吐量最高达到了 FasterTransformer 的 22 倍。由于倍数会随对比对象和负载的不同而产生很大差异,将“2~4 倍”作为代表性数值来理解即可。
3. 连续批处理:在每个步骤中填补空缺
即便节省了内存,如果批处理的组织方式不佳,内存空间依然会闲置。这是因为每个请求的输出长度各不相同。 静态批处理会将若干个请求打包开始执行,并且 在全部结束之前 不接收新请求。如果一个 10 个 token 的回答提前完成了,该位置直到 500 个 token 的回答结束前都会处于闲置状态。
Orca(Yu 等人,OSDI 2022)将调度单位改为了 模型的单步(iteration)粒度。每一步结束时,已完成的请求会立即返回,等待中的请求则从下一步开始加入。这就是目前大多数引擎采用的 连续批处理。论文报告称,在 GPT-3 175B 级模型(16 张 A100 40GB)上,当把每个 token 的延迟中位数控制在 190ms 时,FasterTransformer 每秒处理 0.185 个请求,而 Orca 处理了 6.81 个请求(达到 36.9 倍)。该对比的基准是静态批处理,且这一差距不仅归功于连续批处理,还包含 Orca 的其他设计。
连续批处理与 PagedAttention 互为前提。若要让请求在每个步骤中自由进出,就必须能够随时分配和释放 KV 空间,而大块预留只会让空隙越来越多。基于区块的分配机制使这成为了可能。
新问题:长 Prefill 会暂停解码
连续批处理依然会面临图(b)所示的问题。当新请求进入时,必须在单步内完成该请求的 Prefill,如果提示词很长,该步骤就会耗时很久, 而在此期间,批处理中的其他请求将无法获得下一个 token。 Sarathi-Serve 论文(Agrawal 等人,OSDI 2024)将此称为生成停滞(generation stall),并报告称在 Yi-34B、双卡 A100 的配置下,这种暂停可持续长达数秒。
解决方法如图(c)所示:将 长 Prefill 切割成多个分块,分摊到多个步骤中,并在每一步与解码 token 混排(分块 Prefill)。在同一篇论文的表 4 中(Yi-34B,双卡 A100,单步 token 预算 1,024),仅进行混排时的 P99 token 间隔为 0.68 秒,而同时采用分块与“优先填充解码”策略时为 0.14 秒。在尾部延迟约束下,Mistral-7B(单卡 A100)和 Yi-34B(双卡 A100)所能承载的负载分别达到了 vLLM 的 2.6 倍和 3.7 倍(论文摘要)。
我们的实测。 在 8GB 级 GPU 上使用 Qwen3-1.7B,在单个请求解码 3,000 个 token 的过程中插入 3 个 3,500 token 的输入,并改变单步输入的 token 数量上限(--max-num-batched-tokens),测量了插入区间的 token 间隔。
| 单步输入 token 上限 | 插入区间最大 token 间隔 | 长输入本身的首个 token 耗时 |
|---|---|---|
| 512 | 69ms | 481~494ms |
| 1,024 | 129ms | 468~493ms |
| 2,048(此版本默认值) | 227ms(3 次测量分别为 227、226、226) | 466~480ms |
| 8,192(实效 3,500) | 445ms | 461~474ms |
- 最大 token 间隔几乎与分块大小成正比。这与根据该 GPU 的输入处理速度(约 8,200 token/秒)计算出的单个分块耗时(512 个 token 约 62ms,2,048 个 token 约 250ms)相符。
- 将分块设小,长输入的第一个 token 会延迟 3%~5%,但解码的卡顿会减少。对话、语音等需要均匀输出 token 的服务适合小分块,而批量处理长文档的任务则适合大分块。
分块 Prefill 只能 缩短 间隔,而无法将其消除。只要 Prefill 和解码共享同一张 GPU,它们就会互相干扰。第 4 篇的分离式服务将用另一种方式解决这个问题。
4. 前缀缓存:相同的前半部分只需计算一次
请求之间往往存在许多相同的前半部分。例如附加在所有对话中的系统提示词、针对同一文档多次提问的 RAG,以及原封不动带上上一轮对话的多轮对话。正如我们在第 1 篇中所看到的,前半部分相同,其 KV 也相同。因此, 如果在 GPU 中找到了现成的区块并直接使用,就能跳过相应部分的 Prefill。
vLLM 的自动前缀缓存(设计文档,2026-10-13 查阅)会为每个区块生成一把密钥。
- 密钥 = 哈希(前一个区块的密钥, 本区块的 token ID 列表, 附加信息)
- 附加信息包含 LoRA 适配器、多模态输入的哈希以及区分不同用户的 cache salt。
- 由于包含了前一个区块的密钥,因此单个密钥能够代表 从头开始到该区块为止的整个前半部分。即使有 16 个相同的 token,只要前半部分不同,密钥也会不同。
- 只有填满的区块 才会被缓存。
- 请求结束后,区块不会被立即删除,而是放入“虽然空闲但内容仍保留”的队列中,当需要空间时,按照最久未使用(LRU)原则进行淘汰。已结束请求的区块会 从尾部开始 放入队列,使得被再次使用概率较低的尾部优先被淘汰。
- 自 v0.11 起,哈希函数默认采用 sha256。
我们的实测。 我们向 3,000 个 token 的公共前缀附加了每个请求各不相同的 32 个 token 尾部,并依次发送。
| 设置 | 首个请求首个 token 耗时 | 后续请求首个 token 耗时(中位数) |
|---|---|---|
| 开启前缀缓存(默认) | 394ms | 28ms |
| 关闭 | 395ms | 393ms |
命中时跳过了 3,000 个 token 的 prefill,首 token 速度提升了 14 倍。相反,在前半部分完全不重叠的负载(输入 2,048,并发 32)下,开启它的代价是吞吐量降低约 1.2%,首 token 中位数增加约 8%(推测为哈希计算开销,未作分解)。
测量时的陷阱。 如果重复相同的句子来测量性能,前缀缓存会命中,从而产生 GPU 无法达到的速度。性能测试的输入必须每次都不同,只有在想要观察缓存效果时,才刻意制造公共的前半部分。
5. 当 KV 空间不足时
批处理大小的上限取决于 KV,这一点从数字上也看得出来。在相同的实际测试环境中,KV 空间为 22,048 个 token,如果是输入 512 + 输出 128 = 640 个 token 的请求,大约可以容纳 34 个。
| 并发请求 | 吞吐量 | 首 token 中位数 |
|---|---|---|
| 1 | 66 token/秒 | 73ms |
| 8 | 383 token/秒 | 351ms |
| 32 | 831 token/秒 | 666ms |
| 64 | 787 token/秒 | 5.7秒 |
将并发从 32 增加到 64 时,吞吐量反而下降,首 token 延迟增加了 8.6 倍。空间不足的请求会在队列中等待。如果更加短缺, 已经在运行的请求会被迫交出 KV 并被驱逐(抢占),然后从头开始重新 prefill。 在输入 2,048、并发 32 的情况下发生了 7 次抢占,在输入 1,500、输出 256、并发 64 的情况下发生了 22 次,首 token 中位数分别达到了 12.5 秒和 35.4 秒。由于需要对相同的 KV 进行两次计算,吞吐量不会增加,只有延迟会增加。
每次启动服务器时,KV 空间的大小都有所不同,在 22,048 到 27,504 个 token 之间,而在并发 64 这样的接近极限状态下,这 20% 的差距让首 token 延迟改变了 2.7 倍。在运维中,最好记录服务器日志中的 GPU KV cache size ,并将并发上限调整到接近「由平均请求长度决定的 KV 可容纳请求数」附近。
6. 总结:以及 GPU 内存的高墙
| 技术 | 依据 | 验证的效果 |
|---|---|---|
| PagedAttention | 论文 | 将浪费限制在单个块内,在相同延迟下吞吐量提升 2~4 倍 |
| 连续批处理 | 论文(Orca) | 相比静态批处理,在相同延迟下提升 36.9 倍(175B 级别,包含其他设计) |
| 分块 prefill | 论文(Sarathi-Serve)·实测 | P99 token 间隔 0.68→0.14秒(论文),最大间隔 445→69ms(实测) |
| 前缀缓存 | 实测 | 3,000 token 前缀的首 token 延迟从 394 降至 28ms |
这些技术全都在 GPU 内存内部 运行。如果 GPU 空间不足,前缀缓存会从最早的块开始清除,当被清除的前半部分再次到来时,会从头开始计算。如果有多个引擎实例,它们各自独立缓存,邻近的实例无法使用已经计算好的 KV。第 3 篇将介绍把 KV 放在 GPU 外部(CPU 内存、磁盘、其他服务器)的 LMCache。
Titi 的碎碎念。 如果把乒乓球桌「由一个团队包场一整天」,就会有很多空桌子。如果按 30 分钟为一个单位切分,在前一组刚结束的位置立刻安排下一组,并让同一个球友会共用一张桌子,那么在同一个体育馆里就能让更多的人打球。PagedAttention 和连续批处理做的就是这件事。
参考
- Kwon 等人, Efficient Memory Management for Large Language Model Serving with PagedAttention (SOSP 2023): https://arxiv.org/abs/2309.06180 (第 1、3.1、4.3、6.2、6.3、7.2 节,表 1)
- Yu 等人, Orca: A Distributed Serving System for Transformer-Based Generative Models (OSDI 2022): https://www.usenix.org/conference/osdi22/presentation/yu (第 6.2 节,图 10b)
- Agrawal 等人, Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve (OSDI 2024): https://arxiv.org/abs/2403.02310 (摘要、第 3.2、4 节,表 4)
- vLLM 自动前缀缓存设计: https://docs.vllm.ai/en/latest/design/prefix_caching.html (开发版文档,查阅于 2026-10-13)
- 实测引自:同网站「Toss ML/AI Infra 准备」第 14 篇(8GB 级 GPU、Qwen3-1.7B、vLLM 0.31.0)