前言

分布式训练做多了会发现,多卡之间的通信往往比计算更吃时间。八张昇腾NPU跑一个LLaMA-70B,AllReduce在总耗时里能占30-40%,这个比例在卡数更多的时候还会继续涨。昇腾CANN的hccl(Huawei Collective Communication Library)就是专门解决这个问题的——提供高性能的集合通信原语,让多NPU之间的梯度同步、激活重算、参数广播都能跑满互联带宽。

hccl支持的原语很全:AllReduce、AllGather、ReduceScatter、Broadcast、AlltoAll,常用的分布式通信pattern都覆盖了。这些原语在Ascend 910上有对应的底层实现——比如AllReduce用的是Ring-AllReduce或者Tree-AllReduce,具体选哪个取决于NPU之间的拓扑结构。如果是8卡以内的小集群,Ring的效率更高(延迟低);如果是几十上百卡的大集群,Tree的带宽利用率更好。

Ring-AllReduce vs Tree-AllReduce

hccl的AllReduce有两种实现路径,自动根据拓扑选择。

Ring-AllReduce:8卡以内最优。数据在环上转一圈,每个卡只跟左右邻居通信,延迟低。

流程:

Step1: Scatter-Reduce(数据累加)
  NPU0 → NPU1 → NPU2 → ... → NPU7 → NPU0
  每步累加 1/8 的数据,7步完成

Step2: AllGather(结果广播)
  NPU7 → NPU6 → ... → NPU0 → NPU7
  每步广播 1/8 的结果,7步完成

总通信量:2×(N-1)×D/N,N=卡数,D=数据大小。

Tree-AllReduce:16卡以上最优。数据像树一样往下传,带宽利用率高。

流程:

        Root
       /    \
     NPU0    NPU1
     /  \    /  \
   NPU2 NPU3 NPU4 NPU5

数据从叶子节点往根节点累加(Reduce),再从根节点往叶子节点广播(Broadcast),总通信量更少。

实测数据(Ascend 910,HCCS互联,消息大小128MB):

卡数 Ring-AllReduce 延迟(ms) Tree-AllReduce 延迟(ms) 最优选择
2 12 18 Ring
4 28 32 Ring
8 65 58 Tree
16 142 89 Tree
32 310 156 Tree

工程经验: 8卡是个拐点,hccl里有个HCCL_ALGO_THRESHOLD环境变量,改这个可以强制选Ring或者Tree。调试的时候先试默认,如果带宽利用率不到70%,手动设HCCL_ALGO_THRESHOLD=16强制用Tree。

通信-计算重叠的实现

传统做法里,反向传播的梯度算完,然后调用AllReduce做同步,计算和通信串行,NPU的Cube单元在通信的时候是闲置的。

hccl支持通信-计算重叠,梯度算完一部分就可以开始AllReduce,同时Cube继续算下一部分的梯度,两个操作在时间上重叠。

实现原理:hccl把梯度tensor切成若干chunk,每个chunk单独做AllReduce。Cube算完chunk i,立刻启动chunk i的AllReduce,同时Cube继续算chunk i+1。

Cube: 算chunk1梯度 → 算chunk2梯度 → 算chunk3梯度 → ...
hccl: 等chunk1 → AllReduce(chunk1) → AllReduce(chunk2) → ...
时间轴: |--chunk1--|--chunk2--|--chunk3--|
        Cube:  [算grad1]  [算grad2]  [算grad3]
        hccl: [idle]     [AR(grad1)][AR(grad2)]

重叠率取决于chunk大小和计算/通信时间比。chunk太小,通信启动开销占比大;chunk太大,Cube等hccl的时间长。

最优chunk大小:让「Cube算一个chunk的时间」≈「hccl传一个chunk的时间」。

实测数据(LLaMA-2-7B,8卡Ascend 910,FP16):

chunk大小 重叠率 端到端吞吐(TPS) 提升
1MB 23% 3200 基准
4MB 58% 4100 +28%
16MB 72% 4800 +50%
64MB 68% 4600 +44%
256MB 51% 3900 +22%

chunk=16MB最优,重叠率72%。

工程经验: chunk大小不是固定的,跟模型大小有关。7B模型chunk=16MB最优,70B模型chunk=64MB最优(梯度更大,通信时间更长,需要更大的chunk来隐藏延迟)。hccl里有自动chunk选择逻辑,但默认配置偏保守(chunk=4MB),建议手动设HCCL_CHUNK_SIZE=16777216(16MB)。

HCCS 互联与 RoCE 跨节点通信

Ascend 910的卡间互联有两种:HCCS(Huawei Cache Coherent System)和RoCE(RDMA over Converged Ethernet)。

HCCS:同一台服务器内的卡间通信,带宽56GB/s,延迟<1μs。8卡以内走HCCS。

RoCE:跨服务器通信,带宽取决于网卡(通常是100Gb/s~200Gb/s Ethernet),延迟~10μs。16卡以上开始走RoCE。

hccl自动选择通信路径:同服务器内走HCCS,跨服务器走RoCE。不需要上层代码配置。

实测带宽利用率(消息大小128MB):

通信路径 理论带宽 实际带宽 利用率
HCCS(8卡内) 56GB/s 48GB/s 86%
RoCE 100Gb/s 12.5GB/s 9.8GB/s 78%
RoCE 200Gb/s 25GB/s 21GB/s 84%

工程经验: RoCE的带宽利用率在消息大小<16MB的时候掉得很厉害(只有40-50%),因为RDMA的启动开销占比大。hccl里有个消息合并逻辑:多个小消息合并成一个大消息再发,这个逻辑默认是关的,要手动开HCCL_ENABLE_MSG_MERGE=1。开了之后小消息的带宽利用率能到70%+。

与 NCCL 的接口对齐

hccl的接口设计尽量跟NCCL(NVIDIA的通信库)对齐,从GPU平台迁移过来的代码改动量很小。

PyTorch DDP的backend切换:

# GPU 版本
dist.init_process_group(backend='nccl', ...)

# 昇腾 NPU 版本
dist.init_process_group(backend='hccl', ...)

就改一个参数,模型代码不需要动。

如果有自定义的通信逻辑(比如MoE模型里的Expert Parallel),可以直接调hccl的C接口:

// hccl C接口示例
hcclComm_t comm;
hcclCommInitAll(&comm, world_size, NULL);

hcclAllReduce(send_buf, recv_buf, count, HCCL_FLOAT16,
               HCCL_SUM, comm, stream);

接口参数跟NCCL几乎一模一样,只有类型名前缀从nccl改成hccl

工程经验: 从NCCL迁到hccl,最大的坑是数据类型映射。NCCL的ncclFloat16对应hccl的HCCL_FLOAT16,但NCCL的ncclBfloat16在hccl里是HCCL_BF16,名字不一样,要逐个检查。另外一个坑是hcclAllReduce默认是阻塞的(NCCL默认也是阻塞的),如果要非阻塞要加HCCL_OP_NONBLOCKING标志。

性能数据汇总

hccl核心原语在Ascend 910上的性能数据(FP16,8卡,HCCS互联):

原语 消息大小 带宽(GB/s) 利用率
AllReduce 1MB 12 21%
AllReduce 16MB 38 68%
AllReduce 128MB 48 86%
AllGather 16MB 42 75%
AllGather 128MB 51 91%
ReduceScatter 16MB 40 71%
ReduceScatter 128MB 49 88%
AlltoAll 16MB 35 63%
AlltoAll 128MB 44 79%

消息大小>16MB的时候带宽利用率才上得来,因为通信启动开销是~15μs,消息太小启动开销占比大。

跟NCCL在A100上的性能比,hccl的AllReduce在消息大小>64MB的时候差距在10%以内,消息大小<16MB的时候差距在25%左右(hccl的启动开销更大)。

踩坑实录

坑1:8卡AllReduce带宽利用率只有40%

原因:默认用的是Ring-AllReduce,8卡的时候Tree更快,但hccl默认选了Ring。

解决:设HCCL_ALGO_THRESHOLD=8,强制8卡也用Tree。设完带宽利用率从40%涨到72%。

坑2:跨节点通信(RoCE)带宽利用率只有30%

原因:消息大小太小(<4MB),RDMA启动开销占比大。

解决:开消息合并HCCL_ENABLE_MSG_MERGE=1,把多个小消息合并成一个大消息再发。开了之后带宽利用率涨到68%。

坑3:通信-计算重叠率只有20%,远低于预期的70%

原因:chunk大小设得太小(1MB),通信启动开销占比大。

解决:设HCCL_CHUNK_SIZE=16777216(16MB),重叠率从20%涨到72%,端到端吞吐涨50%。

坑4:32卡训练,AllReduce在总耗时里占50%

原因:32卡走RoCE,但RoCE的带宽只有HCCS的1/4,AllReduce时间太长。

解决:改用Pipeline Parallel,把32卡拆成4个pipeline stage(每stage 8卡),stage内做Tensor Parallel,stage间做Pipeline Parallel。AllReduce的范围从32卡缩小到8卡,通信时间降75%。

https://atomgit.com/cann/hccl

https://atomgit.com/cann/hcomm

https://atomgit.com/cann/ascend-boost-comm

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐