#AI#DevOps

LLM 应用的缓存策略

LLM 调用贵且慢,缓存是降本提速的利器——但缓存层做错,要么没命中、要么返回陈旧错误答案。本文讲清 LLM 应用的三层缓存:prompt cache(前缀)、语义缓存(相似 query)、结果缓存(精确 key),各自的适用、命中条件、一致性陷阱,以及组合策略。

本文要点

  • 主题聚焦:AI、DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2025年12月6日·更新于 2026年7月23日

LLM 调用有两个原罪:贵(按 token 计费,输入输出都收钱,一个 agent 请求动辄几十次模型调用)和慢(秒级首 token,用户体感像在等拨号上网)。而 LLM 应用的流量里,重复请求占比往往高得惊人——同一个 FAQ、同一份被反复总结的文档、同一类"帮我改改这段代码"。

缓存是降本提速最直接的杠杆,但它不是"加个 Redis 就完事"。缓存层做错,有两种结局:要么命中率低到可以忽略,等于没做;要么命中了陈旧/错误的答案,比不缓存更糟。 下面把 LLM 应用的缓存拆成三层讲清楚。

一、为什么 LLM 缓存值得单独立一章

传统 Web 缓存的是"计算结果",key 是请求参数,失效靠 TTL。LLM 应用多了两个维度:

  1. 缓存粒度跨层。从模型 API 内部的 KV cache,到应用层的整次调用结果,都能缓存,但命中条件和成本结构完全不同。
  2. "相似"也能命中。传统缓存要 key 精确相等,LLM 的语义缓存允许"问法不同、意思一样"也命中——这是红利,也是最大的坑。

理解了这两点,才不会把三层缓存混为一谈。

二、第一层:Prompt Cache(模型 API 层,稳定前缀打折)

这是最该先开、却最容易被忽略的一层。原理:模型推理时会为输入算一份 KV cache,如果两次请求的前缀完全一样,第二次就能复用这份 cache,provider 因此给你折扣。

  • Anthropic prompt caching:显式声明 cache_control,命中的前缀 token 读取成本约为普通的 1/10,写入有 25% 溢价,缓存存活 5 分钟(可指定 1 小时)。
  • OpenAI 自动前缀缓存:无需声明,前缀 ≥1024 token 自动生效,命中部分约 5 折。
  • Gemini context caching:显式创建 cached content,适合把大文档长期挂在上下文里。

命中条件就一条:前缀必须逐 token 精确匹配。所以黄金法则是——稳定的放前面,易变的放后面。

ts
// Anthropic:把 system prompt、few-shot、长文档放前面并标记缓存
const res = await anthropic.messages.create({
  model: "claude-opus-4-5",
  system: [
    { type: "text", text: SYSTEM_PROMPT },                    // 稳定指令
    { type: "text", text: PRODUCT_DOCS, cache_control: { type: "ephemeral" } } // 稳定长文档
  ],
  messages: [{ role: "user", content: userQuestion }]         // 易变内容放最后
});

最常见的踩雷:在前缀里塞了当前时间、随机 ID、user_id、请求序号——任何一个字节的抖动都会让整段前缀 cache 失效。把这类变量挪到 messages 末尾,命中率立刻回升。

三、第二层:语义缓存(应用层,相似 query 命中)

prompt cache 只省 token,还得真去调模型。语义缓存更狠:相似问题直接返回历史答案,整次调用都省了。原理是 query → embedding → 在历史 query 向量里找最近邻 → 相似度高于阈值就返回缓存答案。

Redis 8 自带向量检索,一个实例同时做 KV 和向量,很适合:

ts
// 写入:把 query、答案、embedding 一起存
await redis.sendCommand([
  "VADD", "semcache", embeddingBytes, `ans:${id}`,        // 向量入队
  "HSET", `ans:${id}`, "answer", answer, "ts", Date.now() // 答案存哈希
]);

// 查询:找最相似的邻居
const [hitId, score] = await redis.sendCommand([
  "VSIM", "semcache", embeddingBytes, "COUNT", 1, "WITHSCORES"
]);
if (score > 0.95) {                       // 阈值,越严越安全
  return await redis.hget(`ans:${hitId}`, "answer");
}
// 未命中 → 真正调模型 → 写回缓存

阈值是这门手艺的核心:0.95 以上偏保守,0.90 以下误命中明显增多。语义缓存的经典翻车是"形似神不似":

  • "如何退款" vs "如何帮别人申请退款" —— embedding 很近,答案不能复用。
  • "Python list 怎么反转" vs "Python 字符串怎么反转" —— 一字之差,API 不同。

所以语义缓存只适合事实性、无歧义、非个性化的回答。创意生成、带用户上下文的对话、时效性内容,一律不要进语义缓存。

四、第三层:结果缓存(应用层,精确 key)

当你能定义一个稳定的业务键,就别用语义缓存——直接精确匹配,100% 确定性,不担心"相似误命中"。典型 key 是 doc_id + operation(+ version):

ts
const key = `summarize:${doc.id}:v${doc.version}`;
let summary = await redis.get(key);
if (!summary) {
  summary = await llm.summarize(doc.content);
  await redis.set(key, summary, "EX", 7 * 24 * 3600); // 7 天 TTL 兜底
}

适合确定性操作:同一份文档的摘要、结构化字段提取、固定模板的改写。同一个 doc_id 被一万个人点开摘要,只调一次模型——这层缓存 ROI 最高,趋近零成本。

五、命中率优化:让缓存真的被命中

缓存做了没人命中,等于白做。三个杠杆:

  1. prompt 结构稳定。指令、角色、few-shot、引用文档放前面,用户输入和时间戳放后面。这同时喂饱了 prompt cache 和结果缓存。
  2. query 归一化。算 key 或 embedding 前,先做 trim + lower + 去标点 + 同义词归一。" RAG 怎么做?? " 和 "rag 怎么做" 应该命中同一条。
  3. 热点预热。冷启动时缓存空,头部 20% 的问题占 80% 流量——上线前拿历史日志把 top-N 预跑进缓存,首日命中率就不会难看。

六、一致性陷阱:缓存比没缓存更可怕

这一节决定你是工程师还是埋雷的。

  • 缓存陈旧:源文档改了,摘要还是老的。解法是把 version / content_hash 写进 key,数据一变 key 就变,旧的自然失效;必要时在数据更新链路上主动 DEL。
  • 时效性内容不该缓存。"今天股价""现在几点""最新公告"——这类 query 必须在入口就短路,或只给极短 TTL。
  • 个性化 vs 共享缓存。带用户私域上下文(user_id、聊天历史、权限范围)的答案,绝不能进共享语义缓存,否则就是跨用户信息泄漏。要么按 user 分 namespace,要么干脆不缓存。

一句话:可缓存的是"对所有人一样对"的答案,不是"对这个用户刚好对"的答案。

七、组合策略与监控

三层不是三选一,是叠加。按命中后的成本节省排序:

text
结果缓存(精确 key)   → 趋近零成本,优先级最高
语义缓存(相似 query) → 省整次调用,次之
prompt cache(前缀)   → 省 token + 省时间,兜底

请求进来按这个顺序逐层判,前一层没命中再走下一层。即便最后真去调模型,prompt cache 还能在 token 层给你兜底打折。

没有监控的缓存等于黑盒。盯三个指标:

  • 命中率:每层分别统计,低于预期就查归一化、阈值、key 设计。
  • 缓存错例:语义缓存尤其要采样复检(随机抽命中项重新跑模型比对,或收用户"答非所问"反馈),发现降质立刻调高阈值或下线。
  • 失效策略:TTL 兜底 + 版本号主动失效 + 数据更新时 DEL 三件套,只靠 TTL 迟早出陈旧事故。

小结

  • 三层各管一段:prompt cache 省 token、语义缓存省整次调用、结果缓存趋近零成本,按精确度从高到低优先命中。
  • prompt cache 看前缀稳定:易变内容(time、user_id、随机串)一律挪到末尾。
  • 语义缓存看阈值和场景:0.95+ 起步,只缓存事实性、非个性化、非时效答案。
  • 一致性是底线:版本号写进 key,时效内容不缓存,个性化答案不进共享池。
  • 没监控就别上:命中率分层统计,语义缓存必须采样复检。

金句:LLM 应用的缓存,不是"加个 Redis",而是"在三个不同尺度上押注确定性"——你押得越准,省得越多;押错了,省下的是 token,赔掉的是用户信任。

Related

相关推荐