RAG 工程与检索系统设计
第21章 成本、延迟、缓存与生产调优
成本、延迟、质量是 RAG 生产的三个硬约束:没法同时最大化,但能找到业务上的甜点。
本章要点
- RAG 生产的三个硬约束:成本 / 延迟 / 质量——没法全部最大,但能找到业务甜点
- 三层缓存:query 级(完整响应)、embedding 级(去重)、prompt cache(LLM 前缀)
- prompt caching 能把命中的那段前缀的输入价降到基础价的一个零头——2024 年后最大的单点优化杠杆,但省多少要按前缀占比算
- 延迟预算要分阶段精打细算——每一跳都有削减空间
- 成本优化的 ROI 顺序:prompt caching → 模型分层 → 缓存 → 批处理
21.1 生产 RAG 的三元约束
每个 RAG 项目都要在三件事上取舍:
- 质量:答对率、faithfulness、用户满意度
- 延迟:TTFT、P50/P99、端到端响应时间
- 成本:LLM 调用、基础设施、运维人力
三者常常冲突:
- 想提质:加 rerank、加 Contextual Chunk、用更大 LLM → 延迟和成本 ↑
- 想降延迟:减 chunk、用小模型、删 rerank → 质量 ↓
- 想省成本:缓存狠、减 LLM 调用、用便宜 embedding → 质量可能 ↓
flowchart TB
classDef q fill:#efe,stroke:#080
classDef l fill:#def,stroke:#06c
classDef c fill:#fed,stroke:#c60
QUAL[质量]:::q <--> LAT[延迟]:::l
QUAL <--> COST[成本]:::c
LAT <--> COST
没有银弹——工程的工作是在业务场景下找到三者最佳平衡。本章讨论每个维度的主要杠杆和它们之间的相互影响。
21.2 延迟预算的分配
延迟预算是先定总数、再往下分。以第 4 章那个"端到端 P99 = 2000ms"的对话场景为例,各阶段的量级关系大致是这样:
| 阶段 | 量级 | 优化方向 |
|---|---|---|
| 查询理解(含 rewrite) | 百毫秒级 | 规则优先 / 小模型 / 缓存 |
| 多路召回(并行) | 几十到百毫秒 | 索引调优 / 并行化 |
| 融合 + Rerank | 百毫秒级 | batch / 小 reranker / 候选减少 |
| Context pack | 毫秒级 | 纯内存操作,几乎为零 |
| LLM 生成(到完成) | 秒级,占大头 | 流式 / prompt cache / 并行工具 |
| 网关 / 日志 / 其他 | 几十毫秒 | 保持精简 |
具体每一格填多少毫秒,只能自己压测后往里填——它取决于你的模型、索引规模、部署拓扑,任何一份现成的毫秒数都不适用。这张表要用的是量级关系:LLM 生成通常比其余所有阶段加起来还大。
由此得到优化顺序:先压 LLM、再压 rerank、最后才是 retrieval。花半天把 context pack 从 5ms 优化到 2ms,而 LLM 那边有一秒多没动——这是最常见的 ROI 算错。
分预算时还有一条纪律:各阶段预算之和要留出余量,不能刚好等于总预算。每一跳都有自己的抖动,全部取上界相加会直接超出 SLO——实践中要在各阶段 P99 之和之外留出 buffer(留多少按自己压测出的抖动幅度定),或者干脆按"关键路径上只允许一个阶段用满预算"来配。
TTFT vs 端到端
对话体验更关注 TTFT(Time To First Token)——用户看到第一个字的时间。端到端完成两秒,但只要第一个字出得快,感知就是"快"。所以流式输出几乎是必选项:它把用户感知的等待从"端到端"变成了"TTFT + 阅读速度"。
TTFT 由三段构成,都要单独测:检索链路的耗时 + prompt 的 prefill 时间 + 首 token 的解码。中间那段和 input token 数直接相关——这正是 §16.16 讲降 top-k 能改善 TTFT 的原因。
长尾延迟是真正的挑战
P50 快不代表不会慢——同一套系统的 P99 常常远高于 P50,这个倍数本身就是要监控的指标(它变大说明系统在接近饱和,见 §21.4)。长尾来源:
- GPU / API 偶发高延迟(抢占、网络抖动)
- Rerank 遇到长 chunk 超时
- LLM 某次采样运气差,生成超长
对策:
- 关键请求设 timeout + fallback(第 4 章降级链)
- 并行冗余调用(对关键 API 同时发两请求,取快的)
- 监控 P99 而不是 P50——优化 P99 才能改善用户感知
21.3 三层缓存
缓存是延迟和成本的双重杠杆。RAG 的三层缓存各有适用。
L1:Query-level 缓存
完整响应缓存。Key = hash(query + user_context + knowledge_version)。Value = 完整 JSON response。
- TTL:分钟到小时级,由"知识多久会变"决定
- 命中率:完全取决于 query 分布的集中度——FAQ / 客服这类问题高度重复的场景命中率可观,开放域问答几乎命中不了。上线前先拿一周的 query 日志跑一遍去重,重复率就是命中率的上界,这个数一算就知道值不值得做
- 延迟收益:命中时直接跳过检索和 LLM,只剩一次 Redis 往返——从秒级降到毫秒级,这是所有优化里收益最大的一档
- 成本收益:命中即零 LLM 成本
坑:包含用户信息的 response 不能跨用户共享缓存。Key 一定要包含 user_context hash,否则就是跨用户数据泄漏——这不是性能 bug 是安全事故。
L2:Embedding 缓存
query 和 chunk 的 embedding 去重。Key = hash(text + model_version)。
- TTL:可以很长——同一段文本 + 同一个模型版本,embedding 是确定的。key 里必须带 model_version,否则换模型后拿到的是旧向量,这类 bug 极难发现
- 命中率:查询端看 query 重复率;离线索引端在增量重建场景下很高——没改过的文档不必重算
- 延迟收益:命中时省掉一次模型推理往返
- 成本收益:
命中次数 × 每次 embedding 的 token 数 × embedding 单价。注意 embedding 单价比 LLM 低两三个数量级,所以这一层的价值主要在延迟和离线重建时间,不在省钱
存储量可以直接算:1024 维 fp32 是 1024 × 4 = 4KB 一条,一千万条就是 40GB。量化到 int8 能降到四分之一(第 9 章 §9.11)。
L3:Prompt Cache(2024 年后的大杠杆)
Anthropic、OpenAI 等都支持 prompt caching——让 LLM 的 input 里可 cache 的前缀只算部分费用。
- cacheable 前缀:system prompt + few-shot 示例 + 稳定 context(如 core memory)
- dynamic 后缀:query + 动态 retrieved chunks
[cacheable prefix]
system: 你是企业知识助手...
few-shot: 示例 1... 示例 2...
---
[dynamic]
context: {retrieved chunks}
query: {user query}
计价是两档、不是一档,这一点经常被写错:以 Anthropic 为例(官方定价页,核对日期 2026-09-25),写入缓存比基础输入价更贵(5 分钟 TTL 为 1.25 倍,1 小时 TTL 为 2 倍),命中读取则只要基础输入价的一个零头(一般为 0.1 倍,例外是 Claude Opus 5.5 为 0.05 倍、Claude Fable 5.1 为 0.025 倍)。也就是说第一次是亏的,之后每次才赚——倍率会调,以定价页当期为准。
省多少可以直接算。设可 cache 前缀占 input 的比例为 p、读取折扣为 d:
稳态下 input 成本倍率 ≈ (1 - p) + p × d
p = 0.6, d = 0.1 → 0.4 + 0.06 = 0.46,input 省 54%
p = 0.3, d = 0.1 → 0.7 + 0.03 = 0.73,input 省 27%
注意这只作用于 input。总成本还要算上 output——output 不参与缓存,而它的单价通常是 input 的好几倍。所以"prompt cache 省 90%"这种说法是把"缓存段的单价折扣"当成了"总账单降幅",实际降幅要按上面的式子再叠加 output 那一块。
生产用法:
- 把 system prompt + few-shot + 工具定义 这些每次都一样的内容放在最前面,在它们末尾打缓存断点(Anthropic 的写法是在内容块上加
cache_control: {"type": "ephemeral"}) - 动态部分(每次不同的 query 和 retrieved chunks)放在断点之后,不标
- 缓存默认存活时间较短(Anthropic 默认 5 分钟,每次命中会续期),高频调用能一直续上;调用稀疏的场景可以用 1 小时 TTL(
cache_control: {"type": "ephemeral", "ttl": "1h"}),代价是写入价从 1.25 倍升到 2 倍 - 两条硬约束(Anthropic):一次请求最多 4 个缓存断点;前缀短于模型的最小可缓存长度时不会被缓存,也不报错(
claude-sonnet-4-6是 1024 token,claude-haiku-4-5是 4096,以 prompt caching 文档为准)
顺序不能反:缓存按前缀匹配,一旦把变化的内容放到了前面,它后面的所有内容都不再命中。同理,命中要求前缀字节精确相同——prompt 模板里插一个时间戳、一个请求 ID、或者 JSON 序列化时 key 顺序不稳定,都会让命中率归零。验证方法很简单:看 API 返回的 usage 里读缓存的 token 数,如果重复请求下它一直是 0,就是有东西在悄悄破坏前缀。
21.4 并发和吞吐
延迟只讲单次请求,不够。生产要回答"在高并发下还能撑住吗"。
QPS 和并发的关系
一个直觉(Little 定律):QPS = 并发数 / 平均延迟。平均延迟 1s 时 100 QPS 意味着平均有 100 个请求同时在跑。这对:
- LLM API 的并发上限(Anthropic / OpenAI 都有)
- 向量库的连接池
- GPU 显存(embedding / rerank 模型的 batch)
- Redis 的连接数和内存
都有直接压力。任一环节的并发上限低于业务所需的并发数,整条链路就会崩溃。
容量测试要点
压测时不只看均值——要看饱和曲线的形状。逐步加压,画出 QPS 对 P50 / P99 的曲线,它一定长这样:
- 低负载区:P99 和 P50 贴得很近,加 QPS 延迟基本不动
- 中负载区:P99 先开始抬头,P50 仍然平稳——这是最重要的信号,说明排队已经开始了
- 高负载区:P99 急剧恶化,队列堆积,超时开始出现
- 过载:P50 也崩,整体不可用
要从曲线上读出来的是拐点的位置,也就是 P99 开始明显偏离 P50 的那个 QPS。它就是你的实际容量上限,通常远低于"资源用满"时的 QPS。
生产的常态负载应该明显低于这个拐点——留出的余量用来吸收流量尖峰和扩容的滞后(§21.20 讲 HPA 的扩容延迟)。看到 P99/P50 的比值开始变大就该扩容了,不是"还能再撑撑"。
水平 vs 垂直扩容
扩容两条路:
- 水平:加机器、加 worker 实例。容量线性增长,但要协调(如 LLM API key 配额分配)
- 垂直:单机升级(更大 GPU、更多内存)。简单直接,但有上限
RAG 的垂直扩容典型场景:embedding / rerank 服务换更新一代的 GPU。换代能带来多少吞吐,必须在自己的模型和 batch 配置上实测——它取决于模型是显存带宽瓶颈还是算力瓶颈,不同模型的收益差别很大,厂商标称的算力倍数不能直接换算成吞吐倍数。水平典型场景:在线检索服务加 pod、走 K8s HPA 按 QPS 自动伸缩。
组合用:检索服务水平扩、embedding / rerank 垂直扩——前者无状态好水平扩,后者模型加载重,不宜频繁启停。
21.5 成本的主要来源
RAG 的成本构成大致是这几项,优化前第一件事是把自己的实际占比测出来——不同项目的分布差得很远,一个把 embedding 自托管了的项目和一个全走 API 的项目,账单结构完全不同:
| 成本项 | 这一项为什么大 / 小 | 优化杠杆 |
|---|---|---|
| LLM 生成 | 单价最高、每请求必调,通常是最大一项 | prompt cache / 模型分层 / 缓存 |
| Embedding | 单价比 LLM 低两三个数量级,但离线全量重建时量大 | 自托管 / 缓存 / 批处理 |
| Rerank | 每请求都跑,候选数直接决定量 | 自托管 / 候选减少 |
| 向量库 | 固定成本为主(内存),随索引规模走 | 量化压缩 / 冷热分层 |
| 基础设施 | 通常最小 | 优化完上面几项再看 |
怎么测:在每条请求的 trace 里记下各组件的 token 数和调用次数(§21.18 给了 trace 的字段设计),按月聚合就是真实占比。LLM 生成基本都是大头,所以优化顺序几乎总是从 prompt caching 开始——但真到了要投人力的时候,还是按自己测出来的数字排序。
LLM 选型的成本影响
这里不列各家的价目表——模型价格是本专栏最容易过期的一类数字,各家每年都在调,而且新模型发布后旧型号常常降价。写下来的那一刻就开始失真。要用的时候直接查官方定价页(Anthropic、OpenAI、以及你在用的其他厂商),别信任何二手表格,包括这一节曾经写过的那一版。
需要记住的是结构,它比具体数字稳定得多:
- 同一家的模型分档,相邻档之间的单价通常差两三倍,最强档和最便宜档能差一个数量级(以 Anthropic 官方定价页 2026-09-25 为例:
claude-haiku-4-5输入 $1、claude-sonnet-5$2、claude-opus-5-5$4、claude-fable-5-1$10,每百万 token;首尾10 / 1 = 10倍) - output 的单价通常是 input 的数倍(Anthropic 现行各型号都是 5 倍)——这意味着控制输出长度和控制输入长度同样重要,而多数人只盯着 input
- 国内厂商和开源模型的托管服务通常明显更便宜——但要连同质量一起评估,不能只看单价
算法是固定的,代价单价变了只要重代一次:
单次请求成本 = input_tokens × input 单价 + output_tokens × output 单价
举例:一次 RAG 请求 3000 input + 500 output。如果中档模型是 $3 / $15(每百万 token),换成低档 $1 / $5(这两组恰好是核对日 2026-09-25 时 claude-sonnet-4-6 与 claude-haiku-4-5 的官方定价):
中档: 3000 × 3/1e6 + 500 × 15/1e6 = 0.0090 + 0.0075 = $0.0165
低档: 3000 × 1/1e6 + 500 × 5/1e6 = 0.0030 + 0.0025 = $0.0055
差 3 倍
这里恰好也是 3 倍,是因为两档的 input 单价和 output 单价都正好差 3 倍。一般情况下两项单价的倍数不相同,总账的倍数会落在两者之间,偏向哪一边取决于你的 input/output 长度比——代进上面的式子算,别直接拿单价倍数当账单倍数。
质量那边差多少?没有通用答案,必须在自己的 gold set 上测(第 20 章)——同一对模型在 FAQ 类任务上可能几乎无差,在多步推理上可能差得很远。
模型分层策略
一套 RAG 里不同环节用不同模型:
- Query rewrite:Haiku(任务简单)
- Rerank:专用 reranker(bge-reranker)
- Generation:Sonnet(质量主战场)
- Judge / faithfulness:另一个 Sonnet(独立评估)
- Memory extraction:Haiku(批处理不急)
只在真正需要大模型的阶段用大模型,其他用小模型。能降多少可以直接算:把各环节的 token 量乘上各自模型的单价,和"全用大模型"的账对比。降幅取决于非生成环节占了多少 token——如果你的 token 几乎全在 generation 上,分层能省的很有限;如果 rewrite、抽取、judge 这些辅助环节的 token 量可观,收益就大。先测占比,再决定值不值得引入多模型带来的复杂度。
21.6 Batch 处理
离线索引的成本大头是 embedding 和 rerank——它们都是 GPU 推理,而逐条推理会让 GPU 严重空转:单条输入的算力远喂不饱一张卡,时间大部分花在 kernel 启动和数据搬运上。batch 把多条输入拼成一个矩阵一次算完,利用率和吞吐都会显著上升。
能提升几倍必须自己压测——它取决于模型大小、序列长度分布和显存,从"翻倍"到"一个数量级"都可能。测法是固定数据集,扫 batch size 画吞吐曲线:吞吐会一路上升到显存打满或收益饱和,那个拐点就是你的最佳 batch。
动态 batch
- 队列积累 0-20ms,batch 满 32/64 就 flush
- batch 内尽量选长度相近的(避免 padding 浪费)
- GPU 空闲时强制 flush(不累计太久)
批 embedding API
主流厂商都提供异步批量接口,换取明显折扣:OpenAI 的 Batch API 是 24 小时窗口,五折;Anthropic 的 Message Batches 同样是五折。离线全量索引和离线评测这两类没有延迟要求的负载,默认就该走批量接口——这是本章里少数"不牺牲任何质量"的降本手段之一,和 prompt cache 并列。
# OpenAI batch API 伪代码
batch = client.files.create(file=upload_file, purpose="batch")
job = client.batches.create(
input_file_id=batch.id,
endpoint="/v1/embeddings",
completion_window="24h",
)
# 24 小时后拉结果
大批量一次索引(几千万 chunk)走 batch 接口,embedding 这一项的 API 费用就是原来的一半(0.5 × 原价)。注意 Anthropic 不提供 embedding 接口,它的 Message Batches 用于生成类的离线任务(如 Contextual Chunk 的上下文生成、离线评测)。
21.7 模型选型的成本/质量权衡
具体到业务,小模型 vs 大模型要实测:
| 场景 | 小模型够用 | 必须大模型 |
|---|---|---|
| FAQ 问答 | ✓ Haiku | - |
| 合同分析 | - | ✓ Sonnet/Opus |
| 代码生成 | 部分够 | 复杂需 Sonnet |
| 数据提取 | ✓ Haiku | - |
| 多步推理 | - | ✓ Opus |
实操:先用大模型验证可行,再尝试用小模型看是否保质。反过来(先小模型,质量差再换大)容易让小模型误被判死。
离线蒸馏
用大模型生成高质量数据,训练小模型在这些数据上逼近大模型——离线训一次,在线用小模型。适合量大的稳定任务(如 query rewrite)。
21.8 向量库的成本
向量库的成本按索引大小、QPS、托管方式变化大。
自托管 vs 云
不给具体报价——托管向量库的定价按维度、副本数、QPS 分档,各家口径完全不同,而且改得很勤。要比就去各家的定价页按自己的规模算一遍,或者直接申请试用。
值得记住的是三条成本结构上的差异:
- 自托管的大头是内存和运维人力,不是机器本身。向量索引(尤其 HNSW)常驻内存,规模上去后内存就是账单主体;而"顺手运维一下"的隐性人力成本最容易被漏算——把实际投入折算成 FTE 算进去再比
- 托管服务的大头是按容量和 QPS 计价的固定档位,好处是省掉运维,坏处是到了某个规模会明显贵于自托管
- 如果已经有 Postgres,pgvector 的边际成本接近于零——不用新增组件、不用新增运维对象。这是它在中小规模最被低估的优势,跟性能无关
选型顺序:先问"现有基础设施能不能直接扛",扛不动了再上专用向量库,规模再大才谈自托管。第 11 章有三款产品在多租户、过滤语义上的实现对比,那才是选型的主要依据——成本通常不是。
索引压缩
IVF-PQ 的压缩比是可以直接算的。1024 维 fp32 原始占 1024 × 4 = 4096 字节;PQ 把向量切成 m 个子空间,每个子空间用 1 字节的码本编号表示,m = 32 时每条只剩 32 字节:
压缩比 = 4096 / 32 = 128×
1 亿向量:1e8 × 4096 B ≈ 410 GB → 1e8 × 32 B = 3.2 GB
内存从几百 GB 降到几 GB,是"要一个专用大内存集群"和"塞进一台普通机器"的区别。代价是量化误差带来的召回损失——标准做法是用 PQ 做粗筛,再用原始向量或 rerank 对 top-N 重打分补回精度(第 10 章)。
冷数据下沉
长期不命中的 chunk 移冷存储,节省内存:
- 热数据:HNSW in-memory(30 天内命中过)
- 冷数据:磁盘 IVF-PQ
- 归档:S3 + 按需重激活
能省多少完全取决于你的访问分布:把一段时间的检索日志按 chunk 聚合,画出"命中次数的累积分布"——冷尾占总量的比例,就是这一层能省掉的内存比例上限。分布越长尾,收益越大;如果访问相当均匀,冷热分层不值得做。
21.9 Rerank 的成本优化
Rerank 每次请求都跑,量大成本可观。优化方向:
- 候选减少:cross-encoder 的成本和候选数成正比(每个候选一次前向),所以 top-100 砍到 top-50 就是直接对半。关键是砍到哪里不掉精度——扫一遍候选数对 NDCG 的曲线,找那个开始掉的位置(第 14 章)
- 模型分层:先用轻量 reranker 粗筛,重模型只精排前几十条。两级的总成本 =
轻模型 × 全部候选 + 重模型 × 少量候选,只要轻模型足够便宜就划算 - 自托管:rerank 模型比生成模型小得多,自托管的门槛远低于自托管 LLM。省不省要按
GPU 月成本 / 月调用次数和 API 单价比——调用量小的时候自托管反而更贵 - 缓存:
(query_hash, chunk_id) → score缓存。命中率取决于 query 重复率,和 §21.3 的 L1 是同一个前提
这些叠加起来能降多少,把上面四项各自的算式代进自己的数字。顺序上先做候选减少——它零成本,且同时降延迟。
21.10 一次完整请求的成本账
把前面各节的算式串起来走一遍。下面所有单价都沿用 §21.5 那组价格(低档 $1 / $5 即 claude-haiku-4-5、中档 $3 / $15 即 claude-sonnet-4-6,每百万 token,核对日 2026-09-25);自托管 GPU 和基础设施的分摊额是示意假设值,按 GPU 月成本 / 月调用次数 换成自己的数——单价变了请去官方定价页替换,算式不变:
请求流程:
1. Query rewrite (Haiku): 300 input + 80 output
= 300 * $1/1M + 80 * $5/1M = $0.0007
2. Dense embedding (自托管 bge): 0 API 成本(GPU 成本分摊 $0.0001)
3. Hybrid retrieval + Rerank: bge-reranker 自托管 ≈ $0.0002
4. Generation (Sonnet): 3000 input + 500 output
= 3000 * $3/1M + 500 * $15/1M = $0.0165
5. Faithfulness check (Haiku offline 10% sampling):
分摊 = 0.1 * (2000 * $1/1M + 100 * $5/1M) = $0.00025
6. 向量库 + 网关 + 日志: $0.0002 分摊
单次请求合计: ~$0.018
加 prompt cache 后
system prompt (400 tokens) + few-shot (800 tokens) 共 1200 tokens 标 cacheable。前缀长度要先过门槛:claude-sonnet-4-6 的最小可缓存长度是 1024 token,短于它的前缀标了 cache_control 也不会被缓存,且不报错(§16.7 那笔账里约 600 token 的前缀就是这种情况):
稳态(缓存命中)下的 generation 成本:
- cacheable prefix: 1200 tokens × $3/1M × 0.1 = $0.00036 ← 读取按 0.1 倍计价
- dynamic suffix : 1800 tokens × $3/1M = $0.0054
- output : 500 tokens × $15/1M = $0.0075
合计 $0.01326
对比无 cache 的 $0.0165,降 ~20%(1 - 0.01326 / 0.0165 ≈ 0.196)
(首次写入缓存那一发更贵:1200 × $3/1M × 1.25 = $0.0045,之后每次才开始赚)
只降了约 20%,一是因为这里的 cacheable prefix 只占 input 的 40%(1200 / 3000),二是总账里还压着不参与缓存的 output。把 prefix 拉长,input 那块的收益才明显——比如 prefix 占 input 七成时,input 那块能省六成多(0.3 + 0.7 × 0.1 = 0.37);但 output 始终不参与缓存,所以总降幅始终小于 input 的降幅。这是"prompt cache 省 90%"这句话最常被误读的地方:90% 是缓存段的单价折扣,不是账单降幅。
推论很实用:要让 prompt cache 真正值钱,就得让固定前缀足够长——system prompt、few-shot、工具定义、稳定的 core memory 都往前缀里放;反过来,§16.18 的动态 few-shot 会直接破坏这件事。
成本优化 ROI 图
flowchart LR
classDef high fill:#efe,stroke:#080
classDef mid fill:#def,stroke:#06c
classDef low fill:#fed,stroke:#c60
START[优化起点] --> O1[1. prompt cache<br/>改动小 收益大]:::high
O1 --> O2[2. 模型分层<br/>需要按环节评质量]:::high
O2 --> O3[3. query/embedding cache<br/>看 query 重复率]:::mid
O3 --> O4[4. batch 处理<br/>只对离线链路]:::mid
O4 --> O5[5. rerank 自托管<br/>要有量才划算]:::low
O5 --> END[优化终点]
这个顺序按的是"改动成本 ÷ 预期收益",不是固定的降幅——每一步能省多少,用前面各节给的算式代入自己的数字。排在前面的两步之所以优先,是因为它们直接作用于账单里最大的那一项(LLM 生成),而且不需要改动检索链路。
规模经济
规模一大,百分比就变成了真金白银。沿用上面 $0.018/请求 的账,100 万请求/天:
日成本 = 1e6 × 0.018 = $18,000
年成本 = 18,000 × 365 ≈ $657 万
在这个量级上,降 10% 就是一年六十多万美元(657 万 × 0.1 ≈ 65.7 万)——通常远超做这项优化所需的工程投入。这就是为什么成本优化在大规模 RAG 上 ROI 极高:优化是一次性投入,节省是持续的。
反过来同样成立:日请求量只有几千的项目,同样的优化一年省下的可能还不够那几周工程时间的成本。先算这笔账再决定投不投人(§21.16 讲了成本优化的止损点)。
21.11 质量-成本-延迟的迭代思路
生产 RAG 的优化不是一次性的,是持续迭代。建议的节奏:
阶段 1:跑通(1-2 周)
- 用成熟的默认组合(一个通用 embedding + 一个通用 reranker + 一个中档生成模型),不调参
- 目标:建起 gold set 并拿到第一条基线(第 20 章)——这个阶段真正的产出是"能测",不是某个分数
- 不优化成本,先证明 feasibility
阶段 2:质量(2-4 周)
- 改 chunk / 加 hybrid / 微调 rerank
- 目标:相对阶段 1 的基线持续提升,直到收益开始变平
- 成本延迟次要,允许一定膨胀
阶段 3:降成本(2-4 周)
- prompt cache / 模型分层 / 缓存 / 批处理
- 目标:按前面各节的算式估出可省的量,逐项上线并验证质量不退化
- 延迟可以略升
阶段 4:降延迟(持续)
- 并行化 / rerank 轻量化 / 流式
- 目标:P99 < 2s
- 常态持续优化
顺序错了会浪费。阶段 2 没做先降成本,质量上不去;阶段 3 没做先降延迟,优化 ROI 低。
21.12 P99 长尾的对抗:请求冗余和超时
P99 显著高于 P50 是常态——LLM 和外部 API 的尾部延迟本来就难预测。这个比值是要盯的指标(§21.4),几个对抗策略:
策略 1:并行冗余请求
对关键 API(LLM 生成、Rerank)同时发两个请求,取先回来的那个。数学上,两次独立请求都落进尾部的概率是单次的平方——单次 P99 意味着两次都慢的概率降到万分之一量级,代价是这部分调用的成本翻倍(实践中常只对超过某个等待时间的请求补发第二个,成本增幅远小于翻倍)。适合:
- 关键路径(而非 batch 提取)
- 外部 API(不是自己服务——自己服务不如扩容)
- 成本不敏感(冗余请求翻倍成本)
策略 2:激进超时 + Fallback
- 主请求:设一个期望延迟和一个超时阈值(阈值按自己压测出的 P99 定)
- Fallback:小模型或缓存,立即返回
- 用户体验:超时阈值设在 P99 附近时,约 99% 的请求看不出降级,只有尾部约 1% 的请求看到简短回答
实现:asyncio.wait_for(llm_call, timeout=...) 捕获 asyncio.TimeoutError 走 fallback。
策略 3:speculative decoding
某些 LLM serving 框架(如 vLLM)支持 speculative decoding——用小模型先猜若干 token 的 draft,大模型一次前向批量验证,猜中就一次吐多个 token。加速比取决于 draft 模型的命中率:命中率高的任务(格式固定、可预测性强)收益明显,命中率低时反而因为多跑了 draft 模型而变慢。要不要开、开哪个 draft 模型,得在自己的流量上测。这条只对自托管有意义,API 侧一般不暴露。
策略 4:分段输出
不等完整答案,先把确定部分流式发出。用户看到"正在思考..." → "企业版 SSO 定价..." → "每年 20000 元"。TTFT 低,感知流畅,哪怕完整答案总延迟不变。
长尾监控
把 P99 / P99.9 放在看板上主要位置,和 P50 同等对待。长尾恶化通常是事故前兆——某个下游服务 degraded,先在长尾显现,再影响 P50。
21.13 Rate limit 管理
外部 API 都有 rate limit——OpenAI / Anthropic 按 tenant 或 API key 限 tokens/min 和 requests/min。RAG 高 QPS 下触碰限制会 429 错误。
应对策略
- 配额充裕采购:关键 API 提前申请更高的 usage tier 或企业额度
- 客户端 rate limiter:自己限,比被服务端 429 体面(token bucket + burst)
- 多 key 轮询:企业账号能申请多个 key,轮流用分散压力
- 全局 vs per-user:用户级限流防单用户抢光所有额度
- 优雅降级:被限后切到 fallback(缓存、小模型、静态回复)
监控
必看:
rate_limit_error_rate:429 错误率(正常应接近 0)tokens_used_per_min:实时使用量 / 配额retry_rate:重试比例
接近配额 80% 时告警,采购要提前——等到 100% 再动,用户都受影响了。
21.14 成本监控
生产 RAG 的成本看板要有:
- 单次请求成本分布:P50 / P99、按 segment 分
- 总成本趋势:按天、按 component(LLM / embedding / rerank)拆
- cache 命中率:query / embedding / prompt cache 各自
- 模型用量 per category:Sonnet 被用了多少次,都是什么请求
- 异常成本:单次请求 > 2x 平均的 case 清单
每周看一次,每月做一次预算 review。没有这层监控,成本能在没人察觉的情况下一路往上爬——等财务问起来时,已经找不回是哪一次改动带上去的了。
21.15 常见反模式
- 反模式 1:默认大模型干所有事。Query rewrite / 抽取 都用 Sonnet——浪费。用 Haiku 绰绰有余
- 反模式 2:cache key 加时间戳。"加个时间戳防陈旧"——每次 key 不同,cache 永不命中。用内容 hash + knowledge_version
- 反模式 3:并发数拉满。上来就 1000 并发请求 LLM API——被 rate limit 打爆。渐进式加压,有队列
- 反模式 4:只看均值成本。均值 $0.02 但 p99 $0.5——少数复杂请求吃掉一大半预算。长尾要单独看
- 反模式 5:优化 5% 的部分。context pack 优化半天省了 5ms——LLM 生成那边 1500ms 没动。ROI 算错了
21.16 FinOps 视角:从请求成本到单位经济学
前面几节都在谈 "单次请求成本"——但单次请求成本低不代表生意健康。真正决定项目能否持续的是单位经济学(unit economics):每个用户、每个付费订阅、每个成功回答对应的成本。
三层单位指标
flowchart TB
classDef raw fill:#fed,stroke:#c60
classDef biz fill:#def,stroke:#06c
classDef sla fill:#efe,stroke:#080
R1[cost per request<br/>$0.018]:::raw
R2[cost per successful answer<br/>cost / answer_quality]:::biz
R3[cost per MAU<br/>月活用户单位成本]:::biz
R4[cost / revenue ratio<br/>占收入百分比]:::sla
R1 --> R2 --> R3 --> R4
- Cost per request:工程视角,容易测
- Cost per successful answer:业务视角,失败的回答花的钱是浪费,应该用成功率折算
- Cost per MAU / DAU:产品视角,决定商业模式能否跑通
- Cost / ARR ratio:财务视角,决定毛利率
一个常见陷阱:成本优化只看 cost per request,把生成模型换小,单次成本降了,但答对率也降了。该看的是 cost / 答对率:
换模型划算的条件: 新单价 / 新答对率 < 旧单价 / 旧答对率
等价于:新单价 / 旧单价 < 新答对率 / 旧答对率
也就是说,成本的降幅必须大于答对率的降幅,换模型才真的省钱。举例:单次成本降到原来的 60%(降 40%),只有当答对率保持在原来的 60% 以上时才划算——如果答对率从 85% 掉到 45%(是原来的 53%),那就亏了。
而且这还是保守估计:答错的那次不只是白花钱,用户通常会重问一次,实际成本要再乘上重试系数。把重试算进去,容忍答对率下降的空间比上面的不等式更小。
成本归因的粒度
仅仅看总成本不够。按维度拆才能发现可优化点:
- 按用户类型:免费用户 vs 付费用户的成本对比,免费吃了太多资源是商业模式问题
- 按 query 类型:FAQ 类 vs 复杂推理类的成本比,定价应反映这个差异
- 按时间段:峰值时段 vs 平时,错峰调度能不能省
- 按 feature:搜索 vs 摘要 vs 对话,哪个 feature 毛利率最高
RAG 的单请求成本分布通常是长尾的:少数重度用户吃掉的成本占比远高于他们的人数占比(具体多少要在自己的账单上按 user 聚合一次才知道)——找出这批人,要么限流要么加价,杠杆极大。
什么时候应该"加钱升级"
前面章节都在谈降本,但不是所有场景都应该压成本。三种情况应该反向投钱:
- 合规/法律/医疗场景:答错的代价远大于单次成本差——不要为了省 $0.01 从 Opus 换 Haiku
- 关键决策场景:CEO 每天用来辅助决策的 AI 助手——一次好答案价值 $1000,不在意每次多 $0.05
- 品牌时刻:新用户首次使用、客诉解决等——用最好的模型确保体验,留存 > 成本
工程师本能是降本,产品决策要求懂得在哪些场景应该花更多。
成本优化的止损点
成本优化不是无止境的——几个信号说明该停:
- 进一步优化会伤质量:再降 LLM 层级或模型大小,答对率开始下降
- ROI 倒挂:花 2 人月工程优化省 $500/月——工程师时间本身更贵
- 复杂度爆炸:优化引入的缓存/分层/降级路径超过了核心业务逻辑,可维护性崩塌
- 绝对值已经很小:单次请求 $0.005 以下,再降也没多少钱
"还能再优化 5%"不值得。工程产能应该转到质量提升或新 feature——那里的 ROI 高得多。
单位经济学驱动的路线图
好的 RAG 成本策略按这个顺序:
- 先算清 unit economics:知道现在每 MAU 多少成本,和收入比
- 找异常成本:归因分析,发现那一小撮超重用户或那类超贵 query
- targeted 优化:对准异常点优化,不做地毯式优化
- 监控止损:每次优化后看业务指标,不好就回滚
- 到一定点之后停:资源转投提质或新 feature
这套路径比"挨个优化每个组件"高效得多。
预算要拆到哪些类别
比"各占多少百分比"更重要的是别漏项。RAG 的预算至少要拆成这六类,其中后两类最常被漏掉:
| 支出类别 | 备注 |
|---|---|
| LLM 生成(API 或自托管 GPU) | 通常最大一项 |
| Embedding + 向量库 | 向量库是固定成本,随索引规模而不是流量走 |
| Rerank + 辅助模型 | 随候选数走 |
| 基础设施(网关 / 日志 / 监控) | 通常最小 |
| 工程人力(分摊) | 经常被完全漏算,但它常和 GPU 同量级 |
| 评估 / Gold set 维护 | 也经常被漏,见第 20 章 §20.19 的人工标注成本 |
各类占比按自己实测的填,本节不给参考分布——一个把 embedding 自托管了的项目和一个全走 API 的项目,分布完全不同,拿别人的比例当基准只会误导。有意义的判断是看趋势:某一类的占比在几个月里持续上升,就去查是什么在推它。
21.17 成本回归测试与自动化优化 pipeline
前 16 节讲的都是"如何优化"——但一个被忽略的角度:优化后的成本会悄悄回退。Prompt 模板改了一行、context 多塞了两个 chunk、某个 prompt cache 的前缀改动让命中率降了——单次改动省钱的迭代很多,但每周某处不经意的改动也在消耗这些收益。没有回归机制,半年后你会发现"钱也没省下来"。
成本回归的几种隐形来源
flowchart TB
classDef src fill:#fed,stroke:#c60
classDef effect fill:#fdd,stroke:#c00
S1[Prompt 模板修改]:::src --> E1[cache miss 率上升]:::effect
S2[top-k 默认值调大]:::src --> E2[token 成本上升]:::effect
S3[模型版本静默升级]:::src --> E3[跳到更贵的定价档]:::effect
S4[对话历史累积]:::src --> E4[context 逐日膨胀]:::effect
S5[新 feature 上线]:::src --> E5[单请求叠加 3 次 LLM]:::effect
S6[Rerank 候选扩大]:::src --> E6[GPU 成本翻倍]:::effect
这六类每一类都在生产里反复出现——不是"谁故意浪费",是正常迭代的副作用。不加监控就是温水煮青蛙。
CI 里加成本 check
参考软件工程的性能回归测试——成本回归测试应该在 CI 阶段就跑。典型检查:
def test_cost_regression():
fixture = load_fixture("typical_requests_100.json")
total_input_tokens = 0
total_output_tokens = 0
for req in fixture:
result = rag.process(req)
total_input_tokens += result.trace.input_tokens
total_output_tokens += result.trace.output_tokens
# 基线 (上次 release 时记录)
baseline_input = 198000
baseline_output = 53000
# 允许 5% 回归
assert total_input_tokens < baseline_input * 1.05, \
f"input token 回归 {total_input_tokens}/{baseline_input}"
assert total_output_tokens < baseline_output * 1.05, \
f"output token 回归 {total_output_tokens}/{baseline_output}"
基线随版本滚动更新——每次有意的成本改动后重新录制基线。意外的回归被 CI 挡在上线前——成本作为一等质量指标而非事后审计。
生产观察的异常检测
CI 只能防已知的,生产观察要防未知的。自动化异常检测:
- 滑动窗口对比:今天的平均成本 / 上周同一天的平均成本。告警线按自己的历史波动定——先看几周的正常日间波动幅度,把线设在它之外,否则周一周五的自然差异就能天天报警
- 租户级异常:某租户成本突增(可能是新上线 feature,可能是滥用)
- Feature 级拆分:搜索 / 摘要 / 对话三个 feature 各自的成本趋势独立看
- cache 命中率漂移:命中率明显下降 → 十有八九是 prompt 前缀被改动了(哪怕只是加了个空格),去 diff 最近的 prompt 变更
这些告警的触发时机比每月 review 早,止损效果更好。
自动化扩缩容策略
成本优化不仅是减少单次请求成本,也是让资源利用率高。自动化扩缩容的基本政策:
- 按 QPS 自动伸缩:检索服务根据 QPS 维持 40-60% 利用率,高峰扩容,低谷缩容
- GPU 池按任务弹性:embedding 批任务用可中断的 spot / preemptible 实例(各家的折扣力度以当期定价页为准,通常相当可观),在线路径用 on-demand 保稳定
- 按业务 SLA 分层:核心业务用预留容量(低单价),新 feature 用按量计费(灵活)
- 关键路径冗余:P0 服务至少 2 个可用区,防单点故障导致流量转移引发成本爆增
扩缩容策略和成本直接挂钩——配错就是浪费或事故。
时段定价与批处理窗口
云供应商和 LLM API 的时段定价策略差异大:
- OpenAI / Anthropic 的 batch API:非实时任务承诺 24 小时内完成,价格 50% 折扣
- 云厂商的 spot / preemptible 实例:折扣力度大但可能被随时回收,只适合能断点续跑的任务;具体折扣以当期定价为准
- 电价低谷:自建 GPU 集群在电费低谷时段跑大 batch(夜间)
RAG 离线任务(索引全量重建、gold set 评估、memory 提取)都可以用这些便宜窗口。典型做法:
任务分类调度:
- 紧急(RAG 在线查询) → 主区域 on-demand GPU 实时
- 日常(增量索引) → 主区域 on-demand 队列、白天处理
- 批量(全量重建、评估) → 夜间 spot + batch API、next-day 完成
- 实验(A/B 评估、微调) → 任意时段 spot、可中断重启
把同一批算力需求放到这四层里,单价差距是倍数级的——大规模项目省下的不是小数目。关键前提是任务本身要能容忍中断和延迟:批量重建和离线评估天然可以,在线查询绝对不行。
持续优化飞轮
成本优化不是"一次项目",是持续过程。飞轮的四个环节:
flowchart LR
classDef phase fill:#def,stroke:#06c
M[监控看板]:::phase --> A[异常归因]:::phase
A --> O[制定优化]:::phase
O --> D[灰度上线]:::phase
D --> M
- 监控:每日、每周的成本面板持续刷
- 归因:成本异常时定位是哪个 feature / 租户 / 时段
- 优化:针对性措施(调 top-k、换模型、加 cache)
- 灰度上线:带 CI cost check 和 shadow 验证
成熟团队会持续跑这个飞轮。它的价值不是某个累计降幅,而是防止回退——上一节说过,成本会随着日常迭代自己涨回去;飞轮的作用是让每次涨回去都在几天内被发现,而不是几个月后在账单上被发现。
常见反模式
- CI 只跑质量不跑成本:性能在 CI 查,成本在财务部查——两者不同步
- 大改动才 review 成本:小改动累积成大问题,半年才发现
- 成本归因不到 feature 粒度:只看总成本,不知道哪个新功能吃掉了预算
- 优化项目结束即忘:做完一轮优化团队散了,没人维护监控——半年回归
成本纪律和代码纪律同等重要——工程团队对成本的掌控能力决定了项目商业模式能否长期成立。
21.18 多租户成本归因与计费设计
前 17 节的成本讨论默认整体视角——RAG 系统每月花多少钱、怎么优化。SaaS 级 RAG 产品还有一个维度:每个租户各自花多少。这个问题不解决,公司层面整体成本控制了,单租户成本仍可能爆炸——某个大客户每月吃掉 30% 资源但只付了 10% 订阅费。多租户成本归因和计费设计是 SaaS RAG 的商业命脉。
为什么 per-tenant 成本必算
典型 SaaS RAG 的租户成本分布规律:
flowchart TB
classDef heavy fill:#fdd,stroke:#c00
classDef mid fill:#fed,stroke:#c60
classDef light fill:#dfd,stroke:#080
T[所有租户]
T --> T1[头部少数租户<br/>吃掉大部分成本]:::heavy
T --> T2[中部租户]:::mid
T --> T3[长尾大多数租户<br/>成本占比很小]:::light
这是典型的长尾分布——具体的分位数每家不同(自己按 tenant 聚合一次月成本、排序、算累积占比就知道),但"少数租户占掉大部分成本"这个形状在企业 RAG 里几乎必然出现。头部大客户:
- 知识库大(百万 chunk)→ 向量库内存贵
- 活跃用户多(日均千 query)→ LLM 成本高
- 功能用全(hybrid + rerank + memory + agent)→ 全链路贵
如果定价一刀切,头部客户的毛利可能为负——不知道这件事的 SaaS 早晚亏死。
归因到租户的工程挑战
per-tenant 成本归因的核心是每次请求的成本能追溯到租户:
| 成本项 | 归因难度 | 方法 |
|---|---|---|
| LLM 调用 | 低 | trace 里记 tokens + tenant_id |
| Embedding 调用 | 低 | 同上 |
| 向量库存储 | 中 | chunk 数 × 维度 × 单价、按 tenant 聚合 |
| 向量库查询 QPS | 中 | query 数 × 平均延迟占用、按 tenant |
| GPU 推理 | 中 | rerank / embedding 调用次数 |
| 基础设施 | 高 | 按 tenant 使用量比例分摊 |
| 研发 / 运营 | 最难 | 按 ARR 比例分摊 |
低难度项直接从 trace 拿,中高难度项按比例分摊——大致准确 > 精确但未实现。
Trace-based cost accounting
生产 RAG 的每条请求 trace 加成本字段:
{
"request_id": "req-abc123",
"tenant_id": "tenant-acme",
"user_id": "u-456",
"timestamp": "2026-04-25T10:00:00Z",
"cost_breakdown": {
"llm_input_tokens": 3200,
"llm_output_tokens": 520,
"llm_model": "claude-sonnet-4-6",
"llm_cost_usd": 0.0174,
"embedding_calls": 1,
"embedding_cost_usd": 0.00005,
"rerank_calls": 50,
"rerank_cost_usd": 0.0002,
"vector_db_ops": 3,
"vector_db_cost_usd": 0.00001,
"total_cost_usd": 0.01766
}
}
总成本按 tenant_id 聚合,每月出账单。定价和真实成本对比,亏本的租户要重新谈判。
存储类成本的分摊
LLM / embedding 是"按次"成本,向量库 / GPU 是"按占用"成本——后者的 per-tenant 归因更麻烦:
- 向量库存储:每租户 chunk 数 × 每 chunk 存储成本(含 replica + index overhead)
- GPU 推理:按每租户的调用次数 / 总次数 × GPU 月费
- 网关和日志:按流量比例(请求数 + bytes)
这些用 Prometheus metric 采集,月末汇总。精确不到个位数,但足够支撑商业决策。
计费模式的三种选型
SaaS RAG 的计费模式:
- 固定订阅:$X/月,不限用量。简单,但必须按最贵用户定价(否则亏)。低使用用户觉得贵
- 按量计费:按 query 数 / token 数付费。灵活但用户预算不可控——怕被"超量炸"
- 混合(tier + overage):$X/月包含 N 个 query,超出按 $Y/query。主流选择
典型混合定价(下表的金额是结构示意,用来说明「月费包量 + 超量单价」这个形状,不是市场行情——你的档位和单价要按自己的单请求成本反推):
| Tier | 月费 | 包含 | 超量 |
|---|---|---|---|
| Starter | $99 | 1000 queries | $0.10/query |
| Pro | $499 | 10000 queries | $0.05/query |
| Enterprise | 定制 | 无限 | 定制 |
定价要预留毛利——每 query 的收入要高于成本 × 2-3(覆盖研发 / 支持 / 坏账)。
限流 + 配额的工程实现
光计费不限流,用户可能一次刷爆配额:
- tier 级别 rate limit:Starter 10 QPS、Pro 100 QPS、Enterprise 无限
- 月度配额:按合约 quota 用完就暂停(或降级到弱模型),避免 overage 无限增长
- 成本告警:单租户当月成本超 quota 80% → 发邮件预警,超 100% → 暂停服务
- Fair use 约束:明确 "单 query 最多 X token",避免被 prompt injection 式滥用
这些措施是商业安全网——没有就是在给客户送钱。
成本可视化 per tenant
成本看板必备 per-tenant 视图:
- tenant cost leaderboard:按月成本排序,头部一眼见
- cost per query by tenant:哪些租户单位成本异常高
- 成本构成拆解:每租户 LLM / 向量库 / rerank 的占比,找异常
- 成本 vs 订阅收入:红色高亮亏本租户
- 月度 trend:按租户的成本趋势,突增找原因
这些看板不是技术同学看的——是产品 + 销售 + 财务看的,决定定价策略、续约谈判、客户成功策略。
Bad tenant 的处理
发现某租户"严重亏本"后的处理方式:
- 谈判涨价:续约时按真实成本调整定价
- 技术限流:加强该租户的 quota,降成本
- 迁移低成本 tier:企业版用户改用按量计费,减浪费
- 功能降级:该租户关掉某些昂贵 feature(如 Agent Memory)
- 最后手段:不续约 / 升价到他离开
技术和商业配合——纯技术优化解不了"定价错了"的根本问题。
成本归因的反模式
- 总成本看一次,分租户从不看:头部亏损在均值里被掩盖
- 计费数据和成本数据脱节:财务只看定价,工程只看成本,中间没人对齐
- 不 log 每次请求的成本字段:月末汇总时要从各个系统抓,数据对不上
- 定价静态多年不调:模型降价了你没降,客户长期觉得贵;或成本涨了你没涨,亏死
- 超量告警只给工程:应该直接给销售和客户,让他们提前应对
多租户成本纪律是商业护城河
认真做 per-tenant cost accounting 的 SaaS 会发现客户按毛利自然分成三档:一批该涨价、一批毛利好要留住、剩下的按规矩续约。这种细颗粒度的成本认知,让定价持续合理,毛利持续健康——是长期商业护城河。不做的公司最终会被"头部客户不涨价、中部客户不续约"慢慢卷死。
21.19 LLM 自托管 vs API 的 TCO 决策
LLM 生成通常是 RAG 成本的大头(§21.5)。§22.16 泛泛讨论了 buy vs build。LLM 的 self-host 决策值得专门展开——因为它有 GPU 硬件、吞吐 / 延迟特性、模型选择等独特考量,不能用通用 TCO 框架套。很多团队在"API 太贵了要自托管"的想法上拍脑袋、没算清 TCO——最后要么失望(比 API 还贵)、要么走回头路。这节给出 LLM 自托管的详细决策框架。
两种路线的性能特征
flowchart LR
classDef api fill:#dfd,stroke:#080
classDef self fill:#fed,stroke:#c60
API[商用 API<br/>Claude/GPT/Gemini]:::api
API --> A1[起步即开箱即用]
API --> A2[按 token 计费]
API --> A3[模型自动升级]
API --> A4[厂商锁定]
SELF[自托管]:::self
SELF --> S1[前期投入大]
SELF --> S2[按 GPU 固定成本]
SELF --> S3[完全控制]
SELF --> S4[运维负担重]
两者不是简单"便宜 vs 贵"——是使用模型 vs 运营模型两种完全不同的商业关系。
成本模型:API
API 侧的成本是纯线性的——用多少付多少,没有固定成本:
月 API 成本 = 月请求数 × (input_tokens × input 单价 + output_tokens × output 单价)
以 3000 in + 500 out、单价 $3 / $15(每百万 token)为例:
单次 = $0.0165
日均 100 万请求 → 月成本 ≈ 3000 万 × 0.0165 ≈ $49.5 万/月
成本模型:自托管
自托管的成本结构完全相反——几乎全是固定成本:
月自托管成本 ≈ GPU 月成本(采购摊销 or 租用)
+ 运维人力(别漏算,这项经常和 GPU 同量级)
+ 监控 / 网络 / 存储
它不随请求数变化,直到把集群跑满为止。
关键的一步是算出这套固定成本能撑多少 QPS,也就是分母:
可服务 QPS = 集群总吞吐(token/s)/ 每请求平均 token 数
自托管的单请求成本 = 月固定成本 / (可服务 QPS × 月秒数)
"集群总吞吐"这一项必须自己压测,不能查表——这是这一节最容易出错的地方,也是很多"自托管 TCO 测算"给出荒谬结论的原因:
- 不要拿单用户的解码速度当集群吞吐。单个用户看到的每秒几十 token,是单序列的解码速度;而 serving 框架用 continuous batching 把大量请求拼在一起跑,集群的聚合吞吐远高于单序列速度。用前者去除,会把自托管的成本严重算高
- prefill 和 decode 的成本完全不同。RAG 的特点是 input 长(几千 token 的 context)、output 短,属于 prefill 密集型;而 prefill 是高度并行的,对吞吐的贡献方式和逐 token 的 decode 不是一回事。用"总 token 数 ÷ 解码速度"估算会严重失真
- 吞吐和延迟是此消彼长的。把 batch 开大,吞吐上去了但单请求的延迟也上去了。所以正确的压测方式是:固定你的延迟 SLO,在满足 SLO 的前提下测最大吞吐——这个数才是能拿来算成本的分母
《vLLM 推理内核深度解析》专栏讲的 PagedAttention、continuous batching、chunked prefill 正是决定这个分母的机制。
拐点是怎么来的
两条曲线的形状决定了结论:API 是过原点的直线,自托管是一条阶梯(每加一个集群跳一级,集群内加请求几乎不加钱)。
低流量:自托管的固定成本摊到很少的请求上 → 单请求成本极高,API 完胜
高流量:API 成本线性上涨、自托管仍在同一级台阶上 → 自托管开始便宜
拐点就是两条线相交处:
拐点月请求数 ≈ 月固定成本 / 单次 API 成本
把你自己的两个数字代进去,就得到你的拐点,不需要相信任何人给的"XX QPS"。要注意的是,右边分母会随 API 降价而变小,左边分子会随 GPU 降价而变小——所以这个拐点每年都要重算。
一个稳健的判断:如果你现在的流量离算出来的拐点还差一个数量级,就不用再往下看了——先把 prompt cache 和模型分层做透,收益比自托管确定得多。
隐性成本
拐点只考虑了显性成本——自托管的隐性成本常被低估:
- 模型升级跟不上:开源模型的能力通常落后于最新的商用 API,质量差距影响用户体验
- 微调维护:有 fine-tune 时,新 base 模型出来后要重新微调
- 故障响应:API 挂了是厂商的事,自托管挂了是你的事
- 初期磨合:从第一次部署到稳定运行,团队往往要经历数月的调试期
- 机会成本:运维 LLM 占 ML 工程师精力,少做产品优化
这些成本很难折算成美元,但方向是一致的:它们全都把拐点往右推。所以按纯显性成本算出来的拐点应该当作下界——真到了那个流量也别急着切,再观察一段时间。
混合策略
不是"非此即彼"——混合策略最常见:
- API 主路径:关键业务 / 复杂任务用 Claude / GPT——保证质量
- 自托管辅助:简单任务(分类、摘要、抽取)用自托管小模型——省钱
- 离线任务全自托管:索引 pipeline 的 embedding / summary 用自托管 GPU 批处理——高性价比
- cold fallback:API 挂了切自托管——可用性提升
典型组合:
def pick_llm(task_type, priority):
if task_type == "generation" and priority == "high":
return claude_api # 质量最重要
if task_type == "classification":
return self_hosted_small # 简单任务用小模型
if task_type == "offline_summary":
return self_hosted_batch # 离线批处理
return claude_api_haiku # 默认
混合策略的好处不在某个降幅,而在于它让自托管变成一个可以分步试的决定:从离线批处理任务开始(没有延迟 SLO、出问题不影响线上),跑顺了再往简单的在线任务扩,最后才考虑核心生成路径。一步到位把主链路切到自托管,是这件事上最大的风险。
决策 checklist
自托管 LLM 前逐项检查:
- 规模达标:实际流量已经超过你自己算出来的拐点,且持续了半年以上——不是一次促销带来的尖峰
- 团队能力:有 2+ 名懂 LLM serving(vLLM / TensorRT)的工程师
- 预算:为前期磨合期预留了额外预算
- 模型能力可接受:开源模型(Llama、Qwen)的能力够用——质量差距不致命
- 运维基础设施:GPU 采购 / 租用渠道、K8s 集群、监控栈就绪
- 长期战略:不是为了"省钱",是因为数据隐私 / 定制化等硬需求
- 退路:发现不行时能切回 API,不被沉没成本绑定
少 2 项不过关 → 再等等,别自托管。
自托管的真实成本优势场景
有些场景自托管 ROI 高,不止是规模:
- 数据强隐私要求:金融 / 医疗 / 政务,数据不能出境或不能过第三方 API——自托管是唯一选项
- 极度定制:需要深度微调 base 模型,超出商用 API 的 fine-tune API 能做的
- 超高 QPS 单任务:同一 prompt 模板的调用量极大——商用 API 的 rate limit 够不上
- 已有 GPU 集群:公司 ML 其他任务已经买了 GPU,复用几乎免费
这些场景下即使流量远未到拐点,自托管也值。
半自托管:LLM gateway
近年流行半自托管——用商用 API 作后端,自己做 gateway:
- 自己维护 prompt cache、多模型路由、A/B 测试逻辑
- 底层调 Claude / GPT / Gemini 等多厂商
- 和厂商谈企业协议拿折扣
它的收益来自三处,每一处都能自己算:统一的 prompt cache 层(跨服务复用前缀)、按任务路由到便宜模型(§21.7 的模型分层,只是把它做成了基础设施)、以及规模化后和厂商谈的折扣。运维负担远低于自托管模型——对大多数生产 RAG 是甜点。
什么时候重新评估
决定不是一次性的——每年重新评估:
- API 降价了吗?(同档位模型的单价长期在下行,新模型发布往往伴随旧型号降价)
- 开源模型进步了吗?(质量差距缩小 → 自托管 ROI 升)
- 业务规模变了吗?(大幅涨 / 降)
- 团队能力变了吗?(招到 LLM serving 专家后门槛降)
每年 Q1 做一次,和年度预算配套——保持决策动态调整。
最终建议
多数 2026 年的 RAG 项目:
- 流量离拐点还差一个数量级:用 API + gateway,把工程投在质量上
- 流量接近或超过拐点:认真评估自托管,从离线任务开始试,别一上来切主链路
- 合规敏感:自托管 + 国内 LLM(如 DeepSeek / Qwen)
- 研究 / 实验:API 优先,快速迭代
不要盲目自托管,也不要盲目 API——数清账、选对路。
21.20 用量预测与容量规划
前面几节讲的是当前成本和容量怎么算,生产规划还要回答另一个问题:下季度、下一年的用量会怎样,成本多少,容量买多少。没有预测的结果只有两种——容量不足被挤爆,或者容量浪费花冤枉钱。
flowchart TB
classDef bad fill:#fdd,stroke:#c00
classDef ok fill:#dfd,stroke:#080
P[不做预测]:::bad --> P1[促销时容量不够]:::bad
P --> P2[低谷时过度采购]:::bad
P --> P3[预算预估常 off ±50%]:::bad
G[做预测]:::ok
G --> G1[提前扩容]:::ok
G --> G2[spot 实例省钱]:::ok
G --> G3[预算准确 ±10%]:::ok
预测的价值可以概括成一句:把不可预测的 cost spike 变成可控的 cost curve。
三种预测方法
基于历史趋势最直接,拿过去三到六个月的 QPS 和成本数据做时间序列外推,适合增长比较平稳的项目:
from prophet import Prophet
df = historical_qps_data # Prophet 要求两列:ds(时间戳)、y(QPS)
model = Prophet(seasonality_mode='multiplicative')
model.fit(df)
future = model.make_future_dataframe(periods=90) # 预测 90 天
forecast = model.predict(future)
周期性模式要单独识别,因为它决定的不是均值而是「什么时候会高」:企业 B2B 产品工作日与周末、工作时段与凌晨的流量差距都很大,双十一、黑五这类促销的峰值又远高于日常——具体倍数要看自己历史流量的日/周曲线,财报季和招聘季对特定行业也有明显影响。
事件驱动的部分则完全算不出来——产品发布带来的用量跃升、营销活动导致的 QPS 陡增、签下大客户后单一租户翻倍、竞品出故障导致流量转移。这些只能靠产品、市场、销售提前告诉你,所以预测本身必须是跨团队的动作,不是工程闷头跑模型。
预测有误差是常态,主要来自四处:用户增长常常不是线性而是 S 曲线;用量分布会变(原来每天 5 条 query 的用户变成 20 条);上线新 feature 会改变成本结构;市场和竞品事件本就不可预测。所以关键不是追求准,而是定期校准——周度比对实际与预测(差距超 20% 就调整)、月度更新下月预测和容量规划、季度和财务对齐预算、年度做长期战略对齐。每次 review 都要用最新数据重训模型,而不是拿半年前的预测当准。
从预测到容量
有了未来 90 天的 QPS 轨迹,容量要分段配而不是一刀切:
目标 SLA: P99 < 2s、99.9% 可用
预测 QPS 范围:
P50: 50 QPS(日常)
P95: 150 QPS(高峰)
P99.9: 400 QPS(黑五级)
容量配置:
基础 baseline: 60 QPS 容量(稍高于 P50)
自动扩容上限: 200 QPS(覆盖 P95 + buffer)
burst 预案: 450 QPS(手动启用、应对极端)
这里有个容易踩的坑:K8s HPA 扩容不是瞬时的。从触发阈值到新 pod 起来并完成 warmup 是分钟量级(镜像拉取 + 进程启动 + §10.14 那段索引预热,各占多少要自己掐一次表),而这段时间里 QPS 还在涨,可能直接击穿。所以 buffer 必须领先于流量——HPA 阈值设 70% 而不是 90%、min replicas 按 baseline QPS 的 1.5 倍容量来配、预判到高峰时提前半小时手动扩。
采购策略同样分层:基础容量用 Reserved Instances 或年度承诺换低价,增长部分用 on-demand 保持灵活,峰值 burst 用 spot 或 preemptible 拿最低价。典型配比是 reserved 占大头、on-demand 次之、spot 少量兜峰值,具体比例按自己流量曲线的基线和峰值定——全 on-demand 明显更贵(折扣幅度查云厂商当期的预留实例页),全 reserved 则承担用不完的风险。
规划时还要把极端但可能发生的场景写进 runbook,而不是等出事再想:竞品挂一天导致流量涨 5 倍扛不扛得住、核心客户上线让单租户 QPS 翻倍、云厂商削减现有配额、以及 LLM 厂商大幅涨价或断供时的备用方案。
报预算:给范围,而不是一个数
预测数字带不确定性,对外沟通时要给区间:
2026 Q3 RAG 基础设施成本预测:
Best case: $120K(用量低 + 优化到位)
Base case: $180K(预期轨迹)
Worst case: $300K(用量暴涨 + 紧急扩容)
建议预留: $234K(Base × 1.3 = 180 × 1.3)
拍死一个数,最后实际差出一大截,损失的是后续所有预测的可信度。除了用量本身,还有几类完全不可控的因素要做敏感性分析——经济周期、行业趋势、以及云厂商和 LLM 厂商的涨价降价(如果单价涨 10%,总成本变多少)。
预算也不是工程一家的事:产品承诺 DAU 增长,销售提供新大客户的上线节奏,财务给出总盘子,法务说明合规带来的额外成本。工程做预测,拿去和这几方对齐,最终得到一致的预算承诺——各做各的数字、最后对不上,是典型的大公司病。
实际成本超出预测时要有标准流程,让每次异常都沉淀成经验:
flowchart LR
classDef step fill:#def,stroke:#06c
S1[成本异常告警]:::step --> S2[归因分析]:::step
S2 --> S3[定位具体原因]:::step
S3 --> S4[制定优化计划]:::step
S4 --> S5[A/B 上线]:::step
S5 --> S6[持续监控]:::step
最后是投入要匹配规模:QPS 不到 1 的项目凭经验就行,1–100 QPS 用 Prophet 一类的简单时间序列,100 QPS 以上值得配专门的工具和人,1000 QPS 以上才谈得上自研并与业务指标深度耦合。小项目上专业预测工具属于过度工程。
21.21 RAG 的能耗与可持续性:碳足迹的工程视角
成本控制讲的是钱,但 AI 系统的真实代价还包括能耗和碳排放。一次 LLM 调用的电耗明显高于一次传统网页搜索——公开的估算跨度很大(取决于模型规模、输出长度和硬件代次),但方向是确定的:生成式的每次调用都要跑一次模型推理,而搜索主要是查索引。规模一大,这个差距就变成了实打实的电费和排放。
这一节值得读的工程理由不是环保口号,而是它和整章其余部分高度重叠:减排措施就是降本措施(模型分层、缓存、prompt 压缩,下面每一条都指回本章前面的小节),量化方法相似,监控 dashboard 共用。区别只在视角——成本是钱,可持续是环境;两者并行考虑会互相强化。
驱动力也不只有理想主义:ESG 报告要求上市公司披露,B2B 客户开始在采购问卷里问碳排放,EU AI Act 已要求通用 AI 模型的提供方在技术文档里记录模型的能耗(附件 XI),而降耗本身直接降电费。
能耗从哪儿来,怎么算
典型的能耗分布和成本结构几乎一致——LLM 推理是绝对大头:
能耗排序(从大到小,占比自己测):
1. LLM 推理(GPU) ← 绝对大头
2. Embedding / Rerank(GPU)
3. 向量库(内存常驻 + 磁盘)
4. 网关 / 应用层 ← 通常可忽略
× 数据中心 PUE ← 在以上总和之上再乘一个系数
这个排序和 §21.5 的成本排序是同一个——这不是巧合:两者的分母都是 GPU 时间。所以成本 dashboard 上的组件拆分,乘上各自的能耗系数就是能耗拆分,不需要另起一套采集。
不要抄任何一份"每 token 多少 kWh"的系数——网上流传的数字彼此差着一两个数量级,因为它们背后的模型规模、硬件代次、batch 大小、输入输出比例完全不同,抄错一个系数结论就废了。要估就自己搭这条链路:
第 1 步:量单卡功率
自托管:直接读 GPU 的功耗计数(nvidia-smi 一类)
用 API:拿不到,只能用公开估算,并明确标注这是估算
第 2 步:量吞吐
在你的延迟 SLO 下压测出集群的聚合 token/s(§21.19 讲过怎么测)
第 3 步:得到每 token 能耗
每 token kWh = 集群功率(kW) / 吞吐(token/s) / 3600
第 4 步:叠数据中心开销
乘上机房的 PUE(现代数据中心通常在 1.1-1.5 之间,具体问你的云厂商或机房)
第 5 步:转碳
乘上所在区域电网的碳强度(kg CO2 / kWh,各国各区域差好几倍,
用当地电网公布的排放因子)
按这条链路算出来的数字才是你的数字。关键结论不依赖于系数的精确值:能耗和成本高度同源,所以下面五条减排措施和前面的降本措施几乎完全重合。
有两条结构性规律值得单独记住:
- input token 和 output token 的能耗不对等。prefill 是高度并行的,单位 token 便宜;decode 是逐 token 串行的,单位 token 贵得多。所以把 input 和 output 加起来乘同一个系数是错的——削减输出长度的减排效果通常好于削减输入长度
- 能耗随模型规模大致同比例上涨,而质量的边际收益递减。参数量翻十倍,推理能耗也大致跟着涨一个量级,质量却只涨一点。所以"换更大的模型"在能耗账上往往是最差的一笔投资——这正是 §21.7 那条成本/质量权衡的另一面
五条减排措施
好消息是前三条你多半已经在做了——它们就是本章前面的降本手段,减排是顺带的:
措施 1:模型分层(§21.7)。小模型的推理能耗比大模型低一个档,简单任务不必用大模型。降本和减排在这里完全同向。
措施 2:缓存(§21.3)。cache 命中几乎不耗电——这是唯一一条"减排幅度等于命中率"的措施,命中率提多少,那部分请求的能耗就归零。
措施 3:控制 token(§16.16 的动态 top-k、§16.17 的 prompt 压缩)。能耗随 token 数走,尤其是输出侧。
措施 4:选新一代硬件。新一代 GPU 的每 token 能效通常优于上一代,但具体优多少要在自己的模型上测——标称的算力提升不等于同比例的能效提升。自托管的好处是能自己选硬件代次,用 API 则由厂商决定。
措施 5:选对厂商与区域。同一家云的不同区域,碳强度能差几倍:
各家云厂商都公布了区域级的碳强度或可再生能源占比(AWS、GCP、Azure 都有各自的可持续性页面和区域碳数据),以官方公布的当期数据为准——电网结构和厂商的采购协议每年都在变,不要照抄任何一份区域清单。选区的一般规律是水电、风电、光伏资源丰富的区域碳强度更低。
自托管与用 API 在这件事上各有各的路径:API 厂商替你处理基础设施,有些会公开 carbon report;自托管则是完全自控,能挑最绿的电网。
报告、合规与 offset
对外披露是最低要求,一份 ESG 报告级的季度报告大致长这样:
RAG Service Carbon Report <季度>
================================
Total requests: <请求数>
Total kWh: <按上面五步链路算出的总电耗>
Total CO2 (kg): <总电耗 × 区域电网排放因子>
Breakdown(按组件):
- LLM inference: <占比> ← 一般是大头
- Embedding: <占比>
- Other: <占比>
Mitigation:
- Cache hit rate: <命中率>
- Renewable energy %: <所用区域的可再生占比>
- vs 上一季度: <每请求碳排的同比变化> ← 这一行才是重点
Offset purchased: <抵消量>
报告里最重要的一行是"每请求碳排的环比变化",不是总量——总量会随业务增长自然上升,只有单位指标才反映工程有没有在改善。
2025–2027 年的法规方向:EU AI Act 要求通用 AI 模型的提供方记录已知或估算的能耗(附件 XI);加州 SB 253 要求大企业披露温室气体排放(不专门针对 AI,但算力用电会计入);中国的双碳目标给科技公司持续的减排压力;GRI / SASB 则提供自愿报告框架。对上市公司而言,走在法规前面比被罚划算。
报告里那行 offset 需要一句提醒:买 carbon credit 抵消排放是有争议的做法。支持者认为市场机制确实能推动减排,反对者指出很多 offset 项目并不真实,等同绿色洗白。主流意见是先减排、剩余部分再 offset,不能只 offset 不减排。
投入产出上,这件事的账相当好算:量化能耗是一次性投入,优化措施与成本优化高度重合(等于顺带做了),季度报告的工作量也不大。收益是省下的电费、监管合规,以及潜在客户的 ESG 偏好。
边界:不要为减排牺牲业务
这一节最容易被读歪的地方是把减排当成单一目标。下面三条都是错的:
- 「一律用最小模型」→ 答错率上升,商业损失远大于省下的电费
- 「完全靠 cache」→ 用户体验变差
- 「限制使用次数」→ 用户流失
可持续要和业务质量平衡,目标不是减到 0,而是合理地减。
还有一个产品层面的选择:要不要把碳足迹展示给用户。
🌱 这条答案的环境足迹:
- 能耗:<按本节方法算出的每请求能耗>
- CO2: <上一行 × 区域电网排放因子>
(别在这种面向用户的展示里写"相当于 N 次网页搜索"之类的类比——那个倍数没有可核实的口径,写上去只会招来质疑。给绝对值,并说明它是怎么算的。)
这种展示能让用户对「用一次 AI 的代价」有概念,可能改变滥用习惯。但它不是普适的——消费级产品有发挥空间,企业 SaaS 基本没必要,在后者那里它只会变成噪音。
最后是落地的门槛问题。小团队常以为这是大公司才做得起的事,其实入门四件事几乎零成本:选绿色云区域(改个 region)、把 cache 命中率做上去(本来就要做)、小模型优先(成本驱动,本来就要做)、拉一个 dashboard 跟踪基本指标。RAG 工程师能贡献的也正是这几件——测量自己服务的能耗、优化时把能效和延迟成本一起看、把减排成果记录下来供合规与销售做 ESG 报告。
21.22 跨专栏关联:LLM 推理成本和 serving
《vLLM 推理内核深度解析》讨论过 PagedAttention、continuous batching 等技术——这些都是 LLM serving 侧的成本优化。RAG 侧的 prompt cache 和 serving 侧的 KV cache 有精神同构——都是"复用计算过的结果"。
对自托管 LLM 的 RAG 项目,学习 vLLM / TensorRT-LLM 等 serving 框架的优化技术直接降成本——比纯应用层优化收益大。
21.23 本章小结
- RAG 的三元约束(质量/延迟/成本)不可能全部最大,业务场景决定甜点
- prompt caching 是 2024 年后最大的单点杠杆——但省多少要按
(1-p) + p×d算,且只作用于 input,不是"账单降 90%" - 三层缓存:query / embedding / prompt cache 各司其职,收益依次是"跳过全链路 / 省推理 / 省 input token"
- 模型分层:不同环节用不同模型,降幅取决于非生成环节占了多少 token——先测占比
- 成本大头是 LLM 生成——优化顺序:prompt cache → 模型分层 → 缓存 → 批处理
- 延迟预算分阶段精打细算——先压大头(LLM),再压小头(context pack)
- 优化节奏:跑通 → 质量 → 降成本 → 降延迟——顺序错了 ROI 低
- 成本监控不可少——均值 + 长尾都要看
下一章是本专栏最后一章——从零构建一个生产级 RAG 系统——把前 21 章的所有机制汇总到一个完整项目落地。