从一次深夜调试说起

上个月排查一个分布式训练卡顿问题,八卡机器上loss震荡得厉害。
nvidia-smi显示GPU利用率忽高忽低,nccl-tests测带宽正常,但实际训练速度只有理论值一半。
抓了nsys时间线一看,通信操作挤满了时间轴——问题不在硬件,而在通信模式选错了。

我们用了All-Reduce做梯度同步,但模型参数只有一部分需要同步,其余是局部参数。
全量All-Reduce导致大量冗余数据传输,把PCIe和网卡都堵死了。
改成分组通信+参数服务器混合模式后,训练速度直接翻倍。

今天我们就拆解三种核心通信模式:All-Reduce、All-to-All、参数服务器。
理解它们,才能在设计AI集群时不做“盲人摸象”。


All-Reduce:集体归约,但别滥用

All-Reduce是分布式训练最常见操作,每个节点提供一块数据,最终所有节点拿到全局归约结果。
NCCL里一行代码就能调用:

// 假设每个rank已有本地梯度local_grad
ncclAllReduce(local_grad, global_grad, count, ncclFloat32, ncclSum, comm, stream);
// 执行后所有rank的global_grad都是所有local_grad的和

关键点在这里
All-Reduce带宽消耗随节点数增长而增长,但好的实现(如Ring All-Reduce)会把通信量优化到 2*(n-1)/n * 数据量 左右,而不是 naive 的 n*(n-1)
不过即使这样,当模型参数量大时,通信时间依然可能压倒计算时间。

踩坑记录
有一次团队在BERT-large上直接用All-Reduce同步所有参数,包括那些只跟本地数据相关的embedding层。
结果通信时间占了迭代的70%。后来改成只同步Transformer层的梯度,速度立马上来。
别假设“所有参数都要全局同步”,先分析参数性质。


All-to-All:全交换,慎用

All-to-All更重口味:每个节点向所有其他节点发送不同数据,也从所有节点接收数据。
典型场景是MoE(Mixture of Experts)模型,不同专家分布在不同设备上,前向传播时需要交换门控路由结果。

# PyTorch示例,假设每个rank有要发送给其他rank的数据块列表
output = torch.distributed.all_to_all(send_tensors, recv_tensors)
# 执行后recv_tensors[i]收到来自rank i的数据

这个操作通信复杂度是O(n²),节点一多就炸。
去年调试一个64卡MoE模型,All-to-All时间直接让吞吐量崩掉。
后来改用分层分组All-to-All(先机内交换,再机间交换),才把通信开销压到可接受范围。

经验原则
除非模型结构明确需要(如MoE、某些3D并行),否则尽量避开All-to-All。
即使要用,也加两级通信:先在同一节点内交换,再跨节点交换,能利用NVLink等高带宽链路。


参数服务器:老将尚能饭否

参数服务器(PS)架构前几年被All-Reduce压过风头,但在混合同步、超大模型场景里又回来了。
它的逻辑很简单:一组服务器存全局参数,worker计算梯度并push给服务器,服务器更新后pull回新参数。

# 简化的PS通信模式
# worker端
gradient = compute_gradient()
ps_client.push(key, gradient)          # 推送梯度
new_param = ps_client.pull(key)        # 拉取新参数

PS的优势在于灵活

  • 支持异步、半异步、同步多种更新模式
  • 可以轻松做稀疏更新(只推送非零梯度)
  • 方便做容错和checkpoint

但PS容易成为瓶颈。
早期我们部署过一个20 worker的PS架构,中央服务器用4张GPU,结果服务器侧带宽打满,workers排队等更新。
后来改成多分片参数服务器(每个服务器负责一部分参数),瓶颈才缓解。

PS的适用场景

  1. 模型有大量稀疏更新(推荐系统、NLP稀疏Embedding)
  2. 集群异构明显(有些节点快、有些慢),用异步PS可避免快等慢
  3. 参数规模极大,需要分层存储(内存+SSD+网络存储)

混合模式才是现实

生产环境很少只用一种模式。
比如我们最近部署的千亿模型训练:

  • Transformer层用All-Reduce(带宽足够,逻辑简单)
  • 稀疏Embedding用参数服务器分片(只同步活跃embedding)
  • 输出层用All-to-All分组通信(因专家模型结构需要)

通信模式选择本质是带宽、延迟、灵活性的三角博弈。
All-Reduce带宽要求高但延迟稳定;PS灵活但可能引入瓶颈;All-to-All只在特定结构有用武之地。


几条血泪经验

  1. 先画通信时间线
    用nsys、tensorboard profiling抓一次训练迭代,看清楚通信操作在时间轴上的分布。很多时候问题不是“通信太慢”,而是“通信时机不对”,和计算没重叠好。

  2. 小规模测试放大规律
    在8卡上测通信模式,推算到256卡时是否还可行。很多通信算法的开销在节点少时不明显,一上规模就指数上升。

  3. 别忽视拓扑结构
    同一个通信操作,在DGX A100(NVLink全连接)和普通以太网集群上表现天差地别。设计通信模式时要考虑物理链路:机内高速链路优先通信,跨机流量尽量合并。

  4. 留可切换接口
    代码里把通信模式抽象成可配置策略,比如All-Reduce和PS可以做成插件。这样线上随时能根据负载切换,不至于发现性能问题时要重构整个训练框架。

  5. 监控通信健康度
    不仅监控带宽使用率,还要监控通信延迟分布、丢包重传(RDMA场景)、缓冲区溢出。我们曾在RDMA网络上遇到因为PCIe竞争导致的通信延迟抖动,排查了整整一周。


写在最后

通信模式没有银弹。
All-Reduce在均匀同步场景下简洁高效;参数服务器在异构、稀疏场景下更灵活;All-to-All则是特定结构下的必要代价。
实际系统往往是混合模式,按模型层次、参数类型、集群拓扑去匹配通信策略。

调试分布式通信就像调交响乐团——每个乐器(GPU)不仅要自己演奏准确,还要听别人的节奏。
通信模式就是乐谱,谱错了,再好的硬件也奏不出高效训练曲。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐