适用读者:要读懂 kernel 的访存报告、要解释"为什么加带宽没变快"、要做多卡/多 die 放置决策的人。
前置:S23-J13(算术强度与容量)。
覆盖旧文:A-4337 §4.4–§4.5(数据搬移 + 多芯片封装与互联,296 点)→ 296 点。
扩展点:E-S23-039(布局与描述符:32 KiB 有效数据跨 1 MB 地址空间)、E-S23-040(双缓冲只把总时间从 n(t_l+t_c) 降到 t_l + n·max)、E-S23-041(die 间/本地带宽比决定计算放在哪)、E-S23-042(点对点传输的 α+V/R 与 879 KiB 转折点)。
上一篇:S23-J13 · 加速器:计算单元与存储层次
下一篇:S23-J15 · 架构演进与加速器选型。
1. 一句话结论
搬移时间 = 固定开销 + 数据量/带宽,两项各有各的优化手段。 8 KiB 的消息换更快的链路只快 0.5%,2 MiB 的消息能快 26%;而 100 次串行小交换,光启动开销就是 200 μs。先判断你在公式的哪一项上,再决定花钱。
出处:AI Infra 教材 §4.4–§4.5(
id 4337)。
T_edge = α + V / R
转折点 V* = α·R (小于它:合并消息;大于它:减字节或提带宽)
2. 布局:数学上的块 ≠ 内存里的块(E-S23-039)
二维视图的地址:
addr(i,j) = base + i·s_r + j·s_c
行优先存储时 s_c = b(元素字节),s_r = 完整行宽字节
实例:从 BF16、行宽 4,096 的矩阵里取 128 行 × 每行连续 128 个元素:
| 项 | 数值 |
|---|---|
| 有效数据 | 128×128×2 = 32 KiB |
| 每行有效 | 256 B |
| 相邻行起点间距(步长) | 8,192 B |
| 首行起点到末行终点跨越 | 127×8192 + 256 = 1,040,640 B |
判据:32 KiB 的有效数据散落在约 1 MB 的地址空间里。 逐行读只搬蓝区;整段读会把灰色空隙也搬走——差 30 倍。
2.1 描述符:把"重复算地址"交给专用单元
一次规则搬移只需要四个参数就够描述:
起点 base、行数 128、每行 256 B、行步长 8,192 B
| 部件 | 能力 |
|---|---|
| 普通加载指令 | 线程逐步算地址、逐个发起访问 |
| TMA(Hopper 起) | 多维张量搬移,按描述符重复生成地址 |
| NDDMA(昇腾 950) | 多维直接内存访问,同类的描述符式搬移 |
判据:搬移单元接管"重复地址计算",计算线程才能去干别的。
2.2 分块会放大请求量
DeepSeek V4-Flash 共享专家的一次 w1:[32,4096] × [4096,2048],FP8 输入仅 128 KiB。
| 分块方式 | 输入请求总量 | 权重读取 |
|---|---|---|
| 输出宽度分 16 块 × 128 列 | 16 × 128 KiB = 2 MiB(16 倍) |
8 MiB(各块读自己那份) |
输入本身没变大,多出来的请求全部来自"每个输出块都要遍历完整输入"。若这些块共享片上缓存,重复请求可被缓存吸收;若一个计算组保留输入连续算多个输出块,复用又移到局部缓冲。
判据:分块决定"发出多少请求",缓存决定"这些请求打到哪一级"。 两者必须一起设计。
3. 双缓冲:只买到 max 而不是 sum(E-S23-040)
串行:n 块 × (t_l + t_c) = n(t_l + t_c)
双缓冲:t_l + n·max(t_l, t_c) (第一块先加载,之后以较慢阶段为节奏)
| 情形 | 串行 | 双缓冲 | 收益 |
|---|---|---|---|
t_l = t_c |
2n·t |
(n+1)·t |
≈ 2× |
t_l = 4t_c |
5n·t |
n·4t |
仍被加载卡住 |
判据:双缓冲的上限是把"和"变成"最大值",天花板由较慢的那一侧决定。 加载慢就要加大加载并发,计算慢就要减少等待——两者不能靠同一个改动同时解决。
4. 多 die 封装:带宽比决定计算放哪(E-S23-041)
封装内多颗 die 各有本地 HBM,跨 die 走片内链路。判据只一个比值:
k = die 间链路带宽 / 每 die 本地 HBM 带宽
k ≥ 1 → die 可当一颗 GPU 用(跨 die 读取不比本地慢)
k < 1 → 让计算贴着权重走,只传输入与结果
4.1 两款双 die 产品的实数
| 产品 | 每 die 本地 | die 间链路 | k |
|---|---|---|---|
| HGX B200 | 90 GB / 4 TB/s | NV-HBI 10 TB/s | 2.5 |
| 昇腾 910C | 64 GB / 1.6 TB/s | 每方向 270 GB/s | 0.17 |
场景:两颗 die 各存 32 GiB 权重,某阶段要把 64 GiB 全读一遍,计算全放在 die 0。
| 产品 | 本地读 32 GiB | 跨 die 读 32 GiB | 整阶段 |
|---|---|---|---|
| B200 | 约 8.6 ms | 约 8.6 ms(受 die 1 的 HBM 限制) | 8.6 ms |
| 910C | 约 21.5 ms | 约 127 ms(受 270 GB/s 链路限制) | 127 ms |
改成"计算贴着权重走"(两边各读本地):
| 产品 | 整阶段 | 链路传什么 |
|---|---|---|
| B200 | 8.6 ms(不变) | — |
| 910C | 21.5 ms(5.9× 改善) | 输入+结果 64 MiB,经 270 GB/s ≈ 0.25 ms |
判据:k<1 时,链路上该传"小输入/小结果",不该传"大权重"。 CloudMatrix384 的 384 颗 910C 在 decode 中以 die 为单位放专家、每 die 一个专家,正是这条规则的工程化。
5. 主机↔加速器,卡↔卡
5.1 主机侧的两条路
| 路径 | 数据流 | 省掉什么 |
|---|---|---|
| 独立显存(常规) | CPU → 主机内存 → 复制到显存 → GPU 读 | — |
| 统一内存/共享物理内存 | CPU 写同一块,GPU 直接读(同步后) | 单独 H2D 副本与其传输 |
复用判据:一次上传的开销,要跟"加速器读一次"比较。原文给出的结论是 R ≥ 32(复用次数)时,单次上传分摊已不大于一次加速器读取——所以"权重常驻"永远优先于"每次上传"。
5.2 卡间:固定开销主导小消息(E-S23-042)
T_edge = α + V/R
HGX H100:NVLink 双向 900 GB/s → 每方向 R = 450 GB/s;A100 SXM:R = 300 GB/s
取 α = 2 μs
| 传输 | H100 | A100 | 差异 |
|---|---|---|---|
8 KiB(M=1 的 BF16 激活) |
2.02 μs | 2.03 μs | 0.5% |
2 MiB(M=256) |
6.66 μs | 8.99 μs | −26% |
转折点:
V* = α·R = 2 μs × 450 GB/s = 900,000 B ≈ 879 KiB
对应 [M,4096] BF16 激活:M ≈ 110 行
判据:小于 879 KiB,合并消息比换链路更有效;大于它,减字节/提带宽才有意义。 100 次串行 2 μs 小交换 → 光开销 200 μs;跨服务器 16 卡 16 bytes 的 AllReduce 公开实测约 25 μs,100 次就是 2.5 ms。
6. 可复算(实测)
$ cd /root/博客运营官/blog-v2 && python3 tools/s23_book_calc.py | tail -12
布局: 128×128 BF16 子块
128行×128列: 有效 32 KiB,行步长 8192 B,跨度 1,040,640 B
64行×128列: 有效 16 KiB,行步长 8192 B,跨度 516,352 B
分块: 16 块 × 128 KiB = 2 MiB(16×);权重 8 MiB
B200: k=2.50;本地 8.6 ms;跨 die 8.6 ms
910C: k=0.17;本地 21.5 ms;跨 die 127.3 ms
910C 链路上传 64 MiB 输入结果 ≈ 0.25 ms
卡间 α=2 μs, R=450 GB/s
8 KiB: 2.02 μs(启动占 99%)
2 MiB: 6.66 μs(启动占 30%)
512 KiB: 3.17 μs(启动占 63%)
V* = αR = 879 KiB = 900,000.0 B;对应 M ≈ 110 行
7. 前提与边界
| 结论 | 成立条件 | 不成立时 |
|---|---|---|
| 双缓冲收益 ≈ max | 加载与计算使用独立资源 | 共用同一内存接口 → 争用 |
k≥1 可当一颗 GPU |
链路带宽 ≥ 本地 HBM | 链路拥塞、协议开销 → 实际更低 |
V* = αR |
α 固定、单向带宽稳定 | 拥塞/多跳 → α 变大 |
| 权重常驻优先 | 有足够显存 | 模型大于显存 → 分层卸载(J23) |
8. 常见误区
| 误区 | 纠正 |
|---|---|
| 用"整段读"取子矩阵 | 32 KiB 数据要跨 1 MB 地址,浪费 30 倍 |
| 认为双缓冲能叠加收益 | 上限是"和 → 最大值" |
k<1 时还把权重跨 die 传 |
应让计算贴着权重,只传输入/结果 |
| 小消息也换更快的链路 | 8 KiB 只快 0.5%,应合并消息 |
| 忽略固定开销累计 | 100 次小交换 = 200 μs,比数据本身大得多 |
9. 自测
- 从行宽 4,096 的 BF16 矩阵取 64 行 × 128 列,有效数据多少?跨度多少?
t_l=4t_c、n=10时,串行与双缓冲各是多少t_c?- B200 与 910C 的
k各是多少?k<1时正确的放置策略是什么? - H100 上 512 KiB 的消息,传输时间约多少?启动开销占比多少?
答案
64×128×2 = 16 KiB;跨度63×8192+256 = 516,352 B。- 串行
10×5 = 50 t_c;双缓冲4 + 10×4 = 44 t_c。 - B200
10/4 = 2.5;910C0.27/1.6 ≈ 0.17;让计算在权重所在 die 上执行,链路只传输入与结果。 2 μs + 512 KiB/450 GB/s ≈ 2 + 1.17 = 3.17 μs,启动占 63%。
10. 下一篇
S23-J15 · 架构演进与加速器选型 —— 从 TPU v1 到 Blackwell/昇腾 950 的演进脉络,以及"按负载选卡"的检查表:算力、带宽、容量、互联、生态。