前文不用重算,大模型到底缓存了什么?|讲透大模型 A2

16 阅读6分钟

让大模型写一封请假邮件,它已经写到了:

您好,因个人安排,明天下午……

后面接什么,要参考最初的要求,也要参考已经写出的内容。但这带来一个问题:每多写一点,都需要前文,难道前文也得重新算一遍?

用到旧信息,与重新计算旧信息,不是一回事。常见的生成实现会把部分历史计算结果保存下来,后面直接复用。这就是 KV Cache。

不过,Attention 明明有 Q、K、V,为什么只缓存后两个?要回答它,得先把两个问题分清:旧结果为什么仍然有效,后面又需要哪些旧结果?

图片

一、算过一次,为什么还能继续用?

设想你根据一摞资料写邮件。每写一句,都可能再次查看资料,却没必要每写一句,就把整摞资料重新整理一遍。

但这有个前提:资料和整理规则没变。否则,昨天整理好的东西,今天未必还能照搬。

模型同样需要这个前提。这里讨论常见的因果自注意力:每个位置只能利用自己和前面的位置,不能读取后面的位置。因此,后来追加的内容,不会倒过来参与旧位置的计算。这种约束贯穿各层,不只是第一层。

只要模型参数、原有前缀和位置规则等计算条件不变,在末尾继续追加 Token,旧位置已经算出的 K/V 就仍然有效。

它们能够复用,不是因为“以前算过”,而是因为决定这些结果的条件没有变。

图片

二、为什么留下 K/V,却不留下 Q?

旧结果有效,还不够。下一步根本用不到的东西,没必要专门存下来。

模型利用其他位置信息时,会用到 Attention,也就是注意力机制。先借前面那摞资料,把三个角色分开:

| 角色 | 在资料比喻里像什么 | 在计算中做什么 | | :-- | :-- | :-- | | Q:查询 | 这次查阅所用的检索线索 | 与各位置的 K 比较 | | K:键 | 资料上的索引标签 | 提供匹配线索 | | V:值 | 资料中可供读取的内容 | 按权重参与汇总 |

这里的“标签”和“内容”只是比喻。真实的 Q、K、V 都是模型算出的数字表示,K 不是人工贴的关键词,V 也不是原文复印件。

当前 Q 与可访问位置的 K 匹配,得到权重,再按权重汇总 V。它可以从多个位置读取不同比例的信息,不必只挑出一张资料。

回到邮件:这一次查资料的线索已经用完,下一次会产生新的查询;资料的索引和内容,却还可能被反复使用。

历史 Q 也是如此。它负责旧位置当时的查询,那次计算已经完成。到了新位置,需要的是新位置自己的 Q,而不是把旧查询重新执行一遍。历史 K/V 则仍然要参与新查询的匹配和读取。

不是 Q 存不下来,而是后续位置不需要复用旧 Q;K/V 既仍然有效,又还会被用到。

三、把缓存画成一个容器,就看清了

第一次处理完整输入时,模型会建立各层的 K/V 缓存,并给出第一个输出 Token 的候选。选出这个 Token 后,再把它送回模型,计算它的 Q/K/V,用处理结果预测后一个 Token。

先有已经确定的 Token,才能计算它的 Q/K/V;不是尚未生成的 Token 先拿着 Q 来提问。

下面只展开其中一个注意力层:

       当前已确定 Token 在本层的表示
                      │
                计算 Q、K、V
                 ↙        ↘
              当前 Q      新 K/V
                │            │ 追加
                │     ┌──────▼─────────┐
                │     │ 本层 KV Cache  │
                │     │ 旧 K/V+新 K/V  │
                │     └──────┬─────────┘
                │            │ 读取
                └──────┬─────┘
                       ▼
          Q 与 K 匹配,按权重汇总 V
                       ↓
                   本层输出
                       ↓
       经过后续网络,预测后一个 Token

每一层都有自己的缓存。在这里保留完整历史的方案中,容器会不断加入新 K/V,已有条目不因后文追加而重算。当前 Q 完成本层计算后,不必存进这个容器,交给未来位置继续使用。

不过,旧资料不用重新整理,不等于这次查阅也可以跳过。新 Q 仍然要与 K 匹配,重新决定怎样读取 V。

缓存省掉的是旧位置的重复计算,没有省掉新位置利用历史信息的计算。

四、修改开头,为什么不能照搬全部缓存?

假设你返回最初的要求,把“明天下午请假”改成“明天上午请假”。

这次还能直接使用整份旧缓存吗?

不能照搬。刚才能复用,是因为原有前缀没变;现在计算条件变了,受影响位置的结果就可能不同。后面即使仍然出现同样的字,也不能据此认定它在各层的 K/V 仍然相同。

仍然一致、计算条件也匹配的前缀可以复用,受到修改影响的部分则需要重新处理。实际的前缀缓存机制,利用的正是这段可以共享的前缀。

这就像基础资料改了,依赖它整理出的记录也得重新核对。不能因为文件名一样,就假装内容没动过。

KV Cache 保存的是特定前缀下的中间结果,不是给每个字准备一份永久不变的缓存。

图片

五、省掉重复计算,也要付出存储成本

KV Cache 承载着当前推理可以利用的历史信息,但保存这些结果,本身不会修改模型参数,也不等于建立了一套独立的长期记忆系统。

它做的是一笔具体的交换:占用空间保存中间结果,换取后续少做重复计算。在保留完整历史的方案里,处理的位置越多,要保存的有效 K/V 数据就越多。

回到开头,为什么是 KV Cache,而不是 QKV Cache?

追加后文不改变旧 K/V,后续查询又需要它们;旧 Q 已完成原位置的查询,后续位置不再需要复用。

不用把三个字母背成口诀,记住判断缓存的两个问题就够了:结果还有效吗?以后还用得上吗?

图片

关掉页面前,换一种缓存试试

有人提出:既然每次都要算权重,不如把上一轮“各位置该占多少权重”也保存下来,下一轮原样使用,岂不是更快?

这个办法能保持原来的注意力计算吗?

先想一想,再看下面。

不能保证。新的 Q 不一定与上一轮相同,可访问的 K/V 也可能多了一个位置。即使旧 K/V 没变,匹配分数和权重仍然可能变化,不能把上一轮的读取比例原封不动搬过来。

资料可以继续用,查询却要重新做。缓存不能把已经改变的问题,当成上一轮的问题来回答。

图片

昊哥

#讲透大模型 #大模型 #KVCache #注意力机制 #Transformer #推理优化 #大模型原理 #AI科普