LLM 应用的语义缓存:基准测试没告诉你的真相
每个销售 LLM 网关的供应商都会向你展示一张标有“95% 缓存命中率”的幻灯片。那张幻灯片不会告诉你的是小字说明:这个数字是指在找到匹配项时的匹配准确度,而不是找到匹配项的频率。实际的生产系统命中率为 20–45% —— 营销与现实之间的差距正是大多数团队踩坑的地方。
语义缓存(Semantic caching)是一项非常有用的技术。但在不了解其失效模式的情况下部署它,会导致你以极高的置信度向用户返回错误答案,并让你纳闷为什么支持工单翻了一倍。
语义缓存究竟在做什么
传统缓存是确定性的:对请求进行哈希处理,查找哈希值,返回存储的响应。当相同的字节重复出现时,这种方法非常有效。但 LLM 查询很少是字节完全一致的。“我该如何重置密码?”和“重置密码的步骤”是不同的字符串,但在查询意图上是完全一 致的。精确缓存(Exact caching)在第一次之后会错过这两个查询。
语义缓存通过将每个查询转换为嵌入向量(embedding vector),并使用余弦相似度(cosine similarity)将新查询与存储的向量进行比较来解决这个问题。当相似度分数超过阈值时,系统会直接返回缓存的响应,而无需调用 LLM。
架构如下:
- 第一层(精确缓存):对完整查询字符串进行哈希处理。如果命中,立即返回。在大多数生产系统中,这可以捕获 15–30% 的流量 —— 自动化流水线和用户重试产生的精确重复比你预期的要多。
- 第二层(语义缓存):对查询进行向量化,搜索向量索引,评估余弦相似度。如果分数超过你的阈值,返回缓存的响应。
- 未命中路径:转发给 LLM,缓存该“查询-响应”对供将来查找。
运行第二层的嵌入模型通常比生成模型更轻、更快 —— 比如 sentence-transformer 或小型双编码器(bi-encoder)。缓存查找路径的延迟预算需要控制在 ~50ms 以下,否则你会抵消掉因避免调用 LLM 而节省的延迟优势。
命中率的现实情况
研究论文通常报告 60–70% 的缓存命中率。但生产流量要复杂得多。真实部署中的实际范围大致按应用类型细分如下:
- FAQ 和支持机器人:40–60% —— 这是语义缓存大放异彩的地方。用户会反复询问那几个固定的问题。
- 分类任务:50–70% —— 输入空间离散,查询多样性有限。
- 基于 RAG 的问答:15–25% —— 用户在广泛的事实领域提问;真正的重复很少见。
- 开放式对话:10–20% —— 几乎每一轮对话都是唯一的。语义缓存在这里基本无效。
- Agent 工具调用:5–15% —— 查询取决于先前的上下文和当前状态;表面上相同的问题可能需要完全不同的响应。
FAQ 性能与 Agent 性能之间的差距至关重要,因为团队通常会在最简单的用例上评估语义缓存,然后进行全局部署。如果你的系统处理多种查询类型,25% 的综合命中率比 60% 更现实。
如果每月 LLM 支出为 5,000 美元,25% 的命中率在扣除基础设施成本前大约能节省 1,000 美元/月。这是一笔真金白银,但值得与正确调整和维护缓存所需的工程时间进行对比 —— 这就涉及到了最困难的部分。
阈值是旋钮,而非固定设置
余弦相似度阈值决定了你的缓存是有用还是危险。它也是语义缓存部署中最常被错误配置的组件。
阈值过低(低于 0.80):你会针对表面相似的查询返回缓存响应。一家金融服务机构就直接遇到了这个问题 —— 一位客户说“我不想再要这个商业账户了”,系统以 88.7% 的余弦相似度置信度将其路由到了自动取消付款程序,而该查询实际上需要触发账户关闭审核。这两个查询虽然相关,但所需的响应完全不同。
阈值过高(高于 0.95):你的行为会接近精确缓存。你承担了基础设施的复杂性,却没有获得语义匹配的好处。
通常推荐的最佳平衡点是 0.92 —— 但这只是一个全局默认值,而全局默认值对于异构工作负载来说是错误的。在稠密代码嵌入空间中,单一阈值会将 "sort_ascending" 和 "sort_descending" 视为相同的(它们的向量非常相似),同时在稀疏的对话空间中错过有效的改写。
更好的方法:
- 按类别设置阈值:根据意图或主题(分类、编程、事实查询、开放式)对查询进行标记,并应用不同的阈值。分类任务可以容忍 0.85;技术查询则需要 0.95 或更高。
- 命名空间隔离:按查询类别划分缓存。命中错误的分类分区比未命中更危险。
- 置信度衰减:跟踪每个条目的命中次数。具有多次命中和正面反馈信号的条目可以使用较宽松的阈值;新条目则默认使用保守阈值。
当你升级嵌入模型时,阈值问题会变得更加复杂。新模型会为相同的查询生成不同的向量表示。你存储的所有嵌入都会失效 —— 它们无法再与新查询进行有效的比较。每当你更改嵌入模型时,请务必制定全量缓存失效和重建时间的计划。
标题级别的故障模式(错误回答)往往备受关注,但还有另外四种模式常常被忽视:
幻觉响应导致的缓存污染:当 LLM 产生幻觉或错误响应时,你会缓存该错误。未来语义匹配的查询将在不调用 LLM 的情况下直接获取错误答案。错误会随之传播并复合。在缓存之前,你需要进行质量验证——可以使用模型的置信度评分,或者定期对新鲜响应进行抽样对比以检测漂移。
缺乏过期逻辑的陈旧数据:昨天正确的缓存响应在今天可能就是错误的。产品定价、政策详情、库存情况——这些都会变化。当变化不规律时,仅靠 TTL 的失效策略就会失效。更好的 approach 是:为缓存条目附加元数据,说明它们依赖哪些事实领域,并在底层数据变化时按领域进行失效处理。
流式输出中断:如果你的应用使用流式响应(Token 逐个显示),简单的语义缓存会破坏这种机制。将预先缓存的完整响应作为流返回需要不同的代码路径——你是在逐个字符地回放存储内容,而不是消耗实时的 Token 流。在现有流式流水线中添加语义缓存而未考虑这一点的团队,会引入微妙的用户体验退化。
上下文窗口污染:在多轮对话中,用户共享会话状态。取决于中间发生了什么,第 3 轮中的相同查询可能需要与第 7 轮完全不同的响应。仅根据独立查询文本进行缓存而忽略对话上下文,产生的响应虽然在孤立状态下语义正确,但在用户上下文中却是错误的。
最后一点是语义缓存在没有重大架构工作的情况下,从根本上不属于对话式 AI 流程的原因。缓存键需要代表完整的上下文,而不仅仅是当前轮次——而到那时,缓存命中的概率将趋于零。
