第 01 章

注意力与 KV Cache:从逐词生成到上下文管理

用 Token、自回归生成、Query-Key-Value 注意力与 KV Cache 解释四件事:模型为什么无状态、窗口变长为什么效果变差、首字为什么慢、改动开头为什么让缓存失效。最后落到四条可执行的 Agent 使用习惯。

首发于 2026.10.30
科普llmattentionkv-cachecontext-management
字号
目录
篇内目录

一个日常困惑

同一个聊天窗口里聊得越久,模型越容易忘掉最开始定好的要求,改代码的质量也肉眼可见地下降。与此同时,社区里反复出现几条经验:Skill 要按需加载、探索性任务丢给子 Agent、不要随便改动系统提示词的开头。

这些现象不是玄学,它们由两个机制决定:注意力机制与 KV Cache。把这两件事拆开看,上面每一条经验都能找到对应的原因。

先建立三个最小事实

模型看到的不是句子,而是 Token

输入文本会先被切成一段段文本块,每一块叫一个 Token。一个 Token 可能是一个常见英文单词、半个长单词,或一两个汉字。「上下文窗口 200K」「这一轮花了多少额度」,说的都是 Token 数量。

Token 是最小处理单位,这解释了很多看起来奇怪的能力边界:模型「看不见」字母,只看见词块。后面关于拼写与计数的失败,都与此有关。

模型本身是无状态的

聊了十几轮,模型好像一直记得前面说过的话。但模型每回答完一次,自身不保留任何对话状态。

之所以第 10 轮还能接上第 1 轮的需求,是客户端在背后做了拼接:发送第 10 轮时,把前 9 轮的全部对话、读过的文件内容、工具返回的日志,加上刚输入的那句话,拼成一份完整长文本,从头重新发送。

由此得到一个直接推论:同一窗口里轮次越多,每次请求携带的文本就越庞大,延迟与费用也随之上升。

回答是逐词预测出来的

模型不是先想好整篇再打印。它做的是自回归生成:每次只根据前面所有 Token,预测下一个位置最可能出现什么 Token;预测出来就追加到末尾,再预测下一个,直到输出结束标记。

问「法国的首都是哪里」,它先算出「巴」,把「巴」接到问题后面,再算出「黎」。每一步都只看前文,不看后文。

注意力机制:当前词回头找谁

一个指代例子

小狗跑得气喘吁吁也没追上那只猫,因为它实在太累了。

人一眼看出「它」指小狗。模型是从左往右处理的,「它」与「小狗」之间隔着好几个词,它靠什么建立联系?

靠注意力:读到「它」时,模型拿当前词的需求,回头和前面每个词块计算关联程度,发现「跑得气喘吁吁的小狗」关联最高,「没」「那只」几乎没有关联,于是把主要权重分给「小狗」。

Q、K、V 三组特征

对每个 Token,模型会算出三组特征:

  • Query(我要找什么):当前词想在前文里寻找什么线索。读到「它 / 累」时,Query 在找「发出奔跑动作的主语」。
  • Key(我是什么):每个前文词块结合语境算出的标签。「小狗」的 Key 标识出「名词、动物、前半句主语」。
  • Value(我能给什么):这个词块携带的具体语义内容。

计算过程是:用当前词的 Q 去和前面所有词的 K 逐一打分,分数归一化成权重,再按权重把各词的 V 加权求和。注意力机制做的就是这件「打分—分配—汇总」的事。

为什么聊久了会漏掉要求

关键在于:每一步预测时,分配给前文所有词块的权重总和是固定的。

对话刚开始,窗口里只有一两千字,核心需求、代码规范、边界条件很集中,能分到很高的权重。而在同一窗口里连续聊很久,或让 Agent 读了十几个源码文件、塞进大量终端日志,上下文膨胀到几万甚至十几万 Token 时,当前 Q 要面对几万个 K,原本集中在关键指令上的权重就被大量过时日志和无关代码稀释。

最开始那几句关键要求分到的比例大幅下降——这就是「窗口上限 200K 甚至 1M,但塞满无关信息后表现仍明显变差」的原因。窗口容量不等于有效注意力。

KV Cache:算过的不再重算

重复计算的问题

生成是逐词的。让模型读一篇一万字的文档、写两百字总结:

  • 生成第 1 个字,要把前面一万字的 K 和 V 全部算一遍;
  • 生成第 2 个字时,前面一万字没变。如果从头再算,写两百字就等于把一万字重复计算两百遍。

这样越写越慢,算力全花在重复计算上。

缓存键值

模型有一个关键性质:处理是从前往后单向的。第 100 个词的 K、V 已经融合了前 99 个词的语境,而后面新生成的第 101 个词不会反过来改变前面已算好的 K、V。

既然前文不变,就把每算完一个词的 K 和 V 存进显存,这就是 KV Cache。生成第 101 个字时,前面部分直接从显存读,只算新词自己的 Q、K、V。

这也解释了两个可观察现象:

  • 按下回车后要等一小段才出现第一个字,因为模型正在集中计算输入文本的 KV Cache,这个阶段叫 Prefill(预填充);
  • 第一个字出现后,后续文字连续快速输出,这个阶段叫 Decode(解码生成),每步只需算一个新词。

跨请求复用:提示词缓存

早期 KV Cache 只在一次回答内有效,回答结束就释放。Agent 场景不同:一次任务里,Agent 往往连续调用二三十次工具,每次都要把「系统提示词 + 工具定义 + 项目规则 + 之前的对话与工具记录 + 最新执行结果」重新拼成完整请求。这几十次请求里,前面绝大部分文本完全相同,只在末尾追加了最新结果。

于是厂商提供跨请求的提示词缓存(Prompt Caching):服务端把算好的 KV Cache 保留一段时间,下次请求只要前面的内容与上次完全一致,就直接复用。命中部分既省时间,也通常按约一折计费,相当于节省约 90% 的输入成本。

为什么改开头会让后面全部失效

提示词缓存要求精确前缀匹配。如果在系统提示词最开头放一句每秒变化的时间戳,例如 当前时间:2026-10-30 14:20:05,那么每次请求开头都不同,后面哪怕跟着几万字相同的代码与文档,缓存也无法命中。

原因回到计算方式:模型从前往后算,每个词块的 KV Cache 都依赖排在它前面的全部内容。最前面一个词变了,从该位置往后所有词的「前文语境」都变了,旧缓存无法复用,只能从改动点重新计算。

四条可执行的用法

理解了「权重会被稀释」和「缓存依赖前缀一致」,常见操作就能对号入座。

1. 专项知识用 Skill 按需加载

如果把几万字的 UI 规范、文案规范、发布流程全写进每次默认加载的 AGENTS.md 或 CLAUDE.md,哪怕只是改一行代码,这些当下用不上的长文档也会占用上下文、分散注意力。拆成独立 Skill,平时只加载名称与触发描述,真正执行任务时再读详细文件。

2. 产生大量中间信息的任务交给子 Agent

「在几十个文件里检索某段逻辑」「排查几千行报错日志」,过程中要读大量文本,最终有用的可能只有几句结论。全部放在主窗口里,这些中间文件与日志会一直留在历史记录中,之后每一轮都继续占权重、耗 Token。交给子 Agent 在独立上下文里做,只把结论带回主对话。

3. 独立任务结束后新开对话或压缩历史

一个模块做完、上一批报错解决之后,前面几十轮调试过程对新任务就是干扰。新开窗口,或在长任务中途用 /compact 把冗长的工具调用历史压成摘要。压缩那一次会重新生成前缀缓存,但比起之后每一轮都带着几万字无用记录,整体仍更省。

4. 固定内容在前,动态内容追加在后

按前缀匹配机制安排顺序:

  • 固定不变的系统指令、工具列表、通用规范放最前面,对话中途尽量不增删工具、不改前面内容;
  • 动态状态、补充说明、新一轮工具结果统一追加到消息列表末尾。

只要长前缀稳定,多轮连续执行就能持续命中缓存,等待更短、消耗更少。

本节结论

  • 模型无状态,对话历史由客户端每次重新拼接发送;轮次越多,请求越大。
  • 生成是逐词自回归,每步只预测下一个 Token。
  • 注意力用 Q 与所有 K 打分、归一化后加权汇总 V;权重总和固定,无关内容越多,关键指令分到的比例越低。
  • KV Cache 利用「单向处理」特性缓存已算的 K、V;Prefill 决定首字延迟,Decode 决定后续速度。
  • 提示词缓存要求精确前缀匹配;开头一改,后面全部重算。
  • 对应的实践:Skill 按需加载、子 Agent 隔离中间过程、及时新开对话、固定前缀 + 末尾追加。

理解检验

1. 既然模型本身无状态,为什么换个窗口它就不记得之前的对话了?

参考答案:无状态指模型每次调用只处理这次传入的 Token 序列,调用结束不保留任何内部对话状态。所谓「记得」,完全来自客户端把历史拼进本次请求。新开窗口等于把历史文本清空,模型收到的前缀里不再包含之前的内容,自然接不上。

2. 用 Q、K、V 解释模型如何判断「它」指代小狗。

参考答案:读到「它」时,模型为它算出 Query,语义上是「寻找前面发出奔跑动作的主语」。前文每个词块都有自己的 Key:「小狗」的 Key 带有「动物、主语、奔跑者」等特征,「那只」「猫」的 Key 不匹配。用 Q 分别与这些 K 打分,「小狗」得分最高;归一化后它的权重最大,于是聚合 Value 时主要取「小狗」的语义,完成指代消解。

3. 为什么上下文窗口更大,不代表效果更好?请结合权重分配说明。

参考答案:每一步预测时,注意力权重在所有可见前文上归一化,总和为 1。窗口变长只是允许塞入更多 Token,并不会增加权重总量。若新增的是无关日志与旧代码,它们会分走一部分权重,使核心指令分到的比例下降。极端情形是:关键要求只有两句,却被几万 Token 的日志包围,模型在生成关键逻辑时仍可能「抓不住重点」。因此有效注意力取决于信噪比,而不只取决于容量。

4. Prefill 与 Decode 分别是什么?为什么首字出现前有明显停顿?

参考答案:Prefill 是对整段输入一次性计算并建立 KV Cache 的阶段,计算量大且并行度高;Decode 是逐词生成阶段,每步只需为新 Token 计算 Q、K、V,并复用已有缓存,因此每步很快。首字的延迟来自 Prefill:在第一个输出 Token 产生之前,必须先把输入的 KV 算好,输入越长,等待越久。

5. 为什么在系统提示词最前面加一个动态时间戳,会让后面的缓存全部失效?

参考答案:提示词缓存要求精确前缀匹配。KV Cache 中每一层的每个位置都编码了「它之前的所有内容」。开头的时间戳变化,使第 1 个位置起的所有表示都不同,后续位置无法复用旧缓存,只能从改动处重新计算。把时间戳放到消息末尾,前面的长前缀保持不变,缓存仍可命中。

6. 你要为一个小型编码 Agent 设计提示词结构,怎样排列才能兼顾注意力与缓存命中?

参考答案:从前往后依次放:固定系统指令与角色、稳定的工具定义、项目通用规范(这些在整段会话中不变);随后放本轮任务描述;最后追加每一步的工具调用结果与新观察。这样前段长前缀保持稳定,KV Cache 可持续命中;同时把每一步的最新信息放在末尾,模型在生成时天然更关注近处内容,不会被几十轮前的旧日志淹没。任务切换时新开对话,避免无关历史继续占据权重。

与前后内容的关系

这一篇给的是推理侧的机制:Token、注意力、缓存。前一篇《大语言模型全流程》讲的是训练侧——预训练、后训练与强化学习如何塑造模型的能力与性格。两者合起来构成一个完整视角:训练决定模型「会什么」,推理机制决定模型「此刻能看到什么、注意什么、花多少成本」。后面的 Agent 工程实践,本质上都是在管理这个可见窗口。

来源