从注意力公式出发,理解 KV Cache
生成每一个新 token 时,模型其实一直在重复计算已经算过的东西。KV Cache 做的事情只有一件:把能复用的留下来。
Understanding the KV Cache, Starting from the Attention Formula
Every time it generates a new token, the model keeps redoing work it has already done. The KV cache does exactly one thing — it keeps what can be reused.
示例文章:用来展示公式、代码、表格和脚注的排版,换成你自己的文章后删掉即可。
为什么会有重复计算
自回归生成时,第 步的 query 要和前面所有位置的 key、value 做注意力:
关键在于:前面位置的 和 只取决于那些位置自己的输入,生成新 token 时它们不会变1。既然不变,就没必要每一步都重算一遍。
代码里长什么样
去掉多头和批次维度,核心只有几行:
def attend(q, k_new, v_new, cache):
# 把这一步新算出的 K、V 追加进缓存
cache.k = torch.cat([cache.k, k_new], dim=-2)
cache.v = torch.cat([cache.v, v_new], dim=-2)
scores = q @ cache.k.transpose(-2, -1) / math.sqrt(q.size(-1))
return scores.softmax(dim=-1) @ cache.v
代价:显存
缓存省下了计算,但要占显存。每个 token 需要存的字节数是:
以 Llama 2 7B、FP16 为例(,,,):
| 上下文长度 | KV Cache 大小 | 相对模型权重 |
|---|---|---|
| 1 token | 0.5 MiB | ≈ 0% |
| 4,096 tokens | 2 GiB | ≈ 16% |
| 4,096 × 批次 32 | 64 GiB | ≈ 5 倍 |
旁注 这就是 MQA、GQA 要解决的问题:让多个 query 头共享同一组 、,把上式里的 变小。下一篇会讲。
Footnotes
-
在使用因果掩码(causal mask)的解码器里成立;编码器的双向注意力不适用。 ↩
Sample article: it shows how formulas, code, tables and footnotes are typeset. Replace it with your own and delete this one.
Where the repeated work comes from
In autoregressive generation, the query at step attends to the keys and values at every earlier position:
The key point: the and of earlier positions depend only on those positions’ own inputs, so they don’t change when a new token is generated1. If they don’t change, there’s no reason to recompute them at every step.
What it looks like in code
Strip away the heads and batch dimensions, and the core is only a few lines:
def attend(q, k_new, v_new, cache):
# Append this step's new K and V to the cache
cache.k = torch.cat([cache.k, k_new], dim=-2)
cache.v = torch.cat([cache.v, v_new], dim=-2)
scores = q @ cache.k.transpose(-2, -1) / math.sqrt(q.size(-1))
return scores.softmax(dim=-1) @ cache.v
The cost: memory
The cache saves compute, but it takes memory. Each token needs this many bytes:
Take Llama 2 7B in FP16 as an example (, , , ):
| Context length | KV cache size | Relative to model weights |
|---|---|---|
| 1 token | 0.5 MiB | ≈ 0% |
| 4,096 tokens | 2 GiB | ≈ 16% |
| 4,096 × batch of 32 | 64 GiB | ≈ 5× |
Aside This is the problem MQA and GQA solve: several query heads share one set of and , which shrinks in the formula above. That’s the next post.
Footnotes
-
This holds for decoders with a causal mask; it doesn’t apply to an encoder’s bidirectional attention. ↩