✏️ LLM 定价的数学原理 02:把推理过程写成方程
上一篇咱们「找找感觉」,理清了 LLM 推理在硬件层面到底是怎么回事。这一篇,咱们正式拿起笔,把那些直觉翻译成方程——看完你会发现,现代大模型推理服务的最佳 Batch 为什么稳稳落在「几千」这个量级、长对话又怎么把这个最佳值往大里推。这些数字不是工程师拍脑袋的经验值,是从硬件参数里直接算出来的。
前情提要:上一篇 大模型推理是怎么回事——从「搬东西 vs 算东西」说起 我们建立了核心直觉——推理耗时 = 搬东西耗时 + 算东西耗时,谁慢听谁的;Batch 能摊销搬参数的成本,但摊不掉 KV cache。
一、给「搬」和「算」起名字
上一篇反复说的两件事,咱们先给它们各自起个名字:
- 搬东西所花的时间 → 叫它
T_memory(内存时间) - 算东西所花的时间 → 叫它
T_compute(计算时间)
那么生成一个 token 的实际耗时是多少?
是 max,不是加和。这个细节非常重要,第六节会专门讲。先记住这个结论:两件事谁慢听谁的。
🔑 这就是 roofline 分析。 名字听起来很学术,本质就是上面这一行公式。整个推理性能分析,都从这一行公式起步。
二、把 T_compute 写出来
回想一下:算的工作量,除以工人速度,就是算的时间。
工人速度
GPU 每秒能做多少次乘法?这个数叫 FLOPS(每秒浮点运算数)。H100 大约是 2 × 10¹⁵,也就是每秒 2000 万亿次乘法。这是一个硬件常数——买回来就定了,软件改不了。
工作量
要算多少次乘法?取决于两件事:
- batch size(同时服务多少用户):每多一个用户,多一份计算。记作
B。 - 每个用户每个 token 要做多少乘法:正比于「模型有多少参数实际参与运算」。
第二个变量需要展开一下,因为它牵出了一个关键概念。
一个新概念:激活参数 vs 总参数
现代大模型大多是 MoE(混合专家) 架构:虽然总共有 700B 参数,但每生成一个 token,只有一小部分参数真的参与运算。
打个比方:你家有一本 700 页的菜谱,但今晚只做番茄炒蛋,只翻其中 37 页。
- 总参数
N_total:菜谱总共多少页(700 页) - 激活参数
N_active:今晚实际翻了多少页(37 页)
比如 DeepSeek V3:N_total = 671B,N_active = 37B。
严格说一句:MoE 的稀疏激活只发生在 FFN(前馈网络)这一部分——attention 层的 Q/K/V/O 投影对每个 token 都是全量参与的。所以 N_active 不是「整模型按比例选一份」,而是「attention 全量 + FFN 里被路由选中的那几个专家」之和。DeepSeek V3 公布的 37B 已经是这个总和。
🔑 为什么要分这两个?因为它们对应不同的成本:
- 算的成本 跟
N_active有关(只算翻开的那几页)- 搬的成本 跟
N_total有关(整本菜谱都要在显存里待着,而且每次还要把它读一遍)
这就是 MoE 模型巧妙的地方——用稀疏激活换更省力的计算,但搬运的代价并没有省。这一点会在第五节变得至关重要。
写出 T_compute
每个 token 要做大约 2 × N_active 次浮点运算(每个权重对应一次乘 + 一次加,按 FLOP 计数算 2 次),所以 batch=B 时:
其中 C = FLOPS / 2,可以理解为「工人有效速度」。
🔑 直觉:用户越多算的越多;激活参数越多每人要算的越多;工人越快时间越短。
三、把 T_memory 写出来
上一篇说过:搬两样东西 —— 模型参数 + KV cache。
搬参数
不管 Batch 多大,整本菜谱都要被完整搬运一遍(每个 token 都用到所有层)。
- 要搬多少:
N_total - 传送带速度:memory bandwidth,记作
BW
H100 的 BW ≈ 3 TB/s,也是硬件常数。
注意:这一项里没有 B。 不管多少用户,搬参数的时间都一样——这就是 Batch 能摊销参数搬运的根本原因。
搬 KV cache
每个用户都有自己的 KV「私人笔记」,摊不掉。
- 每个 token 在 KV cache 里占多少字节:记作
b(小写 b,别和 Batch 的大写 B 搞混)。它实际上是2 × 层数 × KV 头数 × head_dim × 元素字节数的乘积——一个 token 在每一层都要存一份 K 和 V。对一个典型大模型(数十层、几十个 KV 头),b通常在几十到几百 KB / token 这个量级。数值精度越低(FP16 → FP8)、KV 头共享越激进(GQA / MLA),b越小 - 每个用户的对话长度:记作
L(context length) - B 个用户的 KV 总大小:正比于
B × L × b
这一项有 B——Batch 越大要搬的 KV 越多,摊不掉。这个差别决定了一切。
把搬的部分合起来
这里是加和,因为两件事用的是同一条传送带——同一条传送带不能同时搬两样东西。这一点很关键,第六节再展开。
四、一张图看懂全部
现在我们有两个时间随 batch size 变化的函数。横轴是 B,纵轴是时间。
T_compute:从零点出发的直线
T_compute
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱
└───────────► B
0T_memory:从一个高度起步的直线
T_memory
│ ╱
│ ╱
│ ╱ ← 斜率 = L × b / BW(KV 部分)
│ ╱
│ ╱
│ ╱
│ ╱
│╱ ← y 轴截距 = N_total / BW(参数部分)
│
└───────────► B
0实际耗时:取上面那条
两条线画在一起,T 是它们的 max——哪条在上面,听哪条的:
时间
│ ╱ ← T_compute (B × N_active/C)
│ ╱ 从零点出发
│ ╱
│ ╱
│ ╱ ╱ ← T_memory (N_total/BW + B×L×b/BW)
│ ╱ ╱ 与 y 轴交于 N_total/BW
│ ╱╱
│ ╳ ← 交叉点 = 甜蜜点
│ ╱╱
│ ╱ ╱
│ ╱ ╱
│ ╱ ╱
│ ╱
│ ╱
│ ╱
└──────────────────────► B
交叉点三个区域的物理意义
| 区域 | 谁主导 | 状态 | 意义 |
|---|---|---|---|
| 左边(B 小) | T_memory | memory-bound | 传送带在等工人 → 加 Batch 是免费午餐,延迟几乎不变 |
| 交叉点 | T_memory = T_compute | 完美平衡 | 传送带和工人速度匹配,效率最高 |
| 右边(B 大) | T_compute | compute-bound | 工人忙不过来 → 再加 Batch 没用,延迟开始线性上涨 |
🔑 优化推理的核心目标,就是把系统钉在交叉点附近。 这就是为什么所有推理服务都要做 batching、continuous batching、动态调度等等——都是为了让 B 落在那个甜蜜点。
五、解出甜蜜点:神秘的 300 是怎么来的
甜蜜点就是两条线交叉处,即 T_memory = T_compute。
为了让代数清爽,先简化一步:假设上下文不太长,KV 那部分相比参数那部分小到可以忽略(这个假设很快会回头检查)。也就是说:
让两边相等:
解出 B:
🔑 妙就妙在这里:这个公式恰好分成了两半——左边只跟模型有关,右边只跟硬件有关。两件事互不干扰。
模型那一项:稀疏度的倒数
N_total / N_active 就是「整本菜谱 / 今晚翻的页数」——也就是稀疏度的倒数。
- 稠密模型(Dense):所有参数都用,
N_total = N_active,比值 = 1 - DeepSeek V3:
671B / 37B≈ 18 - 一些更稀疏的 MoE:可以到 30+
硬件那一项:神秘的 300
C / BW:把工人速度除以传送带速度。代入 H100 的数字(C ≈ 10¹⁵,BW ≈ 3 × 10¹²),单位换算后:
这就是工程师们口中那个神秘的 300。它是当代 GPU 的物理常数——不管 A100、H100、B100,这个比值都在 200 ~ 400 之间。
🔑 为什么这个比值这么大? 因为过去十年里,GPU 的算力涨得比内存带宽快得多。算力翻了几十倍,带宽只翻了几倍——比值就拉大到了 300 这个量级。这正是「现代 GPU memory-bound」的物理根源。
最终结果
综上,我们会获得甜蜜点公式(KV 可忽略时):
左边是硬件常数(≈ 300),右边是模型稀疏度倒数。
代入 DeepSeek V3:
这就是为什么大厂的推理服务都把 Batch 配在「几千」这个量级——这不是工程师拍脑袋的经验值,是从硬件参数算出来的物理结论。
六、两个容易绊倒人的细节
这套公式里有两个地方很反直觉,但理解之后会通。
细节 1:为什么 T = max(搬, 算),不是 T = 搬 + 算?
答案藏在 GPU 设计的核心思想里——
🔑 GPU 里搬运电路和计算电路是两套独立硬件,可以并行工作。
打个比方:工厂里传送带工人(搬运电路)和装配工人(计算电路)是两组不同的人,可以同时干活——传送带在搬下一批材料的时候,装配工正在加工上一批。
情况 A:搬运慢、计算快(memory-bound)
搬运: ████████████████████ (20ms)
计算: ████ (4ms,然后干等)
总时间: 20ms ← 听搬运的
情况 B:搬运快、计算慢(compute-bound)
搬运: ████ (4ms,然后干等)
计算: ████████████████████ (20ms)
总时间: 20ms ← 听计算的两个工人重叠工作而不是接力工作,所以是 max,不是加和。所有现代 GPU/TPU/AI 加速器都做了这种重叠设计——这正是它们快的核心原因之一。
细节 2:为什么 T_memory 内部又是加和?
你可能立刻发现矛盾:上面说 T = max,那 T_memory = T_搬参数 + T_搬KV 为什么不也写成 max?
答案很简单:
🔑 判断准则:用同一个资源 → 加和;用不同资源 → max。
- T_memory 内部(搬参数 + 搬 KV):用同一条传送带(HBM 带宽),不能并行 → 加和
- T_memory vs T_compute:用两套不同硬件(传送带 vs 工人),可以并行 → max
搞清楚这个准则,整个推理性能分析的逻辑就稳了。
七、回头检查:长上下文怎么办?
第五节有一个偷懒——我们把 KV 那一项扔掉了。如果用户聊了超长对话(比如 200K token 的文档分析),KV 大到不能忽略,会发生什么?
几何上看:
- KV 项让 T_memory 的斜率变大(更陡)
- 而 T_compute 完全不变
- 两条线的交叉点会往右移
🔑 结论:长上下文场景需要更大的 Batch 才能达到平衡。
这也解释了一个现实现象:处理长文档/长对话的服务,运营起来反而更看用户量。因为 KV 压力让甜蜜点右移,需要更多并发用户才能填满 GPU。如果用户量不够,机器就只能在低效区间里晃——这就是为什么长上下文 API 又贵又难做。
更进一步:当上下文长到某个临界点,KV 搬运时间会完全压过参数搬运时间,系统从「weight-bound」切换到「KV-bound」。这正是历史上 Gemini 1.5 Pro 在长上下文阈值后采用阶梯定价的物理根源——临界点不是拍脑袋定的,是算出来的。
下面是一个交互式模拟器,亲手拉一拉 L、b、BW、N_active 这些滑杆,看 T_compute 与 T_memory 两条线的交点 B* 怎么左右移动:
T_compute / T_memory Batch Size Simulator
T_compute = B × N_active / C, T_memory = N_total / BW + B × L × b / BW
T_compute 的斜率是 N_active / C;T_memory 的斜率是 L × b / BW,截距是 N_total / BW。b 是每 token 在 KV cache 里占的总字节数(已经把层数、KV 头数、head_dim 都乘进去了,落在几 KB ~ 几百 KB 量级),所以调 L 或调 b 都会立刻让 T_memory 斜率明显变化。
N_active=37G,C=1P ops/s,N_total=671G,BW=4.8T B/s,L=1.02K,b=71.68K B/token。
Compute / Memory 比值:3.34e-2。KV 项占比:1.38%。继续提高 L、提高 b、降低 BW,都能明显拉高 T_memory 的斜率。
八、一张表收尾
| 符号 | 含义 | 谁决定 |
|---|---|---|
B | batch size,同时服务的用户数 | 调度策略 |
L | 上下文长度 | 用户行为 |
N_total | 模型总参数 | 模型设计 |
N_active | 每 token 激活的参数 | 模型设计(稀疏度) |
b | KV 缓存里每个 token 占多少字节(= 2 × 层数 × KV 头数 × head_dim × 元素字节数) | 模型架构 + 数值精度 |
C | 有效计算速度(≈ FLOPS / 2) | 硬件常数 |
BW | 显存带宽 | 硬件常数 |
C/BW | ≈ 300,硬件天平 | 硬件常数 |
核心方程:
甜蜜点(KV 可忽略时):
九、留几个可以自己想的问题
- 如果一家公司声称用了「新算法」让推理速度快 10 倍,应该追问哪些问题?提示:他们改的是
N_active、b、还是C/BW? - 一个稠密模型(
N_total = N_active)的最优 batch 大约是多少?为什么稠密模型在长上下文场景下日子更难过? - 如果未来的 GPU 把
BW翻 10 倍,但C不变,C/BW会变成多少?甜蜜点会怎么移动?哪类模型会受益最大? - 一家服务商如果只有少量用户,但每个用户都聊超长对话,它的 batch 会被卡在哪个区间?利润率会怎样?