前缀缓存命中 50%,预填充为什么没有快 2 倍?

从因果注意力的三角计算量出发,逐层推导前缀缓存命中后的剩余注意力计算量、完整预填充的理想加速上限,以及它与真实 TTFT 之间的差距。

Code walkthroughsLLM serving internalsLLM servingprefix-cacheKV cacheprefill

前缀缓存命中一半 token,预填充就能快两倍——这个直觉只对每个 token 成本相同的线性计算成立。

完整因果注意力不是这样。越靠后的查询能看到的键越多:第一个查询只看一个位置,最后一个查询要看完整前缀。命中前 50% token,跳过的是注意力矩阵左上角较小的三角形,而不是一半面积。

本文只回答一个问题:从逻辑前缀命中率出发,怎样逐层推导剩余的注意力计算、完整预填充的理想上界,以及最终需要实测的真实收益?

先统一六个容易混用的术语

  • 前缀命中率:索引逻辑匹配到的连续前缀 token 数与完整输入长度之比,本文记为 α。它描述匹配结果,不保证状态已经可用。
  • 可复用边界:所有续算必需状态都完整、版本一致并对齐到的最晚 token 边界。不同状态组共同可用时,通常取最短的那个边界。
  • 有效复用:推理引擎真正恢复成功并能够跳过计算的 token 范围。它不大于逻辑前缀匹配,还会受到页、检查点与状态完整性的限制。
  • 回载:从本地或远端缓存找到对象后,把它读回推理引擎可以使用的位置,并完成必要同步。
  • 注意力计算加速比:基线注意力计算量与剩余注意力计算量的比值;不包含 MLP、投影、通信和缓存恢复。
  • 完整预填充加速比:完整预填充基线成本与复用后端到端成本的比值;真实值应包含排队、回载、重算和运行时开销。

因此,缓存命中不等于推理引擎已经成功恢复状态,逻辑前缀匹配也不等于实际可复用前缀。后文的计算量曲线是理想上界;除非明确写成实测,否则它们都不是 TTFT 或生产吞吐结论。

把完整输入长度记为 N,前缀缓存命中的长度记为 P=αN。只看注意力主体时:

剩余 FLOPs=1α2,
理想加速比=11α2.

α=0.5,仍有 75% 的注意力计算需要执行,理想加速只有:

110.52=10.751.33×.

下面这张图可以直接拖动命中率。它展示的不是缓存容量,也不是端到端 TTFT,而是完整因果注意力的计算几何

因果几何实验

拖动前缀命中率,观察真正被跳过的 Attention 三角形

带斜纹的绿色网格是已缓存 Query 行,带点纹的蓝色网格是仍需计算的后缀。这里归一化的是完整因果 Attention FLOPs。

Prefix Cache 的因果 Attention 矩阵前缀缓存复用了 50% 的 Query 行。被跳过的 Attention 三角形占原始因果 Attention 矩阵的 25.0%。带斜纹的绿色单元格表示已跳过,带点纹的蓝色单元格表示仍需计算。Key 位置 →Query 位置 →0N
斜纹 · 已跳过的前缀行点纹 · 仍需计算的后缀行
50%

逻辑前缀长度占完整输入长度的比例。

0%90%

剩余 FLOPs

75.0%

1 − 0.50²

理想加速比

1.33×

1 / (1 − α²)

仅限 Attention 主体的理想上界

命中 50% 的 token,只跳过左上角面积为 25.0% 的旧 Query;新 Query 仍要读取命中前缀中的 Key/Value。

前缀缓存跳过的是旧查询,不是旧键

没有前缀缓存时,长度为 N 的预填充会形成一个下三角注意力矩阵。忽略对角线和常数项,其面积是:

Ffull0Nxdx=N22.

命中长度为 P 的前缀后,前 P 个 token 的各层输出与 KV 或低维状态已经存在,旧查询行不必重算。但新增查询仍然要读取旧前缀的键和值:

text
已缓存的前缀查询 [0, P)
  不再执行

新增的后缀查询 [P, N)
  仍需读取键 [0, 当前位置]

因此跳过的工作量是左上角边长为 P 的旧三角形:

FskipP22.

剩余工作量为:

Fremain=FfullFskipN2P22=N2(1α2)2.

除以完整预填充的 N2/2,就得到 1α2

这个推导揭示了一个容易忽略的事实:前缀最前面的 token 最便宜,最后面的 token 最贵。 命中 50% token 跳过的恰好是较便宜的 25% 注意力面积;剩下 50% 查询行包含更长的历史依赖。

同一个命中率,在 MHA、MLA、SWA、KDA 上不是同一条曲线

前缀缓存的收益取决于注意力机制如何消费历史。下面的交互曲线统一把完整请求的注意力工作量归一化为 1,并比较三种理想模型:

  • MHA / MLA:完整因果注意力,剩余比例是 1α2
  • SWA:只看最近窗口 W,本文可视化固定为 W=0.25N
  • KDA 递推部分:递推扫描与 token 数近似线性,但只能从不晚于命中位置的完整检查点继续。

Attention 加速比探索器

相同前缀命中率,不同 Attention 的可跳过工作量并不相同

曲线只比较理想化的 Attention 主体:MHA/MLA 使用完整因果依赖,SWA 取 W = 0.25N;KDA 只计算递推部分,并把续算边界向下对齐到 checkpoint。

Prefix Cache 的理想 Attention 加速比曲线展示逻辑前缀命中率从 0% 到 90% 时,完整因果 MHA/MLA、滑动窗口 Attention 和 KDA 递推部分的理想加速比。KDA 示例的输入长度为 32768 个 token,每 4096 个 token 保存一次 checkpoint;阶梯曲线是理想上界,不包含 checkpoint 加载、传输和重放成本。1×4×10×0%50%90%前缀命中率 · α
50%

这里显示逻辑匹配比例;KDA 可能只能从更早的 checkpoint 恢复。

0%90%
4,096 个 token

固定示例长度 N = 32,768 个 token。

1,0248,192

MHA / MLA

1.33×

完整因果 Attention

二次增长的因果三角形

SWA

1.75×

W = 25% · N

先二次增长,随后受窗口约束

KDA 递推部分

2.00×

理想上界

checkpoint 精确对齐;不含加载与传输

逻辑匹配前缀
16,384 个 token
KDA 实际恢复位置
16,384 个 token
未对齐前缀重放量
0 个 token

KDA 递推部分的理想上界:必须在对齐边界拥有完整递推状态和 ShortConv 等续算状态。曲线虽把未对齐 token 算入剩余 token,但仍忽略 checkpoint 查询、回载、传输和恢复开销。

图中的数值不是模型之间的绝对速度比较。MHA、MLA、SWA 与 KDA 的内核常数、维度和带宽压力完全不同;曲线只回答:对同一种注意力机制,在自己的完整预填充基线上,前缀命中能跳过多少工作。

前缀命中率MHA / MLASWA,W=0.25NKDA 递推部分,N=32768,C=4096
25%1.07×1.17×1.33×
50%1.33×1.75×2.00×
75%2.29×3.50×4.00×
90%5.26×8.75×8.00×

MHA、GQA、MQA 与 MLA:缓存表示变了,因果三角形没有消失

MHA、GQA 与 MQA 都逐 token 保存 K/V,但共享粒度不同:MHA 为每个查询头保存独立 K/V,GQA 让一组查询头共享 KV 头,MQA 则让所有查询头共享一组 K/V。这些变化会缩小缓存宽度,却不会改变“新查询需要读取全部历史状态”的完整因果依赖。

MLA 进一步把历史压成 KV 低维表示与解耦的 RoPE 键,显著改变每个 token 的缓存宽度和读写方式。但只要主注意力仍对完整历史执行因果注意力,它的查询与键依赖仍是下三角:

RMHA/MLA(α)=1α2.

MLA 可以让缓存更小、内存流量更低,也可能改变内核常数;这些收益不能把前缀命中率公式直接改成 1α

SWA:上下文超过窗口后,边际成本趋向线性

滑动窗口注意力的第 t 个查询最多读取 W 个键。累计工作量可近似写成:

AW(x)={x22,xW,WxW22,x>W.

命中比例为 α 时,剩余比例是:

RSWA(α)=AW(N)AW(αN)AW(N).

当前缀和完整请求都远大于窗口,后续每个 token 的注意力成本接近固定的 W,于是曲线逐渐接近线性模型。这里仍不能只看逻辑命中率:SWA 的尾部状态、局部窗口页与其它注意力层必须落在同一个可恢复边界。

KDA:递推计算可以线性跳过,恢复边界却必须对齐

KDA 一类线性注意力不再为每个查询回读完整历史,而是递推有限状态。若完整预填充的递推扫描成本近似为 O(N),逻辑匹配长度为 Pmatch=αN,检查点间隔为 C,能够直接恢复的位置要先向下对齐:

Peff=PmatchCC,
RKDA-recurrence=NPeffN.

交互图默认取 N=32768C=4096。例如逻辑命中率为 90% 时,匹配位置约为 29491,但最近检查点在 28672;递推部分至少还要处理 4096 个 token,所以理想上界是 8×,不是把 1/(10.9) 直接算出的 10×。调整检查点间隔可以看到这条阶梯曲线如何变化。

这里的“存在检查点”仍是强条件。一个可续算的检查点必须包含该边界完整的递推状态;如果 ShortConv 或其它续算状态存在,也必须一起保持完整、版本一致且对齐。只有 token 哈希命中、只有 MLA 页命中,或只有递推矩阵存在,都不能证明请求能从该边界直接恢复。

曲线只计算递推部分的剩余 token,并假设对齐的检查点可以立即使用。它忽略检查点查询、加载、跨层或跨设备传输、同步,以及恢复状态本身的成本;未对齐的前缀 token 已计入剩余 token,但没有额外建模检查点重算的实现开销。因此它是检查点精确对齐条件下的理想上界,不是生产环境收益预测。

现实中的注意力模式

上面的公式描述依赖模式,而不是某个模型版本。下面的家族名称只是帮助把抽象模式映射到常见实现;具体模型仍应以自己的配置和代码为准。

模式代表性示例缓存含义
GQALlama / Qwen / GLM 系列KV 缓存;因果几何不变
MLADeepSeek 系列压缩的 KV 低维状态
SWAMistral 系列滑动窗口历史复用
KDAKimi 系列递推检查点状态

这张表不改变推导:先识别历史依赖是完整因果、滑动窗口还是递推,再选择对应的剩余计算量模型。

为什么逻辑命中还要经过可复用边界

前缀缓存能跳过计算的前提,不只是索引找到了相同 token。推理引擎必须恢复该注意力模式续算所需的完整状态:它可能是 KV 页、MLA 低维状态、窗口尾部状态,或递推检查点。下面的演进图用于比较这些恢复边界,而不是给模型做分类。

状态演进

Attention 缓存正在从 KV 张量演化为可恢复的状态集合

选择任一阶段,比较它保存什么、如何增长,以及前缀复用真正需要恢复到哪个边界。

使用系统原生选择器,在五种状态表示之间切换。

阶段 1 · 逐 token 历史

KV Cache

O(T · Hkv · d)

MHA/GQA/MQA 将每个历史 token 的 Key 与 Value 直接保存下来;下一条 Query 读取这段完整历史。

状态表示
每层 Kₜ + Vₜ
存储增长
O(T · Hkv · d)
恢复条件
恢复 [0, t) 的全部 KV 页
  • 物理对象通常按层、页、K/V 与并行 rank 拆分
  • 前缀命中的是连续 token 边界,不是任意页集合

这里的系统结论只有一个:对象或页的 match(匹配)只说明某个键存在;有效复用要求所有必需状态在同一 token 边界完整命中,并成功回载到执行引擎。对混合模型,最终可跳过的长度通常是各状态组共同连续前缀的最小值。

注意力计算加速不等于完整预填充加速

前面的 1/(1α2) 只描述完整因果注意力主体。预填充还包含词嵌入、归一化、QKV 投影、输出投影、MLP/MoE、集合通信与推理调度。

假设基线 Transformer 模块的计算量中,注意力占比为 β,其余按 token 近似线性的算子占比为 1β。忽略缓存开销时:

Rattention=1α2,Rtoken-wise=1α,
Rblock=β(1α2)+(1β)(1α),
Sblock, ideal=1Rblock.

下面的交互把注意力主体、逐 token 算子与完整模块的理想加速放在一起。默认 α=50%β=50%;此时模块剩余 62.5%,理想加速为 1.60×。同样在 50% 命中率下,β=30%,50%,70% 分别约为 1.74×、1.60×、1.48×:注意力占比越高,前半段较便宜的因果三角形对整体加速的限制越明显。

完整 Prefill FLOPs 模型

Attention 加速不是整个 Transformer 模块的加速

α 控制前缀命中比例,β 控制 Attention 在原始模块 FLOPs 中的占比;结果是理想化计算量模型,不是实测 TTFT 或生产吞吐。

50%

逻辑前缀匹配长度占完整输入序列的比例。

0%90%
50%

Attention 在未使用缓存时的 Transformer 模块 FLOPs 中所占的比例。

0%(全为逐 token 算子)100%(全为 Attention)

Attention 局部加速比

1.33×

剩余 75.0% · 1 − α²

只计算完整因果 Attention FLOPs

逐 token 算子加速比

2.00×

剩余 50.0% · 1 − α

投影、MLP 和其他逐 token 计算

完整模块理想加速比

1.60×

剩余 62.5%

按 FLOPs 加权得到的理想上界

模块剩余工作量

β(1 − α²) + (1 − β)(1 − α)

剩余 Attention 工作量 · 37.5 个百分点;原始占比为 50%

剩余逐 token 工作量 · 25.0 个百分点;原始占比为 50%

50% 命中率参考

β = 30%
1.74×
β = 50%
1.60×
β = 70%
1.48×

模型边界: 这里只把未命中 token 的计算量加权相加;没有计入缓存 查询、回载、传输、同步、内核效率、调度或 checkpoint 重放。因此完整模块的理想加速比仍不能直接当作 TTFT 或吞吐提升。

这个完整模块计算模型可以作为完整预填充的第一层近似,但它仍不是端到端延迟模型。再把缓存查询、主机或存储加载、反序列化、H2D、同步、检查点重算与调度等待归一化为 h,才可以粗略写成:

Se2e1β(1α2)+(1β)(1α)+h.

例如 α=0.5β=0.6 且暂时令 h=0,剩余模型计算约为:

0.6×0.75+0.4×0.5=0.65,

对应约 1.54×,仍不是 2×。远端缓存的 h 若大于省下来的计算,命中甚至可能比本地重算更慢。

这个模型只是建立量纲,不应替代性能分析。FlashAttention 分块、分块预填充、批次打包、张量并行通信和 MoE 路由都会让真实曲线偏离连续面积模型。注意力计算量的加速比不等于 TTFT 加速比,理想上界也不等于生产吞吐。

把命中率变成可解释的性能指标

只上报一个 prefix_hit_rate,很难解释 TTFT。至少应把下面几层分开:

指标回答的问题
逻辑匹配 token 数Radix/hash 最长匹配到了多少 token?
可恢复匹配 token 数所有必需注意力状态共同完整到了哪个边界?
重算 token 数因页或检查点粒度不对齐,需要补算多少 token?
状态查询与回载延迟命中状态从元数据到设备可用花了多久?
后缀预填充延迟恢复后真正计算新 token 花了多久?
TTFT排队、调度、传输、预填充与首 token 解码合计多久?

对完整因果 MHA/MLA,还可以同时上报一个由命中率推导出的注意力工作量指标:

避免的 Attention 工作量=α2,

而不是用 α 代替。这样 50% token 命中会明确显示为避免了 25% 的注意力工作量,监控、容量规划和性能回归分析就不会混用两个不同的量。

从前缀命中率到真实吞吐

全文的推导可以压缩成下面一条因果链。每一步都需要额外信息,因此不能从最上面的命中率直接跳到最下面的生产吞吐。

总结模型

前缀命中率是输入,不是性能结论

先由注意力依赖决定剩余计算,再从计算量上界走向包含恢复开销的实测收益。

  1. 1

    前缀命中率

    α = P_match / N

    索引匹配到的逻辑前缀

  2. 2

    注意力依赖模式

    完整因果 / 滑动窗口 / 递推

    决定历史如何参与新查询

  3. 3

    剩余计算量

    R(α, 可复用边界)

    结合计算几何与可恢复边界

  4. 4

    理想加速比

    S_ideal = 1 / R

    归一化计算量的理想上界

  5. 5

    真实吞吐

    实测

    再计入回载、重放、调度与内核效率

常见误区

四个等号都不成立

左边是可观测输入或局部上界,右边才是需要继续推导或实测的量。

命中不等于节省的计算

  • 前缀命中率 ≠ 节省的计算量
  • 逻辑前缀匹配 ≠ 实际可复用状态

局部上界不等于生产延迟

  • 注意力加速比 ≠ 预填充加速比
  • 理想计算模型 ≠ 生产环境 TTFT

所以“前缀缓存命中 50%”不是一个性能结论,而是一条输入事实。对完整因果注意力,它首先意味着只跳过 25% 的注意力计算;对完整预填充,还要继续计入逐 token 计算;对生产系统,则必须再验证状态恢复和数据搬运是否真的比重算便宜。只有走完这条链,才能回答预填充到底会快多少。