KV Cache 与 Prompt Cache
近期有用户发现 DeepSeek 存在一个可疑 Bug:输入特定 think 标签时,模型会突然吐出看似"接管他人对话"的内容。
关于这些数据的来源,目前有两种猜测:
| 可能 | 分析 | 结论 |
|---|---|---|
| 训练数据 | 泄露内容都在对话开头位置,如果是真实对话记录,理应包含对话中途的内容 | 可能性较大 |
| 真实对话泄露 | 如果真是泄露的用户对话记录,问题将非常严重 | 可能性较小 |
如果真是后者,那问题很可能出在提示词缓存上------这也是今天要讨论的主题。
二、KV Cache:单次生成中的 KV 复用机制
KV Cache 是大模型推理优化中最重要的技术之一。"KV"指注意力机制中的 Key 和 Value。
大模型的本质是不断计算下一个 token
输入: "宝塔镇河"
输出: "塔"
输入: "宝塔镇河塔"
输出: "正"
输入: "宝塔镇河塔正"
输出: "..."
计算每个 token 时,输入的变量包括:
- Q(Query):当前 token 的查询向量
- K(Key):所有 token 的键向量
- V(Value):所有 token 的值向量
在类似的计算过程中,同一个 token 的 KV 值不会改变。原因:
- KV 只与当前 token 的向量、模型参数、之前的 token 向量有关
- 这三个因素在计算过程中都不会变
2.3 计算流程
无 KV Cache 时:
第1次计算:输入 "宝塔镇河" → 计算全部 KV → 输出 "塔"
第2次计算:输入 "宝塔镇河塔" → 重新计算全部 KV → 输出 "正"
第3次计算:输入 "宝塔镇河塔正" → 重新计算全部 KV → 输出 "..."
有 KV Cache 时:
第1次计算:输入 "宝塔镇河" → 计算全部 KV 并缓存 → 输出 "塔"
第2次计算:输入 "宝塔镇河塔" → 只计算 "塔" 的 Q,读取已缓存的 KV → 输出 "正"
第3次计算:输入 "宝塔镇河塔正" → 只计算 "正" 的 Q,读取已缓存的 KV → 输出 "..."
2.4 KV Cache 的效果
| 指标 | 无 KV Cache | 有 KV Cache |
|---|---|---|
| 首个 token 生成 | 正常速度 | 正常速度 |
| 第 2+ 个 token | 与首个相同 | 显著加快 |
| 整体耗时 | 累加所有 token | 大幅减少 |
| 速度变化 | 越来越慢 | 保持稳定高效 |
没有 KV Cache,每个 token 的生成速度都会很慢,而且会**越来越慢**。
三、Prompt Cache:多次调用中的前缀复用
Prompt Cache(提示词缓存) 是指在多次模型调用中,缓存之前提示词的 KV 结果。如果后面的调用和前面的调用有相同的前缀,这一部分不需要重新计算 KV,可以直接复用。
3.2 应用场景:Agent 模式
以 AI 回答"明天天气怎么样"为例:
用户: "明天天气怎么样"
模型: 返回工具调用指令 ← 第1次调用
工具调用完成,后台再次发起调用
模型: "天气晴朗,温度15-22°C" ← 第2次调用
问题:第二次调用时,模型实际收到的输入是什么?
python
# 实际上需要包含完整的历史对话,模型才能理解上下文
实际输入 = [第1次调用的系统提示词, 第1次用户的问句, 第1次模型回复, 第2次用户的新问题]
3.3 Prompt Cache 的价值
由于第二次调用的前缀部分 (第1次调用的全部内容)与第一次调用的内容相同,这部分的 KV 值已经计算过,可以直接复用。
第1次调用输入: [系统提示词] + [用户问句] → 计算所有 KV → 返回工具调用指令
第2次调用输入: [系统提示词] + [用户问句] + [模型回复] + [工具结果]
↑___________________↑
这部分 KV 已缓存,可直接复用
3.4 缓存命中(Cache Hit)
缓存命中 是指在多次模型调用中,后面的调用复用了前面调用已计算的 KV 结果。
| 指标 | 数据 |
|---|---|
| 常规对话中缓存命中率 | 可达 98% 以上 |
| 原因 | Agent 本质是反复调用模型,每次调用都在之前基础上追加新内容 |
四、KV Cache vs Prompt Cache
| 维度 | KV Cache | Prompt Cache |
|---|---|---|
| 作用 | 单次生成过程中 |
多次模型调用之间 |
| 关注 | 生成下一个 token 时复用之前的 KV | 下一次调用复用上一次调用的 KV |
| 场景 | 生成一句话时的加速 | Agent 多轮对话、Claude Code 系统提示词 |
| 缓存 | 当前输入序列的历史 KV |
上一次模型调用的完整前缀 KV |
两者本质相同,都是通过缓存 KV 值来避免重复计算,只是应用场景和粒度不同。
五、缓存的现状
5.1 DeepSeek:赛博善人
近几个月来,DeepSeek 被称为"赛博善人",原因在于:
- 缓存命中几乎不收费
- 用户反馈"用过的都说好"
5.2 Token 中间商
与 DeepSeek 形成对比的是某些"90后 Token 中间商":
表面:多优惠大酬宾
实际:吃掉全部缓存命中的差价
| 操作 | 说明 |
|---|---|
| 常规价格打折 | 随便打,甚至打骨折 |
| 缓存命中偷偷涨价 | 调成官方的 10 倍 |
| 赌的是什么? | 赌你眼神不好,看不清少数点 |
黑心中间商的套路远不止这些:收大模型的钱换小模型、虚报 token 用量......总之"从南京到北京,买的没有卖的精"
5.3 Claude Code 的启示
Claude Code 源码泄露后,有人发现密密麻麻只写着两个字:缓存
官方自己也表示:提示词缓存就是一切
一:静态前缀优先,动态内容靠后
┌─────────────────────────────────────────────────────────────┐
│ 静态提示词(可复用) │
├─────────────────────────────────────────────────────────────┤
│ · 身份规则("你是一个专业的代码助手") │
│ · 行为守则("始终保持简洁、直接的回答风格") │
│ · 安全红线("不允许执行危险命令") │
├─────────────────────────────────────────────────────────────┤
│ 动态提示词(可能因人而异) │
├─────────────────────────────────────────────────────────────┤
│ · 环境信息(当前目录、操作系统) │
│ · 语言偏好(用户的语言设置) │
│ · 输出风格(用户的格式偏好) │
└─────────────────────────────────────────────────────────────┘
静态部分可被所有用户共享缓存,大幅提升命中率。
二:通过标签增量变更,而非修改原始前缀
问题:历史提示词内容发生变化时,如何保持前缀不变?
错误:直接修改系统提示词中的日期
[原始系统提示词:当前日期是10号] → [修改后:当前日期是11号]
→ 前缀改变 → KV 需要重新计算
正确 :使用 system_reminder 标签追加变更
[原始前缀:当前日期是10号]
[system_reminder:注意,当前时间是11号]
→ 前缀保持不变 → KV 可复用
建议
- 尽量把提示词中的变量放在后面,这样前缀更容易保持稳定
- 区分静态和动态内容 :静态内容前置,
动态内容后置 - 使用
增量标记:而非直接修改已有提示词 - 考虑本地部署:用户数据完全可控,不依赖第三方缓存
附录
| 概念 | 作用 | 关键 |
|---|---|---|
| KV Cache | 单次生成中复用历史 KV | 避免重复计算,加速 token 生成 |
| Prompt Cache | 多次调用中复用前缀 KV | 降低成本,提升效率 |
| 缓存命中 | 复用已计算的 KV |
成本可降低到"脚踝价" |
| Claude Code 策略 | 优化前缀结构 | 静态前置 + 增量标记 |
缓存虽好,但的确危险。
缓存管理没做好可能导致:
- A 用户的对话缓存被错误地丢给 B 用户的模型调用
- 模型"说出不该说的话"
重视隐私的时代是非常重大的问题
Ralph + mutil agent
当试图给AI丢一个很长的任务,随着工作展开,模型可用的上下文空间会越来越少,理解负担越来越大,导致工作质量越来越差,最终上下文耗尽,模型停止工作。
Anthropic 官方设想:
- 制定详细工作计划,把大任务拆分成很多具体任务
- 每次启动全新
会话执行其中一个任务 - 不断重复,直到所有工作完成
每个任务都在新会话中执行,因此每个任务都有独立的全新上下文窗口
二、方案一:Ralph 方案(循环启动新会话)
用 while 循环不断用 claude 命令启动新会话,通过文件系统衔接上下文。
bash
# Ralph 项目核心代码(伪代码)
while [ 任务未完成 ]; do
claude --no-input --print "根据当前进度执行下一个任务"
# 通过文件存储上下文信息
done
| 关键 | 说明 |
|---|---|
| 循环维护 | 循环由 shell 脚本维护(后续多智能体方案用智能体维护) |
| claude 命令 | 在后台静默执行,claude "你好" 等同于在客户端里写"你好" |
| 相同提示词做不同事 | 每次启动新会话时,AI读取相同提示词,但通过文件了解需求和进度,做不同任务 |
2.3 提示词设计
每轮会话的工作流程:
- 读取文件了解需求和进度
- 从待办任务中挑选最重要的执行
- 完成后在相关文件中标记任务为
已完成
信息传递方式 :前一个会话的AI和后一个会话的AI通过**文件**来交流。
2.4 确保高质量产出
提示词做了几个约束:
- 提供所有上下文:需求文档、进度文档、工作规范、踩坑记录
- 要求严格测试确保质量
自己开发自己测试效果一般,更好的办法是另外找子智能体来测试。
2.5 应用方式
可以使用原生 Ralph,或 ragfor claude code 框架(在原版基础上增加更多功能)
操作流程:
- 准备需求文档(越清楚越好)
- 用框架生成开发计划、设计规格、提示词等文档
- 运行 shell 脚本启动循环
三、方案二:多智能体方案(推荐)
| 角色 | 职责 |
|---|---|
| 主智能体(协调者) | 只负责协调,不负责任何编码工作,保持上下文简洁 |
| 子智能体 | 专门负责制定计划、开发、测试等具体工作 |
- 设计工作流程
- 按流程写出主智能体和子智能体的提示词
四、工作流程设计
4.1 示例:视频素材制作
一次视频可能要做几十页PPT,一次性丢给AI经常有布局问题。
解决:拆解任务,每次只开发一个页面,开发一个测一个,然后再开发下一个。
4.2 流程
主智能体接收需求文档
↓
主智能体把文档路径丢给「计划子智能体」
↓
计划子智能体制定计划,返回计划文档路径
↓
主智能体取出任务,按模板启动「开发子智能体」
↓
开发子智能体完成任务,**返回文件路径**(不是代码内容)
↓
主智能体将文件路径提交给「测试子智能体」
↓
测试子智能体完成测试,**返回测试结果文件路径**(不是大段内容)
↓
┌─ 测试失败 ─→ 将失败信息返回给**刚才负责开发的子智能体**,让它继续修复
│ (不是启动新的开发子智能体,因为历史上下文很重要)
│ ↓
│ 修复后让**刚才提出问题的测试子智能体**验收
│ (不是新开测试子智能体)
│
└─ 测试成功 ─→ 更新任务进度,读取新任务,启动新的开发子智能体
↓
重复上述循环,直到全部任务完成
谁写的 bug 谁修,谁提的 bug 谁验收
这是保证质量的关键设计
五、提示词设计
5.1 主智能体提示词
markdown
【强调】只负责调度,不要阅读具体文件
【初始化】
1. 创建日志文件,记录工作进程
2. 获取子智能体ID(通过子智能体创建时生成的文件名获取)
【执行工作流程】
1. 使用 agent 工具启动计划子智能体(jazen),要求返回文件路径
2. 进入开发循环:
- 启动开发子智能体
- 完成开发后,并行启动三个测试子智能体
- 收集判定结果和测试报告路径
3. 进入修正循环(最多3次,避免无限循环):
- 修复时启动**之前的开发子智能体**
- 验收时启动**之前的测试子智能体**
- 使用 `agent` 方法的 `resume` 参数传入子智能体ID
4. 完成循环后更新日志
5. 全部完成后做总结汇报
技巧:把踩过的坑的经验也写进提示词,让AI不要犯相同的错误。
5.2 子智能体模板结构
Cloud Code 对子智能体有规范,需要做两步:
第一步:按照模板完成子智能体设计
模板分为两部分:
markdown
--- 顶部:子智能体配置 ---
name: xxx
model: xxx
skills: xxx
permissions: xxx
--- 底部:系统提示词 ---
[具体的工作指令]
第二步:放置到指定路径
将模板文档放在 .cloud/agent/ 目录下,按子智能体名字命名。后续主智能体可以通过 agent 方法传入子智能体名字,按照模板直接生成。
5.3 子智能体提示词设计要点
两个作用:
- 衔接流程(不管有没有流程原本都得做的)
- 办好事情(根据实际需要设计)
两个重点:
| 重点 | 说明 |
|---|---|
| 文件传递 | 规定好开始工作前读什么文件,结束后写入什么文件 |
| 输出格式 | 只输出文件路径给主智能体,不要输出开发内容或测试内容(占用主智能体上下文空间) |
示例(测试子智能体):
- 开始时:读取开发写的代码文件
- 结束时:把测试结果写入本地文件,返回文件路径给主智能体
5.4 经验沉淀机制
markdown
开发子智能体改完 bug 之后,要把经验沉淀到本地文件。
作用:
1. 同一个大循环中,前面踩的坑马上成为后来者的经验
2. 事后用于完善 skill
效果:有一次AI自己也发现,前面踩完坑后,后面的测试速度明显加快
六、实践
6.1 子智能体
| 子智能体 | 数量 | 用途 |
|---|---|---|
| 计划子智能体 | 1个 | 制定计划 |
| 开发子智能体 | 1个 | 执行开发 |
| 测试子智能体 | 3个 | 从页面布局、美观、动画三方面测试 |
6.2 模型配置
| 子智能体类型 | 模型 | 说明 |
|---|---|---|
| 测试子智能体 | GLM-1.7(黑户) | 测试工作简单,用便宜模型加快速度 |
| 其他智能体 | GLM-5.1 | 负责复杂工作 |
6.3 Skill 分配
给开发和测试分别分配专属 Skill,让他们知道怎么开发、怎么测试。
不要迷信流行的 skill,要根据自己需求量身定制。流行的 skill
注重设计感,但如果更注重传递信息,可能不适合
七、执行流程
1. 设计主智能体提示词
2. 设计各子智能体模板和提示词
3. 准备好需求文档
4. 把需求文档 + 主智能体提示词发送给 Claude Code
5. AI 开始执行,你该干嘛干嘛
6. 回来验收成果
只要AI在干活,可以心安理得的吃喝玩乐😋,并没有在浪费时间。
附录
| 概念 | 说明 |
|---|---|
| Harness 工程 | 让 Agent 长时间不间断工作的技术 |
| Ralph 方案 | 用 shell 循环 + claude 命令不断启动新会话 |
| 多智能体方案 | 主智能体协调 + 子智能体各司其职 |
| Agent Team | Cloud Code 的多智能体功能 |
| Resume | 恢复子智能体上下文的方法 |
| 文件路径传递 | 子智能体之间通过文件路径而非内容传递信息 |
| 问题 | 解答 |
|---|---|
| 子智能体完成任务就退出? | 子智能体虽然被销毁,但对话上下文已存储到本地文件,可以从本地文件恢复 |
| 主智能体挂了怎么办? | 使用 resume 命令恢复 |
| 权限怎么设置? | 权限全放开即可 |
| 设计文档需要人工审阅吗? | 视频中提到子智能体可以自己完成计划制定和设计 |
| 成本如何? | 确实会消耗较多 token,但"不断重复直到任务完成" |
多智能体方案的核心:
- 独立上下文空间:用子智能体创造独立上下文给AI干活
- 主智能体协调,子智能体执行:主智能体不干活,只调度
- 文件传递信息:子智能体之间只传文件路径,不传具体内容
- 谁写 bug 谁修,谁提 bug 谁验收:保证修复质量
- 经验沉淀:让AI不犯相同错误
- 分层模型:复杂工作用好模型,测试等简单工作用便宜模型
结论:不要迷信框架或流程,多智能体只是帮你把流程维护好,真正决定交付质量的是对子智能体的设计。