深入篇 21 / Transformer 逐步拆解 · 2026-11-08

KV cache:用等价实验理解“只算新 token”

解释为什么存 K/V 不存历史 Q,并验证完整前向与增量前向在误差容限内一致。

核心阅读 12–18 分钟练习另需 10–20 分钟前置:17 的 causal mask;19 的位置

本课只拿下这一件事

解释为什么存 K/V 不存历史 Q,并验证完整前向与增量前向在误差容限内一致。

缓存沿 token 轴增长。示意有效前缀增长;不是多个 decoder 层共用同一份 K/V。
示意有效前缀增长;不是多个 decoder 层共用同一份 K/V。

省掉的是哪些重复计算

没有缓存时,为生成下一个 token,会对整个前缀重新执行各层投影、FFN 和 attention。因果结构下,旧位置的本层输入不因未来 token 追加而变化,其 K/V 可以复用。下一轮需要的是新 query 对历史 key 的匹配,再用历史 value 汇总;历史 query 对未来输出无直接用途,所以通常不持久缓存 Q。

复杂度要说清“每一步”还是“全过程”

固定层数和维度,前缀长度为 t 时,不缓存的 dense attention 完整重算约 O(t²) 个 query-key 对;缓存后单步 decode 只有一个新 query,需要 O(t) 个。若从空序列生成 N 个 token,累计 attention 对数从 Σt²=O(N³) 降至 Σt=O(N²)。这不是“整段生成变成 O(N)”;投影、FFN、prefill、batch 等成本还要另外算。这个结论是对标准全注意力的运算量计数,不能直接套到 sliding window 或稀疏 attention。

一条容易错一位的不变量

正常 decode 轮开始前(无额外待验证 draft):
num_tokens = num_cached_tokens + 1
本轮输入 = last_token;position = num_tokens − 1
forward 后 cached += 1;采样后 num_tokens += 1

prefill 把 prompt 的 KV 全算完,采样出的第一个输出 token 此时没有 KV。下一次 decode 输入它,才创建它的 KV。nano-vllm 的 postprocess 先 hash_blocks,再更新 cached,最后 append_token;chunked prefill 尚未完成时会跳过 append。这些顺序正是在维护这个边界。

显存容量与生命周期

标准 K/V 缓存字节数是2LBTHkv d bytes。它包含每层、每请求、每 token 的两份张量;分页改变分配方式,不会让有效 token 的 K/V 字节神奇消失。权重、工作区和页尾浪费不在此式内。实验中允许小浮点差异;并行形状和 kernel 路径可能改变舍入,不能一律要求逐位完全相等。

把维度变化拆到每一步

  1. 追加的是cache的token轴;query只包含本轮新token。
  2. 分页写入依靠slot_mapping,逻辑追加不等于反复cat整段缓存。
逻辑时间轴到分页 KV 物理坐标。T=5、block_size=2、block_table=[3,0,4] 时逐token计算逻辑块、偏移、物理块和slot,并将每个K分量填进物理池的格子。
T=5、block_size=2、block_table=[3,0,4] 时逐token计算逻辑块、偏移、物理块和slot,并将每个K分量填进物理池的格子。打开原图放大 ↗

打开元素级图解:矩阵、轴、索引与可点击反例 →

一个 decode step 真正做的工作。最后刚采样的 token 已存在于 token_ids,但还没有自己的 KV。
最后刚采样的 token 已存在于 token_ids,但还没有自己的 KV。

改变变量,验证直觉

启用 JavaScript 后可交互;上面的图和下面的手算练习始终可读。

数值练习:先算,再核对

运行 python labs/transformer_lab.py cache。实验用固定随机种子、两层 pre-norm toy decoder、RoPE、GQA 和 SwiGLU,比较完整 causal 前向、逐 token 前向与两段 chunk。故意错误的 chunk mask 必须产生明显偏差,否则实验没检验到关键问题。

闭卷检索

不要回看正文。答错后先解释原因,再重新打开这一课。

把结论落到源码

  1. 按 scheduler.postprocess 的实际顺序跟踪 cached、scheduled 与 num_tokens。
  2. 用 prepare_decode 的 context_lens 和 positions 解释为什么两者相差1。

以下行号由本地 vendor 文件生成;上游链接固定完整 commit,离线摘录可直接打开。

优先阅读:本地 scheduler.postprocess 和 PagedAttention 论文 §2–3。

速查:形状与公式 · 三框架映射 · 术语表 · 复习与实战路线 · 4070 Ti本地实验

记录你此刻能做到什么

只在当前浏览器保存自评;不会自动写成已掌握的 learning record。

随时追问老师。把“第 21 课 + 哪个公式 / 哪个张量 / 哪一行 + 你的推导”发给我,我们可以继续拆到每一步。