Skip to Content
AI 时代✏️ 重新学习 LLM 02:把推理过程写成方程

✏️ 重新学习 LLM 02:把推理过程写成方程

📖

一篇写给非技术读者的科普文。读完你会理解:为什么所有现代大模型推理服务的最佳 batch size 都在「几千」这个量级,以及为什么长对话会让这个最佳值增大 —— 这个数字不是经验值而是从硬件参数算出来的物理结论。

🧭

前情提要:上一篇 大模型推理的工作原理、推理速度及推理成本,——从「搬东西 vs 算东西」说起 我们建立了核心直觉——推理耗时 = 搬东西耗时 + 算东西耗时,谁慢听谁的;batch 能摊销搬参数的成本,但摊不掉 KV cache。这一篇我们正式拿起笔,把那些直觉翻译成方程,看看 GPU 工程师每天在白板上画的那张图到底是什么。

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

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

  • 搬东西所花的时间 → 叫它 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

🔑 为什么要分这两个?因为它们对应不同的成本:

  • 算的成本N_active 有关(只算翻开的那几页)
  • 搬的成本N_total 有关(整本菜谱都要在显存里待着,而且每次还要把它读一遍)

这是 MoE 模型的精妙之处——用稀疏激活换便宜的计算,但搬运的代价没省。这一点会在第五节变得至关重要。

写出 T_compute

每个 token 要做大约 2 × N_active 次乘法(系数 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「私人笔记」,摊不掉

  • 笔记里每个数值占多少字节:记作 b(小写 b,别和 batch 的大写 B 搞混)。它由数值精度决定:FP16/BF16 ≈ 2 字节、FP8 ≈ 1 字节、FP32 ≈ 4 字节。精度越低,每条记录越省地方
  • 每个用户的对话长度:记作 L(context length)
  • B 个用户的 KV 总大小:正比于 B × L × b(这里把每 token 的元素数视为常数,量级简化,不影响读图)
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 在 200K context 之后涨价 50% 的物理根源——临界点不是拍脑袋定的,是算出来的。

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

使用更适合观察 KV cache 影响的中等规格推理卡参数。这里故意降低带宽与总参数量,让 L 与 b 对 T_memory 的影响更加明显。
当前 B
当前 batch size,标记图上竖线
12K
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
tok
1281M
b
每元素字节数:FP16≈2,FP8≈1,FP32≈4
B
18
Bottleneck
Memory-bound
交点 B* ≈ 600.49 · Estimated Latency 60.01 ms
T_compute @ B=128
12.8 ms
B × N_active / C
T_memory @ B=128
60.01 ms
weight + KV
Estimated Latency
60.01 ms
max(T_compute, T_memory)
Memory weight term
60 ms
N_total / BW,决定截距
Memory KV term
0.01049 ms
B×L×b/BW,占 0.02%
Memory slope
0.00008192 ms
每加 1 个 B 的 T_memory 增量
读图小抄

T_compute 的斜率是 N_active / C;T_memory 的斜率是 L × b / BW,截距是 N_total / BW。在超高带宽和超大参数量下,KV 项通常远小于权重读取项,所以调 L 和 b 不容易让总 T_memory 斜率明显变化。

当前参数速览

N_active=8G,C=80T ops/s,N_total=12G,BW=200G B/s,L=8.19K,b=2B。

Compute / Memory 比值:2.13e-1。KV 项占比:0.02%。继续提高 L、提高 b、降低 BW,都能明显拉高 T_memory 的斜率。


八、一张表收尾

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

核心方程:

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

甜蜜点(KV 可忽略时):

 B最优300×NtotalNactive \boxed{\ B_{\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 会被卡在哪个区间?利润率会怎样?
最后更新于: