通信 101:GPU 之间怎么传数据
128 GB 的 model state 单卡放不下,只能上多卡,于是多出一笔新开销:卡与卡之间传数据(communication)。这一课讲几种标准传法(collective)各做什么、每张卡要发多少、在 NVLink 和 InfiniBand 上各要多久。下一课的 DDP 和 ZeRO 全靠它。
为什么卡之间非“说话”不可
多卡训练只有两种切法,哪种都要传数据:
- 切数据(data parallel):每张卡一份完整模型,各吃不同的样本,算出的 gradient 不一样,该用的是它们的平均值。不同步的话,几步之后就成了两个不同的模型
- 切模型:每张卡只放模型的一部分,要用别的卡算出的中间结果。TP(tensor parallel)切每层的矩阵,PP(pipeline parallel)按层切,EP(expert parallel)把 MoE 的 expert 分到不同的卡,各自传什么见下面的表
传数据时 GPU 常常只能干等,所以要少搬,或者边算边搬(overlap)。
三种距离:厨房、楼、货车
- 一张卡 = 一个厨房:厨师(Tensor Core)从自家仓库(HBM)取货,3.35 TB/s
- 一台机器的 8 张卡 = 一栋楼里的 8 个厨房:之间有传菜电梯 NVLink,每卡每个方向 450 GB/s(两个方向合计 900 GB/s)
- 不同机器 = 不同的楼:只能开货车 InfiniBand(IB)。每张卡配一块 400 Gb/s 的网卡,小写 b 是 bit,除以 8 得 50 GB/s,比 NVLink 慢 9 倍
后面每一课都在想办法让重的通信留在楼里。
五个常用的 collective(外加 broadcast)
collective 是一组卡一起参与的通信操作,面试直接说英文名。rank 是卡的编号。下面都用 2 张卡举例:rank0 有 (1, 2),rank1 有 (3, 4)。
| collective | 做什么 | 例子的结果 | 谁在用 |
|---|---|---|---|
| all-reduce | 对应位置求和,每张卡都拿到完整的和 | 两张卡都得到 (4, 6) | DDP 同步 gradient;TP 合并部分和 |
| reduce-scatter | 也求和,但每张卡只拿和的 1/N | rank0 得 4,rank1 得 6 | ZeRO、FSDP 同步 gradient;SP(第 5 课) |
| all-gather | 每张卡出一块,拼成完整的,发给所有卡 | rank0 出 4、rank1 出 6 → 两张卡都得到 (4, 6) | ZeRO、FSDP 拼回 parameter;SP |
| all-to-all | 每张卡把第 j 块发给 rank j,只交换、不求和 | rank0 得 (1, 3),rank1 得 (2, 4) | MoE 把 token 送到 expert(EP) |
| send / recv | 一张卡发、一张卡收,点对点 | rank0 把 (1, 2) 发给 rank1 | PP 在相邻 stage 之间传 activation |
| broadcast | 把一张卡的数据复制给所有卡 | 两张卡都是 (1, 2) | DDP 开始时把 rank0 的初始 weight 发给大家 |
第 2、3 行连起来就是第 1 行。这是这一课最重要的等式:all-reduce = reduce-scatter + all-gather。第 4 课的 ZeRO 就是在这两步中间插了一步。
ring all-reduce:4 张卡围成一圈
最笨的做法是全发给 GPU0,加完再发回去。GPU0 要收 3 份、发 3 份,瓶颈全压在它一张卡上。ring 让 4 张卡围成一圈,gradient 切成 4 块,每一步每张卡只给右边的邻居发 1 块,所有链路同时在用,像传送带。每一步的数值见 6.12 的图。
- 第一阶段 reduce-scatter,3 步:收到的那块加到自己对应的块上,再往右传。3 步后每张卡各有一块加完了的结果
- 第二阶段 all-gather,3 步:把加完的那块再传一圈,收到的直接覆盖。3 步后每张卡都有完整的和
每张卡发了多少:6 步,每步 1/4 份,一共 1.5 份。一般地,N 张卡、数据大小 S:
\[ \text{每卡发送} = 2(N-1)\cdot\frac{S}{N} = \frac{2(N-1)}{N}\,S \]N = 4、8、32 时分别是 1.5S、1.75S、1.94S,不超过 2S,几乎和卡数无关。单独做一次 reduce-scatter 或 all-gather 是它的一半,\((N-1)S/N\)。
ring 和 tree
ring 发的数据最少,但要走 \(2(N-1)\) 步,每步都有固定的 latency,1024 张卡要 2046 步。tree 沿二叉树往上加、再往下发,只要约 \(2\log_2 N\) 步,1024 张卡约 20 步。所以数据大用 ring,数据小、卡又多用 tree。
粗算:8B 的 gradient 同步一次要多久
8B 模型的 bf16 gradient 是 S = 16 GB。时间 = 每卡发送量 ÷ 链路 bandwidth:
| 场景 | 每卡发送 | 链路 | 时间 |
|---|---|---|---|
| 一台机器 8 张卡 | 2 × 7/8 × 16 = 28 GB | NVLink 450 GB/s | ≈ 62 ms |
| 4 台机器 32 张卡,每一跳都按 IB 算 | 2 × 31/32 × 16 = 31 GB | IB 50 GB/s | ≈ 620 ms |
| 4 台机器 32 张卡,分层做 | 机内 14 GB → 跨机 3 GB → 机内 14 GB | NVLink → IB → NVLink | ≈ 31 + 60 + 31 ≈ 120 ms |
第二行偏悲观,真实的通信库会像第三行那样按拓扑分层。先在机内 reduce-scatter,每张卡只剩 1/8(2 GB),跨机只对这 2 GB 做 all-reduce(2 × 3/4 × 2 = 3 GB),最后在机内 all-gather。准确的数要用 nccl-tests 实测。这笔时间还要藏到 backward 后面,见第 4 课。
NCCL:真正干这些活的库
NCCL(NVIDIA Collective Communications Library,读作 “nickel”)是 NVIDIA 的 GPU 通信库,实现了上面所有 collective。PyTorch 里写 dist.init_process_group(backend="nccl"),DDP、FSDP、DeepSpeed、Megatron 底下都是它。它会自己探测拓扑,机内走 NVLink,跨机走 IB,再按数据大小和卡数选 ring 或 tree。
RDMA 和 GPUDirect:货车直接开到仓库门口
普通网络(TCP)跨机传数据,GPU 的数据要先拷到 CPU 的内存(host memory),再由操作系统 kernel 打包交给网卡,对面再反着来一遍。好比货要先从厨房搬到楼下收发室,登记、重新装箱才能上车。
- RDMA(Remote Direct Memory Access):网卡直接读写内存,不经过 kernel(kernel bypass),没有中间拷贝(zero copy),对面的 CPU 也不用参与
- GPUDirect RDMA:再进一步,网卡直接读写 GPU memory,连 host memory 都不经过。NCCL 跨机用的就是它加 IB
- InfiniBand 和 RoCE:IB 是专为 RDMA 设计的网络;RoCE 把同一套 RDMA 跑在以太网上
写 RDMA 程序用的接口叫 verbs,流程记四步:
- register memory:把 buffer 登记给网卡并 pin 住(不许 OS 挪走),拿到 lkey、rkey,对面要有 rkey 才能读写它
- 建连接:创建 queue pair(QP)和 completion queue(CQ),和对面交换 QP 编号、内存地址和 rkey
- 下单:往 QP 里 post 一个 work request,可以是 send / recv,也可以直接 RDMA write / read 对面的内存
- 等完成:网卡做完往 CQ 里放一条完成记录,程序 poll CQ 就知道传完了
去查一下你在 TikTok 的 32 卡 job 跨了 4 台机器,DeepSpeed 底下就是 NCCL。可以查清楚:集群是 InfiniBand 还是 RoCE?每台机器几张网卡?如果日志开过 NCCL_DEBUG=INFO,启动时会打印走的是 NET/IB 还是 NET/Socket,有没有 GDRDMA 字样(说明用上了 GPUDirect RDMA)。面试官问“跨机走什么网络”时,你能说出具体硬件。
30 秒英文回答
Once the model or the data is split, GPUs exchange data through collectives. All-reduce sums a tensor and leaves the result on every GPU; DDP uses it for gradients. Reduce-scatter leaves each GPU one summed shard, all-gather reassembles the shards, and together they equal an all-reduce, which ZeRO and FSDP exploit. MoE routes tokens with all-to-all, and pipeline stages use send and recv. Ring all-reduce does a reduce-scatter then an all-gather, N minus 1 steps each, so every GPU sends 2(N−1)/N times the data: bandwidth-optimal, but latency grows with N, so NCCL uses trees for small messages. Across nodes, GPUDirect RDMA lets the NIC read GPU memory directly.
自测
4 张卡,数据切成 4 份,每卡 x,ring all-reduce 每卡要发多少?(阿里淘天原题)
6 步,每步发 x/4,共 1.5x,也就是 \(2(N-1)/N \cdot x\)。
RDMA 的工作流程是什么?(NVIDIA Research System AI 实习 2024)
register memory → 建 QP 和 CQ 并交换 rkey → post work request → poll CQ 等完成。
ring 每卡的通信量几乎和卡数无关,那卡多了为什么还会慢?
一是步数 \(2(N-1)\) 随卡数线性增长,每步都有固定 latency。二是一圈的速度取决于最慢的那条边,跨机的 IB 比 NVLink 慢 9 倍。
MoE 为什么用 all-to-all,不用 all-reduce?
每个 token 要去的 expert 不一样,要做的是把不同的数据送到不同的卡,不是求和。
ZeRO、FSDP 为什么用 reduce-scatter 同步 gradient,不用 all-reduce?
每张卡只负责更新 1/N 的 parameter,只需要这 1/N 的 gradient 总和。reduce-scatter 正好给出这一份,发的量只有 all-reduce 的一半。
想看细一点 6.12 all-reduce,ring vs tree(ring 的 NumPy 模拟、busbw、α–β 模型)· 6.13 RDMA 和 GPUDirect(verbs 细节、怎么确认 GDR 生效)· 6.8 MoE 与 Expert Parallel(all-to-all 怎么用)