粗浅理解大模型 KV Cache
我们先来了解一下大模型对于 Token 的处理流程(学习过 RAG 开发的应该大概知道这个处理流程):
Token
│
▼
Embedding // Token向量化
│
▼
Layer1 // 不断加工向量
│
▼
Layer2
│
▼
...
│
▼
LayerN // N次加工向量本文章由于篇幅原因,就不在此展开讲解处理流程相关内容,具体细节可以看:大模型处理Token流程
KV Cache是什么?作用又是什么?
KV Cache(Key-Value Cache)是大语言模型在推理过程中使用的一种缓存机制。它会缓存历史 Token 在注意力(Attention)计算中产生的 Key 和 Value 向量,使模型生成新 Token 时无需重复计算历史内容,从而显著提升推理效率。
假设用户输入一句话:
介绍一下 Go 协程经过 Tokenizer 后,会变成多个 Token(假设状态):
[介绍][一下][Go][协程]进入 Transformer 后,每一层都会计算三组向量:
$$ Q=XW_Q $$
其中:
- Query(Q):当前 Token 用来"查询"其它 Token。
- Key(K):表示每个 Token 的特征,用来和 Query 计算相关性。
- Value(V):真正携带语义信息,被 Attention 加权后输出。
Attention 的计算公式为:
$$ Attention(Q,K,V)=softmax\left(\frac{QK^T}{\sqrt d}\right)V $$
可以发现:
当前 Token 只需要自己的 Query,但需要所有历史 Token 的 Key 和 Value。
这也是 KV Cache 存在的根本原因。
没有 KV Cache 会发生什么?
假设模型已经生成了:
Go 语言是一门现在准备生成下一个 Token。
如果没有 KV Cache,模型实际上需要重新计算整个上下文:
Go
语言
是
一门所有 Token 都要重新经过:
Embedding
↓
Layer1
↓
Layer2
↓
...
↓
LayerN每一层都会重新计算:
Q
K
V即使前面的 Token 已经计算过很多次,它们仍然会被重新计算。
这意味着:每生成一个 Token,整个上下文都会重新 Forward 一遍。
随着上下文越来越长,这种重复计算会迅速成为推理瓶颈。
KV Cache 缓存了什么?
KV Cache 的核心思想非常简单:
历史 Token 的 Key 和 Value 一旦计算完成,在后续推理过程中就不会发生变化。
例如:
Go
语言
是已经完成计算:
Go -> K1 V1
语言 -> K2 V2
是 -> K3 V3这些结果会直接存入 KV Cache。
下一步生成:一门 。
模型只需要计算:
Q4
K4
V4然后把:
K4
V4追加到缓存中。
历史 Token 的:
K1
V1
K2
V2
K3
V3全部直接读取,不需要重新计算。
因此,KV Cache 会随着模型不断生成 Token 而持续增长。
为什么不缓存 Query?
Query 只有一个作用:查询历史。
例如生成第 100 个 Token 时:
Q100会去查询:
K1 ~ K99得到 Attention 权重。
下一步生成:
Q101上一轮的:
Q100已经不会再参与任何计算。
因此,Query 是一次性的。
而:
K1
V1却会一直被未来所有 Token 使用。
所以真正值得缓存的是:
Key
Value而不是 Query。
KV Cache 并不是缓存一句话
很多人第一次接触 KV Cache 时,会误以为:"缓存的是整个上下文"
其实更加准确的说法应该是:
缓存的是每一层、每一个 Token 的 Key 和 Value。
例如一个拥有 32 层的 Transformer:
Layer1
Token1 KV
Token2 KV
Token3 KV
Layer2
Token1 KV
Token2 KV
Token3 KV
...
Layer32因此:
一个 Token 会对应 32 层的 KV。
这也是为什么 上下文 (Context) 越长,显存占用增长得非常快。
Prefill 和 Decode
现代 LLM 推理通常可以分成两个阶段。
第一阶段 Prefill
假设用户输入:
介绍一下 Go 协程因为所有 Token 已经知道,所以 GPU 可以一次性并行计算整个 Prompt。
最终得到:
Prompt
↓
所有 Layer 的 KV
↓
写入 Cache整个 Prompt 只需要 Forward 一次。
这一步通常拥有非常高的 GPU 利用率。
第二阶段 Decode
随后模型开始逐 Token 生成内容。
例如:
Go
↓
语言
↓
中
↓
...每生成一个 Token:
都会:
计算新的 Q
↓
计算新的 K
↓
计算新的 V
↓
追加到 Cache因此 KV Cache 会不断增长。
这里需要注意:模型自己生成的 Token,也会成为后续生成的上下文。
这也是 Transformer 被称为 Autoregressive(自回归)模型 的原因。
为什么 Context Window 越长越慢?
现在很多模型支持:
- 128K
- 200K
- 甚至 1M Context
很多人认为:"上下文越长,只是缓存多一点而已"
事实上,问题没有这么简单。假设已经拥有:
100000 TokenKV Cache 中已经保存了:
100000 Token × 32 Layer × Key × Value当模型生成下一个 Token 时:
虽然不用重新计算历史 Token。
但是,Attention 仍然需要读取:
Q_new × 100000 个历史 Key然后再根据权重读取:
100000 个历史 Value因此:上下文越长,每生成一个 Token,需要读取的 KV 就越多。
真正的瓶颈逐渐从 计算 → 显存带宽(Memory Bandwidth)
超长上下文效果会下降是否与 KV Cache 有关系
阅读完上面对于 KV Cache 的讲解,你可能会联想到我们用 AI 时的超长上下文问题,是不是也会因为 KV Cache 过多而受到影响?
模型在超长 Context 下效果下降,更多来自三个方面:
- Attention 稀释。需要关注的信息越来越多,模型更难把注意力集中到真正重要的位置。
- 位置编码的限制。即使使用 RoPE、YaRN、LongRoPE 等技术,超长距离的位置表示仍然会逐渐失真。
- 训练数据限制。很多模型主要训练于几千到几万 Token 的上下文。
当直接推理 100 万 Token 时,模型实际上并没有见过这么长的上下文,自然容易出现性能下降。
因此KV Cache 主要影响的是推理效率,而不是模型理解能力。
为什么现在几乎所有推理框架都在优化 KV Cache?
对于现代推理框架来说,模型参数通常只需要加载一次。真正不断增长的是 每个用户会话的 KV Cache。
例如:
1000 个用户
↓
1000 份 KV Cache随着 Context 增长,KV Cache 很可能比模型参数本身还占显存。因此,近年来大量推理优化技术,例如:
- FlashAttention
- PagedAttention
- Multi-Query Attention(MQA)
- Grouped-Query Attention(GQA)
几乎都围绕:如何减少 KV Cache 的占用、提升读取效率。
可以说现代大模型推理真正的瓶颈,很多时候不是 GPU 算力,而是如何高效管理和读取 KV Cache。
当然现在很多企业其实一定程度上还是受到 GPU 算力影响,毕竟产品是需要针对于外面客户使用的,越强的算力就意味着能够支撑更好的模型、更快地效率、更强有力的竞争价格。上面的说法只是单独针对于大模型的现阶段的发展方向而言。
总结
KV Cache 的思想其实并不复杂:
- 历史 Token 的 Key 和 Value 不会变化,因此只计算一次。
- Query 是一次性的,因此没有缓存价值。
- Prompt 在 Prefill 阶段一次性建立 KV Cache。
- 模型生成的新 Token 会不断追加自己的 KV,形成新的上下文。
- Context 越长,KV Cache 越大,每一步需要读取的历史信息越多,因此推理速度逐渐下降。
从工程角度来看,KV Cache 是一个强有力的必要优化,是现代 Transformer 推理能够高效运行的基础。如今围绕 KV Cache 的压缩、共享、分页和管理优化,也已经成为 LLM 推理框架中最活跃的研究方向之一。
RoLingG | 博客
评论(0)