• 欢迎关注我的微信公众号“ Falost ” 右边扫描关注 --->>

提示缓存 + 上下文压缩:同一段前缀,怎么做到只算一次钱?

AI / 大模型 神棍 68℃ 0评论

提示缓存 + 上下文压缩:同一段前缀,怎么做到只算一次钱?

上篇我把 KV Cache 那笔账拆完了——GQA、KV 量化、PagedAttention 三层优化轮下来,显存这块心里总算有底。但账单还没看完:打开 API 消费记录,最扎眼的反而不是显存,是输入 token。同一个系统提示、同一份产品文档,用户每问一句,我就得原价付一遍。834 里埋的三笔账,还剩”重复输入花钱”这笔没拆,今天拆完:提示缓存(Prompt Caching)怎么让同一段前缀只算一次钱,缓存不了的场景,上下文压缩又怎么把长文档压进窗口。

一、为什么重复输入要重复花钱

先说原理。模型处理一次请求分两个阶段:prefill(把输入全部过一遍,算出每一层的 K、V,就是上篇说的 KV Cache)和 decode(逐 token 生成)。问题在于:同一份 5 万 token 的文档,第一次请求算一遍 KV,第二次请求完全一样,模型还是会老老实实再算一遍——它不记得上次算过。提示缓存干的事,就是把算好的前缀结果存起来,下次请求前缀完全一致时直接复用,跳过重复的 prefill。

我用 DeepSeek 举例,因为它的官方文档写得清楚,而且默认开启、不用改一行代码。几个关键点:

  • 磁盘缓存,默认对所有用户开启;
  • 命中规则是按”缓存前缀单元”整体匹配:后续请求必须完整复用某个已持久化的前缀单元,才算命中;
  • 三个持久化时机:请求边界(用户输入结尾、模型输出结尾各存一个单元)、公共前缀检测(系统发现多个请求有共同前缀,就单独存下来)、固定 token 间隔(长输入长输出每隔一段切一个单元,避免长前缀永远等不到结尾);
  • 响应 usage 里能看到两个字段:prompt_cache_hit_tokensprompt_cache_miss_tokens,命中多少、没命中多少一目了然;
  • 缓存是 best-effort,不保证 100% 命中;空闲后几小时到几天自动清理。

二、算账:命中缓存差 31 倍

DeepSeek 官方定价(deepseek-v4-flash,非高峰时段):缓存命中 0.007/百万 token,缓存未命中0.22/百万 token——差了 31 倍。沿用 834 那个例子:一个 Agent 每次请求带 10 万 token 上下文,一天跑 1000 次:

  • 全不命中:10 万 × 1000 × 0.22 ÷ 100 万 = $22/天
  • 全部命中:10 万 × 1000 × 0.007 ÷ 100 万 = $0.7/天

理想情况下,同一份上下文一天差 21 美元,一个月就是 600 多。多轮对话是天然命中场景:第一轮发 A+B,第二轮发 A+B+C,A+B 完整复用,直接命中。

三、缓存实战:把命中率提上去

缓存省不省钱,全看命中率。我的做法是四条:

  1. 前缀稳定是命门。缓存匹配的是前缀,所以 system prompt、工具定义、检索回来的文档这些”不变的内容”必须放最前面;用户问题、时间戳这些”每次变的内容”放最后。反过来放,一个 token 的变化就能让后面整段失效。
  2. 别动结构。DeepSeek 按缓存单元整体匹配,前缀里任何位置插入一个 token,后面整段失效。公共前缀检测会兜底把共同部分重新持久化,但要等系统识别,中间几轮请求都是全价。
  3. 把 hit/miss 打日志。prompt_cache_hit_tokensprompt_cache_miss_tokens 记到监控里,命中率低于预期就去查前缀稳定性。我踩过最蠢的坑:代码里把时间戳拼进了 system prompt,命中率一夜归零。
  4. 挑场景。高并发、上下文大、前缀重复率高的最划算(多轮对话、固定知识库的 RAG 问答)。缓存几小时到几天不用会被清掉,低频一次性请求别指望它。

四、缓存不了就压缩:上下文压缩

缓存省的是”重复”的钱,但有些内容每次都是新的——新文档、新对话。窗口装不下、或者全价塞进去太贵的时候,就得压缩。我试过两条路线。

方案 A:LLM 摘要(compaction)。Anthropic 官方上下文工程文章里讲的做法:对话快满时,让模型把关键信息总结出来,带着摘要开新窗口继续。Claude Code 就是这么实现的:保留架构决策、未解决的 bug 和实现细节,丢掉冗余的工具输出。我的实践是摘要提示词里明确要求”保留决策和未解决问题”,不然模型会留一堆废话。

方案 B:LLMLingua 系列(微软,EMNLP 2023)。思路是用一个小模型(GPT2-small 级别)逐 token 判断”删掉这个影不影响理解”,把不重要的删掉,最高能压 20 倍。装好就能用:

pip install llmlingua

from llmlingua import PromptCompressor

llm_lingua = PromptCompressor()
compressed_prompt = llm_lingua.compress_prompt(
    prompt, instruction="", question="", target_token=200
)

返回结果里有 compressed_prompt(压缩后的提示词)、origin_tokenscompressed_tokensratio(压缩比)。官方 README 的例子:2365 token 压到 211,11.2 倍。LongLLMLingua(ACL 2024)是专门为长上下文场景出的:论文报告在 RAG 场景用 1/4 的 token,性能反而最高提升 21.4%——正好呼应 834 里 Lost in the Middle 的结论:不是塞得越多越好,塞太多模型反而找不到重点。

我的取舍:检索回来的文档用 LongLLMLingua 压一遍再进上下文(损失可控、省钱明显);对话历史用 compaction;system prompt 这种高价值内容坚决不压缩。

五、我的组合拳

顺序固定:能缓存先缓存(前缀稳定、重复率高)→ 缓存不了但内容允许损失精度就压缩 → 都不行才考虑直接扩窗口。排查顺序:先看账单里 cache hit/miss 占比 → 命中低就查前缀稳定性 → 还不行上压缩。这套流程跑下来,输入成本基本能压一个数量级。

总结

一句话:重复输入是账单上的无底洞,提示缓存让同一段前缀只算一次钱(DeepSeek 上 31 倍差价),上下文压缩兜底处理缓存不了的场景。我的顺序是先缓存、再压缩、最后才扩容。

下期预告:三笔账还剩最后一笔——上下文越长模型越迷路(Lost in the Middle)。下篇我会拆怎么用检索和路由,让模型只看该看的内容。想一起折腾的,下篇见。

参考文献

转载请注明:Falost的小窝 » 提示缓存 + 上下文压缩:同一段前缀,怎么做到只算一次钱?

如果你觉得这篇文章不错或者对你有帮助,想请我喝一杯咖啡,可以打赏
喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址