跳转至

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 同价。所以只要命中率高,账单会掉得很快。

它省钱的原理是三个层面叠加的:

  1. 推理层面:跳过重复的 prefill 计算,GPU 只算增量部分。
  2. 计费层面:服务商把 cache read 的单价压低,把节省的算力直接让利给调用方。
  3. 延迟层面: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,重点关注三个:

# 拉取 metrics
curl -s http://localhost:8000/metrics | grep -E "prefix_cache|gpu_cache"

关键指标: - 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%。差值主要来自两个地方:

  1. 业务结构调整:一些非核心的 GPT-4o 调用改成了 Haiku 或本地 Qwen2.5-14B,账单基数先降了 30%。
  2. 并发度收敛:一些重试风暴和限流重试被打散,无效调用少了 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%。

踩坑清单总结:

  1. 动态内容混进 prefix:命中率永远上不去。
  2. 忽略 cache write 成本:只看 read 折扣会误判 ROI。
  3. TTL 选错:5m 用于长会话 → 频繁失效;1h 用于高频交互 → 写入成本爆表。
  4. 多副本 vLLM 无路由:命中率掉到 30% 以下,不如不用。
  5. prefix 变更不评估:一次小改让当天账单翻倍。
  6. 监控只看总数:不同业务命中率差异极大,聚合后看不出问题。

八、生产 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 玄学。