想象一下这样的场景:你是一家连锁奶茶店的运营总监,总店每天要把新配方同步给全国 200 家分店,同时分店也要把当天的销售数据汇总回总店。如果每家分店都单独跟总店通话,电话线会被挤爆,时间全浪费在等待上。聪明的做法是:先把数据压缩打包、让分店之间就近互传、边传边干活。这正是分布式训练中“梯度聚合通信优化”要解决的问题——在成百上千块 GPU 之间同步模型参数,就像在调度一场全国范围的奶茶配方同步。通信不畅,再快的计算芯片也得干等;通信优化得当,整个集群的算力就能被榨干。
对 AI 从业者而言,掌握梯度聚合通信优化意味着你能把分布式训练的硬件投入真正转化为训练速度,这是从“会用框架跑通”到“能调优集群性能”的分水岭,也是面试高级分布式训练工程师的核心考点。
可视化演示
展示梯度量化(FP32→INT8)、稀疏化Top-K选择和PowerSGD低秩分解三种通信优化策略,帮助理解分布式训练中减少梯度同步通信量的核心方法。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+captions.quantize+'
通信瓶颈分析
继续我们的奶茶店故事。假设总店要求全国 200 家分店每天打烊前把销售数据汇总到总部,同时把新配方下发到每家店。如果 200 家店都直接打电话给总部,总部的电话会占线一整天。更糟糕的是,每个分店既要上报数据又要接收配方,通信量是 分店数 × 数据量。在分布式训练里,每个 GPU 就是一家分店,模型参数就是配方,梯度就是销售数据——每训练一步都要全局同步一次,通信开销随 GPU 数量线性增长,很快成为整个系统最粗的瓶颈。
在分布式训练中,梯度同步是数据并行里 DataLoader 之后的必经环节。每个 GPU 算出一份梯度,大家要通过 AllReduce 聚合算法,把所有梯度求和取平均,再广播回每个 GPU。最简单的实现是“参数服务器”模式——所有 GPU 把梯度发给一个中心节点(相当于所有分店都打电话给总部),中心聚合后再广播回去。但这种方式的通信量高达 2 × N × 梯度大小,N 是 GPU 数量。当 N 从 8 涨到 64、256,通信时间呈线性增长,计算时间却不涨,最终通信占比趋近 100%,集群越大效率越低——这就是“通信墙”。
好消息是,工程师们发明了 Ring-AllReduce 环形聚合算法:让 GPU 排成一圈,每个 GPU 只跟左右邻居交换数据,把梯度切成 N 份,像击鼓传花一样传一圈。这样每个 GPU 的输出通信量恒定为 2 × 梯度大小,与 GPU 数量无关。这就好比分店之间手递手传优盘,而不是所有人都往总部跑——200 家店也只需要传一圈,而不是 200 趟往返。
- 通信轮数参数服务器模式需要 N-1 次上传加 N-1 次下发,共 2(N-1) 轮传输
- 单轮数据量每次传输的数据是完整的梯度张量,大小为 M(模型参数总量)
- 总耗时轮数乘以单轮耗时,得到总通信时间
梯度压缩原理
好的,通信量只跟 GPU 数有关的问题解决了,但还有一个更朴素的问题:传的数据本身能不能瘦身?回到奶茶店场景,如果总店要下发 100MB 的完整配方文档,但每家店其实只需要改动几个关键参数,能不能只传差异?或者把文档从高精度 PDF 转成低精度图片,10MB 就够?梯度压缩做的就是这件事——让通信的数据包变得更小。
梯度压缩的两大主流手段是低精度量化和梯度稀疏化。低精度量化是把 32 位浮点数(FP32)梯度压缩成 8 位整数(INT8)甚至 1 位二进制,直接减少 4 倍乃至 32 倍的数据量。梯度稀疏化则更激进——研究发现,梯度矩阵里大多数元素接近零,真正有信息量的只有一小部分(比如前 1% 最大绝对值)。我们只把这 1% 传出去,其余全部视为零。这相当于 200 家分店每天只报告“销售额超过 1 万的门店”而非全部流水。
需要注意,压缩是有损的。直接丢弃梯度信息会导致模型收敛变慢甚至发散。业界标准做法是加误差补偿机制:每次压缩前,把上次压缩丢失的误差加回来,再一起压缩。相当于快递公司在丢件后,下一班车补发一件——确保总有一个完整的“梯度”最终到达。误差补偿让压缩后的训练效果逼近未压缩的基线,这也是 1-bit SGD、PowerSGD 这类算法能落地的关键。
- 原始数据量原始梯度有 N 总参数个元素,每个 FP32 占 32 位
- 压缩后数据量稀疏化后只有 n 非零个元素,每个量化到 q 位宽
- 压缩率两者相除得到最终压缩比
通信计算重叠
现在我们有了更小的数据包,但如果发送数据包时 GPU 干等着,时间还是浪费。回到奶茶店:如果分店店长一边接电话报数据,一边还能同时指挥店员调奶茶,那沟通成本就被摊薄了。这正是通信计算重叠(Communication-Compute Overlap)的智慧——让 GPU 在等待通信完成的同时继续做计算,把“先算完再通信”的串行模式变成“边算边传”的流水线。
实现重叠的技术叫梯度分桶(Gradient Bucketing)。反向传播(Backward)是按层从后往前依次计算梯度的,不需要等全部层算完再统一通信。我们可以把梯度按层切成多个桶,每算完一个桶就立刻开始传输这个桶,不等后面的层——计算和通信在时间轴上交错进行。就好比奶茶店不是等所有分店都报完才统一处理,而是每接完一个电话就先处理这一单的数据。
实际操作中,桶的大小决定了重叠的粒度。桶太小,通信发起太频繁,延迟开销变大;桶太大,计算和通信的重叠度不够。PyTorch 中通过 torch.distributed.nn.all_reduce 的 bucket_cap_mb 参数控制,典型值从 5MB 到 25MB 不等。配合 gradient_as_bucket_view 设置为 True,还能省去梯度拷贝的内存开销。实践中,把桶大小调到接近单层梯度大小往往能在吞吐和延迟之间找到平衡点。
- 计算时间反向传播计算所有梯度所需的时间
- 通信时间传输所有梯度桶所需的时间
- 总耗时理想重叠下取两者最大值
分层通信架构
解决了传多少和什么时候传的问题,接下来该考虑传的路线了。奶茶店扩张到全国后,总店发现跨省调货很贵、很慢,但省内调货便宜又快捷。于是改为两级调度:省内分店先互相调货,再由省级仓库跟其他省仓库对接。分层通信架构就是这个思路——完全尊重硬件的物理拓扑,在最快的设备上用最快的互联方式通信。
现代 GPU 服务器有清晰的层级结构:单机内 8 张 GPU 通过 NVLink 高速互联(带宽可达 900GB/s),多台机器通过 RDMA 网络(如 InfiniBand 200Gb/s)组网,网络交换机再分为叶脊两层。层级化 AllReduce 分为两步:机内先做一次快速聚合,选出一个“队长 GPU”代表整机,然后队长之间走网络做机间聚合,最后把结果广播回机内所有 GPU。这就像省级仓库之间只交换“省级汇总数据”而非每家的原始流水。
这就是 NVIDIA 的 NCCL 库默认做的事——它会自动检测 GPU 的拓扑结构,选择最优的通信路径。比如 nvlink 模式下机内通信不走 PCIe 而走 NVLink;IB 模式下机间用 InfiniBand 而非以太网。工程师要做的,是理解这些模式背后的拓扑逻辑,在集群配置时确保 NCCL_P2P_LEVEL 和 NCCL_SOCKET_IFNAME 等环境变量指向正确的设备——让 AllReduce 跑在最快的物理链路上。
- 机内通信M 大小的梯度在 NVLink 上传输,按机器内 GPU 数 k 分片后每片只需传部分数据
- 机间通信每台机器只需传一个聚合结果,总量仍为 M(但只传一次而非 K 次)
- 总时间两步之和
拓扑感知调度
拓扑不仅影响通信库内部的路径选择,还影响另一个看似无关的决策:把 GPU 的 rank 编号怎么排。回到奶茶店场景,如果总店派发编号时随机乱排,可能会让上海分店和乌鲁木齐分店频繁直接通信——明明有南京、西安分店可以中转,却偏要走最贵的跨省线路。拓扑感知调度要做的就是让 rank 编号与物理位置对齐,让频繁通信的进程尽量落在同一台机器或同一个交换机下。
弹性 Horovod 和 PyTorch DDP 的 torch.distributed.init_process_group 都支持自定义 rank 映射。假设你有 4 台机器 × 8 卡 = 32 块 GPU,合理的 rank 分配应该让 0-7 号落在机器 1,8-15 落在机器 2,以此类推。这样 Ring-AllReduce 的相邻 rank 大概率在机内,走 NVLink 而非跨机网络。如果 rank 打乱,环上相邻节点可能跨机器,通信延迟成倍增加。大模型训练框架(如 Megatron-LM)甚至会按“张量并行 → 流水线并行 → 数据并行”的顺序排布 rank,把通信量最大的并行维度放在最快的硬件层级上。
实践层面,配置分布式训练作业时要用 CUDA_VISIBLE_DEVICES 和节点列表的排序控制 rank 映射。例如在 SLURM 集群中,确保同一节点的 GPU 获得连续的 rank 编号;在 Kubernetes 中,通过 hostNetwork 和按节点分配 pod 的顺序来保证这一点。此外,NCCL 的 NCCL_TOPO_DUMP_FILE 可以打印拓扑文件,帮你确认通信路径是否符合预期——不少“网络慢”的假象,其实是 rank 映射错位导致的。
- 链路集合所有物理通信链路组成集合 E,包括 NVLink、PCIe、RDMA、以太网和交换机上行
- 承载量rank 分配决定了每条链路上流过的数据量 D_e
- 总延迟每条链路上数据量除以带宽,再累加
超参调优实践
所有的优化手段最终都要落到一组具体的配置上。就像奶茶店有了配送网络、路线规划,还要调试“每辆车装多少货”“多久发一班车”这些参数。梯度聚合通信的超参调优,就是找到让计算和通信最平衡的那组参数,这套组合拳打下来,训练速度往往能翻倍。
最核心的超参是桶大小(bucket size)。它同时影响通信计算重叠的粒度和梯度压缩的批次大小。桶太小,压缩的批效应不明显,通信次数增加;桶太大,延迟让重叠失效。经验法则是:桶大小 ≈ 单层梯度大小 × 4,从默认值出发做网格搜索,观察训练吞吐(samples/s)的变化。另一个关键参数是压缩阈值——梯度稀疏化时保留多少比例的 Top-k 元素。保留 1% 时通信量最小但需误差补偿配合;保留 10% 时通信量下降有限但精度损失小。实践中从 1% 起步,配合动态阈值调整。
一个典型的调优流程是:先用默认配置跑通基线,记录吞吐和通信占比;然后逐项调桶大小、压缩比例、NCCL 环境变量,每调整一项就重新测量;最后对比调优前后的吞吐曲线。我见过一个实际案例:某 64 卡集群在开启 gradient compression + bucket 调优 + 拓扑感知 rank 映射后,训练吞吐从 1200 samples/s 提升到 3100 samples/s,提升 158%——而这些优化没有增加一块 GPU。通信优化的魔力,正在于它从系统层面放大了你已有的算力。
- 总时间公式每步训练耗时近似为计算时间和通信时间的最大值
- 吞吐量每步处理的样本数除以每步耗时
- 性能调优目标让 T_compute ≈ T_comm,避免某一方成为短板
AI 应用场景
场景一:千卡级大语言模型预训练
阶段:分布式训练性能调优
解决的问题:训练 7B 参数的大模型需要 128 张 A100,若用默认 AllReduce,每步通信耗时超过计算耗时 3 倍,GPU 利用率不足 30%。传统做法盲目增加 GPU 数量,但通信瓶颈让扩容收益递减。
具体案例:某大模型团队使用 Megatron-LM 训练 13B 模型,通过三层优化——梯度压缩(1-bit Adam + 误差补偿,通信量降 32 倍)、通信计算重叠(桶大小调至 16MB,反向传播边算边传)、层级化 AllReduce(机内 NVLink + 机间 RDMA 分级聚合)——将 GPU 利用率从 32% 提升至 57%,训练周期从 45 天缩短至 21 天。这就像奶茶店同时用了压缩配方、边做边传、分级配送,整体效率翻倍。
场景二:小批量在线学习推荐系统
阶段:实时推理与增量训练
解决的问题:推荐系统需要分钟级更新模型,但梯度同步延迟让更新周期长达 1 小时。稀疏化压缩(保留 Top-5‰ 梯度)让通信量骤减,使百卡集群的同步周期进入分钟级。
具体案例:某短视频推荐团队用 PowerSGD 低秩压缩替代全量梯度同步,在保证 AUC(模型准确度指标)不降的前提下,将梯度通信量压缩 97%,模型更新频率从每小时提升到每 5 分钟。新鲜的用户反馈能更快融入模型,点击率提升了 12%。
Python 演示
import numpy as npimport time def simulate_gradients(param_count=1000000, seed=42): """模拟一个百万参数模型的梯度张量""" rng = np.random.default_rng(seed) return rng.standard_normal(param_count).astype(np.float32) def topk_sparsify(grad, ratio=0.01): """梯度稀疏化:只保留绝对值最大的 topk 比例元素""" k = max(1, int(grad.size * ratio)) threshold = np.sort(np.abs(grad))[-k] mask = np.abs(grad) >= threshold sparse_indices = np.where(mask)[0] sparse_values = grad[sparse_indices] return sparse_indices, sparse_values def quantize(grad, bits=8): """低精度量化:把 FP32 梯度量化到 int8 再还原""" grad_min = grad.min() grad_max = grad.max() scale = (grad_max - grad_min) / (2**bits - 1) quantized = np.round((grad - grad_min) / scale).astype(np.int8) return quantized, scale, grad_min def error_feedback_compress(grad, error_buffer, ratio=0.01): """误差补偿版压缩:叠加上次误差后再压缩""" compensated = grad + error_buffer indices, values = topk_sparsify(compensated, ratio) new_error = compensated - values # 丢失的误差 return indices, values, new_error def allreduce_time(grad_size, bandwidth_gbps, num_gpus): """模拟 AllReduce 耗时(参数服务器模型 vs Ring 模型)""" data_bytes = grad_size * 4 ps_seconds = 2 * (num_gpus - 1) * data_bytes / (bandwidth_gbps * 1e9) ring_seconds = 2 * (num_gpus - 1) / num_gpus * data_bytes / (bandwidth_gbps * 1e9) return ps_seconds, ring_seconds if __name__ == "__main__": print("=== 梯度聚合通信优化模拟 ===\n") grad = simulate_gradients() print(f"原始梯度: {grad.nbytes / 1e6:.1f} MB, 参数数: {grad.size}\n") print("--- 压缩效果测试 ---") indices, values = topk_sparsify(grad, ratio=1) comp_bytes = indices.nbytes + values.nbytes print(f"1% 稀疏化: {comp_bytes / 1e6:.2f} MB (压缩率 {grad.nbytes / comp_bytes:.1f}x)") quantized, scale, gmin = quantize(grad, bits=8) q_bytes = quantized.nbytes print(f"INT8 量化: {q_bytes / 1e6:.2f} MB (压缩率 {grad.nbytes / q_bytes:.1f}x)\n") print("--- 误差补偿效果 ---") error_buf = np.zeros_like(grad) indices, values, err = error_feedback_compress(grad, error_buf, ratio=1) print(f"第一次压缩误差能量: {np.sum(err**2):.2f}") _, _, err2 = error_feedback_compress(grad * 0.9, err, ratio=1) print(f"第二次带误差补偿误差能量: {np.sum(err2**2):.2f}\n") print("--- AllReduce 通信耗时对比 (25GB/s 带宽) ---") for ngpus in [8, 32, 128]: ps_t, ring_t = allreduce_time(grad.size, 25, ngpus) print(f"{ngpus:4d} GPUs | 参数服务器: {ps_t*1000:8.2f} ms | Ring: {ring_t*1000:8.2f} ms | 提速 {ps_t/ring_t:5.1f}x")
simulate_gradients(param_count=1000000)模拟一个百万参数的模型梯度,作为测试基准topk_sparsify(grad, ratio=0.01)只保留绝对值最大的 1% 参数作为有效梯度,其余视为零,直接减少 99% 通信量quantize(grad, bits=8)用 min-max 方式把 FP32 映射到 INT8 范围,降低 4 倍位宽error_feedback_compress(grad, error_buffer, ratio=0.01)先把上次压缩丢掉的误差加回来再压缩,避免误差累积allreduce_time(grad_size, bandwidth_gbps, num_gpus)对比参数服务器模式和 Ring-AllReduce 的通信耗时,体现环形聚合的优势
常见误区
误区“训练慢就是带宽不够,买 400G 的 InfiniBand 卡就完事了,不需要搞什么压缩和重叠。” 很多团队把性能问题简单归咎于硬件,砸钱换网卡却发现提升有限。
正解通信瓶颈不只是带宽问题,更是利用效率问题。就像奶茶店生意慢不只是电话线路窄,还可能是所有分店同一时间打进来(突发流量)、通话中干等(无重叠)、传了过多冗余信息(未压缩)。软件层的压缩、重叠、拓扑感知往往能带来数倍提升,而这些优化是在现有硬件上免费的。
为何常见:硬件指标直观可见(带宽数字大),而软件调度和算法的收益看不见摸不着,容易被忽略。事实上,硬件升级通常只带来线性提升,算法优化却可能是指数级的。
误区“梯度压缩会损失精度,模型效果变差谁负责?还是老老实实传全量梯度吧。” 很多工程师因此拒绝任何压缩方案。
正解误差补偿机制解决了这个问题——它把每次压缩丢失的信息在下一次补偿回来,让有损压缩的最终收敛效果逼近无损基线。1-bit Adam、TopK 稀疏化+误差补偿都已在生产环境大规模验证。关键在于找到压缩率与收敛速度的平衡点,而非一刀切禁用。就像快递丢件不可怕,可怕的是没有补发机制。
为何常见:对“有损”的直觉警惕让工程师避之不及,却没意识到误差补偿是把有损转化为近似无损的系统性设计。
误区“我用了 PyTorch DDP,它已经帮我做好了通信重叠,我啥都不用配。” 或者“想重叠?手动开个线程传数据就行。”
正解DDP 默认确实做了层级的 allreduce 与反向传播重叠,但重叠的效果取决于桶大小和调度策略——默认配置不一定最优。手动开线程做重叠极易引发线程安全和 CUDA 设备竞争问题,不推荐。正确的优化方式是理解 DDP 的重叠机制,通过 bucket_cap_mb 和 gradient_as_bucket_view 等参数控制粒度。就像餐厅后厨的动线设计,光把食材和厨师放一起不够,还要规划好流程。
为何常见:框架封装让开发者有了“什么配置都是最优”的错觉,而框架的默认值是通用场景的妥协方案,不是针对你的模型、集群的定制优化。
面试题
Q1. 在分布式训练中,通信墙问题指的是什么?
- A.GPU 之间网线连接不稳定
- B.通信时间随 GPU 数量增长,最终超过计算时间← 正确答案
- C.数据中心防火墙阻止了训练通信
- D.梯度数据在传输中经常丢失
答案:B。通信墙指随着 GPU 数量增加,AllReduce 通信时间线性增长而计算时间不变,通信占比趋近 100%,集群扩展效率骤降。选项 B 准确描述了这一现象。A/C/D 都是对“通信墙”字面意思的误解,与分布式训练中的瓶颈本质无关。
Q2. Ring-AllReduce 相比参数服务器模式的核心优势是什么?
- A.不需要网络连接,纯本地计算
- B.通信总量与 GPU 数量无关,恒定为 2 倍梯度大小← 正确答案
- C.每个 GPU 只需要跟一个邻居通信,不需要同步
- D.可以完全消除通信,只需本地计算
答案:B。Ring-AllReduce 让每个 GPU 只与相邻节点交换梯度分片,总通信量恒为 2 倍梯度大小,与 GPU 数无关(原本是 2(N-1) 倍)。B 正确。A/C 是错误描述——通信依然存在且需要同步;D 完全错误,梯度聚合的语义决定了必须有跨卡通信。
Q3. 梯度压缩的两种主流手段是什么?
- A.低精度量化:FP32→INT8 减少位宽← 正确答案
- B.梯度稀疏化:只传输 Top-k 重要梯度← 正确答案
- C.增大 BatchSize 减少通信次数
- D.换用更高速的网络硬件
答案:AB。压缩的本质是在数据层面减小传输量——量化降低每个元素的位宽(A),稀疏化减少传输的元素数量(B)。C 是调整训练策略但没压缩单次通信量;D 是硬件优化而非梯度压缩手段,与题目问的“压缩手段”不符。
Q4. 误差补偿机制的核心作用是什么?
- A.把有损压缩丢失的梯度信息在后续步骤中补回来← 正确答案
- B.减少反向传播的计算量
- C.增加梯度的大小避免梯度消失
- D.替代优化器直接更新模型参数
答案:A。误差补偿将每次压缩丢弃的信息保存下来,加入下一次压缩的输入,保证信息不永久丢失,使有损压缩逼近无损收敛。A 正确。B 混淆了压缩与计算优化;C 与梯度消失问题无关;D 张冠李戴,误差补偿是通信层的机制,不是参数更新方法。
Q5. 梯度分桶(Gradient Bucketing)在通信计算重叠中的作用是?
- A.把梯度随机打乱以增加安全性
- B.将梯度按层切分为多个桶,算完一个桶就开始传输,实现边算边传← 正确答案
- C.减少梯度总量,只保留最后一个桶
- D.把多个 GPU 的梯度合并到一个桶中同步
答案:B。反向传播逐层计算梯度,梯度分桶允许每算完一个桶就立即发起通信,而非等全部层算完。这样通信时间被“隐藏”在后续层的计算时间里。B 准确描述。A 无意义,C 错误(桶是切分而非丢弃),D 混淆了分桶与跨卡聚合。
Q6. 关于层级化 AllReduce(Hierarchical AllReduce),下列说法正确的是?
- A.机内 GPU 通过 NVLink 先做局部聚合← 正确答案
- B.每台机器只传一份聚合结果参与机间通信← 正确答案
- C.需要额外的中心服务器来聚合所有数据
- D.机间通信数据量不再随 GPU 总数放大 N 倍← 正确答案
答案:ABD。层级化策略让机内走 NVLink 快速聚合(A),各机选出代表 GPU 传聚合结果(B),机间只传机器数量的数据而非 GPU 数量的数据(D),消除了 N 倍放大。C 错误——层级化 AllReduce 不需要中心服务器,仍是去中心化的环形或树形聚合。
Q7. 拓扑感知调度中,rank 编号的合理分配方式是?
- A.随机分配,保证公平性
- B.按物理拓扑就近分配,同一机器的 GPU 获得连续 rank← 正确答案
- C.按 GPU 的算力从高到低排序分配
- D.按训练数据的标签分布排序
答案:B。rank 编号影响 Ring-AllReduce 中相邻节点的物理位置。同机 GPU 连续编号让环上相邻节点在机内,走 NVLink 而非跨机网络,降低延迟。B 正确。A 随机分配会打乱局部性;C 与算力无关;D 与数据分布无关——拓扑感知关注的是硬件链路而非数据流。
Q8. 误差补偿的直观比喻最恰当的是?
- A.快递丢件后,下一班车补发一件← 正确答案
- B.奶茶店增加收银台数量
- C.用更大的货车装更多奶茶原料
- D.打电话时提高音量让声音更清楚
答案:A。误差补偿的本质是“这次丢的数据下次补上”,与快递丢件后补发的逻辑一致。A 准确对应。B 是增加并行度(对应更多 GPU),C 是增大带宽,D 是提高信号强度——都与误差补偿的语义无关。
Q9. 关于梯度聚合通信优化,以下哪些做法属于通信计算重叠的实现手段?
- A.反向传播边算梯度边发已完成的梯度桶← 正确答案
- B.使用低精度量化减少单次通信数据量
- C.调整 bucket_cap_mb 控制桶大小以平衡粒度与延迟← 正确答案
- D.把梯度计算放到 CPU 上执行
答案:AC。重叠的核心是让通信与计算同时进行——边算边传(A)和调整桶大小控制重叠粒度(C)都是直接手段。B 是压缩策略,减少数据量但不改变时间重叠关系;D 是计算迁移,反而可能增加设备间拷贝开销,不属于通信计算重叠。
Q10. AllReduce 在数据并行中的功能是什么?
- A.把数据集平均分配给每个 GPU
- B.将所有 GPU 算出的梯度求和取平均并广播回每个 GPU← 正确答案
- C.把模型参数复制到 CPU 内存
- D.减少反向传播的浮点运算量
答案:B。数据并行中,每个 GPU 有完整模型副本和不同数据分片,各自算出局部梯度后必须通过 AllReduce 聚合(求和/平均)再广播,保证所有 GPU 同步更新到相同参数。B 准确描述。A 是数据加载阶段的工作,C 是模型存储,D 是计算优化——均与 AllReduce 功能不符。