通过哈希完整请求(System Prompt+消息+参数)实现缓存,实测生产流量中大量重复调用可通过缓存规避;区分新信息调用与重复调用是降低成本的核心指标。
最便宜的 LLM 调用是不调用:真正划算的缓存层
在上一篇文章中,我介绍了跨提供商路由来削减约 40% 账单的经验。缓存是第二个杠杆——说实话也是更被低估的那个。下面是我们在这上面的一些心得。
路由得到了大部分关注,因为它很性感:流量在提供商之间舞动,failover 介入,仪表盘亮起来。但在路由之后,我们拉动的最大成本杠杆不是更智能的路由。根本不调用模型才是。
当人们谈论 LLM 成本时,他们想到的是每个 token 的价格。这是错误的单位。真正的问题是,你的调用中有多少是真正的新信息,而不是换了马甲的重复请求。
我们对重叠程度感到震惊。一旦开始测量,很大一部分生产流量都在重新询问几乎相同的问题:
这些都不需要新鲜的模型调用。它需要一个有大脑的缓存。
对完整请求(system + messages + params)做哈希。如果见过,就返回存储的 completion。明摆着的事,但大多数团队跳过了,因为"我们的 prompt 是动态的"。它们通常没那么动态。
import hashlib, json
def cache_key(req):
return hashlib.sha256(json.dumps(req, sort_keys=True).encode()).hexdigest()
def complete(req):
k = cache_key(req)
hit = store.get(k)
if hit:
return hit # zero tokens spent
out = model_call(req)
store.set(k, out, ttl=300)
return out
光这一条就干掉了我们最高流量端点的一大部分账单。
精确匹配错过了真正的收益:相似的 prompt 返回相似的答案。对用户轮次做 embedding,将 embedding 存储在向量索引中,每次请求检查相似度阈值以上的邻居(我们用约 0.92)。如果找到,就复用之前的 completion。
陷阱:语义缓存只在确定性较强的任务中是安全的(分类、提取、稳定问答)。不要缓存创造性生成——你会得到陈旧的声音。我们严格限定了范围,但仍然覆盖了惊人的量。
很多"LLM 调用"实际上是确定性工作包装在 prompt 里:解析、规范化、格式转换。我们把这些移到了纯函数中,计算一次并复用。这甚至不算模型缓存——只是不假装需要模型。
按波动性设置 TTL。稳定的参考答案:长 TTL。快速变化的数据:短或无。
查询的 token 预算。embedding + 向量搜索也消耗 token。确保缓存检查比未命中更便宜——对我们来说是这样,而且差距很大。
衡量命中率,而不只是节省金额。命中率告诉你缓存什么时候停止起作用了(prompt 漂移、新用例),这样你就可以重新划定范围。
缓存端点命中率:约 35%。
在路由基础上额外降低账单:显著——两者结合我们现在在同时使用两者的端点上远超原来的 40%。
缓存命中 p95 延迟:低于 50ms 而不是几百 ms。用户对速度的感知比节省金额更明显。
这些都不稀奇。这就是人们几十年来应用于数据库的相同缓存原则,只是应用在模型调用上,而每次命中的节省更大。
路由将流量转移到最便宜的健康提供商(我们用路由削减账单的方式)。断路器防止不稳定的提供商将宕机变成账单爆炸(我们使用的模式)。缓存是两者下面的那层:你跳过的调用才是你不需要路由或保护的调用。
为团队建立可靠、可负担的模型访问有自己的头痛——提供商配额、区域限制、支付摩擦。如果这些听起来很熟悉,我很乐意交流经验。在这里找我或私信我;没有推销,只有实战故事。