提示缓存 + 上下文压缩:同一段前缀,怎么做到只算一次钱?
上篇我把 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_tokens和prompt_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 完整复用,直接命中。
三、缓存实战:把命中率提上去
缓存省不省钱,全看命中率。我的做法是四条:
- 前缀稳定是命门。缓存匹配的是前缀,所以 system prompt、工具定义、检索回来的文档这些”不变的内容”必须放最前面;用户问题、时间戳这些”每次变的内容”放最后。反过来放,一个 token 的变化就能让后面整段失效。
- 别动结构。DeepSeek 按缓存单元整体匹配,前缀里任何位置插入一个 token,后面整段失效。公共前缀检测会兜底把共同部分重新持久化,但要等系统识别,中间几轮请求都是全价。
- 把 hit/miss 打日志。把
prompt_cache_hit_tokens、prompt_cache_miss_tokens记到监控里,命中率低于预期就去查前缀稳定性。我踩过最蠢的坑:代码里把时间戳拼进了 system prompt,命中率一夜归零。 - 挑场景。高并发、上下文大、前缀重复率高的最划算(多轮对话、固定知识库的 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_tokens、compressed_tokens、ratio(压缩比)。官方 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)。下篇我会拆怎么用检索和路由,让模型只看该看的内容。想一起折腾的,下篇见。
参考文献
- DeepSeek API 官方文档:Context Caching:https://api-docs.deepseek.com/guides/kv_cache
- DeepSeek API 官方文档:Models & Pricing:https://api-docs.deepseek.com/quick_start/pricing
- Anthropic:Effective context engineering for AI agents(2025-09-29):https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
- Jiang et al.:LLMLingua: Compressing Prompts for Accelerated Inference of Large Language Models(EMNLP 2023):https://aclanthology.org/2023.emnlp-main.825/
- Jiang et al.:LongLLMLingua: Accelerating and Enhancing LLMs in Long Context Scenarios via Prompt Compression(ACL 2024):https://aclanthology.org/2024.acl-long.91/
- Pan et al.:LLMLingua-2: Data Distillation for Efficient and Faithful Task-Agnostic Prompt Compression(ACL 2024 Findings):https://aclanthology.org/2024.findings-acl.57/
- microsoft/LLMLingua(GitHub):https://github.com/microsoft/LLMLingua
- 上一篇:KV Cache 是怎么把显存吃光的?GQA、KV 量化、PagedAttention 三层优化实战:https://www.fedte.cc/p/835.html