Back to writing

/ Notes

Notes: Why Prefix Caching Should Be Disabled for Compute Benchmarks

A systems note on why prefix caching can turn hardware compute benchmarks into measurements of cache hit rate, serving policy, and workload repetition.

3 minLLM · Inference · Benchmark · Prefix Caching

我第一次看到 prefix caching 对推理性能的提升时,直觉上会觉得:既然 QPS 更高、TTFT 更低,那是不是说明这个平台更强?

但如果测试目标是“对比硬件算力”,这个结论其实很危险。

因为 prefix caching 做的事情不是让同一段 prefill 计算变得更快,而是在命中缓存时,直接复用已经算好的 KV Cache。换句话说,它会把一部分本该由硬件重新计算的 prompt token 跳过去。

所以,在硬件横评里开启 prefix caching,很容易把 benchmark 从:

硬件到底能算多快

变成:

这套 serving 系统在这个 workload 上有多容易命中缓存

这两件事都重要,但它们不是同一个问题。

这篇杂谈想讨论的就是:为什么在对比算力时,通常不应该开启 prefix caching;以及什么时候它反而应该被开启。

1. 先把 prefill 和 decode 分开看#

一次大模型推理通常可以拆成两个阶段:

用户输入 prompt

Prefill:一次性处理整段 prompt,生成初始 KV Cache

Decode:逐 token 生成输出,并持续读取和追加 KV Cache

这两个阶段虽然都在跑 Transformer,但资源特征差别很大。

阶段主要工作更容易卡在哪里
Prefill处理整段 prompt,计算所有输入 token 的 KV Cache算力、矩阵乘法、并行计算
Decode一个 token 一个 token 生成,并反复读取历史 KV Cache显存带宽、KV 读取、调度延迟

如果要比硬件的原始计算能力,尤其是长上下文场景下的算力,prefill 往往更有代表性。原因很直接:长 prompt 会带来大量矩阵乘法和 attention 相关计算,更容易把计算单元压起来。

比如一个请求有 32k tokens 的输入,prefill 阶段需要把这 32k tokens 一次性送进模型,生成后续 decode 要用的 KV Cache。这个阶段的工作量很大,也更接近我们想看的“硬件能不能吃下大块计算”。

decode 则不一样。decode 每一步通常只生成一个 token,单步计算量相对小,但要不断读历史 KV Cache。它当然也很重要,尤其影响 TPOT 和输出流畅度,但它更容易被 memory bandwidth、KV cache layout、batching 策略和调度细节影响。

所以很多硬件算力对比会特别关注 prefill:

同样模型
同样输入长度
同样 batch
同样精度
看 prefill tokens/s、TTFT 或相关吞吐

问题是,prefix caching 会直接改变这里的“同样计算量”。

2. Prefix caching 到底跳过了什么#

prefix caching 的核心思想很朴素:如果多个请求共享同一段前缀,那么这段前缀的 KV Cache 已经算过一次,后面的请求可以直接复用。

典型例子是:

请求 1: system prompt + user question A
请求 2: system prompt + user question B
请求 3: system prompt + user question C

如果 system prompt 很长,并且三次请求完全相同,那么请求 2 和请求 3 没必要重新为这段 system prompt 做完整 prefill。系统可以从 prefix cache 里找到对应的 KV Cache,然后只计算后面不同的 user question 部分。

不开 prefix caching 时,三次请求大致是:

请求 1: 计算 system prompt + question A
请求 2: 计算 system prompt + question B
请求 3: 计算 system prompt + question C

开启并命中 prefix caching 后,会变成:

请求 1: 计算 system prompt + question A,并保存 system prompt 的 KV
请求 2: 复用 system prompt 的 KV,只计算 question B
请求 3: 复用 system prompt 的 KV,只计算 question C

这里的关键不是“算得更快”,而是“少算了一部分”。

如果 benchmark 里存在大量共享前缀,那么开启 prefix caching 后,实际进入硬件计算单元的 token 数会显著减少。表面上看 TTFT 降了、QPS 涨了、吞吐变好了,但这不是纯粹的硬件算力提升。

它更像是系统发现:

这道题我以前做过一部分,答案可以直接拿来用。

这在线上服务里非常合理,但在算力考试里就会破坏公平性。

3. 测到的会变成“缓存命中后的服务吞吐”#

硬件 benchmark 最重要的前提之一,是不同平台应该做同样的工作。

理想情况下,我们希望比较的是:

硬件 A 和硬件 B 在相同模型、相同输入、相同输出、相同计算量下,谁完成得更快。

但 prefix caching 会把结果变成:

硬件能力
+ 推理框架的 prefix cache 实现
+ workload 里的前缀重复程度
+ cache hit rate
+ cache eviction 策略
+ KV cache 查找与复用开销
+ scheduler 对 cache-aware routing 的支持程度

这时 QPS、TTFT、吞吐量当然仍然是真实指标,但它们描述的是另一件事:

这套软硬件 serving 系统,在这个请求分布下,缓存命中之后能服务多快。

这不是“错”的指标,只是它不再是单纯的“硬件算力”指标。

举个极端例子:如果 benchmark 里的请求都共享一个很长的 system prompt,那么一个 cache-aware serving 系统可能会表现得特别好。它不需要反复计算那段 system prompt,只要缓存能命中,就能把 prefill 成本摊掉。

但如果换成没有共享前缀的真实 workload,这个收益可能立刻消失。反过来,如果真实线上业务就是大量共享 system prompt、多轮对话、agent 模板或 RAG 固定上下文,那么 prefix caching 的收益就很有价值。

所以这里的判断标准不是:

prefix caching 有没有用?

而是:

这次 benchmark 到底想测什么?

如果想测线上服务能力,可以测缓存命中后的效果;如果想测硬件原始 prefill 算力,就不应该让缓存替你跳过计算。

4. 它会让不同平台的有效计算量不一致#

硬件横评最怕的不是某个平台更快,而是大家实际做的题不一样。

假设两个平台跑同一组 benchmark 请求:

平台 A: prefix cache 命中率 80%
平台 B: prefix cache 命中率 40%

如果只看结果,平台 A 的 QPS 可能更高,TTFT 可能更低。但这不一定说明 A 的矩阵计算能力更强,因为 A 可能只是少算了更多 token。

实际发生的事情可能是:

平台 A: 100 个 prompt token 里,只有 20 个需要重新 prefill
平台 B: 100 个 prompt token 里,有 60 个需要重新 prefill

这时两边的“有效计算量”已经不一样了。A 的结果里混入了更高的缓存命中率,B 的结果里包含了更多真实 prefill 计算。

如果你还把它们放在同一张硬件算力表里比较,就会得到一个很含混的结论:

A 更快。

但真正原因可能有很多种:

  • A 的硬件算力更强。
  • A 的 prefix cache 查找更快。
  • A 的 cache eviction 策略更适合这个 workload。
  • A 的调度器更会把共享前缀请求路由到同一组 worker。
  • A 刚好保住了更多 KV Cache,没有被显存压力挤掉。
  • benchmark 的请求分布更容易让 A 的实现命中缓存。

这些都是 serving 系统能力的一部分,但它们不是同一个维度的硬件算力。

所以,如果目标是横向比较芯片、GPU、加速卡或不同硬件平台,最好先把 prefix caching 关掉,让每个平台都完整计算同样的 prompt。这样至少可以保证:

同样输入
同样输出
同样模型
同样精度
同样需要被计算的 token

只有在这个前提下,prefill throughput、TTFT 或算力利用率才更容易解释。

5. TTFT 尤其容易被污染#

TTFT,也就是 Time To First Token,通常可以粗略拆成:

TTFT = 排队时间 + prefill 时间 + 首个 decode token 时间

在短 prompt 下,prefill 占比可能没那么夸张;但在长上下文场景里,prefill 往往是 TTFT 的大头。

比如一个 32k prompt 请求,如果没有缓存命中,系统需要完整处理这 32k tokens 才能开始生成第一个 token。用户看到第一个 token 之前,大部分时间都花在 prefill 上。

但如果 prefix caching 命中了其中 28k tokens,那么新请求只需要处理剩下 4k tokens。TTFT 会明显下降。

这时 TTFT 变好,不代表硬件把 32k prompt 算得更快,而是因为它没有重新算完整的 32k prompt。

可以把这件事写得更直白一点:

不开 prefix caching:TTFT 反映完整 prompt 的 prefill 成本。
开启 prefix caching:TTFT 反映 cache miss 部分的 prefill 成本 + cache 复用开销。

两者不是同一个指标。

如果报告里只写:

32k prompt TTFT = 200 ms

但没有说明 prefix caching 命中了多少 token,那这个数字就很难解释。它可能代表硬件真的很强,也可能代表大部分 prefix 已经被缓存。

所以,在硬件算力测试里,prefix caching 对 TTFT 的污染特别明显。因为它直接砍掉了 TTFT 里最能体现 prefill 计算量的那一段。

6. 什么时候应该开启 prefix caching#

前面说了很多“不应该开启”,但这不代表 prefix caching 没意义。恰恰相反,它是 LLM serving 里非常重要的优化。

真实线上请求经常有大量可复用上下文:

  • 多轮对话会反复携带历史前缀。
  • Agent 系统常常有固定工具说明和格式约束。
  • RAG 应用可能有固定 system prompt 和相似模板。
  • 企业内部应用经常复用同一套长 instruction。
  • 长上下文服务会遇到多请求共享同一份背景材料。
  • benchmark 如果刻意模拟共享 prefix,也应该评估 cache-aware serving 能力。

这些场景里,prefix caching 不只是“可以开”,而且应该认真优化。因为用户真正关心的是服务响应速度、成本和吞吐,而不是每次都重复计算同一段前缀。

更合理的做法是把 benchmark 分成两类:

测试目标是否开启 prefix caching主要解释
比硬件原始算力不建议开启避免减少实际 prefill 计算量
比 prefill 算力不应开启prefill token 应该完整参与计算
比线上真实服务能力可以开启缓存复用本来就是生产系统能力
比框架工程优化能力可以开启cache lookup、eviction、调度都是工程能力
比长上下文复用能力应该开启目标就是看重复上下文能省多少
比 cache-aware serving 能力应该开启缓存命中率和路由策略是核心指标

也就是说,prefix caching 不是一个“好或坏”的开关,而是一个会改变 benchmark 含义的变量。

如果你打开它,就应该明确告诉读者:

本测试测的是 cache-aware serving performance,包含 prefix cache 命中收益。

如果你关闭它,也应该明确说明:

本测试希望保持各平台有效计算量一致,用于观察硬件 prefill 算力。

这两个测试都可以存在,但不能混在同一张表里解释。

7. 我会怎么设计这类 benchmark#

如果目标是硬件算力对比,我会尽量让测试条件保持简单、可解释:

  • 关闭 prefix caching,避免缓存命中跳过 prefill。
  • 固定模型、精度、并行策略和输入输出长度。
  • 使用低重复度 prompt,避免隐式共享 prefix。
  • 分开报告 prefill throughput、decode throughput、TTFT、TPOT。
  • 标注 batch size、sequence length、concurrency 和采样参数。
  • 如果框架有自动 prefix cache 或 prompt cache,确认它真的被关闭。

如果目标是线上服务能力,我会反过来把缓存相关指标报清楚:

  • prefix cache hit rate。
  • 命中的 token 数和 miss 的 token 数。
  • cache lookup、block allocation、eviction 的开销。
  • 不同共享前缀比例下的吞吐和延迟曲线。
  • cache warm 和 cold start 分别是什么表现。
  • 显存压力下缓存被挤掉后,性能如何退化。

这样读者才知道自己看到的数字应该怎么用。

最忌讳的是只给一个漂亮的 QPS 或 TTFT,却不说明缓存状态。因为这种数字很可能既不能代表硬件上限,也不能稳定复现到另一个 workload。

8. 总结#

对比硬件算力时不建议开启 prefix caching,核心原因只有一个:

它会减少实际计算量。

prefill 本来是最能体现长上下文计算压力的阶段,而 prefix caching 会在命中时复用已有 KV Cache,把一部分本该重新计算的 token 跳过去。于是 benchmark 测到的不再只是硬件把 prompt 算完的能力,而是混入了缓存命中率、缓存管理、KV 复用、调度策略和 workload 前缀重复程度。

所以可以简单记成:

不开 prefix caching:更像大家都做同一张卷子。
开启 prefix caching:更像有人提前背过一部分答案。

前者适合比较硬件原始 prefill 算力,后者适合比较真实服务和 cache-aware serving 能力。

两者都重要,但不要混着讲。