大模型中的数学(003):为什么每个 Token 的 KV 会受到上下文影响?
从一层到两层:KV 为什么会带上上下文?
讨论 KV Cache 时,很容易产生一个疑问:每个 token 不都是各自计算自己的 Key 和 Value 吗?既然如此,同一个 token 出现在不同句子里,为什么它的 KV 还会不同?
先看第一层
设第 个 token 的初始表示为 。它通常由 token embedding 得到;位置相关的信息如何加入,则取决于模型采用的位置编码方式。
以常见的 Pre-LN Transformer 为例,第一层先对输入做归一化,再计算 Key 和 Value:
先注意一个细节:这两个公式里没有出现其他 token 的隐藏状态。因此,如果模型参数、当前 token 的初始表示以及位置条件都相同,那么第一层的 K、V 可以相同,即使前面的文本不同。
不过,“位置条件相同”不能省略。以 RoPE 为例,投影得到的 Query 和 Key 还会经过位置相关的旋转;如果 token 的位置变了,实际用于 Attention 的 Key 也可能随之变化。所以下文比较不同上下文时,先假设目标 token 的位置相同,暂时排除位置差异的影响。
第一层的 KV 看起来确实可以只由当前位置的初始表示决定。上下文是从哪里进入计算的呢?关键在 Attention。
上下文是怎样进入隐藏状态的?
考虑因果 Attention。位置 只能读取位置 ,不能读取未来 token。对单个注意力头,位置 对位置 的注意力权重为
于是,位置 的 Attention 输出是
这一步才发生了跨 token 的信息交互。 是多个位置的 Value 的加权和,权重则由 Query 和各个 Key 的匹配程度决定。它不只是当前位置自己的信息,也可能包含前面 token 提供的信息。
例如,假设我们关注句子末尾的“苹果”。在“我喜欢苹果”中,它可以读取“我”和“喜欢”;在前缀不同、但目标 token 位置相同的另一句话中,它能够读取的内容或分配的注意力权重可能不同。即使“苹果”的初始表示一样,Attention 输出也不必一样。
实际模型通常使用多头 Attention。不同头会分别计算注意力,再把结果拼接并经过输出投影。为了看清信息依赖,上面的单头公式已经足够;多头结构不会改变这个结论。
Attention 输出如何影响下一层?
Attention 输出并不是直接替换原来的隐藏状态。常见的 Pre-LN Block 还带有残差连接和 FFN。把第一层写完整:
第一条公式把 Attention 提取的上下文信息加回原始表示;第二条公式再对这个结果逐位置地做 FFN 变换。FFN 通常不会直接混合不同位置的向量,跨 token 的信息交换主要发生在 Attention 中。不过,FFN 的输入已经含有上下文信息,所以它的输出也会保留并进一步变换这些信息。
将 Attention 的表达式代入,可以把第一层的输出理解为
这里省略了多头 Attention 的输出投影,只保留单头形式来展示依赖关系。公式不必逐项计算,重点是: 已经可能依赖前面多个位置的表示。
把第二层的 KV 展开
到了第二层,模型使用的输入不再是最初的 ,而是第一层输出的 。因此,第二层的 Key 和 Value 为
把前面两条残差公式代入 Key 的表达式,就得到
而
所以,第二层的 Key 通过 间接依赖前面 token 的 Value 和注意力权重;第二层的 Value 也有同样的依赖关系,因为它同样由 投影而来。
这就是“后续层的 KV 会受到上下文影响”的具体含义。不是说模型在计算 时,额外把所有前文 token ID 拼进了一个公式;而是前文已经通过第一层 Attention 影响了 ,第二层再从这个新表示计算 K、V。
上面的公式采用 Pre-LN 结构。其他 Transformer 变体的归一化位置和残差形式可能不同,但只要某一层的隐藏状态经过了上下文 Attention,后续层从该隐藏状态生成的 KV 就可能带有上下文信息。
这为什么会影响 KV Cache 复用?
假设 Chunk 2 单独运行时,前面没有 Chunk 1;而在完整输入中,顺序变成 Chunk 1 + Chunk 2。
单独运行时,Chunk 2 的 token 无法关注 Chunk 1。合并后,因果 Attention 允许 Chunk 2 的 token 读取 Chunk 1。于是,从第二层开始,Chunk 2 的隐藏状态就可能不同,对应的 K、V 也可能不同。
这也是几种缓存策略之间的关键差别:前缀缓存复用的是相同前缀在相同计算条件下得到的 KV;直接拼接各个独立文本块的 KV,则可能漏掉块与块之间的上下文交互。
最后可以把整个逻辑压缩成一句话:每个位置确实有自己的 K、V,但后续层的 K、V 是从已经经过 Attention 更新的隐藏状态计算出来的,因此不能只看 token ID 判断 KV 是否相同。
评论