前缀缓存命中 50%,预填充为什么没有快 2 倍?
从因果注意力的三角计算量出发,逐层推导前缀缓存命中后的剩余注意力计算量、完整预填充的理想加速上限,以及它与真实 TTFT 之间的差距。
前缀缓存命中一半 token,预填充就能快两倍——这个直觉只对每个 token 成本相同的线性计算成立。
完整因果注意力不是这样。越靠后的查询能看到的键越多:第一个查询只看一个位置,最后一个查询要看完整前缀。命中前 50% token,跳过的是注意力矩阵左上角较小的三角形,而不是一半面积。
本文只回答一个问题:从逻辑前缀命中率出发,怎样逐层推导剩余的注意力计算、完整预填充的理想上界,以及最终需要实测的真实收益?
先统一六个容易混用的术语
- 前缀命中率:索引逻辑匹配到的连续前缀 token 数与完整输入长度之比,本文记为
。它描述匹配结果,不保证状态已经可用。 - 可复用边界:所有续算必需状态都完整、版本一致并对齐到的最晚 token 边界。不同状态组共同可用时,通常取最短的那个边界。
- 有效复用:推理引擎真正恢复成功并能够跳过计算的 token 范围。它不大于逻辑前缀匹配,还会受到页、检查点与状态完整性的限制。
- 回载:从本地或远端缓存找到对象后,把它读回推理引擎可以使用的位置,并完成必要同步。
- 注意力计算加速比:基线注意力计算量与剩余注意力计算量的比值;不包含 MLP、投影、通信和缓存恢复。
- 完整预填充加速比:完整预填充基线成本与复用后端到端成本的比值;真实值应包含排队、回载、重算和运行时开销。
因此,缓存命中不等于推理引擎已经成功恢复状态,逻辑前缀匹配也不等于实际可复用前缀。后文的计算量曲线是理想上界;除非明确写成实测,否则它们都不是 TTFT 或生产吞吐结论。
把完整输入长度记为
当
下面这张图可以直接拖动命中率。它展示的不是缓存容量,也不是端到端 TTFT,而是完整因果注意力的计算几何。
因果几何实验
拖动前缀命中率,观察真正被跳过的 Attention 三角形
带斜纹的绿色网格是已缓存 Query 行,带点纹的蓝色网格是仍需计算的后缀。这里归一化的是完整因果 Attention FLOPs。
逻辑前缀长度占完整输入长度的比例。
剩余 FLOPs
75.0%
1 − 0.50²
理想加速比
1.33×
1 / (1 − α²)
仅限 Attention 主体的理想上界
命中 50% 的 token,只跳过左上角面积为 25.0% 的旧 Query;新 Query 仍要读取命中前缀中的 Key/Value。
前缀缓存跳过的是旧查询,不是旧键
没有前缀缓存时,长度为
命中长度为
已缓存的前缀查询 [0, P)
不再执行
新增的后缀查询 [P, N)
仍需读取键 [0, 当前位置]
因此跳过的工作量是左上角边长为
剩余工作量为:
除以完整预填充的
这个推导揭示了一个容易忽略的事实:前缀最前面的 token 最便宜,最后面的 token 最贵。 命中 50% token 跳过的恰好是较便宜的 25% 注意力面积;剩下 50% 查询行包含更长的历史依赖。
同一个命中率,在 MHA、MLA、SWA、KDA 上不是同一条曲线
前缀缓存的收益取决于注意力机制如何消费历史。下面的交互曲线统一把完整请求的注意力工作量归一化为 1,并比较三种理想模型:
- MHA / MLA:完整因果注意力,剩余比例是
; - SWA:只看最近窗口
,本文可视化固定为 ; - KDA 递推部分:递推扫描与 token 数近似线性,但只能从不晚于命中位置的完整检查点继续。
Attention 加速比探索器
相同前缀命中率,不同 Attention 的可跳过工作量并不相同
曲线只比较理想化的 Attention 主体:MHA/MLA 使用完整因果依赖,SWA 取 W = 0.25N;KDA 只计算递推部分,并把续算边界向下对齐到 checkpoint。
这里显示逻辑匹配比例;KDA 可能只能从更早的 checkpoint 恢复。
固定示例长度 N = 32,768 个 token。
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 / MLA | SWA, | KDA 递推部分, |
|---|---|---|---|
| 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 的缓存宽度和读写方式。但只要主注意力仍对完整历史执行因果注意力,它的查询与键依赖仍是下三角:
MLA 可以让缓存更小、内存流量更低,也可能改变内核常数;这些收益不能把前缀命中率公式直接改成
SWA:上下文超过窗口后,边际成本趋向线性
滑动窗口注意力的第
命中比例为
当前缀和完整请求都远大于窗口,后续每个 token 的注意力成本接近固定的
KDA:递推计算可以线性跳过,恢复边界却必须对齐
KDA 一类线性注意力不再为每个查询回读完整历史,而是递推有限状态。若完整预填充的递推扫描成本近似为
交互图默认取
这里的“存在检查点”仍是强条件。一个可续算的检查点必须包含该边界完整的递推状态;如果 ShortConv 或其它续算状态存在,也必须一起保持完整、版本一致且对齐。只有 token 哈希命中、只有 MLA 页命中,或只有递推矩阵存在,都不能证明请求能从该边界直接恢复。
曲线只计算递推部分的剩余 token,并假设对齐的检查点可以立即使用。它忽略检查点查询、加载、跨层或跨设备传输、同步,以及恢复状态本身的成本;未对齐的前缀 token 已计入剩余 token,但没有额外建模检查点重算的实现开销。因此它是检查点精确对齐条件下的理想上界,不是生产环境收益预测。
现实中的注意力模式
上面的公式描述依赖模式,而不是某个模型版本。下面的家族名称只是帮助把抽象模式映射到常见实现;具体模型仍应以自己的配置和代码为准。
| 模式 | 代表性示例 | 缓存含义 |
|---|---|---|
| GQA | Llama / Qwen / GLM 系列 | KV 缓存;因果几何不变 |
| MLA | DeepSeek 系列 | 压缩的 KV 低维状态 |
| SWA | Mistral 系列 | 滑动窗口历史复用 |
| KDA | Kimi 系列 | 递推检查点状态 |
这张表不改变推导:先识别历史依赖是完整因果、滑动窗口还是递推,再选择对应的剩余计算量模型。
为什么逻辑命中还要经过可复用边界
前缀缓存能跳过计算的前提,不只是索引找到了相同 token。推理引擎必须恢复该注意力模式续算所需的完整状态:它可能是 KV 页、MLA 低维状态、窗口尾部状态,或递推检查点。下面的演进图用于比较这些恢复边界,而不是给模型做分类。
状态演进
Attention 缓存正在从 KV 张量演化为可恢复的状态集合
选择任一阶段,比较它保存什么、如何增长,以及前缀复用真正需要恢复到哪个边界。
使用系统原生选择器,在五种状态表示之间切换。
阶段 1 · 逐 token 历史
KV Cache
MHA/GQA/MQA 将每个历史 token 的 Key 与 Value 直接保存下来;下一条 Query 读取这段完整历史。
- 状态表示
- 每层 Kₜ + Vₜ
- 存储增长
- O(T · Hkv · d)
- 恢复条件
- 恢复 [0, t) 的全部 KV 页
- 物理对象通常按层、页、K/V 与并行 rank 拆分
- 前缀命中的是连续 token 边界,不是任意页集合
这里的系统结论只有一个:对象或页的 match(匹配)只说明某个键存在;有效复用要求所有必需状态在同一 token 边界完整命中,并成功回载到执行引擎。对混合模型,最终可跳过的长度通常是各状态组共同连续前缀的最小值。
注意力计算加速不等于完整预填充加速
前面的
假设基线 Transformer 模块的计算量中,注意力占比为
下面的交互把注意力主体、逐 token 算子与完整模块的理想加速放在一起。默认
完整 Prefill FLOPs 模型
Attention 加速不是整个 Transformer 模块的加速
α 控制前缀命中比例,β 控制 Attention 在原始模块 FLOPs 中的占比;结果是理想化计算量模型,不是实测 TTFT 或生产吞吐。
逻辑前缀匹配长度占完整输入序列的比例。
Attention 在未使用缓存时的 Transformer 模块 FLOPs 中所占的比例。
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、同步、检查点重算与调度等待归一化为
例如
对应约
这个模型只是建立量纲,不应替代性能分析。FlashAttention 分块、分块预填充、批次打包、张量并行通信和 MoE 路由都会让真实曲线偏离连续面积模型。注意力计算量的加速比不等于 TTFT 加速比,理想上界也不等于生产吞吐。
把命中率变成可解释的性能指标
只上报一个 prefix_hit_rate,很难解释 TTFT。至少应把下面几层分开:
| 指标 | 回答的问题 |
|---|---|
| 逻辑匹配 token 数 | Radix/hash 最长匹配到了多少 token? |
| 可恢复匹配 token 数 | 所有必需注意力状态共同完整到了哪个边界? |
| 重算 token 数 | 因页或检查点粒度不对齐,需要补算多少 token? |
| 状态查询与回载延迟 | 命中状态从元数据到设备可用花了多久? |
| 后缀预填充延迟 | 恢复后真正计算新 token 花了多久? |
| TTFT | 排队、调度、传输、预填充与首 token 解码合计多久? |
对完整因果 MHA/MLA,还可以同时上报一个由命中率推导出的注意力工作量指标:
而不是用
从前缀命中率到真实吞吐
全文的推导可以压缩成下面一条因果链。每一步都需要额外信息,因此不能从最上面的命中率直接跳到最下面的生产吞吐。
总结模型
前缀命中率是输入,不是性能结论
先由注意力依赖决定剩余计算,再从计算量上界走向包含恢复开销的实测收益。
- 1
前缀命中率
α = P_match / N
索引匹配到的逻辑前缀
- 2
注意力依赖模式
完整因果 / 滑动窗口 / 递推
决定历史如何参与新查询
- 3
剩余计算量
R(α, 可复用边界)
结合计算几何与可恢复边界
- 4
理想加速比
S_ideal = 1 / R
归一化计算量的理想上界
- 5
真实吞吐
实测
再计入回载、重放、调度与内核效率
常见误区
四个等号都不成立
左边是可观测输入或局部上界,右边才是需要继续推导或实测的量。
命中不等于节省的计算
- 前缀命中率 ≠ 节省的计算量
- 逻辑前缀匹配 ≠ 实际可复用状态
局部上界不等于生产延迟
- 注意力加速比 ≠ 预填充加速比
- 理想计算模型 ≠ 生产环境 TTFT
所以“前缀缓存命中 50%”不是一个性能结论,而是一条输入事实。对完整因果注意力,它首先意味着只跳过 25% 的注意力计算;对完整预填充,还要继续计入逐 token 计算;对生产系统,则必须再验证状态恢复和数据搬运是否真的比重算便宜。只有走完这条链,才能回答预填充到底会快多少。