📑 本页目录(点开跳转)
18 · 成本与容量
⏱ 42 分钟 | 💰 让它跑得起、也跑得久
🎯 一句话
一个效果好但成本翻三倍的模型,在业务上可能是负收益。 这一章讲怎么把「效果」和「成本」放在同一张表上比较 —— 以及在不损失效果的前提下,成本能砍到什么程度。
💵 一、先算清楚:一次预测到底多少钱
单次预测成本 = (机器成本 + 存储 + 网络) / 总请求数
⭐ 但真正该看的是【每个业务单位的成本】:
· 推荐:每千次曝光的推理成本
· 风控:每笔审批的成本
· 客服:每次对话的成本
→ 只有这样才能和【它带来的收益】直接比较
一张该有的表:
| 方案 | 效果 | 单位成本 | 净收益 |
|---|---|---|---|
| 旧模型 | CTR 3.0% | 1.0× | 基准 |
| 新模型 A | CTR 3.3%(+10%) | 1.2× | ✅ 划算 |
| 新模型 B | CTR 3.4%(+13%) | 4.0× | ⚠️ 看情况 |
⭐ 关键问题不是「哪个效果好」,而是「多花的钱换回的收益够不够」。 B 比 A 多 3% 的效果,但成本是 3.3 倍 —— 只有当那 3% 值很多钱时才划算。这个判断必须用业务价值做,不能只看指标。
📉 二、成本从哪来(按占比排)
典型的在线推理服务:
① 计算资源(CPU/GPU) ~50-70% ⭐ 主战场
② 特征获取与存储 ~15-30% ⭐ 常被低估
③ 网络传输 ~5-10%
④ 日志与监控 ~5-10%
⭐ 第 ② 项经常被低估: 很多系统的瓶颈不在模型推理,而在从各处拉特征(Redis、HBase、实时计算)。 优化前先 profile,别默认瓶颈在模型上。
✂️ 三、降本的手段(按性价比排)
① 先确认瓶颈在哪 ⭐
把一次请求的耗时拆开:
特征获取 ___ms | 模型推理 ___ms | 后处理 ___ms | 网络 ___ms
⭐ 很多团队花几周优化模型,
结果发现 70% 的时间花在拉特征上
② 缓存 ⭐⭐ —— 通常是性价比最高的
哪些能缓存:
· 用户侧特征(变化慢)→ 缓存几分钟到几小时 ⭐
· 物品侧特征/向量 → 缓存到失效为止
· 整条预测结果 → 只在输入完全相同时(如同一用户短时间重复请求)
⚠️ 缓存的代价是【时效性】:
缓存 1 小时 = 用户 1 小时前的行为才生效
→ 时效性敏感的场景要谨慎(如新闻推荐)
缓存对成本的影响是线性的,很好算:
命中率 h,缓存本身成本近似 0,未命中成本 1×
→ 单位成本 ≈ (1 − h) × 1×
h = 50% → 0.50×
h = 80% → 0.20× ⭐ 命中率从 50% 提到 80%,成本砍掉 60%
h = 95% → 0.05×
💀 但「命中率」这个数字最容易被算错: 必须看按请求加权的命中率,不是按 key 加权的。
热门 key:命中率 99%,占 40% 的请求
长尾 key:命中率 5%,占 60% 的请求
按 key 平均看:"命中率挺高的"
按请求加权:0.4×99% + 0.6×5% = 42.6% 💀
⭐ 看板上的命中率一定要标清口径,否则你会以为缓存很有效,实际上没有。
③ 模型侧优化
| 手段 | 效果 | 代价 |
|---|---|---|
| 量化(INT8)⭐ | 2–4× 提速,显存减半 | 精度损失通常很小 |
| 蒸馏 | 小模型逼近大模型 | 要重新训练 |
| 剪枝 | 减少参数 | 收益不如量化稳定 |
| 批处理 ⭐ | 吞吐大幅提升 | 增加延迟 |
🔗 具体怎么做在AI 基础设施那套里; 这一章关心的是「什么时候值得做」。
⚠️ 量化后必须重新评估效果: 不能只看 PPL 或整体准确率,要看你真正关心的那个指标,以及分人群的表现 —— 量化的损失常常集中在长尾样本上。
④ 分级:不是所有请求都值得用大模型 ⭐⭐
⭐ 这是降本里最被低估的一招:
简单请求 → 小模型/规则 (占 80%,成本 0.1×)
复杂请求 → 大模型 (占 20%,成本 1×)
→ 总成本降到 0.28×,而效果几乎不变
怎么判断"简单":
· 小模型的置信度高 → 直接用它的结果
· 置信度低 → 升级到大模型
💡 这和全景导论「模型选型」的分级路由是同一个思想, 也和智能体工程的 effort 分级同源: 按任务难度匹配资源,而不是一刀切用最强的。
⑤ 如果是 LLM 服务,成本结构完全不同 ⭐
传统模型:成本 ∝ 【请求数】
LLM :成本 ∝ 【输入 token + 输出 token】
且【输出 token 通常比输入贵好几倍】 ⭐
→ 一个 8000 token 系统提示词 + 20 token 问题的请求,
成本几乎全在那 8000 token 上
所以降本手段的优先级也完全不同:
| 手段 | 效果 | 备注 |
|---|---|---|
限制 max_tokens ⭐⭐ |
常常是最有效的一招 | 让模型「简洁回答」比换小模型容易得多 |
| 前缀缓存 / KV Cache 复用 ⭐ | 长系统提示词场景可降 80%+ | AI基础设施 16 |
| 精简系统提示词 | 直接按比例省 | 但要重新跑评测确认没掉效果 |
| 分级路由 | 简单问题走小模型 | 和上面的 ④ 是同一招 |
| 连续批处理 | 吞吐提升 | AI基础设施 17 |
| 量化 | 显存和延迟 | AI基础设施 18 |
⭐ 顺序很重要:前两条改配置就能做,最后两条要动基础设施。 很多团队一上来就去研究量化,却没试过把
max_tokens从 4096 调到 512。
📈 四、容量规划
要回答三个问题:
① 现在的峰值 QPS 是多少?(不是平均值 ⭐)
② 单机能扛多少 QPS?(压测,不是估算)
③ 留多少余量?
⭐ 经验:按【峰值的 1.5–2 倍】准备容量
因为大促、突发热点、上游重试都会放大流量
实例数不用拍脑袋,一个公式就能算(利特尔法则):
import math
def plan_capacity(qps_peak, avg_latency_ms, concurrency_per_pod, headroom=1.8):
"""利特尔法则:系统内同时在处理的请求数 = 到达率 × 停留时间
concurrency_per_pod: 【压测出来的】单实例并发上限,不是拍脑袋 ⭐"""
need_conc = qps_peak * (avg_latency_ms / 1000) # ⭐ 峰值时刻的并发量
pods = math.ceil(need_conc / concurrency_per_pod * headroom)
return {"峰值并发": round(need_conc, 1), "需要实例数": pods,
"每实例承担QPS": round(qps_peak / pods, 1)}
print(plan_capacity(qps_peak=4000, avg_latency_ms=60, concurrency_per_pod=32))
# {'峰值并发': 240.0, '需要实例数': 14, '每实例承担QPS': 285.7}
⚠️ 三个必须注意的地方,每一个都能让这个估算错一倍:
注意 说明 用平均耗时,不是 P99 利特尔法则算的是平均值;用 P99 会严重高估 耗时会随负载上升 ⭐⭐ 压测必须压到目标 QPS 附近再读耗时;低压下测出来的 60ms,满负载可能是 140ms concurrency_per_pod要压测线程池大小 ≠ 有效并发;超过某个点后加并发只会加延迟,不加吞吐
⚠️ 三个容易被忽略的容量杀手
| 杀手 | 说明 |
|---|---|
| 重试风暴 ⭐ | 服务变慢 → 上游重试 → 流量翻倍 → 更慢 → 雪崩 💀 |
| 缓存击穿 | 缓存集体失效瞬间,请求全打到后端 |
| 特征服务是共享的 | 你的模型扩容了,但特征服务没有 → 瓶颈转移 |
⭐ 重试风暴是最危险的,因为它是正反馈: 越慢越重试,越重试越慢。 对策:上游要有超时 + 退避重试 + 熔断,别无脑重试。
💀 一次 3 秒的抖动,变成 22 分钟的全站不可用
配置(都很"合理",这正是可怕的地方):
· 推荐服务 P99 平时 80ms
· 上游调用超时设 500ms —— 比 P99 高很多,看起来很宽容
· 失败重试 3 次,【无退避】 —— 「反正只是重试嘛」
· 没有熔断
| 时刻 | 发生了什么 | QPS |
|---|---|---|
| 14:32:10 | 特征存储某个分片 GC,P99 从 80ms → 620ms(超过 500ms 超时线) | 4,000 |
| 14:32:13 | GC 结束了 —— 但上游已经开始重试 | 11,000 |
| 14:32:18 | 线程池打满,P99 → 2s,成功率跌到 40% | 18,000 |
| 14:32:41 | 服务基本不可用,推荐位全站空白,降级到热门榜 | 22,000 |
| 14:54:00 | 运维强行把入口限流到 3,000 QPS,队列排空后恢复 | — |
💀 这个事故最该记住的一句话: 「触发原因」和「持续原因」是两回事。 触发它的是3 秒的 GC,让它持续 22 分钟的是重试流量自己。 修好 GC 一点用都没有 —— 要修的是重试。
修完之后的对照:
| 修之前 | 修之后 | |
|---|---|---|
| 重试策略 | 3 次,无退避 | 指数退避 + 随机抖动,最多 1 次 |
| 超时 | 500ms(P99 的 6 倍) | 150ms(P99 的 2 倍)⭐ |
| 熔断 | 无 | 错误率 > 20% 持续 5 秒即熔断,半开探测 |
| 故障时的流量放大 | ×4~5 💀 | ×1.2 |
| 同样的 3 秒 GC 造成的影响 | 22 分钟不可用 | 3 秒的错误率抬升 ⭐ |
⭐ 超时设太宽是个反直觉的坑: 超时时间设成 P99 的 6 倍,等于「让每个慢请求多占用 6 倍的线程」 —— 过载时它会更快地把线程池吃光。 经验值:超时设成 P99 的 1.5–2 倍,并且配降级路径, 让超时的请求快速走兜底,而不是干等(第 3 章)。
💡 随机抖动(jitter)也不是可选项:没有抖动的指数退避会让 所有客户端在同一时刻一起重试,形成一波一波的脉冲。
🧮 五、一个务实的成本优化顺序
① profile,找到真正的瓶颈 ← 别跳过 ⭐
② 加缓存(改动小、收益大)
③ 请求分级(大部分请求用小模型) ⭐⭐
④ 批处理 / 并发调优
⑤ 模型量化
⑥ 蒸馏出小模型
⑦ 换更便宜的硬件 / 调整实例规格
⭐ 顺序很重要:①②③ 通常能拿到 70% 的收益,
而 ⑤⑥ 的工作量是它们的好几倍
💡 六、别忘了「不做」也是一个选项
⭐ 在优化成本之前,先问:
· 这个模型带来的收益,值得它的成本吗?
· 有没有一部分流量【根本不需要】模型?
(比如:新用户没有任何行为数据时,模型和热门榜差别很小)
· 能不能降低调用频率?
(比如:从每次请求都算,改成每天算一次存起来)⭐
→ 有时候最大的成本优化,是【减少调用】而不是【加快调用】
🧭 七、症状 → 该做什么(决策表)
| 症状 | 该做的 | ❌ 别做的 |
|---|---|---|
| profile 显示特征获取占 70% 耗时 | 加缓存、批量拉取、合并 RPC | 别去量化模型 —— 优化了 30% 里的一小部分 |
| GPU 利用率低但延迟高 | 批处理 / 连续批处理 | 别加机器 —— 机器已经在闲着了 |
| 峰谷比 5:1 | 弹性扩缩容 + 队列削峰 | 别按峰值买固定资源 |
| 长尾请求拖垮 P99 | 超时 + 降级兜底 ⭐ | 别整体扩容 —— 扩了还是有长尾 |
| 成本集中在 LLM 输出 token | 先限 max_tokens、要求简洁输出 |
别一上来就换小模型或做量化 ⭐ |
| 80% 请求都很简单 | 请求分级(小模型 + 置信度兜底) | 别一刀切用最强的 |
| 同一批结果被反复请求 | 缓存整条预测结果 | 别优化模型 —— 本来就不该算第二次 |
| 模型收益本来就不确定 | 先做第 15 章的 holdback,量出真实收益 | 别急着优化一个可能不该存在的东西 ⭐⭐ |
⚠️ 八、一张坑表
| 坑 | 后果 | 怎么修 |
|---|---|---|
| 不 profile 就开始优化 ⭐ | 花几周优化模型,而 70% 时间在拉特征 | 先 profile,这是唯一不能跳的一步 |
| 超时设成 P99 的 6 倍 💀 | 过载时每个慢请求多占 6 倍线程,加速雪崩 | 超时设 P99 的 1.5–2 倍 + 降级路径 |
| 重试无退避、无抖动、无熔断 💀 | 3 秒抖动变成 22 分钟不可用 | 指数退避 + 抖动 + 熔断,重试次数 ≤ 1 |
| 按平均 QPS 规划容量 | 峰值直接打挂 | 按峰值 × 1.5–2 |
| 低压下压测出来的耗时 ⭐ | 容量估算错一倍 | 压到目标 QPS 附近再读耗时 |
| 缓存命中率按 key 平均 | 以为 90%,实际按请求加权只有 42% | 看板标清口径:按请求加权 |
| 量化后只看整体指标 | 长尾人群悄悄崩掉 | 分人群重新评估(第 19 章) |
| 模型扩容了,特征服务没扩 | 瓶颈转移,白扩 | 容量规划要覆盖整条链路 |
| 只优化成本,不问收益 | 精心优化了一个负收益的模型 | 先算单位业务成本 vs 单位业务收益 ⭐ |
🔗 九、和站内其他章的关系
| 相关的地方 | 和这一章的关系 |
|---|---|
| AI基础设施 | 量化、批处理的具体做法 |
| 全景导论 06推理优化 | LLM 场景的降本 |
| 智能体工程 02 effort 分级 | 请求分级的同源思想 |
| 推荐算法 06漏斗 | 分级思想的推荐版 |
✅ 检查点
- 该看「单次预测成本」还是别的?为什么?成本构成里哪一项常被低估?
- 优化前必须先做什么?为什么?
- 缓存的代价是什么?命中率 80% 时成本降到多少?为什么说「命中率」这个数最容易被算错?
- 「请求分级」怎么做?为什么说它被低估?举那个数字。
- LLM 服务的成本结构和传统模型有什么根本不同?降本手段的优先级该怎么排?
- 怎么算需要多少实例?这个估算里有哪三个容易出错的地方?
- 用那个 22 分钟的雪崩案例说明重试风暴。「触发原因」和「持续原因」分别是什么?
- 为什么说「超时设太宽」是个坑?经验值是多少?为什么退避一定要加随机抖动?
- 成本优化顺序里,前三步能拿到多少收益?「不做」这个选项指什么?
👀 答案
- 该看每个业务单位的成本(每千次曝光、每笔审批、每次对话)。因为只有这样才能和它带来的收益直接比较。常被低估的是特征获取与存储(约 15-30%)——很多系统瓶颈不在模型推理而在从各处拉特征。
- profile 找到真正的瓶颈。因为很多团队花几周优化模型,结果 70% 的时间花在拉特征上。
- 代价是时效性——缓存 1 小时意味着用户 1 小时前的行为才生效,时效性敏感的场景(如新闻推荐)要谨慎。命中率 80% 时单位成本 ≈ (1−0.8) = 0.20×(从 50% 提到 80%,成本砍掉 60%)。命中率容易算错,是因为必须看按请求加权而不是按 key 加权:热门 key 命中 99% 占 40% 请求、长尾 key 命中 5% 占 60% 请求,按请求加权只有 42.6%,而按 key 平均看会觉得"命中率挺高"。
- 小模型的置信度高就直接用它的结果,低则升级到大模型。简单请求 80% 用 0.1× 成本,复杂 20% 用 1×,总成本降到 0.28× 而效果几乎不变。
- 传统模型成本正比于请求数,LLM 成本正比于输入 token + 输出 token,而且输出通常比输入贵好几倍——一个 8000 token 系统提示词 + 20 token 问题的请求,成本几乎全在提示词上。优先级:①限制
max_tokens/ 要求简洁输出(常常最有效)②前缀缓存 / KV Cache 复用 ③精简系统提示词 ④分级路由 ⑤连续批处理 ⑥量化。前两条改配置就能做,后面要动基础设施;很多团队一上来研究量化,却没试过把 max_tokens 从 4096 调到 512。 - 用利特尔法则:峰值并发 = 峰值 QPS × 平均耗时,实例数 = 峰值并发 ÷ 单实例并发上限 × 余量(1.5–2)。三个易错点:要用平均耗时不是 P99;耗时会随负载上升,压测必须压到目标 QPS 附近再读数(低压下的 60ms 满负载可能是 140ms);单实例并发上限必须压测得来,线程池大小不等于有效并发,超过某个点后加并发只会加延迟不加吞吐。
- 配置都"很合理":超时 500ms、重试 3 次无退避、无熔断。特征存储某分片 GC 让 P99 从 80ms 涨到 620ms 超过超时线 → 上游重试把 QPS 从 4,000 推到 11,000 → 线程池打满、P99 变 2s → 更多重试 → 22,000 QPS 服务不可用,22 分钟推荐位全站空白。触发原因是那 3 秒的 GC,持续原因是重试流量自己——修好 GC 一点用都没有,要修的是重试。修完后同样的 GC 只造成 3 秒的错误率抬升,故障时流量放大从 ×4~5 降到 ×1.2。
- 因为超时设成 P99 的 6 倍,等于让每个慢请求多占用 6 倍的线程,过载时会更快把线程池吃光。经验值是 P99 的 1.5–2 倍,并且要配降级路径让超时请求快速走兜底而不是干等。抖动是必需的,因为没有抖动的指数退避会让所有客户端在同一时刻一起重试,形成一波一波的脉冲。
- 前三步(profile、缓存、请求分级)通常能拿到 70% 的收益,而量化蒸馏的工作量是它们的好几倍。「不做」指:先问这个模型的收益值不值得成本、有没有流量根本不需要模型、能不能降低调用频率(每次算改成每天算一次存起来)——有时最大的优化是减少调用而不是加快调用。
🛑 可以停在这里
⚡ 走神救援
⭐该看「每个业务单位的成本」(每千次曝光/每笔审批),因为只有这样才能和收益直接比;关键问题不是"哪个效果好"而是"多花的钱换回的收益够不够"。成本构成里⭐特征获取与存储(15-30%)常被低估——很多系统瓶颈不在推理在拉特征。降本按性价比:①⭐先 profile 找瓶颈(别跳过,很多人花几周优化模型结果70%时间在拉特征)②⭐⭐缓存(代价是时效性,新闻推荐要谨慎)③⭐⭐请求分级(简单请求80%用0.1×成本、复杂20%用1× → 总成本降到0.28×而效果几乎不变,靠小模型置信度决定要不要升级)④批处理 ⑤量化(⚠️必须重新评估,损失常集中在长尾样本)⑥蒸馏。⭐前三步能拿70%收益,而⑤⑥工作量是好几倍。容量按峰值1.5-2倍(看峰值不是平均);三个杀手:⭐重试风暴(最危险,正反馈:越慢越重试越重试越慢→雪崩,要超时+退避+熔断)、缓存击穿、特征服务共享导致瓶颈转移。⭐别忘了"不做":有时最大的优化是减少调用(每次算改成每天算一次)而不是加快调用。⭐缓存命中率最容易被算错:必须按请求加权——热门 key 命中 99% 占 40% 请求、长尾命中 5% 占 60% 请求,真实命中率只有 42.6%,而按 key 平均会以为很高。⭐LLM 的成本结构完全不同(正比于 token 而非请求数,且输出比输入贵得多)→ 先限
max_tokens、再用前缀缓存,这两条改配置就能做;很多团队一上来研究量化,却没试过把 max_tokens 从 4096 调到 512。⭐实例数用利特尔法则算:峰值并发 = 峰值QPS × 平均耗时(不是P99),除以压测出来的单实例并发上限,再乘 1.5–2 余量;⚠️耗时会随负载上升,低压下测的 60ms 满负载可能是 140ms。💀雪崩案例:超时 500ms、重试 3 次无退避、无熔断 —— 特征存储 3 秒的 GC 让 P99 冲到 620ms,重试把 QPS 从 4,000 推到 22,000,全站推荐位空白 22 分钟。⭐⭐该记住的一句:「触发原因」和「持续原因」是两回事——触发的是 3 秒 GC,让它持续 22 分钟的是重试流量自己,修好 GC 一点用都没有。修法:超时改成 P99 的 1.5–2 倍(设成 6 倍等于让每个慢请求多占 6 倍线程,加速雪崩)、指数退避 + 随机抖动(没抖动会让所有客户端同时重试形成脉冲)、熔断、重试次数降到 1 —— 之后同样的 GC 只造成 3 秒的错误率抬升。
下一节 👉 19-合规审计与模型卡.md