日期:2026-08-07
我们支撑的这个业务是客服加知识库问答,高峰期一天小几十万次调用。最早每轮请求都把一整段系统提示,外加一篇常常上千字的知识文档,原封不动重新喂给模型。跑了一个月看账单,token 成本吓一跳,而且因为要重新处理那一大段前缀,首字延迟也偏高,用户打字问完还得等半天才开始出字。
我们当时用的推理框架支持前缀缓存,但业务侧根本没用起来,因为大家习惯每轮都重新拼 prompt。这个项目的目标很纯粹:把那些高度重复的前缀和上下文真正缓存住,让重复部分只算一次钱、只进一次模型。底层我们是在已有的模型服务化层上做改造,没换框架。
具体做了三块。系统提示前缀缓存,把不变的指令部分定为可缓存前缀,平台第一次见这个前缀时算一次并缓存 KV,后续同一前缀直接命中。共享上下文复用,知识库问答里那篇长文档在很多会话里是同一篇,我们按文档标识做上下文缓存,不同用户问同一篇文档不再重复编码。缓存命中统计与失效,每条缓存带版本号和失效时间,命中率、节省 token 都落监控,文档一更新旧缓存立刻失效。
我们先在知识库问答这条线试点,因为它的前缀重复度最高,一篇文档被几百人问。改造量不大,主要是把请求拼装逻辑改成带缓存键的结构。
第一个难点是缓存键怎么定。前缀里只要有一个字符不同,缓存就失效。我们排查发现业务代码在系统提示里拼了当天的日期字符串,导致每天零点缓存全清。改成把易变信息挪到非缓存后缀,前缀只留真正稳定的指令,命中率一下上来了。
第二个是失效策略。文档更新后,旧缓存要是还在,用户会拿到过时答案。我们给每个上下文缓存绑定文档版本号,文档一发布新版本,旧键直接作废,新请求命中新键,过渡期有极短的混合窗口但不影响正确性。
第三个是多租户隔离。不同客户的知识库不能串,缓存键里必须带租户标识,否则 A 客户的缓存被 B 客户命中就出大事了。我们把租户号作为缓存命名空间前缀,从机制上隔死。
案例片段(已脱敏): 缓存命中日志(JSON 示意):
{ "req_id": "r-7781", "cache_key": "kb#tenantA#doc-205#v3", "prefix_hit": true, "cached_tokens": 1280, "prompt_tokens": 1340, "saved_ratio": 0.95 }压测一例:同一文档连续一千次问答,首字延迟从约 620 毫秒降至约 210 毫秒,千次调用 prompt token 由约 134 万降至约 7 万,节省约 95%。
知识库问答这条线跑满两周,我们看结果。缓存命中率稳定在约 92%,剩下的主要是首次请求和文档刚更新后的冷启动。token 成本,同样调用量下 prompt 侧下降约 88%,整月账单明显变薄。首字延迟,命中缓存的请求从约 620 毫秒降到约 210 毫秒,用户体感差别很大。缓存失效率控制在约 3% 以内,主要是文档更新触发,属正常。
有个点要讲清楚,缓存省的是前缀重复的钱,长尾个性化问题省不了,所以不是所有业务都适合。我们的经验是前缀重复度高的场景(知识库、客服话术、固定系统指令)收益最大,完全自由的闲聊收益很小,别指望一刀切。
做前缀缓存,第一件事是去抖前缀里的易变字段,我们那个拼日期的坑让命中率长期上不去,排查了一天才发现。缓存键一定要带租户,多客户场景下串缓存是事故不是 bug。失效策略要想在前头,文档版本号绑定比定时清理稳得多。别神话缓存,它只在重复前缀多的场景赚钱,闲聊类业务算了半天省不了几个字,评估了再上。
后来我们把这套缓存键规范沉淀成接入规范,新业务接模型服务先过一遍前缀设计,避免再踩拼变量的坑。
做了前缀缓存后,我们顺带发现一个以前忽略的点:很多请求其实问的是同一篇文档的同一段,连后缀都高度相似。于是在前缀缓存之外,我们又加了一层问答结果的短缓存,完全相同的问题直接返缓存答案,命中又省一笔。不过这层要谨慎,知识库内容一更新,旧答案就得失效,我们绑了同样的版本号机制。还有成本监控,我们把缓存节省的 token 单独出了一张看板,老板第一次看到一个月省了多少钱,才真正重视起前缀设计。技术上缓存不难,难的是让业务侧养成写可缓存 prompt 的习惯,我们后来把这条写进了接入规范,新接的模型服务默认走缓存键。
我们后来把缓存命中率也纳入了模型服务的健康看板,低于阈值就报警,因为命中率掉往往意味着前缀被改动了。有一次业务侧改了系统提示的措辞,没走缓存键规范,命中率一夜掉到三成,账单立刻变胖,报警帮我们当天就发现了。这个事让我们下定决心把前缀设计卡进发布流程,改 prompt 必须评审缓存键。另外短缓存那层上线后要小心内容时效,我们给不同知识源配了不同过期时间,新闻类几分钟,手册类可以按天,不能一刀切。