KV Cache 机制详解

KV Cache 机制详解

作者:小H无记

发布日期:2026年9月29日 18:49

来源:原文链接




Study in 2026.9


文章图片


〇、一句话本质

KV cache = 用显存换计算:把「历史 token 的 K/V」存起来,自回归生成时只算新 token,避免 O(n²) 的冗余重算。


往下读,你会看到这句话的每个词都不是随口说的:为什么缓存的是 K/V 而不是别的、为什么是「历史 token」、为什么换来的是「计算」而不是别的。


先交代背景:长上下文推理(128K、1M context)是当前大模型的核心战场,而 KV cache 的显存和带宽正是长上下文的头号瓶颈。要搞懂 KV cache,得先搞懂它缓存的「KV」是什么、从哪来——这就要回到 Transformer 的起源,从 Attention 讲起。


一、Attention 是怎么来的:从 RNN 的困境到 Q/K/V

1.1 起源:RNN 的兴衰

1.1.1 RNN 是什么:一个循环的「记忆」单元

在 Transformer(2017)之前,处理序列数据(文本、语音、时间序列)的主力是 RNN(循环神经网络,Recurrent Neural Network)。它的思想可以追溯到 1980 年代的 Hopfield 网络、1990 年代的 Elman 网络;到了 2010 年代,它成了 NLP(自然语言处理)的绝对主力——机器翻译、语音识别、语言模型,几乎都用 RNN 及其变体(LSTM、GRU)。


RNN 的核心思想非常朴素:序列是有顺序的,前面的信息会影响后面的理解,所以需要一个「记忆」把前面的信息带到后面。这个「记忆」就是隐藏状态(hidden state)h,它每一步都在更新:

h_t = f(W·x_t + U·h_{t-1})

文章图片

读这个公式:当前步的隐藏状态 h_t,融合了「当前输入 x_t」和「上一步的记忆 h_{t-1}」——这就是 RNN 的「记忆」机制,也是它名字里「循环(Recurrent)」的由来。


1.1.2 RNN 的优势

文章图片


1.1.3 RNN 的两个致命问题

RNN 的优势很大,但两个致命问题,最终让它被 Transformer 取代:

文章图片
文章图片

这两个问题的本质:问题①是「计算效率」问题(串行),问题②是「信息传递」问题(长距离衰减)。而后面要讲的 Attention,正是同时解决这两个问题的答案——这为 1.2 的「Attention 诞生」埋下了伏笔。


1.2 转折:Attention 的诞生(2014-2015)

2014 年,Bahdanau 等人提出 Attention 机制(用于机器翻译的 Seq2Seq),核心是一个颠覆性的想法:


与其让解码的每一步「只靠最后一步的隐藏状态」(RNN 的做法),不如让每一步「直接看到输入的所有位置」,自己决定该关注哪里。

文章图片

这个「直接看全局」的能力,一举解决了 RNN 的「长距离依赖丢失」——不管距离多远,注意力都能一步直达。


1.3 集大成:Transformer 与自注意力(2017)

2017 年,Google 的论文《Attention Is All You Need》把这件事推向极致:完全抛弃 RNN,只用 Attention。


关键创新是「自注意力(Self-Attention)」:让序列内部的每个 token,都和其他所有 token 建立「注意力关系」——一个 token 的含义,由它「关注」哪些其他 token 决定。

文章图片

1.4 Q/K/V:怎么算、为什么这么算

1.4.1 计算:embedding 和三个投影矩阵

Q/K/V 的计算分两步:先把「文字」变成「向量」(embedding),再用三个矩阵投影成 Q/K/V。


第一步:token 变向量(embedding)——文本不能直接算,要先变成数字向量:

  1. 分词(tokenize):把文本切成 token("The cat" → "The"、"cat")。


  2. 映射 id:每个 token 查词典得到一个编号(如 "The" → #1001)。


  3. 查 embedding 表:用 id 到「词嵌入矩阵」(词表大小 × d_model)里查一行,得到这个 token 的向量 x(如 4096 维)。

关于「映射 id」:这个 id 不是随便编的,它来自「词表(vocabulary)」,而词表是分词器用 BPE(Byte Pair Encoding,字节对编码)算法从海量语料里「长」出来的:

文章图片


BPE 的思路:从「字符」开始,反复统计并合并「最高频的相邻字符对」("t"+"h"→"th","th"+"e"→"the"…),直到词表达到目标大小(如 5 万)。结果是「高频词」是完整词("the"、"cat"),「低频词」被拆成子词("unhappy"→"un"+"happy")。好处是:既能处理常见词,又不会「不认识」生僻词(OOV 问题)。


文章图片


embedding 表怎么训练——它分两个时代:

  1. 早期(2013 Word2Vec):专门训练词向量——用「上下文预测词」(CBOW)或「词预测上下文」(Skip-gram),让「出现在相似上下文里的词」向量相近。这就是「分布式假设」(distributional hypothesis):语义相近的词,上下文也相似。


  2. 现代 LLM:embedding 表不再是单独训练,而是「整个模型的一部分」,随整个模型用「预测下一个 token」这个任务一起训练。反向传播时,embedding 表和其他参数一起更新,最终学会「把语义编码进向量」。


一句话:embedding 表一开始是随机的,靠训练「逼」它把语义相近的词放到相近的位置。


第二步:向量投影成 Q/K/V——拿到向量 x 后,用三个训练出来的矩阵 W_Q、W_K、W_V 做「矩阵乘法」(线性变换):

Q = x · W_Q  K = x · W_K  V = x · W_V

文章图片

三个矩阵怎么来的:W_Q、W_K、W_V 都是 d_model × d_model 的矩阵,和 embedding 表一样「随机初始化 + 训练更新」。投影(x · W_Q)本质是一次「线性变换」——把 x 从「原始语义空间」变换到「查询空间 / 键空间 / 值空间」。


一句话总结:embedding 把「字」变成「语义向量」,三个矩阵再把「语义向量」投影成「提问(Q)、被匹配(K)、被取值(V)」三个角色。


1.4.2 为什么是「三个不同的投影」

关键点:一个 token 同时扮演三个角色,而这三个角色需要「不同的视角」:

因为「提问」「被提问」「被取值」是三个不同的角度,所以不能用同一个投影,必须用三个不同的 W_Q、W_K、W_V 分别投影。


类比:同一个人,在图书馆里「找书」时是读者(Q),被检索系统「当标签」时是书(K),被借走「当内容」时是书的内容(V)——同一个对象,三个不同的角色,所以要三个不同的投影。


1.4.3 语义来源:为什么叫「查询-键-值」

Q/K/V 这个名字不是随便起的,它来自信息检索(Information Retrieval)和数据库的经典「查询-键-值(Query-Key-Value)」范式:

文章图片

注意力计算三步:① 相关度 score = Q·Kᵀ;② 归一化 softmax;③ 加权求和 output = softmax(QKᵀ)·V。


关键观察(为第二章埋下伏笔):K 和 V 是「每个 token 的属性」——只取决于这个 token 本身(x 和 W_K、W_V 都是这个 token 的),一旦算出来就固定不变;而 Q 是「当前这一步的提问」——每一步的「当前位置」不同,Q 就不同。这个区别,就是后面「为什么缓存 K/V、不缓存 Q」的根源。


二、Attention 的完整流程 + 自回归

第一章讲了 Q/K/V 怎么算,这一章把它们串起来——先看 attention 的完整流程,再看自回归怎么反复用它,这会自然引出「因果掩码」和「KV cache」。


2.1 Attention 的完整流程(回顾)

一个 token 的 attention 输出,分三步算:

  1. 算相关度:score = Q · Kᵀ(当前 token 的 Q 和所有 token 的 K 点积)。

  2. 归一化:softmax 把分数变成概率(谁更该被关注)。

  3. 加权求和:output = 概率 · V(按关注度取走各 token 的内容)。

文章图片

注意这张图里的三个关键点——它们直接决定后面 KV cache 的设计:


2.2 自回归:一次一个 token,反复跑 attention

LLM 是「自回归」生成:一次只预测一个 token,然后把新 token 拼回输入,再预测下一个。生成 "Time flies fast" 的过程是:


输入 "Time"            → 生成 "flies" 

输入 "Time flies"      → 生成 "fast" 

输入 "Time flies fast" → 生成下一个 token


关键:每一步都要跑一遍完整的 attention(上面那三步)。而不做优化的话,每一步都要把「前面所有 token 的 K/V 重新算一遍」——因为步骤①和③需要所有历史的 K 和 V。序列越长,重复计算越多,总计算量是 O(n²)。这就是生成长文本时「越到后面越慢」的原因。


一个几乎人人都会卡住的疑问:预测 "fast" 时,Q 是谁的?


答案:Q 是 "flies" 的(最后一个已生成 token),不是 "fast" 的——"fast" 还没预测出来,它没有 Q。

文章图片


规律是:预测第 N 个词时,Q 来自第 N-1 个词(最后一个已生成的),K/V 来自前 N-1 个词(全部历史)。"fast" 的 Q,要等「预测 fast 的下一个词」时才出现。


类比:读填空题 "Time flies ____",你(Q)站在 "flies" 后面,眼睛扫过 "Time"、"flies"(用 Q 查 K/V),然后猜空格填什么(预测 "fast")。空格里的词还没出现,它不可能「提问」,只能「被预测出来」。


2.3 因果掩码 → KV cache 的诞生

但这里有一个关键洞察,来自「因果掩码」(causal mask):

自回归生成时,当前 token 只能看到它前面的 token(不能看未来)。这意味着:一个历史 token 的 K 和 V,不会因为它后面又生成了新 token 而改变。


既然历史 token 的 K/V 是「一成不变」的,那它们「算一次、永久复用」就是天经地义的——这就是 KV cache 成立的逻辑基础。


数据支撑:无缓存 vs 有缓存的实测,约 5 倍速度差(小模型上,无缓存 13.7s vs 有缓存 2.8s)。


下一章(第三章)就正式讲 KV cache 具体怎么「缓存 K/V、不缓存 Q」。


2.4 输出向量怎么变回 token(lm_head)

前面讲的是「token 怎么变成向量」(embedding),这里补上闭环的另一半——最后输出的向量,怎么变回预测的 token。


答案:不是「查表反查」,而是经过 lm_head(语言模型头)——一个线性层,把 512 维向量投影到「词表大小」维,再 softmax 取最大:

文章图片

关键一步是 logits = h · W_lm:h 是 512 维,W_lm 是「512 × 词表大小」的矩阵,乘出来是「词表大小」维——logits 的每个维度,就对应一个 token 的得分。softmax 之后变成概率,取最大(或采样)就是预测的 token。


为什么像「查表反查」:很多模型的 lm_head 权重 = embedding 表的转置(权重共享,tie weights)。所以「输入查 embedding 表」和「输出乘 lm_head」用的是同一个矩阵 E,方向相反:


直觉:一个 token 的 embedding 就是「这个 token 的语义坐标」,最后一层的 h 是「当前要预测内容的语义坐标」。h 和某个 token 的 embedding 越「近」(点积越大),就越可能是这个 token——所以「取点积最大」=「找和 h 最像的 token」。


三、KV Cache 原理:缓存什么、不缓存什么

基于上面的洞察,KV cache 的做法是:

于是推理被清晰分成两个阶段:

关键:Decode 阶段是「带宽敏感」,不是「算力敏感」——每步只算一个新 token(算力需求极小),但要「读」全部的 KV cache(数据量巨大)。这一句是理解后面「存储层级」和「KV cache 优化」的关键。


四、显存代价:KV Cache 到底占多大

前置:先懂 Transformer 和 Attention 的本质(基于《Attention Is All You Need》)


先看整张全景图——从输入到输出的完整结构(注意「× N 层」和「多头」两个关键概念):

文章图片

KV cache 的「KV」来自 attention,而 attention 的本质是论文里的核心公式——缩放点积注意力(Scaled Dot-Product Attention):


Attention(Q, K, V) = softmax( Q·Kᵀ / √d_k ) · V


读这个公式:当前 token 的 Q 和所有历史 token 的 K 做点积(算相关度)→ 除以 √d_k 缩放 → softmax 归一化 → 加权求和 V。这就是 attention 的全部。而「Q 每步变、K/V 是历史 token 的属性」这个性质,就藏在这个公式里——它是 KV cache 成立的根源。


再看 Transformer 的「一层」长什么样(论文里 encoder/decoder 每层都是这个结构):

文章图片

这一层 = Multi-Head Attention(产生 K/V)+ FFN(前馈网络),然后堆叠 N 层。每层有独立的 W_Q / W_K / W_V,所以每层的 K/V 都不同——这就是「KV cache 要 × 层数」的根源。


为什么需要「多层」堆叠?


因为一层 attention 只能做「一次浅层的信息混合」(softmax(QKᵀ)·V 只是一次加权组合),只能学到「词与词」的直接关系。要表达「深层、组合、抽象的语义」,必须堆叠多层,让信息逐层提炼:


这跟 CNN 堆深度学「边缘 → 纹理 → 物体」是同一个道理:深度 = 表达能力。而且同样的参数量,「堆深」比「加宽」更能表达复杂函数(深度神经网络的万能近似)。


这也解释了为什么「每层都要存 KV」:既然信息要逐层提炼,每一层都有自己的「中间表示」(这一层的 K/V),自回归时每层都要复用「自己这层的历史 K/V」——所以 KV cache 要「× 层数」。


前置:先搞懂「多头注意力」,公式才看得懂


第 4 章公式里的「层数、KV头数、头维度」,都来自 attention 的「多头(multi-head)」结构。一个 token 的向量不是「一整块」做 attention,而是被切成多个「头」,每个头独立算自己的 K/V:

文章图片

三个关键关系:头数 = 向量切成几个头;头维度 = hidden ÷ 头数(如 4096 ÷ 32 = 128);层数 = 这样的多头注意力块堆叠了几层。每个「头」都有一份 K/V,每份长度 = 头维度——这就是 KV cache 要「层 × 头 × 维度」铺开的原因。


KV cache 是「用显存换计算」,代价就是显存。这一章把「大小到底怎么算出来」一步步讲清楚。


4.1 公式逐项拆解

总 KV cache 大小的公式是:

KV = 2 × batch × 层数 × KV头数 × 头维度 × token数 × 字节/元素


每一项的含义:

文章图片

关于「token 数」:它 = prompt 的 token 数 + 已经生成(decode)出来的 token 数。KV cache 是「边生成边增长」的——prefill 阶段一次性缓存 prompt 所有 token 的 KV,decode 阶段每生成一个新 token 就追加一个新 token 的 KV。所以序列越长(长上下文),KV 越大。


4.2 为什么和「网络结构」有关

先回答一个最常见的疑问:为什么每一层都要存一份 KV?


因为 Transformer 是「多层堆叠」的,每一层都有自己独立的一套权重(W_Q / W_K / W_V),所以每一层的 K/V 是「这一层独有的向量」——第 1 层的 K¹V¹ 和第 2 层的 K²V² 是用不同权重算出来的,完全不同。

文章图片

所以自回归时,每一层都要缓存「自己这一层的」历史 K/V,才能下一步复用——这就是公式里「× 层数」的来历。


看公式就明白:KV cache 的大小,一大半由「网络结构」的四个参数决定——层数、KV头数、头维度、精度。同一个模型,结构一旦定了,「每个 token 的 KV 大小」就是一个固定常数,KV cache 只会随「token 数」线性增长。



4.3 具体算例:Llama2-7B 一步步算

Llama2-7B 的网络结构:32 层、32 个 KV 头(MHA)、头维度 128、FP16。一步步算「一个 token 的 KV」:


  1. 先算「一个 token、一层、一个头」的 K 向量:128 维 × 2 字节 = 256 字节。

  2. 一层有 32 个 KV 头:256 × 32 = 8KB(一层、一个 token 的 K)。

  3. K 和 V 各一份:8KB × 2 = 16KB(一层、一个 token 的 K+V)。

  4. 32 层:16KB × 32 = 512KB(一个 token 的全部 KV)。


所以 Llama2-7B 每个 token 的 KV cache = 512KB。

再乘以 token 数:4096 token(batch=1)= 512KB × 4096 ≈ 2GB。这就是「Llama2-7B 4096 ctx ≈ 2GB」的来历。


4.4 MHA vs GQA:为什么 GQA 省 8 倍

关键在「KV 头数」:MHA 每个 Q 头都有独立的 KV 头(32 个 Q 头 = 32 个 KV 头);GQA 让多个 Q 头共享一个 KV 头(比如 Qwen3 的 32 个 Q 头只配 4 个 KV 头,8:1 共享)。


因为 KV 大小正比于「KV 头数」,所以 GQA(4 头)比 MHA(32 头)省 8 倍;更激进的 MQA(只 1 个 KV 头)省 32 倍。(Llama2-70B 也是 64 头 MHA → 8 头 GQA,同样省 8 倍。)


4.5 长上下文爆炸

KV cache 随 token 数线性增长。256K 上下文的模型:512KB × 256K = 128GB,超过单卡显存——这就是 KV cache 优化的核心痛点。


五、优化技术全景(六个维度)

围绕「怎么让 KV cache 更小、更快」,有六个优化维度,每个都对应一个具体痛点:



六、实战:国内四大开源模型的 attention 与 KV cache 设计

6.1 DeepSeek:MLA(多头潜在注意力)

DeepSeek 从 V2 起用 MLA,是「低秩压缩 KV」路线的代表,也是最激进的 KV cache 优化之一。


下面这张图展示了 MLA 的完整流程——「写入时压缩缓存潜向量、读取时解压还原 K/V」:

文章图片


会损失 KV 信息吗?


会,但损失的是「冗余」,不是「有效信息」——而且压缩是训练学出来的,所以实际性能几乎不降,甚至更好。



6.2 Qwen:GQA → Gated DeltaNet

Qwen 走了「从 GQA 到线性注意力」的演进路线,两代思路完全不同:


Qwen3:GQA(分组查询注意力)——核心是「让多个 Q 头共享 KV 头」,用「减少 KV 头数」来省 KV cache,而 Q 头数不变(保持表达能力):


文章图片

Qwen3 典型配置:32 个 Q 头 / 4 个 KV 头(8:1 共享),head_dim 128,KV ≈ 96KB/token。还加了 QK-Norm(对 Q/K 归一化)提升训练稳定性。但 GQA 复杂度仍是 O(n²)——它只压缩了 KV 头数,没改变「每个新 token 要看所有历史」的本质。


Qwen3.5+:Gated DeltaNet(线性注意力)——范式级改变,不再用 KV cache,改用「固定大小的循环状态」:

文章图片


核心思想:用核函数近似 softmax,把「先算所有分数再加权」变成「边读边更新状态」,复杂度从 O(n²) 降到 O(n)。Gated DeltaNet 额外加了 delta rule(精准更新)+ 自适应门控(智能遗忘),解决线性注意力的「记忆饱和」问题。


拆开理解这个名字:Delta + Gated


状态 S 怎么设计——它是线性注意力的核心:

文章图片

一句话:S 是一个「固定大小的矩阵」(d_k × d_v),装「历史 K/V 的累加」,用它代替 KV cache。因为 output = φ(q)·S 数学上等效于注意力,所以不用存每个 token 的 KV,只需维护一个 S。代价是「有损压缩」(记忆饱和),所以需要 delta rule(精确更新)+ 门控(智能遗忘)来补救。


为什么它没有「完全解决」KV cache 爆炸,大家只学「混合」?

因为线性注意力本质是「有损压缩」——把「每个 token 的 KV」压进「一个固定大小的状态 S」,状态容量有限,长上下文会「记忆饱和」(旧的被新的挤掉)。这在「多跳推理」「长距离依赖」上暴露得很明显(MiniMax 实测:SFT 后超 32K 上下文的多跳推理有严重缺陷)。


所以它不是「银弹」,而是「用精度换显存」的权衡——大家的选择是「3:1 混合」(75% 线性层省显存 + 25% 全注意力层保精度),而不是「完全替代」:


此外还有两个现实障碍:硬件/生态不匹配(现有 GPU 和 FlashAttention 都为 softmax 注意力优化,线性注意力的「循环状态更新」需要全新 kernel);训练更难(需要特殊优化器如 Muon)。


6.3 GLM:MHA → MLA+DSA → KDA

GLM 的演进,核心是「怎么压缩 KV」。三种算法从三个不同维度压缩 KV:

文章图片

逐个讲清楚三个算法的具体算法:



DSA 的「选 top-K」发生在哪个阶段?

答案是 decode 阶段——prefill 阶段照常做稠密注意力(不稀疏)。这正好呼应前面「prefill 算力敏感、decode 带宽敏感」:


而且 decode 的 top-K 还能「复用」——上一步选的 top-K 和下一步高度相关(时间相关性),可以「猜 + 验证 + 修正」,不必每次从头算;prefill 没有上一步可复用,硬算反而更贵。


补充:注意力分数怎么算——就是 Q 和 K 的矩阵乘(点积)score = Q·Kᵀ/√d_k(当前 token 的 Q 和所有 token 的 K 点积,每个 token 一个分数,分数越大越相关)。DSA 里「算分数选 top-K」那一步,用的就是这个。


一个重要澄清:DSA 省「计算」,不省「存储」

DSA 是「动态」稀疏——每个 query 选「不同的」top-K,所以任何一个 token 的 KV 都可能被某个 query 需要,不能「只存一部分」。因此 KV cache 还是全量存,DSA 省的是 decode 时的「读取带宽 + 注意力计算」,存储量一点没省。


那「存储量」靠谁省?靠 MLA。这正是 GLM-5 把两者组合的原因——分工不同:



类比:MLA 把每本书「压成摘要」(书变薄,省书架空间=存储);DSA 找资料时只翻「最相关的 20 本」(省翻书时间=计算)。书一本没少(DSA 不省存储),书变薄(省存储)是 MLA 的功劳。


DSA 的三步流程(重点看「算分数 → 选 top-K → 精确注意力」):

文章图片

所以 GLM 的演进逻辑是「一层层加压缩」:



6.4 Kimi:KDA(线性注意力)+ MLA 混合

Kimi(月之暗面)是「长上下文」的代表,KDA(Kimi Delta Attention)是它原创的线性注意力:



Kimi 的架构是 KDA(线性)+ MLA(全注意力)的 3:1 混合:

文章图片


6.5 四条路线的本质对比

文章图片


6.6 对内存管理的启示(重点)


七、参考论文



   小  H


文章图片

  读书 | 思考 | 写作 | 心情


本文档由 易微信 整理导出