CS336 · 第 7 课

分布式训练并行

单张 GPU 会遇到两个上限:模型状态可能放不下,训练速度也可能不够。增加 GPU 之后,问题并不会自动消失——计算被拆开了,但不同设备必须交换参数、梯度或激活。本讲要建立的主线是:先看必须交换什么,再按通信频率和物理拓扑选择并行策略。

授课:Percy Liang 预计用时:80 分钟 难度:困难

Spring 2026 官方来源:Lecture 7 trace · 课程日程

本页是中文学习导读;讲次主题、教师与材料版本以 Stanford 官方页面和讲义为准。


这一讲到底解决什么问题?

上一讲解决的是单张 GPU 内部的数据搬运:怎样通过 fusion、tiling 和更好的 kernel 减少 HBM traffic。模型扩展到多张 GPU 后,同一个问题跨越了设备边界:本地 memory traffic 变成了 network communication。

单卡是否能训练一个模型,不能只看参数量。梯度、优化器状态、激活、临时 buffer 和通信 workspace 都会占用显存;即使模型勉强放得下,单卡完成全部 FLOPs 也可能太慢。因此我们增加 GPU,但随之得到一个新问题:

多张 GPU 分别完成一部分计算后,需要交换哪些数据,交换多少次,又应该经过哪一类互联?

30 秒知识地图

单卡容量或速度不足 → 多 GPU 引入通信 → collective 描述“交换什么” → cost model 核算“多少 bytes” → topology 决定“这些 bytes 走多快” → DP / FSDP / TP / PP 按通信模式映射到合适的硬件域。

问题与 prerequisite refresher

并行不是“加卡自动加速”,而是选择复制、切分和通信哪些 tensors。Rank 是一个参与进程;collective 是所有 ranks 共同遵守的数据变换。先能画出每个 rank 操作前后的 tensor,才使用 All-Reduce 等名字。

并行策略是在切不同轴

DP: batch shards | full model replicas | synchronize gradients
FSDP: batch shards | model-state shards | gather/scatter states
TP: same tokens   | tensor dimension shards | communicate layer partials
PP: microbatches  | layer-range shards | send activations/gradients

logical tensor bytes → collective algorithm → physical links → wall time

两卡 DDP 为什么要平均梯度

GPU0 在样本 A 得梯度 \(g_A\),GPU1 在样本 B 得 \(g_B\)。若两卡各自 update,下一步权重分叉;All-Reduce 得到 \(g_A+g_B\),再除以 2(或按 loss reduction 约定缩放),两卡用同一 global-batch gradient 更新,权重继续一致。通信的是 gradient,不是“把两个完整模型合成一个更大模型”。

为什么不总用 DDP

DDP 简单且计算局部,但每个 rank 保存完整 model states;一个模型若单卡放不下,加 8 张卡仍然每卡放不下。FSDP/TP/PP 通过 sharding 解决容量,却引入更频繁、shape 不同的通信。反例也说明 aggregate HBM 不是天然共享内存。

先记住一个统一视角

并行策略不是一组互不相关的名词。它们都在同一组资源之间做交换:可以复制计算、保留更多显存,或把数据放到另一张 GPU 后再通信。后文每介绍一种策略,都只问三件事:分片了什么、通信了什么、通信发生多频繁。


通信原语

假设每张 GPU 都根据自己的 mini-batch 算出一份梯度。下一步不是“让 GPU 聊天”,而是一个具体的数据一致性问题:怎样让所有 GPU 获得聚合后的同一份梯度?分布式程序用 rank 标识一个参与者,用 world size 表示参与者总数,再用 collective 描述整组 rank 之间的数据变换。

先掌握三种核心 collective

操作直觉为什么现在需要它
All-Reduce聚合每个 rank 的输入,并把完整结果交给所有 rankDDP 需要所有副本得到一致梯度
All-Gather收集每个 rank 持有的分片,让所有 rank 暂时看到完整张量FSDP 在计算前需要恢复当前参数组
Reduce-Scatter先聚合,再把最终结果按 rank 分片FSDP 在反向后只保留各自负责的梯度分片

这三种操作并不是孤立 API。对带宽高效的 ring 实现,可以先让每个 rank 得到最终结果的一片,再交换这些分片:

\[\text{All-Reduce}=\text{Reduce-Scatter}+\text{All-Gather}\]

Reference · 完整 collective 对照表
操作输入输出典型用途
Broadcast1 个 rank 有数据所有 rank 有数据一对多分发
Scatter1 个 rank 有大张量所有 rank 各有一片分片分发
Gather所有 rank 各有一片1 个 rank 有完整张量集中收集
Reduce所有 rank 有数据1 个 rank 有聚合结果sum / max 等聚合
All-Gather所有 rank 各有一片所有 rank 有完整张量参数或 activation 收集
Reduce-Scatter所有 rank 有数据所有 rank 各有一片聚合结果梯度聚合并分片
All-Reduce所有 rank 有数据所有 rank 有聚合结果梯度同步
All-to-All每个 rank 有发往各 rank 的分片每个 rank 收到来自各 rank 的分片MoE token routing

从直觉到 cost model

如果每个 rank 都把完整梯度直接发送给所有其他 rank,链路会重复搬运大量数据。Ring 的直觉是让 rank 只和邻居交换分片:第一阶段边传边聚合,第二阶段再传播已经聚合好的分片。这样每个 rank 搬运的总字节数与张量大小同阶,而不是随 GPU 数线性增加。

Deep Dive · Ring collective 的每-rank 通信量推导

设 world size 为 \(N\),输入张量大小为 \(S\) bytes。忽略启动延迟和协议开销:

这是逻辑字节成本,不是 wall-clock 时间;后者还取决于启动延迟、链路竞争、拓扑和 overlap。


网络 substrate:这些 bytes 经过什么硬件?

到这里我们已经知道 collective 需要传输多少数据,但通信量只是逻辑成本。真正的 wall-clock 时间取决于这些 bytes 走的是节点内 scale-up fabric,还是经过 NIC 进入节点间 scale-out 网络。

GPU compute 连接 HBM 后分成两条路径:通过 NVLink 或 NVSwitch 到节点内 peer GPU,或通过 PCIe、HCA/NIC 和 InfiniBand/RoCE 到其他节点
PointBreaker 概念图:NVLink/NVSwitch 与 PCIe→HCA/NIC 是从 GPU/HBM 出发的两类路径,不应理解成跨节点流量必然先经过 NVLink。具体机器的 switch、rail 与 NUMA wiring 会更复杂。
Reference · 互联层次、职责与常见限制
层职责关键限制
NVLink / NVSwitch节点内 GPU 直连与交换 fabric;B200 NVLink 5 的每 GPU 聚合带宽可达约 1.8 TB/s只覆盖有限的 scale-up domain
PCIeGPU 连接 CPU 与 HCA/NIC 的主机 I/O 总线链路代际、lane 数与拓扑会形成 oversubscription
HCA / NIC把 GPU/主机流量送入节点间网络NIC 数量、rail 绑定和 NUMA/PCIe 亲和性
InfiniBand面向数据中心的低延迟、高带宽 fabric,原生支持 RDMA跨交换机跳数、拥塞与 rail 拓扑
Ethernet + RoCE在融合以太网上承载 RDMA,绕开传统 TCP/CPU 数据路径需要无损/拥塞控制工程,性能更依赖网络配置
GPUDirect RDMA远端 NIC 直接读写 GPU memory,避免 CPU bounce buffer需要 GPU、NIC、驱动与拓扑共同支持

Scale-up、scale-out 与并行策略放置

NCCL 为什么必须感知 topology

NCCL 的作用是把逻辑 collective 映射到实际硬件。它会探测节点、PCIe switch、NVLink/NVSwitch 与网络接口,再选择 ring/tree、分层 collective、channel 数和 GPU↔NIC 路径。

一阶时间模型可以写成:

\[T\approx L\alpha+\frac{B}{\beta_{\mathrm{effective}}}\]

其中 \(L\) 是需要串行完成的通信步骤,\(\alpha\) 是每步启动延迟,\(B\) 是字节数,\(\beta_{\mathrm{effective}}\) 是实际有效带宽。最慢链路、拓扑冲突、拥塞、协议效率和计算重叠都会改变它,因此相同的逻辑通信量不保证相同的运行时间。


Data Parallelism (DP)

现在把 collective 放回训练问题。最直接的扩展方式是让每张 GPU 保留同一个模型、处理不同样本;本地反向结束后,各 rank 的梯度必须重新一致,因此刚才的 All-Reduce 在这里第一次真正派上用场。

核心思想

每个 GPU 持有完整模型,处理不同的数据片。梯度通过 all-reduce 同步。

# Data Parallelism 流程
for each GPU in parallel:
    # 1. 获取不同的数据片
    x_local = data[rank * batch_size : (rank+1) * batch_size]

    # 2. 前向传播(本地计算)
    loss = model(x_local)

    # 3. 反向传播(计算本地梯度)
    loss.backward()

    # 4. 同步梯度(通信!)
    all_reduce(gradients)  # 所有 GPU 得到平均梯度

    # 5. 更新参数(所有 GPU 同步更新)
    optimizer.step()

通信开销

若梯度 payload 为 \(S\) bytes,\(N\)-rank ring All-Reduce 的每-rank 通信量约为 \(2(N-1)S/N\)。这说明 DDP 的主要通信量与参数数量同阶;实际时间仍需用有效 collective 带宽和实测 overlap 判断,不能直接除以某条链路的峰值带宽。

Implementation · Gradient accumulation 怎样减少同步频率

当目标 batch 无法一次放进单卡时,可以把它拆成多个 micro-batch,并只在最后一次 backward 触发梯度同步:

# DDP 示意:把一个大 batch 拆成多个 micro-batch
optimizer.zero_grad()
for micro_batch in micro_batches[:-1]:
    with model.no_sync():
        (model(micro_batch) / len(micro_batches)).backward()

# 最后一次 backward 不使用 no_sync(),触发 gradient bucket all-reduce
(model(micro_batches[-1]) / len(micro_batches)).backward()
optimizer.step()

DDP vs FSDP

DDP 解决了吞吐扩展,却没有解决模型状态冗余:每个 rank 仍保存完整参数、梯度和优化器状态。若这些状态本身放不下,就需要进一步分片,这正是下一讲 FSDP / ZeRO 的主题。

策略参数存储通信内存
DDP每个 GPU 完整副本All-Reduce 梯度高(冗余)
FSDP / ZeRO-3参数按组分片,需要时 All-Gather前向/反向 All-Gather + 梯度 Reduce-Scatter模型状态可近似 1/N;激活与临时 buffer 另算

Tensor Parallelism (TP)

DP 让不同 GPU 处理不同样本,但每张 GPU 仍要独立完成整层计算。如果单层权重或中间激活太大,就必须把一次矩阵计算本身拆开;这会把通信从“每步一次梯度同步”推进到“几乎每个 Transformer block 都要交换 activation”。

核心思想

把模型的宽度切分到多个 GPU——每个 GPU 只持有参数的一部分。

以 \(Y=XW\) 为例,按输出维切分 \(W\) 后,每个 rank 只计算一部分输出;按输入维切分时,每个 rank 得到部分和,随后需要 collective 合并。Megatron-LM 将两种切法配对,让部分中间结果尽量留在本地,只在 block 的关键边界通信。

关键代价:TP 的 activation collective 通常每层发生并位于关键路径,因此倾向部署在 NVLink/NVSwitch 等高带宽、低延迟的 scale-up domain。跨节点 TP 并非不可能,但是否划算取决于拓扑、消息大小和实现。

Guided simulation

Tensor Parallelism

逐步查看权重如何分片、每张 GPU 在本地计算什么,以及 collective communication 在何时发生。

Deep Dive · Megatron-LM 切分、通信模式与 4-GPU shape

Megatron-LM 的切分策略

对于 Transformer 的 FFN 和 Attention,有特定的切分方式:

FFN / SwiGLU:
# W_gate 与 W_up 按输出维切分(column parallel)
# 每个 rank 保留自己的中间分片,先在本地做 Swish(gate) * up
# W_down 按输入维切分(row parallel),最后合并各 rank 的部分和

Attention 的切分:
# W_q, W_k, W_v 按 head 切分(每个 GPU 负责部分 head)
# W_o 按输入维切分,最后合并各 rank 的部分和
#
# 基础 Megatron TP 常用 All-Reduce 合并;配合 sequence parallel 时,
# 相邻区域可改用 Reduce-Scatter / All-Gather 组合。

通信模式

Transformer block 的 TP 通信:
1. Attention:
#    - Q, K, V 计算: 无需通信(head 独立)
#    - W_o: All-Reduce

2. FFN:
#    - W_gate, W_up 和逐元素门控: 本地分片计算
#    - W_down 后: All-Reduce(或 sequence-parallel 变体中的 Reduce-Scatter)

总计: 每个 block 2 次 All-Reduce

切分示例(4 GPU)

假设 d_model = 4096, num_heads = 32
# 每 GPU: 8 个 head, d_model_per_gpu = 1024

# W_q: (4096, 4096) → 切成 4 份: 每 GPU (4096, 1024)
# W_k: (4096, 4096) → 切成 4 份: 每 GPU (4096, 1024)
# W_v: (4096, 4096) → 切成 4 份: 每 GPU (4096, 1024)
# W_o: (4096, 4096) → 切成 4 份: 每 GPU (1024, 4096)

Pipeline Parallelism (PP)

TP 在每层切宽度,因此通信频繁。另一种办法是让每张 GPU 持有一段连续层,只在 stage 边界交接 activation;通信更粗粒度,但不同 stage 可能互相等待,形成 pipeline bubble。

核心思想

把模型的深度切分到多个 GPU——每个 GPU 持有连续的几层。

Pipeline Parallel: 4 GPU, 16 层
GPU 0: layers 0-3
GPU 1: layers 4-7
GPU 2: layers 8-11
GPU 3: layers 12-15

Input → GPU 0 → activations → GPU 1 → ... → GPU 3 → Output

把 batch 拆成多个 micro-batch 后,不同 stage 可以同时处理不同 micro-batch。micro-batch 越多,越容易填满流水线;但调度、activation memory 和 stage balance 也更复杂。

Deep Dive · Pipeline bubble、1F1B 与通信量

Pipeline Bubble

朴素的 pipeline 有大量空闲时间——"bubble":

朴素 pipeline (4 stages, 1 batch):
Time →
GPU 0: [====L0-3====]  idle   idle   idle
GPU 1:  idle  [====L4-7====]  idle   idle
GPU 2:  idle   idle  [====L8-11===]  idle
GPU 3:  idle   idle   idle  [====L12-15==]

# 对理想化流水线,bubble time / useful compute ≈ (p-1) / m
# 若写成总时间占比,则约为 (p-1) / (m+p-1)
# p = pipeline stages, m = micro-batches
# 当 m >> p 时,bubble 可以忽略

1F1B Schedule(One Forward One Backward)

减少 bubble 的关键技术:

1F1B 在 warmup 后交替安排 forward 和 backward,而不是等所有 forward 结束后再统一 backward。它通常降低峰值 activation memory,但不会自动消除 warmup/drain bubble;效果仍取决于 stage 数、micro-batch 数、负载均衡和具体 schedule。

通信量

Pipeline Parallel 的通信:
每对相邻 GPU 之间传 activation;反向再传 activation gradient。
bf16 activation bytes = batch_size × seq_len × d_model × 2

# 例如: 一个 bf16 activation tensor,B=4, S=4096, D=8192
# = 4 × 4096 × 8192 × 2 bytes = 256 MiB

# 反向还要传 activation gradients;若叠加 TP/sequence sharding,张量大小会变化。
# P2P 通信可以与其他 micro-batch 的计算 overlap,但受 schedule 和依赖约束。

TP vs PP 的选择:

实际中:常见做法是先按高带宽域选择 TP degree,再用 PP/FSDP 让模型装得下,最后用 DP 扩展吞吐;这只是经验起点,不是固定拓扑。


Sequence Parallelism (SP)

TP 已经切分矩阵乘法,却不一定切分 LayerNorm、Dropout 等逐 token 区域的 activation。为了让这部分显存也随并行度下降,需要继续沿序列维分片。

动机

Tensor parallel 主要切分矩阵乘法的宽度维;LayerNorm、Dropout 等逐 token 区域仍可能复制 activation。Megatron 的 sequence parallel 用序列维分片这些区域,以继续降低 activation memory。

核心思想

  1. LayerNorm / Dropout 是逐 token 独立的,可以自然地沿序列维并行。
  2. 在 row-parallel 与 column-parallel 区域之间,用 Reduce-Scatter 和 All-Gather 改变 activation 的分片布局。
  3. 相关 activation memory 因而可近似随 TP degree 降低。

这不同于为超长上下文设计的 context parallel / ring attention;后者会分片 attention 所需的序列上下文,并引入另一组通信模式。


混合并行策略

没有一种并行策略同时满足“最省显存、最少通信、没有 bubble”。真实集群通常先根据拓扑选择高频通信的局部并行度,再用其他维度扩展容量和吞吐。

3D / 4D 并行

常见分解同时使用 DP(不同数据)、TP(层内矩阵切分)、PP(层间切分),并让 SP 配合 TP 分片部分 activation:

\[N_{\mathrm{GPU}}=N_{\mathrm{DP}}\times N_{\mathrm{TP}}\times N_{\mathrm{PP}}\]

并行策略选择

场景推荐策略原因
模型状态单卡可放下DDP / ZeRO-1实现简单,主要同步梯度或优化器状态
模型状态需要分片FSDP / ZeRO-3 或 TP+PP在参数通信、activation 通信和 bubble 间权衡
高带宽节点内域常用于 TP / EP这些策略的 collective 更频繁
超长上下文SP + context parallel 等分别解决非 TP activation 与 attention context 分片
Deep Dive · 128-GPU 配置的容量核算示例
训练 70B 模型,16 节点 (128 GPU):
TP = 8 (节点内)
PP = 4 (4 个 stages)
DP = 128 / (8 × 4) = 4 (数据并行)
总计: 8 × 4 × 4 = 128 GPU

# 仅参数 shard(未考虑 embedding tie、负载不均等):
# 70B / (TP 8 × PP 4) ≈ 2.19B 参数/GPU
#
# 但不能据此宣布“放得下”:梯度、master weights、Adam moments 是否再按 DP 分片,
# 以及 activation、temporary buffers、pipeline schedule 都会改变峰值内存。

本讲知识地图:每种策略在交换什么

并行策略通信类型每步通信量频率
DDP梯度 All-Reducering 每 rank 约 2(N-1)/N × gradient bytes通常每步,按 bucket 触发
Tensor Parallel每 block 约 2 个 activation collectives与 micro-batch × sequence × hidden × dtype 及 TP degree 同阶每层
Pipeline ParallelP2P activation / gradient每边界每 micro-batch 各传前向 activation 与反向 gradientstage 边界
FSDP / ZeRO-32× parameter All-Gather + gradient Reduce-Scatter若参数/梯度 payload 为 P bytes,大 N 下每 rank 约 3P每 FSDP unit

统一视角:

每种策略都在"计算冗余 vs 内存节省 vs 通信开销"之间做权衡。


知识检查

问题 1:Data Parallelism 每步的通信量是?

问题 2:Tensor Parallelism 的主要特点是什么?

问题 3:与先完成全部 forward 再 backward 相比,1F1B 的主要优势是什么?

问题 4:为什么 TP 通常优先用于节点内,而 PP 更常跨节点?

问题 5:Ring All-Reduce 常分解为哪两个 collective?


Concept Check · 两卡 DDP 新题

两卡从同一权重出发,分别得到 \(g_0=[2,4]\)、\(g_1=[6,0]\),global loss 取样本均值。同步后每卡用于更新的 gradient 是什么?

答案:\((g_0+g_1)/2=[4,2]\),两卡必须使用相同结果保持副本一致。

若直接拼成四维向量,你把“聚合同一参数的梯度”误解成了“拼接不同 batch 的数据”。

你现在应该能够解释

通往第 8 课

DDP 的梯度同步已清楚,但每卡状态复制仍是容量瓶颈;下一课逐项分片 optimizer、gradient 与 parameter,并追踪 FSDP 的峰值生命周期。

如果只能记住 5 件事

  1. 多 GPU 的核心新成本是 communication;先问交换什么数据,再讨论并行名称。
  2. Collective 是分布式训练的数据变换原语:DDP 主要使用 All-Reduce,FSDP 主要使用 All-Gather 与 Reduce-Scatter。
  3. 逻辑通信量不等于运行时间;bytes 必须结合 latency、effective bandwidth、topology 和 overlap 解读。
  4. TP 的通信高频且常在关键路径,因此倾向放在高带宽、低延迟的 scale-up domain;PP 通信更粗粒度,但要管理 bubble。
  5. 真实系统通常组合 DP / FSDP / TP / PP / SP:没有固定最优配置,选择取决于模型 shape、显存、batch、网络和实现。

延伸阅读