3.1 多级缓存设计 一句话带走:把对话引擎的缓存想成一道「三级漏斗」——结果缓存挡掉完全一样的请求,前缀 KV 缓存挡掉重复的 prefill,语义缓存挡掉「问法不同、意思相近」的请求。三级叠加,整体命中率能做到 30%50%,意味着近一半请求根本不用重新把 token 喂给 GPU。 缓存解决的根本问题 在第二章我们靠限流挡住「不该进来的请求」。但进来之后,每一次生成都要经过两个昂贵阶段:prefill(预填充) 把整段上下文算成 KV 状态,decode(逐 token 生成) 一个字一个字往外吐。对话越长,prefill 的上下文越长,单次成本越高,且显存占用随上下文线性增长。 缓存要做的,就是让「本来要重算的东西」被复用掉。
一句话带走:把对话引擎的缓存想成一道「三级漏斗」——结果缓存挡掉完全一样的请求,前缀 KV 缓存挡掉重复的 prefill,语义缓存挡掉「问法不同、意思相近」的请求。三级叠加,整体命中率能做到 30%~50%,意味着近一半请求根本不用重新把 token 喂给 GPU。
在第二章我们靠限流挡住「不该进来的请求」。但进来之后,每一次生成都要经过两个昂贵阶段:prefill(预填充) 把整段上下文算成 KV 状态,decode(逐 token 生成) 一个字一个字往外吐。对话越长,prefill 的上下文越长,单次成本越高,且显存占用随上下文线性增长。
缓存要做的,就是让「本来要重算的东西」被复用掉。在百万级 QPS 下,哪怕只挡掉 30% 的请求,推理集群规模也能相应缩小一个可观比例——这是投入产出比最高的一笔账。
我们把缓存分成三级,从「最便宜、最精确」到「稍贵、更泛化」依次兜底:
最简单也最容易被低估的一级。要点是把「完全相同的请求」识别出来。
键怎么取:最常见的是对「模型 + 版本 + 系统提示词 + 用户问题 + 关键参数(temperature、top_p 等)」做稳定哈希。只有这些因素完全一致才命中——因为 temperature 不同、模型版本不同,同样的问句也可能得到不同答案,不能混用缓存。
适合什么:FAQ 类问题、固定话术、产品咨询、查天气/查汇率这类「答案与上下文无关」的请求。在客服类对话里,这类问题往往占比很高,结果缓存的命中率可以非常可观。
失效策略:答案会随知识更新而过期。给每条缓存打 TTL(如 5 分钟到 24 小时),对强时效内容(股价、天气)设短 TTL;同时当底层知识库或模型版本变更时,按命名空间批量失效。
这一级是降本的大头,但「踩坑率」也最高。原理是:模型推理时,系统提示词、few-shot 示例这类固定前缀在每个请求里都重复出现;如果能让推理框架复用这些前缀已经算好的 KV 状态,就不用每次都重新 prefill 整段前缀。
经典悲剧:命中率为 0。 很多团队接上 KV 缓存后效果为零,原因往往是——他们为了「让每次请求更个性化」,在前缀里插入了用户 ID、时间戳、随机 trace_id,或者每次把系统提示词的措辞微调一下。结果是:每次前缀都不同,缓存永远 miss。记住一条铁律:想被复用的前缀,必须保持字节级稳定。
工程要点:
一点诚实的提醒:前缀 KV 缓存的收益取决于「前缀占比」。如果你的系统提示词很短、用户问题才是大头,那这级收益有限;反之,若你用了很长很精细的系统提示词(很多 Agent 场景正是如此),这级能省下的 prefill 成本非常可观。
前两级都要求「字面一致」,但真实对话里,用户换种问法问同一件事太常见了:「怎么退款」和「我想把钱退回来」意思一样,却字面不同。语义缓存就是为此而生。
原理:把用户问题做向量化(embedding),与缓存库里的历史问题向量比对余弦相似度;相似度超过阈值(如 0.92)就认为命中,返回对应的答案或上下文片段。
关键设计:
三级缓存不是「选一个用」,而是层层兜底:先查结果缓存(精确、零成本),miss 了查前缀 KV(省 prefill),再 miss 查语义缓存(省部分计算)。每一级都替下一级削掉一部分流量,最终整体命中率 = 1 − (1−P1)(1−P2)(1−P3)。
一致性是底线:所有缓存都必须绑定「模型版本 + 知识版本」作为命名空间。一旦模型升级或知识库更新,旧命名空间的缓存必须能一键失效,否则用户会拿到「用旧知识算出来的过时答案」,这种隐性错误比慢更可怕。
把三级缓存做扎实,你会在账单上直接看到推理集群用量下降;这比任何「换更快的框架」都来得实在。
前面讲的是「缓存逻辑上分几级」,但落到生产,你还得决定「缓存放哪」。这直接决定命中率和延迟:
我的取舍建议:结果缓存和语义缓存放集中式(共享优先),前缀 KV 依赖推理框架本机池加会话黏性路由。别把所有缓存都堆到 Redis,那样前缀 KV 的复用链路会被拉长、收益归零。
缓存不是「无限大就最好」。内存和显存都是有限资源,必须做容量规划和淘汰:
假设某业务 100 万 QPS,单次生成平均消耗 2000 token 的算力当量,三级缓存命中率分别为:结果缓存 15%、前缀 KV 40%(在结果未命中后的请求上)、语义缓存 25%(在前两者都未命中后)。则整体命中率约等于 1 减 (1 减 0.15)(1 减 0.40)(1 减 0.25) 约等于 1 减 0.85 乘 0.60 乘 0.75 约等于 1 减 0.3825 约等于 61.75%。
这意味着近 62% 的请求被缓存兜住、不必重新生成,推理集群规模可据此大幅压缩。即便现实里语义缓存调不高(保守取 15%),整体也能到约 53%。这是架构层面「算得起」的关键,而非靠堆 GPU。当然数字会因业务差异很大,这里只是演示「三级叠加的乘数效应」,真实值要靠你的流量画像反推。
把这几项接进监控大盘,缓存策略就从一个「玄学优化」变成了「可度量、可迭代的工程模块」。
语义缓存好不好用,七分看向量检索。几个常被忽略的工程细节:
结果缓存看似简单,键设计却有三个高频坑:
把键设计成「确定性函数」——同样的语义输入永远映射到同一个键——是结果缓存正确性的根基。
一个容易混淆的点:缓存和限流都「拦请求」,但目的不同。限流是「保护系统不被压垮」(第二章),缓存是「省算力、降延迟」(本章)。二者可以叠加:限流挡掉超额流量后,剩下的流量里再由缓存兜掉一部分计算。在容量规划时,建议先把「缓存能兜掉多少」算进去,再去定限流阈值和集群规模——否则你会为「本来就不该算的请求」多买一堆 GPU。
别一上来就三件套全推,那样一旦语义缓存答错,你很难定位是哪一级出的问题。一级一级加,每级都能单独验证,才是稳的打法。