端侧 AI · 15 天工程路线
目录首页 English
DAY 11

KV Cache

用会话内存换取 Decode 阶段的历史投影复用

建议阅读:约 19 分钟

学习目标

理解 Prefill 与 Decode 的工作负载差异、K/V 为什么可缓存、cache 张量布局和容量斜率,并能比较 MHA、GQA、MQA、分页与低精度策略。

本章关键词

关键词解释ESP32 工程类比
Prefill批量处理已有 prompt 并为每层建立初始 K/V 状态的阶段。像连接建立时一次性解析完整握手报文。
Decode根据上一步输出逐 token 追加位置并生成下一 token 的阶段。像事件循环每轮等待上一步结果后再推进状态机。
KV cache保存历史 token 在每层产生的 Key 与 Value 投影的会话状态。像按层保存已核验字段的索引与数据描述符。
GQA / MQA让多组 Query head 共享更少 K/V head,从结构上降低每 token cache 宽度。像多个消费者共享较少的只读索引表。

承上:回顾与定位

上一章把 Attention 的 Q/K/V、causal mask 与多头聚合拆成显式数据流。本章只研究生成阶段如何复用历史 K/V,以及这种复用产生的会话内存、带宽与回收责任。

从源头看今天

历史发展脉络

KV cache 的历史是自回归生成从“数学上可行”走向“系统上可承载”的缩影。Transformer 定义了注意力,随后 MQA/GQA 减少每个 token 的状态,分页、复用与量化又开始解决动态会话带来的内存管理问题。

2017

Transformer 建立缩放点积与多头注意力

《Attention Is All You Need》把序列建模的瓶颈拆成计算量、串行步骤和远距离路径长度,用 encoder–decoder、缩放点积、多头注意力、位置编码与 causal mask 替代循环结构。输入处理因此更易并行,但自回归输出仍然按前缀逐 token 生成。

Attention Is All You Need 原始论文 ↗
2019

MQA 直接瞄准增量解码带宽

Multi-Query Attention 让多个 query head 共享一组 K/V head,显著缩小增量解码反复读取的 K/V 张量。

MQA 原始论文 ↗
2023

GQA 在质量与 cache 规模间增加档位

Grouped-Query Attention 让若干 query head 共享一个 K/V head,在传统多头注意力与单组 MQA 之间提供可调折中。

GQA 原始论文 ↗
2023

PagedAttention 处理动态会话碎片

PagedAttention 借鉴虚拟内存分页,把每个请求增长不定的 KV cache 映射到非连续块,并支持更灵活的共享。

PagedAttention 原始论文 ↗
2024

KV 量化成为独立优化方向

KIVI 分析 key 与 value 的数值分布并采用不同量化维度,说明 cache 精度可以像权重一样被单独纳入容量和质量预算。

KIVI 原始论文 ↗
现在

本地 runtime 暴露 cache 类型与 offload 策略

llama.cpp 等执行器把 K/V 数据类型、offload、上下文与缓存复用变成可配置项,cache 已是部署配置而非隐藏实现细节。

llama.cpp 官方 CLI 文档 ↗
今天为什么仍然重要: 今天选择模型时,权重文件只是静态门票;真正随 token 和会话增长的是 KV cache。容量、读带宽、位置语义、淘汰策略和取消时的资源回收必须在 runtime 设计阶段一并确定。
借熟悉的系统建立直觉

类比图解

侦探桌上不断增长的案卷索引卡

侦探读完每页证词后,不再每次从第一页重读,而是为每层分析保存两类索引卡:一类写“将来怎样找到这条线索”,另一类写“找到后取出什么内容”。新问题拿着查询卡扫过历史索引,形成判断,再把新证词的索引追加进去。

当前查询卡 新 token 在每层产生的 Query
线索目录卡 历史 token 的 Key cache
线索内容卡 历史 token 的 Value cache
分层案卷柜 按 layer、sequence、position、KV head 管理的缓存

类比的边界: KV cache 不是可读摘要、事实数据库,也不是原始 token 的无损副本;它是特定模型权重、位置编码和精度下的中间张量。换模型、改前缀位置或任意编辑历史后,旧卡片通常不能直接沿用,缓存也不会让注意力本身变成常数成本。

本章讲解

从一层因果注意力看懂为什么要存 K 和 V

每个 token 的隐藏状态在线性投影后得到 Q、K、V。当前 Q 与所有可见历史 K 计算相似度,经 causal mask 和 softmax 加权历史 V;模型权重固定时,旧 token 在该层的 K/V 不会因新 token 到来而改变,因此可以缓存。Q 只服务当前计算,通常无需长期保存。缓存省掉的是对历史 token 重做各层 K/V 投影,却仍要读取历史 cache 并完成随上下文增长的注意力计算。

先用模型结构计算 cache 斜率

常见近似为 batch×layers×2×tokens×kv_heads×head_dim×每元素字节数,其中 2 代表 K 和 V;实现还含对齐和分页元数据。不要用 attention heads 代替 kv_heads,GQA/MQA 模型二者不同。把公式化成“每新增一个 token、一个会话增加多少字节”,再叠加权重、临时 workspace 与 runtime 开销。若实测斜率不符,检查 cache dtype、滑窗和预分配。

用增长曲线、容量算例与状态测试验证 cache

MQA/GQA 从模型结构上减少 KV heads,低比特 cache 减少容量和带宽,滑动窗口限制可访问的远端历史,而分页主要减少碎片;这些节省不能混为一谈。固定模型和采样参数,逐级增加 prompt 长度并记录 prefill、每 token 延迟和 KV 字节斜率;以 32 层、8 个 KV head、head_dim 128、FP16 为例,每 token 约 128 KiB,4096 token 约 512 MiB。再随机启动、增长和取消分页会话,核对空闲块回到基线,并用关闭 cache 的参考路径检查 logits 与位置没有被破坏。

Prefill 与 Decode 必须分开记账

Prefill 批量处理 prompt 的全部 token,通常有更高并行度和计算密度;Decode 每轮只产生一个新位置,却反复读取逐渐增长的历史 K/V,常更受内存带宽和调度开销影响。TTFT 包含排队、tokenize、prefill 与首轮采样,ITL 则关注后续 token 间隔。把两者平均成一个 tokens/s,会掩盖长 prompt、短回答和多会话场景中完全不同的瓶颈。

Cache 正确性依赖模型、前缀、位置和精度

K/V 是特定层权重、特定 token 前缀、位置编码和 cache dtype 下产生的中间张量。模型或 adapter 改变、前缀中间被编辑、position 偏移或旋转位置参数不一致时,旧 cache 通常不可直接复用。Prompt cache 的键必须覆盖这些身份信息;取消、超时和会话淘汰还要把页面或槽位完整归还,否则系统会在长时间运行后出现隐性容量泄漏。

让数据真正跑起来

动态过程演示

一个新 token 如何读取并扩展 KV Cache

播放器区分只发生一次的 prefill 与重复发生的 decode,并让缓存随步数增长。

步骤 1 / 6

Prefill 提示 · N prompt tokens

并行处理已有序列,在每层生成 K/V

观察点

首轮集中计算并建立历史状态

循环 / 返回条件: “追加并循环”回到“产生新 Query”,直到 EOS、长度上限或取消;取消必须释放该序列占用的页或槽位。

查看静态全景图
Prompt tokens ──Prefill──► per-layer K/V cache
New token ──Q,K,V──► Q × cached Kᵀ → weighted V → logits → sample
                         └──── append new K/V and repeat ────┘

代码或命令示例

kv_bytes = batch * layers * 2 * tokens * kv_heads * head_dim * bytes_per_value
bytes_per_new_token = batch * layers * 2 * kv_heads * head_dim * bytes_per_value
# 实现还需计入对齐、分页元数据与预分配策略

动手实验

为一个实际模型读取 layers、kv_heads、head_dim 与 cache dtype,先手算每新增 token 的 KV 字节数,再分别估算 512、2048、4096 token。运行本地 runtime 改变 context 长度,记录内存、prefill 时间与每 token 延迟;若支持 GQA/MQA 或 KV 量化,再比较容量斜率和输出质量。
实验记录与导出

工程陷阱

避免误判: 只按权重文件估算 RAM,或把分页、滑窗与 GQA 都称为“压缩 KV”。它们分别处理碎片、可见历史和每 token 状态宽度,容量、语义和质量代价不同。

核心测试

完成 3 道单选题后提交;提交前不会显示答案。

1. KV cache 容量与 context length 的关系通常是?
2. GQA 如何降低 KV cache?
3. PagedAttention 主要直接改善什么?

一起把本章讲清楚

在 GitHub 上讨论本章

登录 GitHub 后提问、补充实测或分享你的实现;评论会保存在本课程的 GitHub Discussions 中。

评论区需要 JavaScript;也可以直接打开 GitHub 讨论区: 打开 GitHub 讨论区 ↗

延伸阅读

启下:下一章如何使用本章能力

下一章进入 LLM 量化与 GGUF:把权重、张量类型、block 开销、metadata 与输出质量连成可验证的模型包。

下一天: Day 12 · LLM 量化与 GGUF