Skip to Content
AI 时代✏️ LLM 定价的数学原理02:把推理过程写成方程

✏️ LLM 定价的数学原理 02:把推理过程写成方程

📖

上一篇咱们「找找感觉」,理清了 LLM 推理在硬件层面到底是怎么回事。这一篇,咱们正式拿起笔,把那些直觉翻译成方程——看完你会发现,现代大模型推理服务的最佳 Batch 为什么稳稳落在「几千」这个量级、长对话又怎么把这个最佳值往大里推。这些数字不是工程师拍脑袋的经验值,是从硬件参数里直接算出来的。

🧭

前情提要:上一篇 大模型推理是怎么回事——从「搬东西 vs 算东西」说起 我们建立了核心直觉——推理耗时 = 搬东西耗时 + 算东西耗时,谁慢听谁的;Batch 能摊销搬参数的成本,但摊不掉 KV cache。

一、给「搬」和「算」起名字

上一篇反复说的两件事,咱们先给它们各自起个名字:

  • 搬东西所花的时间 → 叫它 T_memory(内存时间)
  • 算东西所花的时间 → 叫它 T_compute(计算时间)

那么生成一个 token 的实际耗时是多少?

Tmax(Tmemory, Tcompute)T \geq \max(T_{memory},\ T_{compute})

是 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 = 671BN_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 时:

Tcompute=B×NactiveCT_{compute} = \frac{B \times N_{active}}{C}

其中 C = FLOPS / 2,可以理解为「工人有效速度」。

🔑 直觉:用户越多算的越多;激活参数越多每人要算的越多;工人越快时间越短。


三、把 T_memory 写出来

上一篇说过:搬两样东西 —— 模型参数 + KV cache

搬参数

不管 Batch 多大,整本菜谱都要被完整搬运一遍(每个 token 都用到所有层)。

  • 要搬多少:N_total
  • 传送带速度:memory bandwidth,记作 BW

H100 的 BW ≈ 3 TB/s,也是硬件常数

T搬参数=NtotalBWT_{\text{搬参数}} = \frac{N_{total}}{BW}
💡

注意:这一项里没有 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
T搬KV=B×L×bBWT_{\text{搬KV}} = \frac{B \times L \times b}{BW}
⚠️

这一项有 B——Batch 越大要搬的 KV 越多,摊不掉。这个差别决定了一切。

把搬的部分合起来

Tmemory=NtotalBW搬参数+B×L×bBW搬KVT_{memory} = \underbrace{\frac{N_{total}}{BW}}_{\text{搬参数}} + \underbrace{\frac{B \times L \times b}{BW}}_{\text{搬KV}}

这里是加和,因为两件事用的是同一条传送带——同一条传送带不能同时搬两样东西。这一点很关键,第六节再展开。


四、一张图看懂全部

现在我们有两个时间随 batch size 变化的函数。横轴是 B,纵轴是时间。

T_compute:从零点出发的直线

T_compute │ ╱ │ ╱ │ ╱ │ ╱ │ ╱ │ ╱ └───────────► B 0

T_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_memorymemory-bound传送带在等工人 → 加 Batch 是免费午餐,延迟几乎不变
交叉点T_memory = T_compute完美平衡传送带和工人速度匹配,效率最高
右边(B 大)T_computecompute-bound工人忙不过来 → 再加 Batch 没用,延迟开始线性上涨

🔑 优化推理的核心目标,就是把系统钉在交叉点附近。 这就是为什么所有推理服务都要做 batching、continuous batching、动态调度等等——都是为了让 B 落在那个甜蜜点。


五、解出甜蜜点:神秘的 300 是怎么来的

甜蜜点就是两条线交叉处,即 T_memory = T_compute

为了让代数清爽,先简化一步:假设上下文不太长,KV 那部分相比参数那部分小到可以忽略(这个假设很快会回头检查)。也就是说:

TmemoryNtotalBWT_{memory} \approx \frac{N_{total}}{BW}

让两边相等:

NtotalBW=B×NactiveC\frac{N_{total}}{BW} = \frac{B \times N_{active}}{C}

解出 B:

B=NtotalNactive模型这边×CBW硬件这边B = \underbrace{\frac{N_{total}}{N_{active}}}_{\text{模型这边}} \times \underbrace{\frac{C}{BW}}_{\text{硬件这边}}

🔑 妙就妙在这里:这个公式恰好分成了两半——左边只跟模型有关,右边只跟硬件有关。两件事互不干扰。

模型那一项:稀疏度的倒数

N_total / N_active 就是「整本菜谱 / 今晚翻的页数」——也就是稀疏度的倒数

  • 稠密模型(Dense):所有参数都用,N_total = N_active,比值 = 1
  • DeepSeek V3:671B / 37B18
  • 一些更稀疏的 MoE:可以到 30+

硬件那一项:神秘的 300

C / BW:把工人速度除以传送带速度。代入 H100 的数字(C ≈ 10¹⁵BW ≈ 3 × 10¹²),单位换算后:

CBW300\frac{C}{BW} \approx 300

这就是工程师们口中那个神秘的 300。它是当代 GPU 的物理常数——不管 A100、H100、B100,这个比值都在 200 ~ 400 之间。

🔑 为什么这个比值这么大? 因为过去十年里,GPU 的算力涨得比内存带宽快得多。算力翻了几十倍,带宽只翻了几倍——比值就拉大到了 300 这个量级。这正是「现代 GPU memory-bound」的物理根源。

最终结果

🎯

综上,我们会获得甜蜜点公式(KV 可忽略时):

B最优300×NtotalNactiveB_{\text{最优}} \approx 300 \times \frac{N_{total}}{N_{active}}

左边是硬件常数(≈ 300),右边是模型稀疏度倒数。

代入 DeepSeek V3:

B最优300×185400B_{\text{最优}} \approx 300 \times 18 \approx 5400

这就是为什么大厂的推理服务都把 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 在长上下文阈值后采用阶梯定价的物理根源——临界点不是拍脑袋定的,是算出来的。

下面是一个交互式模拟器,亲手拉一拉 LbBWN_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

默认参数:DeepSeek V3(671B 总参数 / 37B 激活,MLA 把每 token KV 压到 ~70 KB)跑在 H200 上(4.8 TB/s HBM3e、FP8 等效算力 ~1 PFLOPS),上下文长度 1K(单轮 query 量级)。这套配置下甜蜜点 B* ≈ 6400 清晰可见。把 L 拖到 8K 看长上下文场景——你会看到甜蜜点从右边滑出 BMax 之外,进入 KV 主导工况;换硬件或多卡并发都救不回来,因为 C/BW 这个硬件常数(≈ 300)跨代变化非常慢。
当前 B
当前 batch size,标记图上竖线
18K
B 最大值
横轴范围,常用 512 / 2048 / 4096
1288K
N_active
每 token 激活的参数量。MoE 通常更小
params
1M500G
C
有效计算吞吐
ops/s
10G5P
N_total
需要从内存读取的总参数 / 数据规模
params
1M1T
BW
有效内存带宽
B/s
1G10T
L
序列长度均值 / KV 长度均值,最大 1M token
token
1281M
b
每 token 在 KV cache 里占的总字节数(= 2 × 层数 × KV heads × head_dim × 元素字节数),典型 4 KB ~ 数百 KB
B/token
1.02K1.05M
Bottleneck
Memory-bound
交点 B* ≈ 6.44K · Estimated Latency 141.7 ms
T_compute @ B=128
4.736 ms
B × N_active / C
T_memory @ B=128
141.7 ms
weight + KV
Estimated Latency
141.7 ms
max(T_compute, T_memory)
Memory weight term
139.8 ms
N_total / BW,决定截距
Memory KV term
1.957 ms
B×L×b/BW,占 1.38%
Memory slope
0.01529 ms
每加 1 个 B 的 T_memory 增量
读图小抄

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 的斜率。


八、一张表收尾

符号含义谁决定
Bbatch size,同时服务的用户数调度策略
L上下文长度用户行为
N_total模型总参数模型设计
N_active每 token 激活的参数模型设计(稀疏度)
bKV 缓存里每个 token 占多少字节(= 2 × 层数 × KV 头数 × head_dim × 元素字节数)模型架构 + 数值精度
C有效计算速度(≈ FLOPS / 2)硬件常数
BW显存带宽硬件常数
C/BW≈ 300,硬件天平硬件常数

核心方程:

T=max(NtotalBW+BLbBW, BNactiveC)T = \max\left(\frac{N_{total}}{BW} + \frac{B \cdot L \cdot b}{BW},\ \frac{B \cdot N_{active}}{C}\right)

甜蜜点(KV 可忽略时):

B最优300×NtotalNactiveB_{\text{最优}} \approx 300 \times \frac{N_{total}}{N_{active}}

九、留几个可以自己想的问题

  1. 如果一家公司声称用了「新算法」让推理速度快 10 倍,应该追问哪些问题?提示:他们改的是 N_activeb、还是 C/BW
  2. 一个稠密模型(N_total = N_active)的最优 batch 大约是多少?为什么稠密模型在长上下文场景下日子更难过?
  3. 如果未来的 GPU 把 BW 翻 10 倍,但 C 不变,C/BW 会变成多少?甜蜜点会怎么移动?哪类模型会受益最大?
  4. 一家服务商如果只有少量用户,但每个用户都聊超长对话,它的 batch 会被卡在哪个区间?利润率会怎样?
最后更新于: