<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>KV Cache on Zewang's Blog</title><link>https://zewang0217.github.io/tags/kv-cache/</link><description>Recent content in KV Cache on Zewang's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Thu, 13 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://zewang0217.github.io/tags/kv-cache/index.xml" rel="self" type="application/rss+xml"/><item><title>深入理解 AI Agent（二）：上下文的结构与 KV Cache</title><link>https://zewang0217.github.io/p/ai-agent-in-depth-ch2-kv-cache/</link><pubDate>Thu, 13 Aug 2026 00:00:00 +0000</pubDate><guid>https://zewang0217.github.io/p/ai-agent-in-depth-ch2-kv-cache/</guid><description>&lt;h1 id="深入理解-ai-agent二上下文的结构与-kv-cache"&gt;深入理解 AI Agent（二）：上下文的结构与 KV Cache
&lt;/h1&gt;
 &lt;blockquote&gt;
 &lt;p&gt;资料源：李博杰《深入理解 AI Agent：设计原理与工程实践》第2章（2.1-2.3 节）
开源仓库：https://github.com/bojieli/ai-agent-book （v1.4，2026-08-13）&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="一句话主线"&gt;一句话主线
&lt;/h2&gt;&lt;p&gt;第1章说上下文是 Agent 的&amp;quot;眼睛&amp;quot;。第2章回答怎么设计这双眼睛——作者把整套方法论压缩成一个结构和一个约束：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结构&lt;/strong&gt;：上下文 = 静态前缀（系统提示词 + 工具定义）+ 轨迹（消息历史）
&lt;strong&gt;约束&lt;/strong&gt;：前缀不能动（KV Cache 的经济性），动态信息追加末尾&lt;/p&gt;
&lt;p&gt;这篇（二）讲结构和约束本身：上下文为什么决定能力上限、API 层面长什么样、KV Cache 为什么把&amp;quot;前缀稳定&amp;quot;变成一条架构铁律。&lt;/p&gt;
&lt;h2 id="上下文决定能力上限天才工程师的困境"&gt;上下文决定能力上限：天才工程师的困境
&lt;/h2&gt;&lt;p&gt;模型在标准测试里成绩亮眼，到了业务场景却常常让人失望。原因不是模型笨，而是&lt;strong&gt;通用模型不知道你的业务背景&lt;/strong&gt;。书的比喻很到位：想象一位天才工程师加入你的团队——理论功底深厚、编程能力卓越，但对产品架构、业务逻辑、技术债务一无所知。这位天才即便智力超群，也难以发挥价值。&lt;/p&gt;
&lt;p&gt;以 Coding Agent 为例，&amp;ldquo;帮我修复这个 bug&amp;quot;的效果，完全取决于 Agent 拿到什么：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;实时代码上下文&lt;/strong&gt;：目录结构、模块职责、核心数据结构、代码规范——没有这些，代码语法正确但风格与项目格格不入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流程规范&lt;/strong&gt;：Git 分支策略、提交规范、CI/CD 要求——缺少这些，Agent 可能直接往主分支提交未经测试的代码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境信息&lt;/strong&gt;：开发环境配置、测试数据库地址、API 密钥管理——没有这些，本地能跑通的修复，到测试环境立刻崩溃&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这三类信息构成了 Agent 有效工作的最低信息需求。作者引用了 OpenAI 研究员翁家翌的观点，把这个判断推到极致：&lt;strong&gt;&amp;ldquo;人和模型一样，最重要的是 Context&amp;rdquo;&lt;/strong&gt;——&amp;ldquo;自己在 OpenAI 的工作也没有那么难，如果换一个其他人，如果有他所有的 context，也是能干的。&amp;ldquo;团队合作中最大的问题是 context 的不一致；AI 短时间内无法取代人的最大原因也是 context——因为 AI 跟人并不在同一个环境里面。&lt;/p&gt;
&lt;p&gt;一个直接推论：&lt;strong&gt;一个中等能力的模型配上精心组织的上下文，往往胜过顶级模型在信息匮乏下的盲目摸索。&lt;/strong&gt; 这解释了为什么上下文工程成为利用现有模型开发高效 Agent 的关键。&lt;/p&gt;
&lt;p&gt;书还点出一个容易被忽视的层面：上下文工程不只是技术问题，更是&lt;strong&gt;组织问题&lt;/strong&gt;。大多数团队的关键知识是隐性的——架构决策只有老员工记得、业务规则靠口口相传、背景信息锁在私聊记录里。如果团队本身就是信息黑洞，再好的 Agent 也无计可施。对远程工作友好的团队（如 Linux 内核——全球开发者协作三十多年，靠高度透明、文档驱动的沟通文化）天然对 AI Agent 友好。构建 AI 原生团队，首先是一场文档化运动。&lt;/p&gt;
&lt;h2 id="api-视角的上下文无状态接口--消息列表"&gt;API 视角的上下文：无状态接口 + 消息列表
&lt;/h2&gt;&lt;p&gt;Agent 每次调用模型，本质上是发送一个消息列表（messages）。四种角色：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;角色&lt;/th&gt;
 &lt;th&gt;来源&lt;/th&gt;
 &lt;th&gt;作用&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;system&lt;/td&gt;
 &lt;td&gt;开发者编写&lt;/td&gt;
 &lt;td&gt;系统提示词，最高优先级指令，通常只有一条，放最前面&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;user&lt;/td&gt;
 &lt;td&gt;终端用户&lt;/td&gt;
 &lt;td&gt;用户的输入&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;assistant&lt;/td&gt;
 &lt;td&gt;模型之前生成&lt;/td&gt;
 &lt;td&gt;文本回复 + 工具调用请求，放回列表让模型&amp;quot;记住&amp;quot;自己说过什么&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;tool&lt;/td&gt;
 &lt;td&gt;Agent 框架&lt;/td&gt;
 &lt;td&gt;工具执行结果，通过 tool_call_id 与对应调用关联&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;另外，工具定义（tools）是请求的独立字段而非消息角色。&amp;ldquo;四种消息角色 + tools 字段&amp;quot;恰好覆盖第1章说的五个上下文组成部分——系统提示词、用户消息、模型回复、工具执行结果（四角色）+ 工具定义（tools 字段）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键机制：模型 API 是无状态的。&lt;/strong&gt; 每次调用都必须由 Agent 框架把完整历史送回去。模型不会&amp;quot;记住&amp;quot;上一次对话——Agent 框架的核心工作，就是管理这个 messages 列表：在合适的时机追加消息，然后把整个列表送给模型。本章后续所有技术，本质上都是在优化这个列表的内容和结构。&lt;/p&gt;
&lt;p&gt;书中用&amp;quot;温哥华时间和天气&amp;quot;完整走了一遍两次 API 调用的交互（这是 ReAct 循环在 API 层面的具体实现）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;第一次调用&lt;/strong&gt;：system + user 请求。模型返回两个并行工具调用（get_current_time + get_weather）——两个子问题无数据依赖，模型在一次输出中同时生成两个请求&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent 框架执行工具&lt;/strong&gt;：真正调用时间 API 和天气 API 的是框架，不是模型。模型只负责决策（调什么、传什么参数）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二次调用&lt;/strong&gt;：把完整历史 + 两个 tool 结果送回。模型看到结果，判断信息已足够，直接输出最终回复，循环结束&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;三个关键细节：第二次请求包含第一次的全部历史（无状态）；第一次的 assistant 消息被原样放回（模型能看到自己的决策）；tool 消息通过 tool_call_id 关联（模型知道哪个结果对应哪个调用）。&lt;/p&gt;
&lt;p&gt;最简实现就是一个 while 循环：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;assistant_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 模型回复原样放回&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# 无工具调用 = 最终回复&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tool_call&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;execute_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tool&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tool_call_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;注意注释里那句：生产代码必须加 max_iterations 上限——否则 Agent 可能卡在重复调用同一个工具的死循环里。这是第1章消融实验&amp;quot;缺工具结果 → 无限循环&amp;quot;的工程化后果，也是第1章思考题 Q4 讨论的循环问题的第一个具体答案。&lt;/p&gt;
&lt;p&gt;实验 2-1 还展示了另一个反直觉的事实：&lt;strong&gt;0.6B 的超小模型，在合理提示词和架构下也能可靠完成工具调用&lt;/strong&gt;（作者在 M2 芯片上跑出每秒 100+ token）。模型大小固然重要，但不是唯一决定因素——端侧 Agent 的时代比大多数人预期的更近。&lt;/p&gt;
&lt;h3 id="chat-templateapi-消息到模型-token-流的转换"&gt;Chat Template：API 消息到模型 Token 流的转换
&lt;/h3&gt;&lt;p&gt;一个重要的中间环节：API 层面的结构化 JSON 消息，并不是模型直接处理的形式。API 服务端（vLLM、Ollama 等）会根据模型的 &lt;strong&gt;Chat Template（聊天模板）&lt;/strong&gt;，把消息列表转换为模型实际处理的线性 token 流——用特殊标记（如 &lt;code&gt;&amp;lt;|im_start|&amp;gt;system&lt;/code&gt;、&lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt;）界定每条消息的角色和边界。可以想象成信封格式：API 消息是信的内容，Chat Template 规定了如何在信封上写明寄件人、收件人。不同模型家族（Qwen、Llama、Gemma）使用不同的&amp;quot;信封格式&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;理解它的存在对 Agent 开发有两个实用价值：&lt;/p&gt;
&lt;p&gt;第一，&lt;strong&gt;解释了为什么必须使用标准 API 格式&lt;/strong&gt;。如果开发者绕过 API、自行拼接消息（比如把工具结果作为普通 user 消息而非 tool 类型传递），Chat Template 会误将工具响应识别为新的用户查询。以 Qwen3 为例：模型在多轮工具调用中会把之前的内部思考过程（&lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; 标签内容）保留下来，像草稿纸上的推导步骤；但当 Chat Template 检测到新的用户查询时，会默认&amp;quot;用户换了个话题&amp;rdquo;，清理之前的思考过程重新开始。工具结果被错误标记为用户消息，就会误触发这种清理——模型正算到一半，草稿纸被收走了，只能从头再来。&lt;/p&gt;
&lt;p&gt;不同模型家族对历史思考链的处理策略差异很大且快速演变：DeepSeek R1 时代是剥离全部历史思考（R1 训练时历史 CoT 从不出现在输入里，塞回去属于分布外输入，且省 token）；DeepSeek V4 则彻底反转，强制要求把每轮 assistant 消息（包括带 tool_calls 的）的 reasoning_content 原样回传，否则直接报错——Kimi K2、GLM-5 也采用同样协议；Claude 要求客户端在工具调用循环中把 thinking block（带签名校验）原样回传。&lt;strong&gt;选择模型时务必查阅对应模型的最新文档。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第二，&lt;strong&gt;解释了 KV Cache 为什么对前缀如此敏感&lt;/strong&gt;。Chat Template 把 system 消息和工具定义转换为固定的 token 序列放在最前面，这些 token 的键值对缓存后可跨请求复用；但前缀中某个 token 一旦变化——哪怕多一个空格——首个不同 token 及其后的缓存就无法复用。&lt;/p&gt;
&lt;h2 id="kv-cache为什么前缀不能动是一条铁律"&gt;KV Cache：为什么&amp;quot;前缀不能动&amp;quot;是一条铁律
&lt;/h2&gt;&lt;h3 id="事故开篇一行时间戳账单翻倍"&gt;事故开篇：一行时间戳，账单翻倍
&lt;/h3&gt;&lt;p&gt;某团队的客服 Agent 每天处理 10 万次对话，原本一切正常。某天工程师为了让 Agent &amp;ldquo;知道&amp;quot;当前时间，在系统提示词里加了一行 &lt;code&gt;Current time: {{now}}&lt;/code&gt;。第二天监控告警：所有对话的首 token 延迟从 0.5 秒涨到 3-5 秒，月度推理账单几乎翻了一倍。代码看起来完全没问题，模型也没换——问题出在哪？&lt;/p&gt;
&lt;p&gt;答案是：那一行时间戳使每次请求的 token 序列从时间戳所在位置开始不同，因此该位置及其后的 KV 状态无法复用。系统提示词位于上下文前部，模型不得不重新计算它之后的大部分 token。开发者写下的一行看似无害的代码，可能让整条推理链路慢一个量级。&lt;/p&gt;
&lt;h3 id="注意力机制为什么需要缓存"&gt;注意力机制：为什么需要缓存
&lt;/h3&gt;&lt;p&gt;先建立直觉（实验 2-2）。假设模型正在处理&amp;quot;北京的天气怎么样&amp;rdquo;，读到&amp;quot;怎么样&amp;quot;时，需要决定前面哪些词最重要。注意力机制用三个向量完成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Query（查询）&lt;/strong&gt;：当前词发出的&amp;quot;搜索请求&amp;rdquo;——&amp;ldquo;怎么样&amp;quot;问：哪个词和我最相关？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key（键）&lt;/strong&gt;：每个词的&amp;quot;标签&amp;rdquo;，用于被搜索匹配——&amp;ldquo;北京&amp;quot;的标签偏向&amp;quot;地名&amp;rdquo;，&amp;ldquo;天气&amp;quot;偏向&amp;quot;气象&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Value（值）&lt;/strong&gt;：每个词的内容，匹配成功后被提取&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每个新词生成时，它的 Query 要与前面所有词的 Key 做点积打分，再用权重对所有 Value 加权求和。&lt;strong&gt;每生成一个新词，都要&amp;quot;回头看&amp;quot;前文所有 token 的中间计算结果&lt;/strong&gt;。如果不缓存，第 N 个 token 要重算前面 N-1 个 token 的 K、V——总计算量与 N² 成正比。KV Cache 就是把已算过的 K、V 缓存起来，新词直接复用。&lt;/p&gt;
&lt;p&gt;但注意：&lt;strong&gt;KV Cache 省去的是历史 token 的 K、V 投影重算，每个新 token 的注意力计算仍要遍历全部缓存的 K、V&lt;/strong&gt;——这就是长上下文解码越来越慢、KV Cache 的显存与带宽成为推理瓶颈的原因。&lt;/p&gt;
&lt;p&gt;注意力热力图还揭示了两个值得知道的模式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;注意力储存池（Attention Sink）&lt;/strong&gt;：序列第一个 token 往往吸收异常高的注意力权重（有时超 70%）。原因是 softmax 硬性要求所有权重加起来等于 100%，模型无法表达&amp;quot;不关注任何东西&amp;rdquo;，于是把剩余权重集中倾倒到第一个 token 上——像公共回收站，是数学必然，不是模型缺陷&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置偏好（Position Bias）&lt;/strong&gt;：模型对上下文开头和结尾的信息分配更高注意力，中间部分更容易被忽视——所以把最关键信息放在开头或结尾是重要的设计原则&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="三条铁律"&gt;三条铁律
&lt;/h3&gt;&lt;p&gt;理解 KV Cache 后，书给出三条核心结论（即使跳过所有技术细节也要记住）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;系统提示词和工具定义一旦确定就不要改&lt;/strong&gt;——哪怕多一个空格，都使首个不同 token 及其后的缓存无法复用；改动越靠前，延迟和成本影响越大&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态信息永远追加到末尾&lt;/strong&gt;——时间戳、用户状态等变化内容，作为新消息追加到对话末尾，而不是修改已有的系统提示词&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用标准 API 格式，不要自行拼接消息&lt;/strong&gt;——结构化消息会被 Chat Template 翻译成模型训练时见过的固定 token 序列；自己拼成 &amp;ldquo;USER:&amp;hellip; ASSISTANT:&amp;hellip;&amp;rdquo; 偏离训练格式，会削弱模型的多步思考能力&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一个容易混淆的细节：缓存只认 token 字节序列——只要拼接出的前缀字节级稳定，照样能命中；文本格式化真正的问题不是缓存，而是偏离训练格式。&lt;/p&gt;
&lt;h3 id="常见但有害的上下文管理模式实验-2-3"&gt;常见但有害的上下文管理模式（实验 2-3）
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动态系统提示词&lt;/strong&gt;（时间戳）：破坏缓存（就是上面的事故）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态用户配置&lt;/strong&gt;（账户余额等）：破坏缓存&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具定义的动态排序&lt;/strong&gt;：工具定义占大量 token（每个工具数百 token），改变顺序就废缓存——而实验表明保持固定顺序对模型选择工具几乎没影响&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;滑动窗口&lt;/strong&gt;（只保留最近 N 条消息）：两个严重问题——破坏前缀一致性 + 可能丢失关键工具调用结果。实验中滑动窗口的 Agent 经常陷入循环，因为它&amp;quot;忘记&amp;quot;了之前已经获得的结果，反复执行相同的工具调用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文本格式化&lt;/strong&gt;（把 role-content 消息转成 &amp;ldquo;USER:&amp;hellip; ASSISTANT:&amp;hellip;&amp;rdquo; 纯文本流）：偏离训练格式，导致重复执行已完成的操作、忽略工具调用结果、在应该调用工具时却生成文本响应&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;小结的教训：这些错误模式的解法最终都收敛回三条铁律。模型提供商为标准接口做了大量优化，偏离标准格式往往是在给自己挖坑。&lt;/p&gt;
&lt;h3 id="缓存是架构约束不是事后优化"&gt;缓存是架构约束，不是事后优化
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;KV Cache 与 Prompt Cache 是两个层级&lt;/strong&gt;：KV Cache 是模型内部机制（单次请求内避免重复计算）；Prompt Cache 是推理引擎的跨请求优化（复用相同前缀的计算结果，Anthropic/DeepSeek/GPT-5 缓存读取成本约为首次计算的十分之一）。&lt;/p&gt;
&lt;p&gt;在生产级 Agent 系统中，缓存的经济性会反过来主导架构决策（2.3.4，以 Claude Code 为例）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;提示词的结构由缓存边界决定&lt;/strong&gt;：系统提示词被一个缓存边界标记一分为二——标记之前的内容可以跨用户、跨会话全局缓存，标记之后是用户/会话特定信息。每个运行时条件（操作系统、模式、用户偏好）如果放在边界之前，就会让缓存键的变体数量翻倍（N 个二值条件产生 2^N 种组合），所以所有动态元素都必须放到边界之后&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子 Agent 必须与父 Agent 字节级对齐&lt;/strong&gt;：派生子 Agent 时，提示词、工具定义、模型配置、消息前缀、思考配置必须逐字节匹配，才能命中 Prompt Cache，减少费用和延迟&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具结果的替换字符串在首次出现时就被冻结&lt;/strong&gt;：大工具输出被替换为摘要预览时，替换字符串被持久化保存，即使会话重启也用完全相同的字符串——保证恢复后的消息序列与缓存字节流一致&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;核心启示：&lt;strong&gt;在设计 Agent 架构时，缓存经济性不是事后优化，而是前置约束。&lt;/strong&gt; 越早纳入架构设计，后续工程代价越小。&lt;/p&gt;
&lt;h3 id="研究前沿kv-cache-未必是一次性的"&gt;研究前沿：KV Cache 未必是一次性的
&lt;/h3&gt;&lt;p&gt;2.3.5 是深水区选读（初读可跳过）。作者提出一个反直觉的观察：在 prefill 阶段，模型其实在&amp;quot;做笔记&amp;quot;——读到一个字段（&amp;ldquo;用户所在城市：北京&amp;rdquo;）时，不是原封不动缓存它，而是把&amp;quot;这意味着什么&amp;quot;的结论写进了后面每一层的 KV 状态。测量发现，一个字段自己那几个 token 的 KV 对最终决策的贡献往往不到 1%——真正影响输出的，是它在下游留下的那些&amp;quot;读书笔记&amp;quot;。&lt;/p&gt;
&lt;p&gt;这打开了两种以前认为不可能的操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编辑（Editing）&lt;/strong&gt;：既然结论已写进下游笔记，改掉一个字段后，只要模型有显式思考链（CoT），改动就能顺着已缓存的思考传播下去，用约 1% 算力得到与整段重算一致的结果&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组合（Composition）&lt;/strong&gt;：把一段预先算好的&amp;quot;技能&amp;quot;缓存，通过旋转位置编码（RoPE）挪到新位置拼接进另一段上下文——上下文组装从 O(L²) 重算降到 O(L) 拼接&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;论文在 vLLM 上实现后，首 token 延迟（p90）最高降几十到几百倍，前缀缓存命中率约 98.5%，输出与逐字重算在决策上完全一致。它指向一种&amp;quot;上下文可变、但缓存收益还在&amp;quot;的可能——但作者明确说：这仍属研究阶段，当前生产系统依然遵守三条铁律。&lt;/p&gt;
&lt;h2 id="思考与延伸读-kv-cache-时的四个确认"&gt;思考与延伸：读 KV Cache 时的四个确认
&lt;/h2&gt;
 &lt;blockquote&gt;
 &lt;ol&gt;
&lt;li&gt;chat template 转换会把 api 看到的 json -&amp;gt; 类似 &amp;lt;|im_start|&amp;gt; 这样标签包裹的内容（为了生成 token 流）。&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;ol start="2"&gt;
&lt;li&gt;必须用标准 api 格式的原因：不同的模型可能有自己的格式。qwen3 会保留内部思考过程（&lt;think&gt;）。deepseek r1 剥离全部历史思考。deepseek v4 要求把每轮的 assistant 消息（包括带 tool_calls 的）reasoning_content 原样回传。&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;ol start="3"&gt;
&lt;li&gt;kv cache 的原理与约束。prefill 阶段（模型生成回复之前，处理输入的全部 token 的阶段）注意力计算量随上下文长度平方级增长。&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;ol start="4"&gt;
&lt;li&gt;KV cache 和 prompt cache 是不一样的，是两个层级的。前者是单次会话内的，后者是多次会话之间复用。&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这四条是对&amp;quot;标准 API 格式&amp;quot;和&amp;quot;缓存层级&amp;quot;两个概念的精准确认。其中第 2 条是正文没有展开的细节：不同模型家族对历史思考链的处理策略差异很大且快速演变——R1 剥离（历史 CoT 属分布外输入）、V4 强制回传（否则报错）、Claude 要求带签名校验回传 thinking block。选型时务必查最新文档。&lt;/p&gt;
&lt;h2 id="小结"&gt;小结
&lt;/h2&gt;&lt;p&gt;这篇（二）建立的地基：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;上下文决定能力上限&lt;/strong&gt;——天才工程师需要背景信息才能发挥作用；上下文工程首先是文档化运动&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API 是无状态的&lt;/strong&gt;——Agent 框架的核心工作就是管理 messages 列表；生产代码必须有 max_iterations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KV Cache 让&amp;quot;前缀稳定&amp;quot;成为架构铁律&lt;/strong&gt;——三条铁律（前缀不动/动态追加末尾/标准 API 格式）+ 缓存是前置架构约束&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;滑动窗口是个陷阱&lt;/strong&gt;——破坏缓存 + 丢关键结果 + 导致重复执行循环&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下一篇（三）讲&amp;quot;前缀里到底放什么&amp;quot;——提示工程（写给模型看的员工手册）和提示注入（外部内容劫持上下文的头号威胁）。&lt;/p&gt;</description></item></channel></rss>