07-KVCache与缓存友好架构

07 · KV Cache 与缓存友好架构:稳定前缀、命中率与成本

「AI-Agent 面试深度指南」· 模块二 · 上下文工程 · 第 7 篇 / 共 32 篇

引言

两个团队用同一个模型、做同一个客服 Agent,月度账单差了 4 倍。排查下来,差别不在模型,而在缓存命中率:A 团队的系统提示与工具定义被固定在上下文最前面,命中率 92%;B 团队把当前时间戳放在了 system 提示的第一行,命中率常年接近 0。

KV Cache 是 Agent 工程里少有的"既降成本又降延迟还提稳定性"的杠杆,但它的收益完全取决于上下文布局是否"缓存友好"。本文讲清原理、两个缓存层级、四个典型杀手,以及一套可落地的布局规范。

一、KV Cache 原理与两个层级

1.1 为什么需要缓存

自回归生成时,生成第 n 个 token 需要用到前 n−1 个 token 的 Key 和 Value。若不缓存,每生成一个 token 都要重算整个前缀,总复杂度约为 O(n²) 的矩阵运算------这在长上下文下完全不可接受。

缓存后:Prefill 阶段 一次性算完输入的 K/V 并存储;Decode 阶段只计算新 token 的注意力,其余从显存读取。这是典型的"空间换时间",也是为什么长上下文会显著消耗显存。

一个值得记住的量化感受:KV Cache 的显存占用 ≈ 2 × 层数 × 头数 × 维度 × 序列长度 × batch × 精度字节数。这就是为什么长上下文服务更容易 OOM,也解释了 MQA/GQA(共享 K/V 头)为什么能大幅降低显存。

1.2 关键性质:按前缀匹配

KV Cache 的复用单位是前缀 。只有当新请求的前缀与缓存中的某段完全一致(逐 token 相同)时,后续计算才能被复用。前缀中间任何一处不同,后面的缓存全部失效。

这条性质把"上下文布局"从一个风格问题变成了架构约束:稳定的内容必须放前面,易变的内容必须放后面。

1.3 两个层级的缓存

推理引擎层的 KV Cache:同一请求内 Decode 阶段复用,通常引擎自动处理,开发者无感。

服务层的 Prefix / Prompt Cache:跨请求复用公共前缀。多数厂商对缓存命中的 token 给予显著折扣(通常是输入单价的几分之一),对长系统提示 + 大工具集的 Agent 收益尤其明显。

二者的关系可以理解为:前者解决"一次生成别重复算",后者解决"不同请求别重复算"。Agent 场景的收益主要来自后者------因为同一个 Agent 的每一轮、每一个用户的请求,都共享同一份系统提示与工具定义。

二、四个典型的缓存杀手

2.1 动态信息插在开头

最常见的错误:在系统提示第一行写"当前时间:2026-09-07 11:23:52"。每过一秒,这个前缀就变一次,后面几万 token 的缓存全部作废。

解法:把时间、进度、剩余预算等动态信息统一放进尾部的"状态栏"区块。模型同样能读到,但缓存前缀不受影响。

2.2 工具列表顺序不稳定

若工具定义按"使用频率动态排序"或"从 dict 随机顺序生成",每次请求的顺序都可能不同。

解法 :工具顺序固定(按注册顺序或固定排序规则),变化时通过版本号显式管理------即工具集更新时整体换一版,而不是每次微调顺序。

2.3 系统提示中嵌入动态用户信息

"当前用户:张三(VIP,上次登录 3 天前)"这类内容若放在 system 提示里,等于给每个用户一个独立前缀。

解法:用户信息放在用户消息或尾部状态栏;系统提示只放与用户无关的规则。

2.4 消息历史被重排或改写

有些实现会在每轮对历史做"格式化整理"(重新编号、调整顺序、去除空格),导致前缀变化。

解法 :历史采用只增不改(append-only)策略,新增内容追加在尾部,已有内容不再改动。压缩操作也只在尾部区块内进行。

三、缓存友好的上下文布局规范

3.1 推荐的五段式布局

复制代码
[1] 系统提示词        ← 完全静态,最长,最该缓存
[2] 工具定义          ← 静态,顺序固定
[3] 长期知识/索引      ← 半静态,按版本更新
[4] 消息历史          ← 只增不改,增量追加
[5] 状态栏 + 当前输入  ← 完全动态,放最后

这个布局同时满足三件事:缓存命中率最高、模型对首尾敏感区被有效利用(开头放规则、结尾放当前任务)、压缩与裁剪操作只需触碰尾部。

3.2 缓存与压缩的关系:看似矛盾,实则互补

一个常见疑问:压缩会改写历史,破坏前缀,那还能不能压?

答案是能,但要分区域:

  • 前缀区(1--3):永不改动,保证命中;
  • 历史区(4):采用"稳定段 + 活跃段"双层结构------较老的历史压缩一次后固定下来(此后成为稳定前缀的一部分),最近几轮保持原样增量追加;
  • 尾部区(5):自由变化。

这样既获得了压缩带来的 token 节省,又保住了主要前缀的命中率。实践中往往能达到"压缩省下的成本 > 缓存损失的成本"。

3.3 可观测性:必须监控命中率

没有度量就没有优化。每次模型调用的返回里通常包含 cached_tokens 字段(不同厂商命名不同)。要做的三件事:

  1. 在 trace 中记录每次调用的 cached/total 比例;
  2. 按场景聚合,找出命中率低的调用方;
  3. 对命中率异常下跌设置告警------它通常意味着有人改动了系统提示或工具顺序。

四、成本与延迟的连带收益

4.1 成本账

假设某 Agent 的系统提示 + 工具定义共 4000 token,每次任务 10 轮,日活 1 万次任务:

  • 无缓存:10 万次调用 × 4000 token × 输入单价;
  • 命中 90%:仅 10% 的部分按原价,其余按折扣价。

在长系统提示场景,缓存通常能带来数倍的输入成本下降,且轮数越多收益越大。

4.2 延迟账

命中缓存的前缀跳过了 Prefill 的计算,直接降低首 token 延迟(TTFT)。对交互型应用,TTFT 是影响"快不快"主观感受的最关键指标。

4.3 与分层模型的协同

缓存与"小模型路由 + 大模型决策"并不冲突,但要注意:缓存是按模型实例绑定的,换模型即失效。因此切换模型要有节制,频繁在模型间跳会同时损失缓存收益与行为一致性。

五、面试考点与答题框架

5.1 高频真题

Q1:为什么 ReAct 循环的缓存读取量随轮数近似二次方增长?

答:每轮请求都要携带(并可能重新读取)越来越长的上下文前缀;轮数为 n 时,累计读取量约为 O(n²)。优化方向有三个:稳定前缀提高命中、增量追加避免重排、减少无效轮数(更好的规划与首次成功率)。

Q2:怎么设计上下文以最大化命中率?

答:五段式布局(系统提示 → 工具定义 → 长期知识 → 只增不改的历史 → 动态状态与当前输入);禁止在头部插入任何随时间或用户变化的内容;用版本号管理工具集变更。

Q3:缓存命中对成本影响有多大?命中不了怎么排查?

答:命中单价通常是输入单价的几分之一,长系统提示场景可省下数倍输入成本。排查方法:打印每次调用返回的 cached token 数;若为 0,逐段比对前后两次请求的原始文本,找出第一处不一致的位置------90% 的情况是时间戳或工具顺序。

5.2 答题加分点

  • 能解释"前缀匹配"并推出"稳定前置、易变后置";
  • 能说出缓存与压缩的分区域共存方案;
  • 能把 TTFT 与缓存关联起来,说明你理解用户体验层面的收益。

小结

缓存友好架构可以浓缩成八个字:稳定前置、易变后置 ,再加四个字的执行纪律:只增不改。它同时改善成本、延迟与稳定性,是投入产出比最高的一项优化。做 Agent 而不做缓存治理,等于每次请求都在为同一份系统提示重复付费。

相关推荐
linan1013 小时前
android调用C++通用方式
linux·架构·智能硬件
白远山3 小时前
自助健身小程序源码:架构拆解、核心链路与本地部署实战
java·架构·uni-app·需求分析
国科安芯3 小时前
商业航天星载数据管理单元的存储容错与接口集成方案研究
嵌入式硬件·架构·ecc·商业航天·抗辐射
白远山5 小时前
无人自助健身平台搭建:从架构设计到设备联动的完整实战
java·开发语言·架构·需求分析
许彰午5 小时前
47-MetaGrid元数据表格
java·低代码·架构
Android打工仔5 小时前
不要在 Data 层随意把 Cold Flow 转换成 Hot Flow
android·架构·kotlin
AI行业应用研究6 小时前
会务问答机器人落地拆解:三级路由、知识库组织与防幻觉——会务小程序能自己回答参会者提问吗?
大数据·人工智能·安全·小程序·架构
海宇服务6 小时前
零信任架构实战:基于海宇公安二要素认证即时版构建自动化号码发卡网关
运维·人工智能·架构·自动化
bullkingluo6 小时前
从零到一搭建企业级智能问答系统:Ch05 · 向量库
人工智能·架构