你写了一个 allreduce(grads),背后发生了多少事情?hccl 的架构会告诉你答案。
框架里调用 allreduce 跟点一份外卖一样简单——一行代码,等结果。但如果你是一个在做分布式训练的工程师,迟早会有一天发现:同样的代码,八卡跑得飞起,十六卡就开始鬼打墙——吞吐不涨反跌,GPU 利用率掉到 60% 以下。
查下来,十有八九是通信在拖后腿。
hccl(昇腾集合通信库)就是 CANN 生态里专门负责分布式通信的那个模块。它把多张昇腾 NPU 卡之间的数据传输封装成标准的集合通信原语——AllReduce、AllGather、ReduceScatter 等——让框架调用起来像本地操作一样简单。
但「封装成简单接口」和「实际处理底层通信」之间,有很长的路要走。
链接:https://atomgit.com/cann/hccl
一、hccl 在 CANN 架构中的位置
从 CANN 五层架构看,hccl 位于第4层(执行层),与 Runtime、DVPP、Graph Executor 同级。
第3层:编译层(图编译器 / ATC) ↓ 编译后的执行图 第4层:执行层 ├─ Runtime(运行时管理) ├─ Graph Executor(图执行器) ├─ HCCL(集合通信库) ← 在这里 ├─ DVPP(数字视觉预处理) └─ AIPP(AI 预处理) ↓ 第5层:基础层(驱动 / 内存管理)hccl 的上游是框架适配器层——PyTorch、MindSpore 等框架通过适配层调用 hccl 的接口。hccl 启动后,依赖下层的基础层(驱动、内存管理)来完成实际的通信操作。
二、设计理念:两步走的通信抽象
hccl 的架构设计围绕一个核心问题展开:怎么让上层框架不用关心底层网络拓扑,同时又不牺牲性能?
答案是「两步走」的抽象:
第一步:逻辑通信域(Communicator)
框架层面,hccl 暴露一个 Communicator 对象。你只需要告诉它「有多少张卡,我怎么访问它们」,hccl 会自动把这些卡组织成一个逻辑通信域。框架调用 hcclAllReduce 时,只需要指定 Communicator 和要通信的显存地址。
第二步:物理算法选择
hccl 内部会根据卡之间的拓扑关系(是否同机、是否同交换机、链路带宽等),自动选择最优的通信算法。这不是简单的「同机用 Ring,跨机用 Tree」——实际的算法选择远比这个复杂。
这种抽象的前半段(逻辑通信域)保证了接口统一性,后半段(自动算法选择)保证了性能。两者缺一不可。
三、核心通信原语
hccl 实现了标准集合通信原语,每个原语都针对 NPU 显存做了优化:
AllReduce——所有卡求和,每张卡拿到相同的结果。这是分布式训练中最常用的操作(梯度同步)。hccl 的 AllReduce 实现会根据数据量和卡数自动选择 ReduceScatter + AllGather(小数据量)或直接 Ring AllReduce(大数据量)。
AllGather——每张卡把自己的数据广播给所有卡,最后每张卡都拿到全部数据。常用于模型并行中收集各卡的计算结果。
ReduceScatter——先求和再按卡切分。这是 AllReduce 的一半操作,单独使用时可以减少内存占用。
Broadcast——一张卡的数据发给所有其他卡。常用于模型初始化时分发参数。
hccl 对这些原语的实现有统一的优化框架:通信与计算重叠、多流并发、自适应分段(把大数据切成小段并行传输)等。
四、关键机制:拓扑感知的算法选择
hccl 的一个核心竞争力是拓扑感知。选对了算法,通信效率能差好几倍。
举个具体场景:你在做数据并行训练,八张卡都在一台 Atlas 服务器上。这八张卡通过机内的高速互连(HCCS,昇腾高速缓存一致性互联)两两相连,带宽远超机间网络。hccl 检测到「八卡同机」这个拓扑后,会选择 Ring 算法——数据在八卡之间沿着环流转,每张卡只跟前后的邻居通信,总通信量等于 2(N-1)/N × 总数据量。
但如果十六张卡分布在两台服务器上(各八张),情况就变了。两台服务器之间通过 RoCE 网卡连接,带宽远低于机内 HCCS。hccl 会选择两级通信算法:先在每台服务器内部做 Ring AllReduce(充分利用 HCCS 高带宽),然后在服务器之间只交换部分聚合数据(减少跨机通信量)。
选择不同算法带来的性能差异很直接——在一些 benchmark 里,算法选择最优 vs 最差,同一个 AllReduce 操作的延迟可以相差3-5 倍。
五、通信与计算重叠
当你写下allreduce(grads)时,一个隐性的性能杀手是等待——你只在 allreduce 完成了之后才能开始下一轮计算。但如果通信和计算能同时跑,你在发数据的同时就能开始做下一层的前向计算。
hccl 通过异步通信 + 多流并发来实现这个重叠:
// 伪代码:通信与计算重叠autostream=hccl_create_stream(device,queue_id);// 在第0层算完之后,立即发起梯度 allreduce(异步)hccl_allreduce_async(layer_0_grad,layer_0_grad,size,dtype,HcclSum,comm,stream);// 不等!同时继续算下一层launch_layer_1_forward(data,&layer_1_output,stream);// 到这里才同步,确保 layer_0 的梯度已经交换完成hccl_stream_sync(stream);launch_layer_1_backward(layer_1_output,layer_0_grad,...);注释解释WHY:这里
queue_id不是随便填的——不同的流对应 NPU 上不同的硬件队列。层的计算和 allreduce 通信分别占用不同的队列,才能真正并行执行。如果共用同一个队列,代码逻辑上写的是异步,底层硬件执行时还是串行。
六、多机通信的可靠性保障
多机通信的最大问题是网络不可靠。RoCE 网络偶尔会丢包,交换机偶尔会拥塞。hccl 的处理方式是重传 + 流控:
重传机制:hccl 在 RDMA 层面做 Flow Control,检测到丢包后立即触发重传。重传的粒度不是整次 AllReduce 全部重来,而是只传丢了的那一个分段。
拥塞控制:hccl 会检测网络拥塞信号(ECN,显式拥塞通知),在出现拥塞时动态降低发送速率。拥塞解除后自动恢复全速。
这些机制对上层框架完全透明。框架调用 allreduce 只会看到「完成」和「失败」,中间的重传和拥塞控制全在 hccl 内部消化。
七、hccl 对比 NCCL 的几个差异点
如果你从 NVIDIA 生态过来,hccl 在概念上对标 NCCL,但实现上有几个值得注意的差异:
拓扑粒度:NCCL 主要针对 NVLink + IB/RoCE 的拓扑做优化,hccl 针对 HCCS + RoCE 做优化。两者的机内互联特性不同——HCCS 是全互联拓扑(每两张卡直接用高速链路连接),而 NVLink 在单机八卡场景下存在桥接拓扑。
多流支持:hccl 对多流并发的支持做得比较深入,可以在同一 Communicator 内创建多个流并同时执行不同的通信操作,这在 MoE 模型的 Expert Parallelism 场景中非常有用——每个 Expert 的通信可以独立异步进行。
八、从哪开始?
如果你要做昇腾平台上的分布式训练,hccl 的价值体现需要一点耐心:
- 先用默认配置跑通:hccl 的默认算法选择在大多数场景下已经不错了,先确认能跑起来。
- 看 Profile 数据:用 CANN 自带的 profiling 工具看看通信时间占比。如果超过 20%,说明通信是瓶颈,值得调。
- 关注拓扑变化:加卡的时候,通信拓扑变了,算法选择可能也跟着变。别假设「加一倍卡,加一倍吞吐」——在跨机扩容前,先跑一下通信 benchmark 确认线性度。
链接:https://atomgit.com/cann/hccl