大模型训练一次迭代(Iteration)的微观真实流程
假设你有一张 8 卡 GPU(数据并行),模型很大。
第 0 步:数据加载与广播(Broadcast)
-
做法:DataLoader 将不同的数据(Micro-Batch)分发到不同的 GPU 上。如果是 DP(数据并行),这步几乎零耗时。
第 1 步:前向传播(Forward Pass)——“留着证据”
-
做法:输入张量逐层流过 Transformer。每一层都会产生大量的激活值(Activations)。
-
关键动作:必须显存驻留。这些激活值会保存在 GPU 显存中,绝不丢弃。
-
为啥:因为反向计算梯度时,
dL/dW依赖于前向的输入X和权重W。这是训练显存爆炸的根本原因。
第 2 步:损失计算(Loss Calculation)——“点燃导火索”
-
做法:计算
Loss标量。 -
物理触发:执行
Loss.backward()命令。这一刻,梯度计算的“导火索”被点燃,自动微分引擎(Autograd)开始工作。
第 3 步:反向传播 + 重叠式 All-Reduce(业界精髓,本文重点)
这不再是一个顺序步骤,而是一个交叉并行的流水线过程。核心机制是 梯度分桶(Bucketing)。
物理发生的过程是这样的(按时间线):
-
反向计算从最后一层开始:引擎开始计算最顶层(Layer N)的权重梯度
dL/dW_N和输入梯度dL/dX_N。 -
梯度进入桶(Bucket):算出的
dL/dW_N不会立刻发往网络,而是暂存在一个显存缓冲区(Bucket)里。 -
桶满触发通信(关键转折):
-
反向计算继续往前(Layer N-1, N-2...)。
-
当这个 Bucket 被填满(比如积攒了 128MB 的梯度数据)时,立即触发该 Bucket 的异步 All-Reduce(通过 NCCL)。
-
与此同时:GPU 的计算单元(CUDA Core) 并没有停下来等网络,而是立即开始计算下一层(Layer N-3)的梯度。
-
-
流水线并行:当反向传播进行到第一层(Layer 1)时,早先触发的那批 Bucket 的 All-Reduce 可能已经通过网络传完了。
最终结果:当
Loss.backward()这行代码执行完毕的那一刻,所有 GPU 上的梯度已经是全局平均后的结果了。通信延迟被完美隐藏在了计算时间之内。
第 4 步:优化器更新(Optimizer Step)——“就地正法”
-
等待网络收尾(同步点):
optimizer.step()会先调用torch.cuda.synchronize()或等待 NCCL 流事件,确保上一步触发的所有异步 All-Reduce 确实全部完成(虽然极少需要等待,因为计算比通信慢)。 -
梯度裁剪:对已经同步好的 FP32 梯度进行 Norm Clipping。
-
更新主参数(FP32):AdamW 优化器利用动量和方差,计算出
ΔW,更新 FP32 的主权重。 -
强转回低精度:将更新后的 FP32 权重 cast 回 FP16/BF16,供下一次前向传播使用。
图解对比(面试时可以在白板上画出来)
-
朴素理解(过时):前向 -> Loss -> 反向(全算完)-> All-Reduce -> 更新。
-
业界真实(现代):前向 -> Loss ->【反向算Layer N(通信桶满)-> All-Reduce异步启动 + 同时反向算Layer N-1 -> All-Reduce进行中 + 同时反向算Layer N-2 -> 反向结束(此时All-Reduce已完成)】-> 更新。
面试终极话术(必杀技)
当面试官问“分布式训练怎么做”时,直接抛出这套流程:
“面试官,现代的分布式训练已经不再是‘先算后传’了。我们利用 Autograd Hook 机制,将 All-Reduce 嵌入到了反向传播的梯度计算流水线中。
具体做法是梯度分桶(Bucketing):每算完几层参数的梯度,桶满了就立即通过 NCCL 发起异步通信,同时 GPU 计算单元不停歇,继续反向传播前面的层。
这样做的目的是掩盖通信延迟。只要单卡的计算时间(Compute Time)大于或等于通信时间(Communication Time),那么 All-Reduce 相对于训练就是零开销(Zero Overhead)的。这才是 Megatron 和 PyTorch DDP 能支撑万卡集群训练的核心工程基石。”

浙公网安备 33010602011771号