16. LLM 应用的多层缓存怎么设计?哪些环节能缓存、缓存命中怎么算钱?
整理 LLM 应用从语义缓存到 Prompt Caching 的多层缓存设计。
简单回答
LLM 应用的缓存不止一层,从上到下至少有四层可以做:响应级语义缓存(同义 query 直接返回历史回答)、RAG 检索结果缓存(同 query 复用召回 chunk)、Prompt Caching(System Prompt 等长前缀复用 KV Cache,由模型 Provider 提供)、以及 Embedding 缓存(同文本复用向量)。每层的命中率、收益和失效复杂度都不一样。设计时关键是想清楚每层的 cache key 怎么定(精确匹配 vs 语义匹配)、TTL 怎么定、失效语义怎么传导,以及命中后怎么计费——Prompt Caching 命中后 input token 单价能降到 1/10 以下,是成本治理里的大头。
详细解释
为什么 LLM 应用特别需要缓存
LLM 推理的两个特征让缓存的 ROI 比传统服务高:
- 单次推理成本绝对值高(一次 GPT-4 调用几分到几角钱)
- 实际请求里有大量重复或近似重复(同一个产品的客服 FAQ、同一个 System Prompt 反复发、同一份文档反复检索)
传统服务一次请求几毫秒几个 CPU 周期,缓存命中收益 100ms-1s。LLM 服务命中收益直接是几秒延迟和几分钱,量级完全不同。所以 LLM 应用做缓存的工程价值远高于传统服务。
四层缓存
Layer 1:响应级语义缓存(Response Cache)
最上层是直接缓存"query → response"对。下次来一个 query 看缓存里有没有同义的,有就直接返回 response,根本不调模型。
Cache key 的难点是 query 的语义匹配。"今天天气怎么样"和"今天天气如何"是同义的——精确字符串匹配命中不了。常见做法是把 query 做 embedding,在向量空间里做相似度检索,相似度超过阈值(比如 0.95)算命中。
收益:命中时延迟从几秒降到几十毫秒,成本从几分钱降到接近零。
风险:误匹配。"今天上海天气"和"明天上海天气"语义相似但答案完全不同,相似度阈值设不好就出错。一般在三种场景下用:FAQ 类高频问答(语义边界清晰)、客服热点问题(同一问题反复来)、知识库问答的稳定查询。开放式对话和创作类不能用。
TTL 设计要看内容是否时变。"公司退货政策"半年都不会变,TTL 可以设几天。"今天股价"几分钟就过期。不可一刀切。
Layer 2:RAG 检索结果缓存
如果应用走 RAG 链路,检索结果是一个值得缓存的中间产物。同一个 query 多次进来时不必每次都查向量库 + 重排——直接返回上次检索到的 chunk 列表。
Cache key 通常是 query 的精确字符串(或归一化后的字符串)+ 知识库版本号。命中后跳过 embedding 计算和向量库查询,直接拿 chunk 列表去喂模型。
收益:省掉 RAG 检索的 100-300ms 延迟和向量库的 QPS 压力。
失效要和知识库索引版本绑定——文档更新后旧的检索结果可能就不准了。生产里常见做法是给每个知识库版本一个 ID,版本号变了缓存自然失效。
Layer 3:Prompt Caching(前缀 KV Cache 复用)
这一层是 2024 年后特别火的点,OpenAI、Anthropic、DeepSeek、Google 都支持了。原理是 LLM 推理的 KV Cache 可以在多个请求之间复用——只要前缀完全相同,前缀部分的 KV Cache 算一次后续请求都能用。
典型场景:
- System Prompt 很长(几千 token 的规则、Few-shot 示例),每个请求都重发——用 Prompt Caching 后这部分 KV 只算一次
- RAG 检索到的文档很长,多个用户问同一份文档不同问题——文档前缀复用
- 多轮对话中之前的对话历史是固定的——历史前缀复用
- Code editing 场景大段不变的代码作为前缀
命中后的收益是双重的:Prefill 时间大幅降低(前缀部分跳过计算),且 Provider 的计费上前缀 token 是大幅打折的。Anthropic 的 Prompt Caching 命中后 input token 收 1/10,OpenAI 是 1/2,DeepSeek 也是几折。这是大规模应用里成本优化的最大杠杆之一。
工程上的关键是 把可缓存的内容放在 Prompt 最前面——前缀必须从开头到某个截断点完全一致才能命中。把变化的内容(用户 query)放在最后,把固定的(System Prompt、Few-shot、共享文档)放在最前。一个常见错误是把用户名拼在 System Prompt 开头,导致每个用户都是独立的前缀,命中率为零。
TTL 由 Provider 控制(Anthropic 默认 5 分钟,可申请到 1 小时),不需要应用层管理失效。
Layer 4:Embedding 缓存
最底层是 embedding 计算的缓存。同一段文本的 embedding 永远是同一个(给定模型版本),完全可以缓存。
适合缓存的场景:用户输入的 query 在短时间内可能多次进入系统、文档入库时同一段文本被多次处理、RAG 链路里的 query embedding 计算。
Cache key 是文本哈希 + embedding 模型版本。失效条件简单——embedding 模型升级时整体清空。
收益相对小(embedding 调用本来就比 LLM 推理便宜很多),但实现简单几乎零成本,值得做。
缓存的总体架构
四层从上到下命中率递减但单次收益不同,可以并存:
不同层之间的命中率乘起来不等于总命中率——它们解决的是不同维度的重复。
缓存命中后怎么算钱
成本治理上几个细节要注意:
Prompt Caching 命中部分单价不同。计费日志里要分别记录 cache_creation_input_tokens / cache_read_input_tokens / output_tokens 三栏。做用户账单时要按对应单价计算,不能笼统用 input_tokens 一栏。
响应级缓存命中算不算用户用量。这是产品/计费策略问题。一种做法是命中也按调用一次扣额度(用户视角是"用了一次"),另一种是命中不计费(成本本来就接近 0)。多数 SaaS 选前者保留商业模型,多数内部系统选后者。
缓存填充成本要回收。Prompt Caching 第一次写入是付费的(创建 KV Cache 比正常 input token 贵 25%),收益要在多次复用中摊回。如果某个 System Prompt 实际只被调用一两次就过期,开 Prompt Caching 反而亏本。生产里要监控 cache hit rate 和 cache amortization——缓存平均被复用多少次决定 ROI。
典型陷阱
语义缓存的相似度阈值过低导致错误回答。这是最危险的陷阱——用户问"上海明天天气"返回了"上海今天天气"的缓存。生产里建议阈值起码 0.95,且关键词匹配(时间、地点、数量等关键实体)必须完全一致才算命中。
Prompt Caching 失效条件被忽视。任何前缀变化(多一个空格、字段顺序换了、System Prompt 改了一个字)都让缓存失效。Prompt 模板要严格控制版本,不能随意改动。版本管理(本专题第 09 篇)和缓存设计要联动。
缓存和合规冲突。如果用户数据按合规要求"立即删除即立即不可见",缓存里的内容也要同步删。隐私数据进缓存前要脱敏,或者直接禁用缓存。
多租户隔离。语义缓存如果在多租户间共享,A 租户的回答可能被 B 租户命中——既是隐私问题也是商业问题。多租户场景缓存 key 必须带租户 ID。
面试时可以这样答
LLM 应用的缓存不止一层,从上到下至少四层。
第一层是响应级语义缓存——把 query 做 embedding,相似度超阈值就直接返回历史 response。命中收益最大,省掉整个推理。但适用场景窄,只能用在 FAQ、客服热点、知识库稳定查询这种语义边界清晰的场景。开放式对话不能用,相似度阈值要设到 0.95 以上避免误匹配。
第二层是 RAG 检索结果缓存。query 字符串作 key,命中跳过 embedding 和向量库查询。失效要绑定知识库版本号。
第三层是 Prompt Caching,2024 年后所有主流 Provider 都支持。把不变的 System Prompt、Few-shot、共享文档放在 Prompt 最前面,前缀完全一致就能复用 KV Cache。命中后 Prefill 时间大降,且 input token 单价大幅打折——Anthropic 是 1/10,OpenAI 是 1/2。这是大规模应用成本优化的最大杠杆。工程上关键是模板设计——可缓存内容放最前,变化部分放最后。
第四层是 embedding 缓存,最简单收益最小但几乎零成本。
几个陷阱要避开。语义缓存阈值过低会误回答,关键实体(时间、地点、数量)必须完全一致才能命中。Prompt Caching 任何字符变化都失效,要和 prompt 版本管理联动。多租户场景 cache key 必须带租户 ID。隐私数据进缓存前要脱敏或禁用缓存。
计费上 Prompt Caching 命中部分单价不同,账单要分栏统计。命中算不算用户用量是产品策略,SaaS 通常算用了一次保留商业模型。
常见追问
- 语义缓存的相似度模型怎么选?用通用 embedding 还是专门训一个 retrieval 模型?
- Prompt Caching 命中率怎么监控?低于多少就该重新评估缓存策略?
- 缓存层和限流层的关系?命中缓存的请求要不要计入限流配额?