状态:草稿(2026-10-07),实测文章。 所有数字均为笔者在下述环境中亲自测得的值,计算得出的标记为「计算」,未能测得的标记为「未实测」。由于这是一个环境、一个模型、一次测试批次的组合,比起绝对值,更希望大家能带走 形态与原理。编写规则:以实测数据为基础,未能测得的标记为「未实测」。
目录
- 1. 想了解什么
- 2. 环境与方法
- 3. KV 缓存容量符合公式
- 4. 增加并发请求时:吞吐量趋于迟钝,随后在 KV 容量处队列爆炸
- 5. KV 8-bit:容量增加时,长输入的延迟会减少
- 6. 前缀缓存:存在共同前缀时,首字延迟快 14 倍
- 7. 分块预填充:长输入暂停解码的时间由块大小决定
- 8. 关闭 CUDA 图形(--enforce-eager)
- 9. 首次启动服务器所需的时间
- 10. 运维中可以借鉴的经验
- 11. 面试中可能出现的问题
- 外部依据:通过论文和官方资料确认的内容
- 未能确认的内容
- 确认的版本与日期
1. 想了解什么
vLLM 的文档有许多选项,而在面试和运维中,常会被问到「开启这个选项会带来什么变化,变化有多大」。第 4 篇通过文档整理了原理(KV 缓存公式、前缀缓存、调度器),而本文则用数字回答同样的问题。
- 当并发请求增加时,吞吐量和延迟会在哪里发生转折。
- KV 缓存容量是否与公式计算值相符,该容量与发生转折的点是如何关联的。
- 前缀缓存、KV 8-bit、CUDA 图形、块大小以及并发序列上限分别会改变什么。
2. 环境与方法
| 项目 | 值 |
|---|---|
| GPU | 单张 8GB 级笔记本 GPU(内存约 7.6GiB),无其他任务 |
| 服务器 | vLLM 0.31.0 官方镜像,PyTorch 2.13(CUDA 13) |
| 模型 | Qwen3-1.7B(bf16,服务器加载的权重 3.22GiB),模型设置为 28 层,KV 头 8,head_dim 128 |
| 公共选项 | --max-model-len 4096, --gpu-memory-utilization 0.85, --max-num-seqs 64(仅对比测试为 16) |
| 客户端 | 与服务器在同一容器内的异步客户端,通过流式传输记录每个 token 的时间戳,以计算首字延迟(TTFT)和 token 间隔(ITL) |
| 输入 | 每个请求采用不同的随机词序列(避免前缀缓存偶然命中),输出 128 个 token(ignore_eos),温度 0 |
| 重复 | 对于并发度 c,请求数为 3c(长输入测试为 2c),前两个请求作为预热丢弃 |
每次测试都重新启动了服务器(初次启动和重新启动时的 KV 缓存容量不同,详见第 3 节)。表中的值从单次批次中读取,重复波动单独进行了测量。 在同一服务器上重复相同测试 5 次时(并发 32,输入 512,输出 128),吞吐量为 831、833、831、832、831 tok/s,TTFT 中位数为 666~670ms,TTFT p95 为 1,606~1,608ms,ITL p99 为 223~224ms,误差在 0.5% 以内。即便将服务器重新启动 3 次,并发 1 仍为 66 tok/s,并发 32 仍为 831~832 tok/s。前缀缓存(3 次)以及插队情况(3 次,最大 ITL 分别为 227、226、226ms)也保持一致。这意味着在此环境(单张 GPU,无其他任务)中,测试本身的波动较小,若更换至其他设备或存在其他邻居进程的环境,则可能会有所不同。
3. KV 缓存容量符合公式
每个 token 的 KV 大小为 2 × 층 수 × KV 헤드 수 × head_dim × 자료형 바이트。该模型拥有 28 层、8 个 KV 头、head_dim 为 128,且 KV 采用 bf16(2 字节),因此计算结果如下(计算)。
2 × 28 × 8 × 128 × 2 = 114,688 바이트/토큰 (112KiB)
| 设置 | 服务器日志中的 KV 缓存内存 | 服务器日志中的 token 数量 | 每个 token 的字节数(计算) | 公式计算值 |
|---|---|---|---|---|
| bf16 KV(默认) | 2.36GiB | 22,048 | 约 114,900 | 114,688 |
fp8 KV(--kv-cache-dtype fp8) |
2.24GiB | 41,920 | 约 57,400 | 57,344 |
- 公式与日志相符。 将日志中的容量除以 token 数量所得的值与公式在 0.2~0.3% 的误差范围内吻合。由于 KV 缓存容量等于「剩余内存 ÷ 每个 token 的字节数」,因此无论哪个模型,在通过设置中的数字启动服务器之前都可以进行估算。
- KV 8-bit 在相同内存中可多容纳 1.9 倍的 token。 从 22,048 增加到了 41,920 个 token,每个 token 的字节数正好减半。
- 初次启动和重新启动时的容量有所不同。 尽管选项相同,但在 Pod 中首次启动的服务器容量为 22,048 个 token,而在同一 Pod 中重新启动的三台服务器均为 26,560 个 token(多出约 20%)。初次启动时,用于编译和性能分析的内存似乎会从 KV 缓存份额中扣除(推测,未进行具体拆解),但在比较容量时,不应仅对比内存,而应使用 每个 token 的字节数进行对比,并记录服务器的启动历史。本文中的表格数据均读取自初次启动的服务器(22,048 个 token)。
- 单个请求消耗的 token 是输入与输出的总和。如果输入为 512,输出为 128,则是 640 个 token,22,048 个 token 的缓存大约可同时容纳 34 个请求(计算)。下方发生转折的点正是由此数字得出的。
4. 增加并发请求时:吞吐量趋于迟钝,随后在 KV 容量处队列爆炸
输入 512,输出 128, --max-num-seqs 64:
| 并发请求 | 吞吐量(tok/s) | TTFT 中位数 | TTFT p95 | ITL 中位数 | ITL p99 |
|---|---|---|---|---|---|
| 1 | 66 | 73ms | 73ms | 15ms | 16ms |
| 8 | 383 | 351ms | 474ms | 17ms | 19ms |
| 32 | 831 | 666ms | 1.6s | 24ms | 223ms |
| 64 | 787 | 5.7s | 6.7s | 25ms | 228ms |
- 并发达到 8 之前基本呈线性增长(从 1 到 8 增长 5.8 倍,效率 73%),之后则开始趋于迟钝(从 8 到 32,并发度为 4 倍,吞吐量为 2.17 倍,从 383 增至 831)。从 1 到 32 整体来看为 12.6 倍(效率 39%)。由于分摊读取权重成本的效果(第 4 篇,第 21 篇)在短文本上下文中在并发 16 以内几乎呈 14~15 倍的线性关系,因此该表中出现迟钝很可能是因为累积的输入 512 导致 KV 读取和输入处理增加(推测,未进行拆解)。外部研究也指出,在较大批次下,由于注意力机制的内存读取,吞吐量可能会在达到 KV 容量上限的远 A 点就趋于平缓(详见下文「外部依据」)。
- 从 32 增加到 64 时,吞吐量反而下降了,而 TTFT 则飙升了 8.6 倍(从 666ms 增至 5.7s)(该行数据来自 KV 为 22,048 个 token 的初次启动服务器,重新启动服务器测得的重复值见本列表末尾)。 若单个请求为 640 个 token,则 KV 缓存最多只能容纳约 34 个请求,因此如果发送 64 个请求,将近一半的请求必须在队列中等待,直到有空位释放。 一旦容量(KV)达到极限,TTFT 就会发生爆炸。 既然吞吐量已经饱和,再提高并发度只会增加延迟。
- 在三个服务器上重复相同的负载测试后发现,并发 64 的这一数值因服务器而异。 吞吐量分别为 792、842、843 tok/s,TTFT 中位数为 5.7、2.1、2.2 秒。只有首次启动的服务器较慢,且该服务器的 KV 为 22,048 个 token(重新启动的服务器为 26,560 个 token)。尽管选项相同,但每次启动服务器时,KV 的空间大小各不相同,分别为 22,048、22,336、26,560、27,504 个 token(六个不同选项服务器之间的范围)。在并发 64 个请求且每个请求消耗 640 个 token 几乎填满缓存的这个临界点上,容量增加 20% 使 TTFT 缩短了 2.7 倍。上述“队列爆炸”意味着这是临界点附近的现象,而极限究竟在哪里可能会随着每次启动服务器而改变。并发 8 和 32 的数值在三个服务器上保持在 1% 的误差范围内(TTFT 分别为 349、669ms,吞吐量分别为 384、831 tok/s)。
- 每次启动服务器时 KV 空间大小发生变化这一现象在较大模型中更为显著。 采用相同选项启动的 Qwen3-4B-FP8 服务器,其 KV 容量分别为 8,880、13,232、14,448 个 token(启用 LoRA 的服务器为 6,320、10,896 个 token),在并发 32 时,TTFT 中位数在 KV 为 8,880 个 token 时为 7.2 秒,在 13,232 个 token 时为 3.4 秒。首次启动的服务器最小,这一点与 1.7B 相同。必须将服务器日志中的
GPU KV cache size与测试记录一同保留,才能对数值进行解析。 - ITL 中位数从 15 缓慢增加到 25ms,而 p99 从并发 32 开始飙升至约 220ms。 处理新请求输入的阶段穿插在解码之间,导致单个 token 的延迟显著拉长(第 7 节)。
若将输入增加到 2,048 个 token,则单个请求为 2,176 个 token,缓存可容纳的请求数约为 10 个(计算)。
| 并发请求 | 吞吐量(tok/s) | TTFT 中位数 | TTFT p95 |
|---|---|---|---|
| 4 | 151 | 0.73s | 1.0s |
| 16 | 207 | 4.0s | 6.7s |
| 32 | 213 | 12.5s | 16.7s |
当并发数从 4 个增加到 16 个(4 倍)时,吞吐量仅增加了 37%,而 TTFT 变为了 5.4 倍。在长输入情况下, 吞吐量开始拐弯的并发度会显著降低。 这就是为什么在运维中,并发限制不应设为「GPU 所能承受的极限」,而应根据「平均输入长度下 KV 所能容纳的请求数」来设定的原因。
当 KV 不足时:是仅排队,还是被驱逐
通过服务器指标(vllm:num_preemptions_total)读取了抢占次数。在重复 5 次输入 512、并发 32 的过程中(KV 可容纳的请求数在 34 个以内),抢占次数为 0次。当发送输入 2,048、并发 32(64 个请求)时为 7次,接着发送输入 1,500、输出 256、并发 64(128 个请求)时,累计达到 29次(在此负载下为 22 次)。此时的 TTFT 中位数分别达到了 12.5 秒和 35.4 秒。当 KV 不足时,请求不仅会在队列中等待, 正在执行的请求还会交出 KV 被驱逐,然后从头开始重新处理(根据文档标准,抢占是指重新计算被回退的请求,因此会额外消耗一次输入处理)。抢占次数不为零的区间是吞吐量不增且延迟急剧上升的区间,因此非常适合用作报警指标。
并发序列上限(--max-num-seqs)只会改变队列的位置
并发 64,输入 512:
| 设置 | 吞吐量 | TTFT 中位数 |
|---|---|---|
--max-num-seqs 64 |
787 tok/s | 5.7s(初次启动服务器,上述重复值为 842 tok/s,2.1~2.2s) |
--max-num-seqs 16 |
608 tok/s | 10.3s |
当上限降低到 16 时,吞吐量下降了 23%,TTFT 变为了 1.8 倍。如果上限低于容量(约 34 个),即使容量有剩余也会产生队列,这是得不偿失的;而即使高于容量,如果 KV 不足也没有好处。 上限是一个契合 KV 容量所能容纳的请求数左右的数值。
5. KV 8-bit:容量增加时,长输入的延迟会减少
| 测试 | bf16 KV | fp8 KV |
|---|---|---|
| 并发 1,输入 512(吞吐量,ITL) | 66 tok/s, 15ms | 66 tok/s, 15ms |
| 并发 16,输入 2048(吞吐量) | 207 tok/s | 303 tok/s (+46%) |
| 并发 16,输入 2048(TTFT 中位数) | 4.0s | 0.94s (−76%) |
| 并发 32,输入 2048(吞吐量) | 213 tok/s | 299 tok/s (+40%) |
| 并发 32,输入 2048(TTFT 中位数) | 12.5s | 7.0s (−44%) |
- 在短上下文中,速度是相同的。 在并发 1 下,KV 读取相比权重读取较小,因此 8-bit 没有带来收益。
- 收益来自于容量。 得益于第 3 节中 1.9 倍的容量,长输入下的队列减少了,TTFT 大幅缩短。也就是说,KV 8-bit 并非「计算得更快」,而是「同时容纳更多」。
- 准确度仅通过困惑度(Perplexity)进行测量,结果与直觉相反。 我们将 Wikitext-2(测试集划分,约 30 万个 Token 中前 1,024 个 Token 的 40 个区间)发送给服务器,通过提示词 Token 的对数概率求出困惑度(越低越好)。对同一种配置测量两次时,数值精确到小数点后第四位(确定性),因此未单独设置误差范围。
| KV 精度 | 注意力后端 | 困惑度 | 相较于 bf16 |
|---|---|---|---|
| bf16 | 自动选择 (FLASH_ATTN) | 17.7775 | 基准 |
| bf16 | FLASH_ATTN(固定,重复 2 次) | 17.7796, 17.7796 | +0.01% |
| bf16 | FLASHINFER | 17.7750 | -0.01% |
| bf16 | TRITON_ATTN | 17.7732 | -0.02% |
| fp8 | FLASHINFER | 17.4228 | -2.0% |
| fp8 | TRITON_ATTN | 17.0340 | -4.2% |
- 即使后端不同,bf16 的结果也保持一致(17.773~17.780,在 0.04% 以内)。因此,后端之间的差异处于测量误差水平。
- fp8 KV 反而降低了困惑度,并且两个后端的值也彼此不同(17.42 对比 17.03)。量化通常会引入误差,因此没有理由变低。不能将此结果解读为「fp8 KV 提高了质量」。同样是 8-bit 格式,不同后端的结果相差 2%p,这表明比起格式本身, 内核的数值处理(应用缩放、累加精度)起到了更大的作用,我们没有对原因进行剖析。服务器日志警告称,如果不进行缩放校正而使用 fp8,准确度可能会下降。
- 总结来说 ,我们没有获得在该模型和该文本下 fp8 KV 会导致困惑度变差的证据,但由于其方向无法解释,因此也不能用作质量得以保留的证据。文献(外部依据表)报告称,在未校正的 fp8 中,推理任务准确度根据模型不同最多会下降 1~2 分,因此在引入之前必须使用自身的任务评估集进行确认(任务准确度未进行实测)。
- 最初的测试(bf16 4.217 对比 fp8 4.015)由于读取的是不同的文本而无效,即使修正为相同的文本,方向也保持一致(4.2173 对比 4.0148),因此如上所述,我们固定了后端进行了重新确认。
6. 前缀缓存:存在公共前缀时,首个 Token 的速度可提升 14 倍
我们将 3,000 个 Token 的公共前缀附加每个请求不同的 32 个 Token 尾部,按顺序逐个发送(输出 1 个 Token)。
| 配置 | 首次请求 TTFT | 后续请求 TTFT 中位数 | 完全没有前缀的 3,032 个 Token |
|---|---|---|---|
| 启用前缀缓存(默认) | 394ms | 28ms | 395ms |
--no-enable-prefix-caching |
395ms | 393ms | 393ms |
- 如果缓存命中,将跳过 3,000 个 Token 的输入处理。 394ms 缩短至了 28ms(14 倍)。对于在前端固定系统提示词或文档的服务(RAG、智能体)而言,这是性价比最高的优化。
- 在本次测试中并未观察到保持开启状态的代价。 无前缀输入的 TTFT 在启用时为 395ms,关闭时为 393ms,保持一致。我们还测量了主要针对前缀完全不重合的输入负载下的代价。当发送 5 次包含并发 32、输入 2,048 个 Token、输出 16 个 Token 的 96 个请求时,吞吐量在启用时为 58,关闭时为 59 tok/s(总时间 26.3~26.4 秒对比 26.0~26.1 秒,约 1.2% 的差异),而 TTFT 中位数值在启用时为 6.5~6.6 秒,关闭时为 6.0~6.2 秒(长了约 8%)。命中次数为 0,查询的 Token 约有 105 万。也就是说, 保持开启的代价在以输入为主的负载下表现为 1% 左右的吞吐量损耗以及个位数百分比的 TTFT 延迟增加 (推测 TTFT 增加的 8% 是由于队列前方的请求承担了更多哈希计算的效果,未进行细分剖析)。
- 这是测量时最需要小心的陷阱。 在撰写本文时,我们重复输入相同的句子来测量输入处理速度,结果却得出了 GPU 无法达到的运算量,原因正是前缀缓存命中了(第 21 篇)。 性能测试的输入必须每个请求都各不相同。 相反,若想看到缓存的效果,就必须刻意制造公共前缀。
7. 分块预填充(Chunked Prefill):长输入暂停解码的时间由分块大小决定
在运行一个正在解码的请求(300 个 Token)期间,插入 3 个 3,500 个 Token 的输入请求,并测量插入区间的 Token 间隔。
--max-num-batched-tokens |
插入区间最大 ITL | 插入区间中位数 ITL | 长输入本身的 TTFT |
|---|---|---|---|
| 512 | 69ms | 53ms | 481~494ms |
| 默认(确认值为 2,048) | 227ms(重复 3 次分别为 227、226、226) | 14ms | 466~480ms |
| 8192(输入为 3,500 个 Token,因此实际生效的分块为 3,500) | 445ms | 14ms | 461~474ms |
- 单个步骤中处理的输入 Token 数越多,解码暂停的时间也就越长。 由于该 GPU 的输入处理速度约为 8,200 tok/s(第 21 篇),单个分块所需的时间在 512 个 Token 时约为 62ms,在 2,048 个 Token 时约为 250ms,在 3,500 个 Token 时约为 430ms(计算得出)。上述的最大 ITL 69、227、445ms 与该计算结果吻合。默认值通过读取服务器配置进行了确认。在该版本中,
max_num_batched_tokens的默认值为 2,048,分块预填充处于开启状态(enable_chunked_prefilltrue),前缀缓存也处于开启状态,块大小为 16 个 Token。 - 将分块设小,付出的代价是输入处理会稍稍变慢,换取的是解码过程更少中断。 长输入的 TTFT 在 512 时为 481~494ms,在 8192 时为 461~474ms,差异约为 3~5%。取而代之的是,插入区间的中位数 ITL 在 512 时增加到了 53ms(因为解码和输入处理在每个分块步骤中交织在一起)。
- 根据用途进行选择。 需要 Token 不间断输出的对话与语音服务适合较小的分块,而一次性处理长文档的批处理任务则适合较大的分块。我们还测试了两个值之间的大小(1,024、4,096)。插入区间最大 ITL 在 1,024 时为 129ms(3 次测试为 128~129ms),在 4,096 时为 445ms(445~446ms);结合默认的 2,048(227ms)、512(69ms)以及 8,192(445ms)来看,最大 ITL 与分块大小成正比(按输入处理速度约 8,200 tok/s 计算,1,024 个 Token 约合 125ms)。4,096 与 8,192 产生相同的值,是因为输入为 3,500 个 Token,实际生效的分块大小为 3,500。长输入本身的 TTFT 在 1,024 时为 468~493ms,在 4,096 时为 461~476ms,几乎相同。
8. 当关闭 CUDA Graph 时(--enforce-eager)
| 配置 | 服务器准备时间 | 并发 1 吞吐量 | 并发 32 吞吐量 | ITL 中位数(并发 1) |
|---|---|---|---|---|
| 默认(CUDA Graph) | 71 秒(首次启动,包含 19 秒编译),后续重启 35~55 秒 | 66 tok/s | 831 tok/s | 15ms |
--enforce-eager |
30 秒 | 64 tok/s | 814 tok/s | 15ms |
对于这种规模的模型,CUDA Graph 对速度的提升微乎其微,仅约 3%(并发 1 时 66 对比 64),而服务器准备时间缩短了约 25~40 秒。推测这是因为在每个 Token 耗时 14~15ms 的模型中,内核启动间隔在整体中所占的比例较小。我们曾写过在更小的模型中收益可能会更大,用 Qwen3-0.6B 重复 3 次相同的测试后,事实确实如此。在并发 1 下,CUDA Graph 默认的 168 tok/s(ITL 6ms)对比 --enforce-eager 108 tok/s(ITL 9ms), Graph 快了 55%,而在并发 32 下,为 1,557 对比 1,503 tok/s,差异约为 3.6%。服务器准备时间为 55 秒对 30 秒。推测这是因为模型较小,当单 Token 时间达到 6ms 级别时,内核启动间隔构成了整体的大头(推测)。我们未能测试更快的 GPU(未实测)。在更大的模型 Qwen3-4B-FP8 中,并发 1 下 Graph 默认的 49 tok/s 对比 --enforce-eager 47 tok/s,收益约为 4%(ITL 19ms 对比 20ms)。Graph 的收益仅在单 Token 耗时很短的极小模型中显著,而在 1.7B 及以上模型中,均稳定在 3~4% 左右。
9. 服务器首次启动所需的时间
根据服务器日志的时间戳读取的数值。
| 阶段 | 数值 |
|---|---|
| 进程启动与模块加载(直至引擎初始化开始) | 约 15~17 秒 |
| 权重加载(从磁盘到 GPU) | 4~6 秒(当模型文件已存在时) |
| 引擎初始化(编译、性能分析、KV 缓存分配、CUDA Graph 捕获、预热) | 首次启动 29 秒(包含 19 秒编译),若有编译缓存则为 7.5 秒 |
| 准备就绪总计 | 首次启动 71 秒,重启 35~55 秒(开启 LoRA 的服务器为 70 秒) |
下载模型的时间已在上表中剔除(独立计算)。在自动扩缩容(Autoscaling)中,新 Pod 能够接收流量所需的时间需在此数字上叠加镜像下载和模型下载的时间,因此, 预热副本与 模型缓存卷的必要性从数字上显现了出来。
10. 运维实操要点
- 并发上限 = KV 容量 ÷ 每个请求的 Token 数。 公式源自第 3 节,依据源自第 4 节。超出上限后,吞吐量不会增加,只有 TTFT 会增加。
- 分别关注 TTFT p95 与 ITL p99。 即使中位数正常,尾部延迟也很大(ITL p99 达 223ms)。
- 通过架构设计来制造公共前缀。 将系统提示词和文档置于前方,将各请求的内容置于后方,前缀缓存能带来 14 倍的收益。
- KV 8-bit 不是吞吐量选项,而是容量选项。 在长输入且队列很长时效果显著。请先进行质量评估。
- 分块大小是流畅度与输入处理速度之间的权衡。 对话服务设小,批处理设大。
- 性能测试应确保每次输入各不相同,并与硬件极限进行对比。
11. 面试常见问题
- vLLM 服务器的最大并发请求数是如何估算的? 用 KV 缓存 Token 总数除以每个请求的平均 Token 数(输入 + 输出)。每个 Token 的 KV 为
2 × 층 × KV 헤드 × head_dim × 바이트是。1.7B 模型有 22,048 个 token,如果是 640 个 token 的请求大约有 34 个,超过这个数量后,TTFT 就会飙升 8 倍(从 666ms 增至 5.7s)。 - 并发性提高了,但吞吐量为什么没有增加? 分摊权重读取带来的收益只在 KV 容量充足时持续,一旦容量耗尽,请求就会在队列中等待。
- 前缀缓存对哪类服务的效果显著? 公共前缀较长的服务。对于 3,000 个 token 的前缀,TTFT 从 394ms 降至 28ms。
- KV 8 位的效果体现在速度上还是容量上? 体现在容量上。由于每个 token 的字节数减半,因此在相同的内存中可以容纳 1.9 倍的内容,长输入的 TTFT 从 4.0s 降至 0.94s。短上下文的速度则保持不变。
- 当有长输入到来时,为什么其他用户的 token 会出现卡顿(中断),该如何减少这种情况? 输入处理阶段与解码在同一个批次中运行,因此会有相应的延迟。通过分块预填充(Chunked Prefill)减少单阶段的 token 数量,卡顿就会与块大小成正比地减少(如果是 512 个 token,则最多为 69ms)。
- 如何质疑性能测试数值是否正确? 将其与硬件极限(计算能力、带宽)进行对比。如果重复相同的句子,前缀缓存就会命中,从而产生不可能达到的处理速度。
外部依据:通过论文和官方资料确认的内容
本文中的所有数值均为实测值。我们查阅了报告相似现象的外部资料,核对数值与条件,并记录了其与我们结果的关系。由于外部数据的模型、GPU 和负载不同,我们 没有进行直接对比,而是仅判定其方向与结构的一致性。
| 主题 | 资料及确认位置 | 确认的内容与条件 | 与本文的关系 |
|---|---|---|---|
| KV 大小公式 | Kwon 等人,PagedAttention, SOSP 2023, arXiv 2309.06180,第 3 节及表 1 | OPT-13B 每个 token 的 KV 为 2(键值)× 5,120(隐藏层大小)× 40(层)× 2(字节)= 800KB。在 A100 40GB 上,减去 26GB 的权重,将 12GB 用于 KV,则为 15.7K 个 token,与 12GiB ÷ 800KiB 的算术结果相符 | 支持。 与本文公式(第 3 节)相同 |
| 现有系统的 KV 浪费 | 同上论文第 1 节及图 2 | 实际以 token 状态写入的 KV 比例为 20.4%~38.2%。这是作者亲自实现的三个 Orca 变体的平均值,不包含 FasterTransformer | 相关。以 16 个 token 为单位进行块管理(本文中确认的默认值)是减少这种浪费的方法 |
| 块大小与 token 间延迟 | Agrawal 等人,Sarathi-Serve, OSDI 2024, arXiv 2403.02310,第 4.3 节、图 6 及第 5.4.1 节 | 块(token 预算)越小,重复时间越短,token 间延迟就越低;在计算受限区间内,重复时间与 token 数量呈线性关系。在块大小为 512 时,总输入处理时间最多增加约 25%(Yi-34B,张量并行度 2),而在 2,048 时几乎可以忽略不计 | 支持。 与第 7 节的方向相同。由于模型和 GPU 不同,我们无法对 TTFT 的增幅(3~5%)进行规模对比 |
| 并发性与吞吐量的形态 | 同上论文第 3.1 节及图 3(Mistral-7B, 单张 A100, 输入 1,024) | 批处理使解码吞吐量几乎呈线性增长,对输入处理几乎没有影响 | 有限支持。 与第 21 篇短上下文(在并发达 16 时为 14~15 倍)的形态相同。第 4 节中输入为 512 的表格饱和得更早 |
| 超出容量时的延迟暴增 | PagedAttention 第 6.2 节 | 当请求率超过系统容量时,队列会无休止地变长,导致延迟暴增 | 支持。 与第 4 节中 TTFT 呈 8.6 倍增长的现象一致 |
| 容量耗尽前吞吐量趋于平缓的情况 | Recasens 等人,《Mind the Memory Gap》,arXiv 2503.08311,第 3~5 节 | 在 H100 上使用 OPT-2.7B 等模型时,即使在大批次下也停留在内存瓶颈处,批次大小为 1 的 225 tok/s 在批次大小为 256 时仅达到 7,607 tok/s(约为 33.8 倍,而非预期的 256 倍) | 有限支持。 证明第 4 节中的性能趋缓可能不仅仅是 KV 容量问题所致的依据 |
| KV 8 位的质量 | vLLM 官方博客《The State of FP8 KV-Cache and Attention Quantization in vLLM》(2026-04-22) | 在未经校准的 FP8(缩放比例 1.0)下,Qwen3-30B-A3B-Thinking-2507 的推理任务准确率最多下降 1~2 分,Qwen3.5-27B 最多下降 0.7 分(H100) | 支持。 文献中的变化虽小,但取决于模型和校准。我们的模型(1.7B)及评估请参考第 5 节 |
| KV 8 位(整数) | LMDeploy 文档(INT8 KV, OpenCompass) | llama2-7b-chat 的 MMLU 从 35.64 变为 35.58,internlm2.5-chat-7b 的 MMLU 从 72.30 变为 72.27。由于这是 INT8 而非 FP8,因此无法直接套用 | 相关。与 vLLM 的 --kv-cache-dtype fp8 方式不同 |
| 抢占(Preemption)方式 | vLLM 官方文档(优化页面)、v0.30.0 scheduler.py,PagedAttention 第 4.5 节和第 7.3 节 |
V1 的抢占默认采用重新计算而非交换,它会归还被牺牲请求的所有块,将计算出的 token 数量重置为 0,并放回队列头部。论文说明在小块大小下重新计算是高效的 | 支持。 与第 4 节的抢占描述一致(抢占后前缀缓存是否命中未进行验证) |
| 默认值 | v0.30.0 config/cache.py |
默认开启前缀缓存,块大小为 16 | 支持。 与在 0.31.0 中直接读取的值(开启、16、块预算 2,048)一致 |
供应商及项目博客(vLLM、LMDeploy)的数值,仅搬运了正文中写明条件的部分。由于无法打开 ACM 的 PagedAttention 页面,故遵循了 arXiv 摘要页面的表述。
未能确认的事项
- 不同设备上的重复波动。 在此环境中波动在 0.5% 以内(第 2 节),但在其他 GPU 以及存在邻近负载的环境中可能会有所不同。对于并发达 64 等会堆积队列的区间,未进行 5 次重复测量。
- 其他模型、其他 GPU、更长的上下文。 仅限 1.7B 单一模型、8GB 级别 GPU、最大输入 4,096。
--max-num-batched-tokens中 512 到 8192 之间的值(默认的 2,048 已确认)。- KV 8 位的输出质量在与前缀不重叠的大量请求中,前缀缓存带来的 CPU 负担。
- 被抢占的请求在恢复时是否会命中前缀缓存。 根据“归还的块在保留哈希的同时保留在空闲块队列中”的设计描述,推测可能会部分命中,但未能通过官方描述或测量予以证实。
- 发生抢占的条件的整体形态。 虽然通过服务器指标读取了抢占次数(第 4 节),但并未密集改变输入/输出长度和并发度来绘制曲线。
- 与 SGLang 等其他服务器的对比、多 GPU、量化模型(第 21 篇中有部分涉及)。
确认的版本与日期
截至 2026-10-07。vLLM 0.31.0, PyTorch 2.13 (CUDA 13), Qwen3-1.7B (bf16)。