缓存这件事,命中率是假的,省钱才是真的
给公司的 AI 网关做缓存层时,我在看板上写了个命中率 63%,被业务方当成 KPI 拿去汇报。后来算了一笔账才发现,这个命中率里约四成命中的是本来就便宜的短请求,真正花钱的长上下文请求几乎没被命中。从那以后我只看两个指标:节省的 token 费用 和误命中导致的返工次数。
精确缓存先做,别一上来就语义缓存
最省事的是 prompt 哈希缓存:对 system 加拼接后的 messages 加采样参数做 SHA256,命中直接回放。这条路能覆盖的场景比想象中多------评测跑批、文档摘要、结构化抽取,这些任务的输入重复率极高。
我们上线第一周只做了精确缓存,就吃掉了 21% 的调用量。关键是它零风险:哈希相同就意味着输入完全相同,最多是答案过期。
过期策略上,我没有用统一 TTL,而是按业务给每个调用方挂一个版本号。模型升级、prompt 改动、知识库更新,都会推动版本号加一,缓存自然整体失效。比起调 TTL 数字,这种事件驱动失效在工程上好维护太多。
语义缓存真正的难点是 key 怎么设计
第二版上了语义缓存:把归一化后的输入做 embedding,去向量索引里找相似度超过阈值的邻居。踩的第一个坑就是多轮对话------如果只对最后一轮 user 消息做向量,继续、再说一遍这类输入会命中一堆毫不相关的历史。
后来的做法是分层 key:
- system 与工具声明的哈希必须完全一致,作为前置过滤条件;
- 历史轮次只取最近两轮拼接后做向量;
- temperature 大于 0.7 的请求直接不走语义缓存,创造性任务和缓存是天然冲突的。
阈值我最终定在 0.94。听起来很高,但低于这个值,改写型问句(这个报错怎么解决和这个报错什么原因)就会互相命中,客服场景里会给出驴唇不对马嘴的回答。宁可少省点钱。
缓存不能命中的三类请求
一是带实时数据的请求。查库存、查订单状态,这些哪怕文本完全一样也不该回放,我们用工具调用标记做了硬排除。
二是流式输出且首字延迟敏感的会话。语义检索本身有 20 到 40 毫秒开销,对首字延迟要求 300 毫秒以内的场景,这点开销换回来的收益并不划算。
三是所有写操作。这个听起来是废话,但真有人在帮我提交这个工单上做了缓存,结果同一个工单被创建了三次。
收益与反思
现在这套缓存层日均节省约 34% 的推理费用,其中精确缓存贡献 25 个点,语义缓存只贡献 9 个点。这个比例挺反直觉的------花大力气做的语义缓存,收益还不如一层哈希。
如果重做一次,我会先把这三件事排好序:日志里 prompt 的重复度到底有多少、缓存失效能不能用版本号而不是 TTL、以及误命中的兜底怎么快速回滚。向量检索是最后才需要碰的东西。多数团队的 AI 成本问题,在第一层就解决了大半。