🏠 总目录📚 本教程 18 · 成本与容量
📑 本页目录(点开跳转)

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漏斗 分级思想的推荐版

✅ 检查点

  1. 该看「单次预测成本」还是别的?为什么?成本构成里哪一项常被低估?
  2. 优化前必须先做什么?为什么?
  3. 缓存的代价是什么?命中率 80% 时成本降到多少?为什么说「命中率」这个数最容易被算错?
  4. 「请求分级」怎么做?为什么说它被低估?举那个数字。
  5. LLM 服务的成本结构和传统模型有什么根本不同?降本手段的优先级该怎么排?
  6. 怎么算需要多少实例?这个估算里有哪三个容易出错的地方?
  7. 用那个 22 分钟的雪崩案例说明重试风暴。「触发原因」和「持续原因」分别是什么?
  8. 为什么说「超时设太宽」是个坑?经验值是多少?为什么退避一定要加随机抖动?
  9. 成本优化顺序里,前三步能拿到多少收益?「不做」这个选项指什么?
👀 答案
  1. 该看每个业务单位的成本(每千次曝光、每笔审批、每次对话)。因为只有这样才能和它带来的收益直接比较。常被低估的是特征获取与存储(约 15-30%)——很多系统瓶颈不在模型推理而在从各处拉特征。
  2. profile 找到真正的瓶颈。因为很多团队花几周优化模型,结果 70% 的时间花在拉特征上。
  3. 代价是时效性——缓存 1 小时意味着用户 1 小时前的行为才生效,时效性敏感的场景(如新闻推荐)要谨慎。命中率 80% 时单位成本 ≈ (1−0.8) = 0.20×(从 50% 提到 80%,成本砍掉 60%)。命中率容易算错,是因为必须看按请求加权而不是按 key 加权:热门 key 命中 99% 占 40% 请求、长尾 key 命中 5% 占 60% 请求,按请求加权只有 42.6%,而按 key 平均看会觉得"命中率挺高"。
  4. 小模型的置信度高就直接用它的结果,低则升级到大模型。简单请求 80% 用 0.1× 成本,复杂 20% 用 1×,总成本降到 0.28× 而效果几乎不变。
  5. 传统模型成本正比于请求数,LLM 成本正比于输入 token + 输出 token,而且输出通常比输入贵好几倍——一个 8000 token 系统提示词 + 20 token 问题的请求,成本几乎全在提示词上。优先级:①限制 max_tokens / 要求简洁输出(常常最有效)②前缀缓存 / KV Cache 复用 ③精简系统提示词 ④分级路由 ⑤连续批处理 ⑥量化。前两条改配置就能做,后面要动基础设施;很多团队一上来研究量化,却没试过把 max_tokens 从 4096 调到 512
  6. 利特尔法则:峰值并发 = 峰值 QPS × 平均耗时,实例数 = 峰值并发 ÷ 单实例并发上限 × 余量(1.5–2)。三个易错点:要用平均耗时不是 P99耗时会随负载上升,压测必须压到目标 QPS 附近再读数(低压下的 60ms 满负载可能是 140ms);单实例并发上限必须压测得来,线程池大小不等于有效并发,超过某个点后加并发只会加延迟不加吞吐。
  7. 配置都"很合理":超时 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。
  8. 因为超时设成 P99 的 6 倍,等于让每个慢请求多占用 6 倍的线程,过载时会更快把线程池吃光。经验值是 P99 的 1.5–2 倍,并且要配降级路径让超时请求快速走兜底而不是干等。抖动是必需的,因为没有抖动的指数退避会让所有客户端在同一时刻一起重试,形成一波一波的脉冲。
  9. 前三步(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

打卡记录保存在你的浏览器里,首页能看到总进度