TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

对连续批处理、chunked prefill、前缀缓存进行重复测量

状态:草稿(2026-10-27),实际测量。 于 2026-10-27 使用单张 8GB 级笔记本 GPU(Ada 架构)、vLLM 0.31.0、Qwen3-Reranker-0.6B 进行测量。相同三大功能的原理和 1.7B 模型的首次测量记录在《LLM KV 缓存剖析》第 2 篇和《Toss ML/AI Infra 准备》第 14 篇中,因此本文仅探讨前几篇文章未涉及的内容(重复测量、关闭功能的基线、关闭 chunk、cache_salt)。 2026-10-27 新增。 在第 6-1 节中补充了同一天使用 vLLM 0.32.0 再次运行相同测试的结果。0.31.0 的数值保持不变。 编写规则:以实际测量数据为基础,无法测量的内容标记为“未测量”,未确认的内容标记为“未确认”。

目录

1. 为什么要重新测量

PagedAttention、连续批处理和前缀缓存的原理在“服务引擎内部的 KV 缓存”中讨论过,而使用 1.7B 模型首次测得的数值在“使用小模型直接重新测量 vLLM”中进行了探讨。当时的测量存在三个漏洞。

  1. 每个条件只测量了一次,因此不知道其离散程度。
  2. 无法关闭连续批处理,因此未能展示“如果没有它会怎样”。
  3. 未观察完全关闭 chunk 的情况,以及多用户共享缓存时的隔离性(cache_salt)。

这一次,所有条件都重复了三到四次,第一组作为预热被单独剔除,未计入中位数。关闭功能的服务器在同一天、同一个 Pod 中紧接着启动。

2. 环境与方法

3. 连续批处理:关闭后吞吐量降至 1/10

没有直接关闭连续批处理本身的选项。相反,如果使用 --max-num-seqs 1 让每个步骤只运行一个请求,它就变成了“逐个处理请求的服务器”。我们以并发度 c 发送了 2c 个输入 256 token、输出 128 token 的请求。

服务器 并发请求数 吞吐量 (tok/s) TTFT 中位数 ITL 中位数 ITL p99
单次处理(--max-num-seqs 1) 1 174.4 18.9ms 5.6ms 6.2ms
单次处理 16 174.6 11,008ms 5.6ms 6.5ms
默认 1 174.2 18.8ms 5.6ms 6.7ms
默认 4 593.8 50.7ms 6.4ms 7.5ms
默认 16 1,683.1 131.0ms 8.2ms 9.4ms
默认 64 2,899.3 272.2ms 16.6ms 90.6ms

三组的吞吐量在 0.3% 的误差范围内保持一致(并发 16 默认值:1,681.3、1,683.1、1,685.4)。

4. chunked prefill:解码暂停的时间与 chunk 大小成正比

在运行一个解码请求(900 个 token)的同时,连续插入五次约 3,700 个 token 的输入,并测量了插入区间内解码 token 间隔的最大值。

服务器 单步输入 token 上限 插入区间的最大 ITL(5 次) 其余 ITL 中位数 插入输入的 TTFT 中位数
--max-num-batched-tokens 512 512 35.6ms(35.5, 35.5, 35.6, 35.6, 35.6) 5.7ms 232.0ms
默认 2,048 109.8ms(108.3, 108.6, 109.1, 109.2, 109.8) 5.7ms 216.2ms
--no-enable-chunked-prefill --max-num-batched-tokens 4096 4,096(无 chunk) 197.6ms(191.8, 195.7, 197.1, 197.4, 197.6) 5.8ms 214.1ms

在此 GPU 上,prefill 速度约为在 174.7ms 内处理 3,200 个 token,即每秒约 18,000 个 token。计算表明,一个 2,048 token 的 chunk 耗时约 111ms,整个 3,700 token 耗时约 200ms,这与表中的最大 ITL 相吻合。五次测量值汇聚在 1ms 左右,绝非巧合。

其形态与使用 1.7B 模型测得的旧值(512 token 时 69ms,2,048 token 时 227ms)相同,但大小随模型计算量的减少而减小。

5. 前缀缓存:重复请求仍为 18.9ms

我们在 3,000 个单词(约 3,200 个 token)的公共前缀后附加了每请求各不相同的 32 个单词进行发送。更换前缀并运行了五组测试。

服务器 冷启动首个请求 TTFT(5 个前缀) 热请求 TTFT(20 次)
默认(开启前缀缓存) 中位数 174.7ms(174.6~175.3) 中位数 18.9ms(最小 18.0,最大 19.9)
--no-enable-prefix-caching 中位数 173.9ms,仅服务器首个请求为 236.0ms 中位数 173.8ms(最小 173.0,最大 178.2)

由于热请求只需计算 3,200 个 token 中的最后一个块和尾部,因此速度提高了 9.2 倍。关闭前缀缓存的服务器的第一个请求为 236ms,比其他冷请求慢 60ms,这就是服务器启动后的预热开销。这就是为什么必须在测量中将第一个值单独剔除的原因。

还要记住缓存是以块(block)为单位的。由于 vLLM 只对充满的块(默认 16 个 token)进行哈希和存储,如果公共部分在块边界处断开,最后一片将重新计算。从 0.11 版本开始,哈希默认使用 sha256,并且可以通过 --prefix-caching-hash-algo 进行更改。

6. cache_salt:即使前缀相同,租户之间也不共享

前缀缓存存在一个安全问题。如果发送与其他用户发送的文档相同的前端部分,且首个 token 返回较快,就可以推断出“有人已经看过这个文档了”。这是一种通过时间差窃取信息的定时攻击。vLLM 允许在请求中附加 cache_salt ,将该值混合到第一个块的哈希中,从而确保只有相同 salt 的请求才能共享缓存。

--enable-prompt-tokens-details 通过让响应返回缓存的 token 数量,并发送相同 3,000 单词前缀但更换 salt 共五组来验证这一点。

一组包含五个具有相同前缀的请求。其中使用 salt a 发送两次,salt b 发送两次,不带 salt 发送一次。

顺序 cache_salt 缓存的 token(响应中的 cached_tokens) TTFT 中位数(第 2~5 组)
1 tenant-a 0 173.9ms
2 tenant-a 3,184 或 3,200 17.9ms
3 tenant-b 0 173.7ms
4 tenant-b 3,184 或 3,200 18.3ms
5 无 0 173.9ms

cached_tokens 必须将服务器以 --enable-prompt-tokens-details 启动才会显示在响应中。如果关闭,该字段将为空,只能通过时间来推测缓存命中。整个服务器的命中情况可以通过 /metrics 的 vllm:prefix_cache_hits_total 和 vllm:prefix_cache_queries_total 来查看。

6-1. 2026-10-27 重新测量:0.32.0 中是否相同

使用相同的 GPU、相同的重排序模型以及相同的客户端,在 vLLM 0.32.0 官方容器中重新进行了测量(“2026-10-27 0.31.0 测量 → 0.32.0 重新测量”)。

项目 0.31.0 0.32.0
吞吐量,并发 1 174.2 tok/s 174.2 tok/s
吞吐量,并发 16(默认) 1,683.1 tok/s 1,683.6 tok/s
吞吐量,并发 16(一次一个) 174.6 tok/s 174.3 tok/s
吞吐量,并发 64 2,899.3 tok/s 2,899.7 tok/s
3,700 个 token 插入时的解码最大停顿(默认分块) 109.8ms 108.2ms
3,200 个 token 前缀的首个 token(冷 → 热) 174.7 → 18.9ms 175.2 → 18.9ms

所有数值在 1.5% 的误差范围内保持一致。0.32.0 版本发布说明中的重大更改(扩展 Model Runner V2、移除级联注意力、 max_tokens 不在上下文中提前剔除等)在这个小模型的默认负载下没有改变数据。对话用的 Qwen3-0.6B(相同结构)在并发 1、16、64 下也保持一致,分别为 174.1、1,678.0、2,898.7 tok/s。

7. 运维层面的经验总结

8. 未验证的事项

参考

登录后即可点赞

评论

还没有评论。

登录后即可发表评论