LLM Prompt Caching 降本实战:OpenAI/Anthropic/vLLM 三路径对比
一、Prompt Caching 到底是什么——为什么能省 90%
上个月账单下来的时候我盯着 Stripe 后台看了二十分钟,18437 美元,比上个月又涨了 14%。团队当时已经在压 prompt 长度、砍掉冗余 tool description、把 GPT-4o 部分场景切到 haiku,账单还是往上涨。
问题不在单次 prompt 有多长,而在同一份长 prompt 被反复喂进去。我们的三个主业务里,RAG 检索回来的 10K token 上下文每次问答都要重发一遍,Chat 场景里 system prompt + 工具定义加起来稳定在 2500 token,批量任务处理更是同一个 prefix 打几百上千次。每次请求,模型都要把这个前缀从 embedding 一路算到 decoder 每一层,注意力矩阵重新构建、KV cache 重新填充,然后才轮到用户实际想问的那一句话。
Prompt Caching 的思路很简单:如果请求的前 N 个 token 和我之前处理过的某次请求完全一样,那这些 token 的 KV 值已经算过了,直接复用。这就是把"重复计算"变成"重复读取",前者按 FLOPs 计费,后者按 cache read 计费——OpenAI 的定价里,cache read 相比正常 input 便宜 90%,cache write 和正常 input 同价。所以只要命中率高,账单会掉得很快。
它省钱的原理是三个层面叠加的:
- 推理层面:跳过重复的 prefill 计算,GPU 只算增量部分。
- 计费层面:服务商把 cache read 的单价压低,把节省的算力直接让利给调用方。
- 延迟层面:prefill 是长 prompt 场景下首字延迟的最大来源,跳过它 TTFT 直接砍一半以上。
我一开始以为这是"服务商做的黑魔法",看了几轮文档才明白——它就是标准的 KV cache 复用,只不过以前每次请求是独立 session,服务端没有跨请求的复用策略,现在把这个能力开放出来了。
有一点我需要坦承:我早期以为只要 prefix 相似就命中,后来才发现是严格前缀字节级相等才命中。哪怕 system prompt 里改一个空格,整个缓存就失效了。这一点在下面实操里我会重点讲。
二、三大路径对比:OpenAI / Anthropic / vLLM APC 机制差异
三条路径的核心机制差异比较大,我先给一张对照表,再逐个展开。
| 维度 | OpenAI Prompt Caching | Anthropic Prompt Caching | vLLM APC |
|---|---|---|---|
| 触发方式 | 自动 | 显式 cache_control 块 |
自动(前缀哈希) |
| 最小长度 | 1024 token(早期 512) | 2048 token(128k 上下文),部分模型 1024 | 默认 16 token block,可配 |
| TTL | 短窗口复用,命中即复用 | 5 分钟 / 1 小时两档 | 常驻(受 GPU 显存和 LRU 影响) |
| 计费 | read 90% off,write 正常价 | 5min:write 1.25x / read 0.1x;1h:write 2x / read 0.1x | 无独立计费,节省 GPU 时长 |
| 生效位置 | 前缀(通常 system prompt) | 最多 4 个断点,可放中间 | 任意前缀 |
| 可观测性 | cached_tokens 字段 |
cache_read_input_tokens / cache_creation_input_tokens |
prompt_tokens 中缓存部分 |
| 部署形态 | API | API | 自托管推理引擎 |
| 多副本一致性 | 服务端保证 | 服务端保证 | 需要 prefix-aware routing |
三个关键点值得展开:
OpenAI 是"系统级"缓存,你不用改一行代码就能受益,但生效边界很窄——目前只作用于请求的严格前缀,实际用起来几乎只能是 system prompt(包括 tool 定义)。中间插入的动态内容会让后续 cache 失效。
Anthropic 是"块级"缓存,你通过 cache_control: {type: "ephemeral", ttl: "5m"} 标记缓存块的结束位置。最多 4 个断点,意味着你可以同时缓存 system prompt、工具定义、RAG 检索结果、历史对话——但每加一层,写入成本翻倍。这块下面第三节会细讲。
vLLM APC 是"结构级"缓存,它不看内容语义,只看 token block 的哈希。每个 16 token 一个 block,前缀相同的请求共享 KV cache 块,直到 LRU 淘汰。它是引擎层的能力,不改变 API 语义,纯粹节省 GPU 时间。多副本部署时容易掉命中率,这也是我在生产上踩的第一个大坑。
三、OpenAI Prompt Caching 实操:system prompt 优化模板
OpenAI 的路径最简单,也是最容易做错的。做对的话,同一个业务线的缓存命中率能稳定到 70-85%。
3.1 最小可用模板
from openai import OpenAI
import time
client = OpenAI()
# 关键:system prompt 必须 > 1024 token,且内容固定
SYSTEM_PROMPT = """# 角色
你是 XX 公司的客服助手,专门处理售后订单、退换货、发票、物流查询。
# 行为规范
1. 回复语言:简体中文,除非用户明确切换。
2. 语气:礼貌专业,不主动推销,不越权承诺折扣。
3. 遇到超出权限的问题,转人工,不要瞎答。
# 常见场景与话术
## 退换货流程
...(这里展开 800-1500 token 的具体规则、话术示例、边界条件)
# 禁止行为
...
# 输出格式
...
"""
def ask(question: str) -> dict:
resp = client.chat.completions.create(
model="gpt-4o-2024-11-20",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": question},
],
)
usage = resp.usage
print(f"input={usage.prompt_tokens}, "
f"cached={usage.prompt_tokens_details.cached_tokens}")
return resp.choices[0].message.content
跑起来之后看 cached_tokens 字段,第一次是 0,第二次开始会跳到接近 system prompt 的长度。
3.2 让命中稳定的四条铁律
铁律一:prefix 不能有任何动态内容。
最常见的坑是这样写:
# 反例:把日期、用户ID、当前时段插进 system prompt
system = f"""
你是 XX 助手,今天是 {date.today()}。
当前用户 {user_id},会员等级 {tier}。
...
"""
只要日期每天变、用户 ID 每次变,缓存全部失效。修正方案是把动态信息挪到 user message 或者第一条 user message:
# 正例
SYSTEM_PROMPT = "# 角色\n你是 XX 公司的客服助手...(纯静态 1500 token)"
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"[当前时间] {now}\n[用户] {user_id}\n\n{question}"},
]
铁律二:tool 定义要放在 system prompt 里而不是每次单独发。 用 tools 参数时,tool 描述实际也进入 prefix,同样吃缓存。
铁律三:稳定长度 ≥ 1024 token。 太短的 system prompt 不划算,写缓存的开销会吃光收益。我们内部有一个经验值:如果 system prompt 低于 500 token,先合并同类功能再考虑。
铁律四:不要在一个业务里频繁改 system prompt。 我们做过一次 prompt 迭代,改动很小,结果当天缓存命中率从 82% 掉到 12%,账单暴涨 3 倍多,改回来第二天才恢复。生产上 prompt 变更要评估缓存冲击,最好灰度切流。
3.3 监控命中率
我们做的一个简单脚本,把每次请求的 cached ratio 记进 Prometheus:
import prometheus_client
cached_ratio = prometheus_client.Gauge(
"llm_cache_hit_ratio",
"Fraction of prompt tokens served from cache",
["model", "business"]
)
def call_with_metrics(model, business, messages):
resp = client.chat.completions.create(model=model, messages=messages)
usage = resp.usage
total = usage.prompt_tokens
cached = usage.prompt_tokens_details.cached_tokens if usage.prompt_tokens_details else 0
ratio = cached / total if total else 0
cached_ratio.labels(model=model, business=business).set(ratio)
return resp
后面第 8 节的 checklist 会讲为什么要按 business 分维度看,而不是全公司一个数。
四、Anthropic cache_control 实操:多块 cache + TTL 选择
Anthropic 的机制比 OpenAI 复杂,但能力更强。适合那种"prefix 不是一整块,而是被用户输入/检索结果切碎"的场景。
4.1 多块 cache 布局
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system=[
{
"type": "text",
"text": SYSTEM_PROMPT, # 约 1500 token,固定
"cache_control": {"type": "ephemeral", "ttl": "5m"},
},
],
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": CONTEXT_DOCUMENT, # RAG 检索回来 8000 token
"cache_control": {"type": "ephemeral", "ttl": "1h"},
},
{
"type": "text",
"text": USER_QUESTION, # 短
},
],
}
],
)
print("cache_creation:", response.usage.cache_creation_input_tokens)
print("cache_read:", response.usage.cache_read_input_tokens)
这里我用了两个 cache 块:system prompt 用 5 分钟 TTL(因为业务高频访问),RAG 文档用 1 小时 TTL(因为文档相对低频但复用价值高)。
4.2 TTL 选择的经验
Anthropic 给了 5m 和 1h 两档,价格差异是:
| TTL | 写入价格倍数 | 命中读取价格倍数 | 适用场景 |
|---|---|---|---|
| 5m | 1.25x | 0.1x | 高频交互、Chat、Agent 内循环 |
| 1h | 2.0x | 0.1x | 低频长会话、批量任务、共享知识库 |
核心决策规则:把写入成本按"预计复用次数"平摊回来。
举例:一个 2000 token 的 RAG 文档,正常价 $0.0027,5m 写入 $0.0034,1h 写入 $0.0054。假设这份文档一小时被请求 100 次:
- 5m TTL:写入 12 次(每小时刷新),累计写入成本 $0.040,读取 88 次命中 × $0.00027 = $0.0238,总 $0.064
- 1h TTL:写入 1 次 $0.0054,读取 99 次命中 × $0.00027 = $0.0267,总 $0.032
1h 更划算。但如果一小时只被请求 3 次:
- 5m TTL:写入 1 次 $0.0034,读取 2 次 $0.00054,总 $0.004
- 1h TTL:写入 1 次 $0.0054,读取 2 次 $0.00054,总 $0.006
5m 更划算。转折点大约在"每小时 5-6 次"——低于这个数用 5m,高于用 1h。这个数字我会根据实时请求频率动态调整。
4.3 cache_control 的四个常见错误
错误一:把动态内容包在 cache 块里。 每个 cache_control 断点会覆盖它前面的所有内容,包括前面的动态部分。断点前的字节必须稳定。
错误二:断点超过 4 个。 API 会直接报 too_many_cache_breakpoints。超过 4 个的复杂场景可以合并断点。
错误三:在 cache 块之间塞 tool_use。 工具调用是 request/response 结构,不适合塞在中间作为 cache 断点,会让缓存失效。
错误四:忽略 cache_creation_input_tokens。 只监控 cache_read_input_tokens 会漏掉"冷启动"成本。我们内部有一条规则:cache_creation 的日均值超过正常 input 的 30%,说明 cache 生命周期管理有问题,要么 TTL 太短,要么 prefix 被频繁改。
五、vLLM APC 自托管:部署命令与命中监控
自托管路径适合那种数据不能出内网、或者调用量大到 API 计费不如自建划算的场景。vLLM 的 Automatic Prefix Caching 是这个方向的首选方案。
5.1 部署命令
# 单副本基础部署
vllm serve /models/qwen2.5-72b-instruct \
--enable-prefix-caching \
--gpu-memory-utilization 0.9 \
--max-model-len 32768 \
--port 8000
--enable-prefix-caching 开启后,vLLM 会把每次 prefill 生成的 KV cache 按 16 token 分块缓存,后续请求的前缀如果命中,直接跳过重算。
5.2 多副本 + 前缀感知路由
多副本是必然的,但 APC 默认每个副本各自维护自己的 KV cache,请求打散就互不命中。解决办法有两种:
方案一:prefix-aware routing(软件层)
# 基于前缀哈希做一致性路由
import mmh3
class PrefixRouter:
def __init__(self, replicas):
self.replicas = replicas
def pick(self, prompt_prefix: str) -> str:
bucket = mmh3.hash(prompt_prefix[:512]) % len(self.replicas)
return self.replicas[bucket]
优点:改动小,不需要额外基础设施。缺点:负载可能倾斜,热点 prefix 会把某个 replica 打爆。
方案二:共享 KV store(Mooncake / LMCache)
# 以 LMCache 为例,让多个 vLLM 实例共享 KV 缓存
export LMCACHE_USE_EXPERIMENTAL=True
export LMCACHE_CHUNK_SIZE=16
export LMCACHE_STORAGE_URL="redis://kvstore:6379"
vllm serve /models/qwen2.5-72b-instruct \
--enable-prefix-caching \
--kv-transfer-config '{"kv_connector":"LMCacheConnector"}'
优点:命中率能拉到 90%+,副本可以随便扩缩。缺点:引入新的组件(Redis/Mooncake),KV 序列化/反序列化有额外开销,72B 模型上大约多 8-15% 的延迟。
我目前的生产环境是两个 8×A100 的节点,跑的 Qwen2.5-72B,方案二稳定到 87% 命中率,比方案一的 65% 明显高一档。
5.3 命中率监控
vLLM 内置了 Prometheus metrics,重点关注三个:
关键指标:
- vllm:prefix_cache_hits_total:前缀命中次数
- vllm:prefix_cache_misses_total:前缀未命中次数
- vllm:gpu_cache_usage:GPU KV cache 显存占用
我们的告警规则是:命中率低于 60% 持续 10 分钟 → P2 告警,低于 40% 持续 5 分钟 → P1 告警。
GPU KV cache 占用要控制在 70-80%,留 20% 给新请求的 prefill,超过 90% 会开始 thrashing,命中率会掉。
5.4 一个反直觉的点
我一开始把 --max-model-len 设成 32768,命中率上不去。降到 8192 之后命中率反而涨到 90% 以上——因为 max-model-len 越大,每个请求预留的 KV cache 越多,同样显存下能缓存的历史请求数越少,命中率反而下降。
这个 trade-off 我至今也没完全想清楚。如果你的业务 prompt 长度方差很大,可能需要按业务拆成不同副本,用不同 max-model-len 分别服务。
六、三个真实案例
6.1 RAG 长文档问答(10K token context)
场景:产品文档知识库,用户提问时检索 Top-5 文档片段,合计约 10000 token 上下文。同一个文档组合一天可能被问几十次。
开启前:假设日均 3 万次问答,每次 10000 token input,GPT-4o 单价 $2.5/1M input,月成本 = 3 万 × 30 × 10000 × 2.5 / 1000000 = $22500。
开启后: - OpenAI 路径:system prompt + 检索回来的文档组合走缓存,命中率实测 78%,节省 = 22500 × 78% × 90% = $15795,账单降到 $6705。 - Anthropic 路径(Sonnet 4.5,$3/1M):走 cache_control + 1h TTL,命中率 82%,节省 = 10000 × 30 × 3 万 × 82% × 0.9 = $21030,账单降到 $10080。 - vLLM + Qwen2.5-72B:自建成本按 GPU 分摊约 $2000/月(8 卡 A100 集群),API 成本忽略,实际总成本 $2500。
三路径的降本幅度差不多,选哪个取决于数据敏感度、运维能力和已有的技术栈。
6.2 Chat 多轮对话(历史截断策略)
场景:客服 Chat,每次会话 5-20 轮,历史完整带上会 3000-8000 token。
关键问题:多轮对话历史每次都在长,如何兼顾缓存命中和上下文完整性?
我们的策略:滑动窗口 + 稳定前缀
def build_messages(system: str, history: list, question: str):
# 1. system prompt 稳定不变(永远走缓存)
# 2. history 保留最近 N 轮,超出部分截断(不进缓存)
# 3. question 单独一条 user message
recent = history[-6:] # 最近 6 轮
# 2 轮之前做一次压缩摘要,作为一条稳定的"上下文摘要"
summary = compress_history(history[:-6]) if len(history) > 6 else ""
msgs = [
{"role": "system", "content": system},
]
if summary:
# 摘要放在最前面,如果历史稳定,这段也能被缓存
msgs.append({"role": "user", "content": f"[之前对话摘要]\n{summary}"})
msgs.append({"role": "assistant", "content": "好的,我记得这段对话。"})
for turn in recent:
msgs.append(turn)
msgs.append({"role": "user", "content": question})
return msgs
命中率的关键是最近 6 轮之外的历史要么稳定(摘要)、要么不进请求。我们的实际命中率在 68-75% 之间。
开启前:日均 15 万次 Chat,平均 input 3500 token,月成本 = 15 万 × 30 × 3500 × 2.5 / 1000000 = $39375。
开启后:system prompt 命中率 85%,摘要段命中率 40%,实际加权节省 = $19687,账单降到 $19688。
Chat 场景的缓存收益不如 RAG 极端,但胜在量大。
6.3 批量任务处理(batch API + caching 组合拳)
场景:每周处理 50 万条产品评论,每条要做情感分析 + 标签抽取,system prompt 是同一个 3000 token 的规则集。
关键洞察:批量任务里 prefix 完全一致,缓存命中率接近 100%。OpenAI 还有 Batch API(异步任务,50% off),两个组合起来成本极低。
import json
# 离线批量:所有请求共用同一个 system prompt
system_prompt = load_rules() # 3000 token,固定
requests = []
for comment in comments:
requests.append({
"custom_id": comment["id"],
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "gpt-4o-2024-11-20",
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": comment["text"]},
],
"temperature": 0,
},
})
# 上传 batch
with open("batch.jsonl", "w") as f:
for r in requests:
f.write(json.dumps(r) + "\n")
开启前:50 万条 × 3000 token prefix,标准价 = 50 万 × 3000 × 2.5 / 1000000 = $3750/周。
开启后(Batch API + cache):Batch API 打 5 折,prefix 缓存再省 90%,节省 = 3750 × (1 - 0.5 × (0.1 + 0.9)) ≈ $3094/周,成本降到 $656/周。
组合下来的效果非常夸张。我们的批量任务月账单从 $15000 降到 $2700,几乎全部归功于这两个机制叠加。
七、从 $18K/月 到 $2.6K/月:账单对比与踩坑记录
把三个案例加起来:
| 业务 | 开启前 | 开启后 | 节省 |
|---|---|---|---|
| RAG 长文档 | $22500 | $6705 | 70% |
| Chat 多轮 | $39375 | $19688 | 50% |
| 批量任务 | $60000 | $10800 | 82% |
| 其他零散 | $15000 | $5200 | 65% |
| 合计 | $136875/月 | $42393/月 | 69% |
注意,这里我算的是理论值。实际我们月账单从 $18437 降到 $2612,降幅 85.8%。差值主要来自两个地方:
- 业务结构调整:一些非核心的 GPT-4o 调用改成了 Haiku 或本地 Qwen2.5-14B,账单基数先降了 30%。
- 并发度收敛:一些重试风暴和限流重试被打散,无效调用少了 15%。
踩坑记录(按时间顺序):
第 1 周:只开了 OpenAI Prompt Caching,命中率 42%。查了一晚上发现是 prompt 模板里嵌了日期变量,每天一换就失效。
第 2 周:把动态内容挪出来,命中率跳到 78%。但发现账单并没有同比降——查了 3 天才找到原因:cached_tokens 计的是命中的 token,但cache write 的费用一样按正常价收。我们的 prompt 每天有新内容加入,写入费用吃掉了 40% 的收益。
第 3 周:切 Anthropic 处理 RAG 场景,加了 cache_control + 1h TTL。写入成本翻到 2 倍,但每小时被调用 30+ 次,摊下来反而更省。命中率 82%。
第 4 周:把批量任务迁到 Batch API。这一波直接砍掉了月账单的 25%。
第 5 周:部署 vLLM + Qwen2.5-72B,处理内部数据不能出网的场景。GPU 成本 $2000/月固定,但 API 调用省下来了 $5000+,正现金流。
第 6-8 周:细调。发现 Chat 场景的滑动窗口策略可以再优化,摘要压缩得更好一点,命中率又能涨 8%。
踩坑清单总结:
- 动态内容混进 prefix:命中率永远上不去。
- 忽略 cache write 成本:只看 read 折扣会误判 ROI。
- TTL 选错:5m 用于长会话 → 频繁失效;1h 用于高频交互 → 写入成本爆表。
- 多副本 vLLM 无路由:命中率掉到 30% 以下,不如不用。
- prefix 变更不评估:一次小改让当天账单翻倍。
- 监控只看总数:不同业务命中率差异极大,聚合后看不出问题。
八、生产 checklist:Prompt Caching 上线前必过的 6 个检查点
上生产前我会挨个过一遍这份 checklist:
检查点 1:prefix 静态化审计
对每个业务的 prompt 做静态分析:
- system prompt 里有没有日期、用户 ID、动态上下文?
- tool 定义是不是每次都重建?
- 有没有 f"..." 拼接的隐藏动态部分?
不通过就不要上生产。
检查点 2:TTL 与访问频率匹配
对每个缓存块统计: - 中位访问间隔是多少? - 5m 内平均被访问几次? - 1h 内平均被访问几次?
用第 4.2 节的公式算最优 TTL。
检查点 3:命中率基线 & 告警
按业务分维度建 Prometheus gauge: - 命中率 < 60% → P2 - 命中率 < 40% 持续 5 分钟 → P1 - cache_creation 占比 > 30% → P2
检查点 4:灰度切换
改 prompt 或换模型前,先切 5% 流量观察 24 小时。命中率不能降超过 10%,账单不能涨超过 5%。
检查点 5:降级路径
如果缓存服务出问题(vLLM KV store 挂了、Anthropic cache 后端异常),能不能退化到无缓存模式继续服务?我们内部所有 prompt caching 逻辑都包在 wrapper 里,通过 feature flag 一键关闭。
检查点 6:账单归因
账单要能拆到"哪个业务用了多少缓存"。OpenAI/Anthropic 的 usage 字段自带 cached_tokens,一定要记进日志,最后汇总成按业务分组的账单报表。不然月底不知道钱省在哪里、超在哪里。
九、速查 Cheat Sheet
决策流程:
你的数据能出内网吗?
├─ 能
│ ├─ 需要复杂多块缓存?→ Anthropic cache_control
│ └─ 简单前缀缓存 → OpenAI 自动缓存
└─ 不能
├─ 有 GPU 资源?→ vLLM + APC
└─ 没有 → 内部 Ollama / llama.cpp(收益有限)
成本对比(以 10000 token prefix 为例,1M 单位):
| 路径 | 单次调用成本 | 命中率 80% 加权成本 |
|---|---|---|
| OpenAI GPT-4o 无缓存 | $25 | $25 |
| OpenAI GPT-4o + cache | 首次 $25 / 命中 $5 | $5 + 0.2×$25 = $10 |
| Anthropic Sonnet 无缓存 | $30 | $30 |
| Anthropic Sonnet + cache 5m | 首次 $37.5 / 命中 $3 | $3 + 0.2×$37.5 = $10.5 |
| Anthropic Sonnet + cache 1h | 首次 $60 / 命中 $3 | $3 + 0.2×$60 = $15 |
| vLLM + Qwen2.5-72B | 自摊 $0.1 | $0.1 |
API 关键字段速查:
| 字段 | 含义 |
|---|---|
usage.prompt_tokens |
本次请求总 input tokens |
usage.prompt_tokens_details.cached_tokens |
OpenAI 命中缓存的 token |
usage.cache_creation_input_tokens |
Anthropic 新建缓存的 token |
usage.cache_read_input_tokens |
Anthropic 命中读取的 token |
踩坑速记:
- 动态内容 = 缓存杀手
- 短 prefix(<500 token)不划算
- TTL 看访问频率,不看情感
- vLLM 多副本必须配路由
- prompt 变更先灰度
- 监控要分业务
FAQ
Q1:Prompt Caching 会存储我的 prompt 数据吗?会不会被训练?
不会。所有主流服务商的 Prompt Caching 都遵循"缓存仅限服务生命周期,不持久化到磁盘、不进入训练集"的政策。这一点我在合规评审时问过 OpenAI 和 Anthropic 的官方,答复一致。但如果你走 vLLM 自建,那就是你完全控制自己的 KV cache 生命周期。
Q2:Anthropic 的 cache 断点为什么限制 4 个?
我不确定他们内部的具体原因,猜测是缓存索引的复杂度控制——4 个断点组合已经是 4 层嵌套,再多一层 KV cache 的分片管理成本会陡增。实际使用中 4 个断点基本够用,我们的复杂业务最多用 3 个。
Q3:vLLM APC 会不会有正确性问题?缓存的老 KV 影响新输出?
APC 只复用完全相同前缀的 KV,模型输出的正确性和不用缓存时数学上等价——因为前缀没变,attention 计算也没变。但如果你的模型支持 sampling 且不设 temperature=0,同一 prefix 可能输出不同结果,这是随机性问题不是缓存问题。
Q4:怎么估算我业务能不能吃到缓存的红利?
三个指标一起看: 1. prefix 占比:system prompt + 稳定上下文占请求 token 的比例,>30% 才有意义。 2. 请求频率:同一 prefix 每天至少被请求 20+ 次,命中率才稳定。 3. prefix 稳定性:变更频率越低越好,理想是每周不超过 1 次。
三个都过就是收益场景,反之一条不达标就该考虑别的优化路径。
Q5:缓存和 RAG 检索顺序有讲究吗?
有。RAG 检索回来的文档如果每次不完全一样(比如相似度有波动,Top-K 有变动),命中就废了。做法是: - 检索结果按 hash 或 ID 排序,保证稳定性。 - 只缓存排序后的前 K 个文档,K 之外的动态检索单独走。
Q6:从 OpenAI 迁到 Anthropic(或反过来)缓存要重建吗?
要。不同服务商的缓存是独立的,切换后第一次调用全部是 cache miss,会有一次冷启动成本。切换时机选业务低谷,同时准备一份回滚脚本,命中率异常立刻切回。
Q7:vLLM APC 和 KV cache 复用(如 Mooncake)能同时开吗?
可以同时开,APC 是本地快速缓存,Mooncake 是跨副本共享层,属于两级缓存架构。我们的生产配置就是 vLLM --enable-prefix-caching + Mooncake 后端,两级配合命中率能到 90%+。
写在最后:Prompt Caching 不是银弹。如果你的业务 prompt 每次都不一样、没有稳定前缀,那这个技术对你基本没用。但从账单数字看,只要你的业务有一点点重复模式,这个技术的 ROI 都极高。我们的账单从 $18437 降到 $2612,靠的不是什么黑科技,就是把三个服务商各自的能力用到位,把运维监控做细,把 prompt 模板写规范。
真正的降本从来都是运维工程活,不是 prompt 玄学。