适用读者:要判断"这块卡为什么跑不满"、要选卡或要读 kernel 性能报告的人。
前置:S23-J07、J12(单步估算与训练账)。
覆盖旧文:A-4337 §4.1–§4.3(从模型计算认识加速器 + 计算单元 + 存储层次,312 点)→ 312 点。
扩展点:E-S23-035(算术强度与 roofline 阈值:M* ≈ 344 token)、E-S23-036(形状效率 η_shape:补零让 MoE 多算 8 倍)、E-S23-037(容量不等式与 B_max)、E-S23-038(面积/功耗预算:TPU v1 的 37% 缓冲 vs 30% 计算)。
上一篇:S23-J12 · 训练负载、RL 与预算
下一篇:S23-J14 · 数据搬移与多芯封装互联。
1. 一句话结论
同一份权重,M=1 时算术强度约 1 FLOP/byte,M=256 时 228 FLOP/byte,而 H100 的分水岭是 295。 所以 decode 的瓶颈几乎必然在搬数据,不在算;而"把卡换成算力更高的型号"对 M=1 毫无帮助。
出处:AI Infra 教材 §4.1–§4.3(
id 4337),模型 Qwen3-8B。
算术强度 I = F / V(FLOP per byte)
机器分水岭 I* = 峰值算力 / 峰值带宽
I < I* → 带宽受限;I > I* → 算力受限
2. 一次 Q 投影的算术强度(E-S23-035)
Y = XW_Q,X:[M,4096],W_Q:[4096,4096],Y:[M,4096]
F = 2MKN = 2M × 4096² = 33,554,432·M FLOPs
V = 2MK + 2KN + 2MN = 33,554,432 + 16,384·M bytes
(输入一次 + 权重一次 + 输出一次,均 BF16)
| 一次 Q 投影 | M=1 | M=256 |
|---|---|---|
| 计算量 | 33.6 MFLOPs | 8.59 GFLOPs |
| 权重读取 | 32 MiB | 32 MiB |
| 输入读 + 输出写 | 16 KiB | 4 MiB |
| 总访问量 | ~32 MiB | ~36 MiB |
| 算术强度 | ≈ 1.0 FLOP/byte | ≈ 227.6 FLOPs/byte |
判据:行数 ×256,算力 ×256,访问量只涨 1/8。 权重复用是"算力长在带宽上"的唯一途径——这就是 batch 的全部意义。
2.1 H100 的分水岭
I* = 989.4 TFLOP/s ÷ 3.35 TB/s ≈ 295 FLOP/byte
| 情形 | I | 谁受限 |
|---|---|---|
M=1(decode) |
~1 | 带宽(差 295 倍) |
M=256 |
~227.6 | 仍偏带宽 |
M≈345 |
~295.3 | 刚好吃平 |
判据:这个 4096×4096 投影要一次处理约 345 个 token 才够"喂饱"H100。 单请求 decode 永远达不到——只能靠 batch 或更大的矩阵。
3. 计算单元:矩阵与向量分工
Transformer 层的两类计算交替出现:
矩阵:投影、QKᵀ、PV(规则乘加,块内复用)
向量:归一化、RoPE、Softmax、门控激活(逐元素、特殊函数、归约)
注意力就是 矩阵 → 向量 → 矩阵:QKᵀ(矩阵)→ Softmax(向量)→ PV(矩阵)。
3.1 两种架构的分工对照
| 维度 | NVIDIA GPU(CUDA 高性能路径) | 昇腾 DaVinci |
|---|---|---|
| 矩阵计算 | SM 内 Tensor Core 处理 tile | Cube 处理矩阵 tile;Vector/Scalar 处理逐元素、归约、控制 |
| 全局→片上 | L2/L1 通常硬件管理;可显式用 shared memory、异步拷贝、TMA | MTE/NDDMA 按描述符在 Global / L1 / UB / L0 之间搬移,路径由算子或编译器规划 |
| 部分和 | 寄存器、shared memory 或专用矩阵存储 | L0C 保存 Cube 部分和 |
| 搬移与计算重叠 | memcpy_async、TMA、barrier、pipeline |
MTE 与 Cube/Vector 通过 flag/event 同步 |
| 同步层级 | warp / block / cluster / device | 核内阶段、Cube/Vector 交接、核间协议 |
| 软件承担 | 高性能路径仍需安排 tile、共享内存、布局、异步依赖 | 更早面对 L1/UB/L0 容量与交接(Developer 模式可自动插同步) |
判据:GPU 把"缓存"交给硬件、把"复用"留给软件;DaVinci 把"路径"显式化给编译器和算子。 责任边界不同,不是谁更先进。
4. 形状效率:补零吃掉多少算力(E-S23-036)
矩阵指令按固定块执行(比如行方向最小 16 行),不足整块补零:
η_shape = M·N·K / (⌈M/m_t⌉·m_t · ⌈N/n_t⌉·n_t · ⌈K/k_t⌉·k_t)
MoE 的例子(原文数):64 个 token × 每 token 8 个专家 = 512 次分派,行补齐 16。
| 路由 | 每专家行数 | 实际执行行数 | 有效行 |
|---|---|---|---|
| 均匀覆盖 256 专家 | 2 | 256×16 = 4,096 |
512 |
| 集中到同一组 8 专家 | 64 | 8×64 = 512 |
512 |
有效计算量相同,执行量差 8 倍(4096 vs 512)
判据:"负载均衡"在系统层是好事,在 kernel 层是灾难。 均匀路由让每个专家只剩 2 行有效数据,14/16 的乘加在算零——这正是 J10 里"集中路由省 32 倍权重读取"的另一半原因。
5. 存储层次与容量(E-S23-037)
常驻权重 W_resident + 每请求 KV (B·S·m_KV) + 工作区 M_workspace ≤ 可用容量 C
B_max = ⌊(C − W_resident − M_workspace) / (S · m_KV)⌋
m_KV = 144 KiB/token(Qwen3-8B,BF16)
RTX 4090(24 GB)实例,取工作区 2 GiB:
| 项 | 数值 |
|---|---|
| 权重(BF16) | 16.4 GB |
| 剩余给 KV | 约 5.47 GB |
| 8K 上下文单请求 | 1.125 GiB ≈ 1.21 GB |
| 4 条 8K 请求 | 约 4.83 GB ✅ |
| 5 条 8K 请求 | 约 6.04 GB ❌ |
| 16K 上下文 | 每条翻倍 → 2 条 |
判据:容量是"不等式 + 向下取整",不是"估算个大概"。 多加一条请求越界,整批就会 OOM——所以服务端宁可少放一条。
5.1 三个问题要分开问(原文强调)
| 问题 | 看什么 |
|---|---|
| 放得下吗 | 容量不等式(权重 + KV + 工作区) |
| 重复使用时从哪一级读 | 寄存器 / 共享内存 / L2 / HBM 的路径 |
| 独立访问够不够维持带宽 | 并发访问数 × 每访问字节数 vs 带宽 |
6. 面积与功耗:为什么缓冲比计算单元还大(E-S23-038)
TPU v1 的面积分布(原文引论文):
| 部件 | 面积占比 |
|---|---|
| 统一数据缓冲(24 MiB) | 37% |
| 计算单元(256×256 8-bit MAC) | 30% |
| 控制电路 | 2% |
缓冲面积超过计算单元——因为阵列要反复使用输入、权重和部分和;若每次从片外读,乘加器就会长时间等数据。
6.1 Amdahl 的复利递减
S = 1 / ((1−f) + f/s)
f=0.6, s=2 → 1/(0.4+0.3) = 1.43
再翻到 4 → 60/4 + 40 = 55 μs(第一次省 30 μs,第二次只省 15 μs)
上限:矩阵时间→0 时仍是 2.5×
判据:局部加速的收益递减,且被"没加速的那部分"封顶。 优化前先确认 f 有多大,否则投的资源全花在 40 μs 的尾巴上。
7. 可复算(实测)
$ cd /root/博客运营官/blog-v2 && python3 tools/s23_book_calc.py | tail -11
Q 投影 4096×4096(BF16)
M=1 : F 33.55 MFLOPs, V 32.02 MiB, I = 1.0 FLOP/byte
M=64 : F 2147.48 MFLOPs, V 33.00 MiB, I = 62.1 FLOP/byte
M=256 : F 8589.93 MFLOPs, V 36.00 MiB, I = 227.6 FLOP/byte
H100 I* = 989.4/3.35 = 295.3 FLOP/byte → M* ≈ 345 token
η_shape(16 行补齐): 均匀 256×16 = 4096 行 / 集中 8×64 = 512 行 → 8.0×
RTX 4090 24 GB: 权重 16.38 GB, 剩余 5.47 GB;8K 单请求 1.21 GB
4 条 8K = 4.83 GB 可行
5 条 8K = 6.04 GB 超限
16K: 每条 2.42 GB → 可放 2 条
8. 前提与边界
| 结论 | 成立条件 | 不成立时 |
|---|---|---|
I* = 算力/带宽 |
单卡、无片上复用 | 数据已在 L2/共享内存 → 有效带宽更高 |
M*≈345 |
4096×4096 投影、BF16 | 不同形状、不同卡 → 重算 |
| 容量向下取整 | 每请求等长 | 变长请求 → 按 p95 长度算 |
| 补零浪费 8 倍 | 行最小 16、每专家 2 行 | 换 GEMV/分组 kernel → 浪费减少 |
9. 常见误区
| 误区 | 纠正 |
|---|---|
| 换更高算力的卡就能提速 decode | M=1 时 I≈1,算力根本用不上 |
| 认为 batch 只是"提高吞吐" | 它把 I 从 1 提到 228,是改变瓶颈性质 |
| 用峰值 TFLOPs 估实际性能 | 要先过 roofline 与 η_shape 两道折扣 |
| 忽略补零 | MoE 均匀路由下执行量是有效的 8 倍 |
| 显存按"权重 + 一点余量"估并发 | 必须写容量不等式并向下取整 |
10. 自测
M=64时该投影的算术强度大约多少?H100 上算力还是带宽受限?- H100 的
I*是多少?为什么M=256仍然"偏带宽"? - 64 token × 8 专家、行补齐 16,均匀路由执行多少行?有效多少行?
- RTX 4090 上跑 Qwen3-8B(BF16)、工作区 2 GiB、上下文 8K,最多几条并发?
答案
I ≈ 62.1(实测);远低于 295,仍带宽受限。989.4/3.35 ≈ 295.3;M=256只有 227.6,差 22.9%。256×16 = 4,096行执行,512 行有效(8 倍浪费)。- 剩余约 5.47 GB,每条 8K 约 1.21 GB → 4 条。
11. 下一篇
S23-J14 · 数据搬移与多芯封装互联 —— 数据从哪来、经过哪几级、用什么带宽;多芯片封装(NVLink/超节点)为什么把"内存池"变成可能。