在这里插入图片描述

前言

想象一下,你站在一座巨大的工厂里,数千个工人(AICore)正在同时处理同一个任务。每个工人手里都拿着一部分数据,他们需要不停地交换信息——“我算完了这部分梯度!”“我这里有个权重需要同步!”“大家准备好,我们要合并结果了!”

这可不是普通的聊天,这是昇腾异构计算架构中 HCCL(Huawei Collective Communications Library)每天都在上演的"协同喊话"。


当 AICore 们需要"喊话":集合通信的本质

在分布式深度学习训练中,模型参数往往庞大到无法放进单个芯片的记忆里。Ascend 910 芯片上的多个 AICore 并行计算时,就像工地上的工人需要协同作业——你算完梯度,我算完损失,他算完前向传播,大家必须时刻保持数据一致,否则训练就会"各说各话",模型永远收不到正确的信号。

HCCL 就是这套"喊话系统"的标准语言。它定义了一套集合通信原语(Collective Communication Primitives),让多个计算设备能够高效地交换数据。不同于点对点通信(两个工人私下耳语),集合通信是"一群人开大会"——所有参与者同时喊话、同时倾听、同时行动。

让我们走进 HCCL 的"喊话训练营",看看五种核心通信模式是如何让 AICore 们默契配合的。


五种"喊话"模式:从广播到全员交互

1. Broadcast:村长的喇叭喊话

比喻:村长站在村口,用大喇叭向全体村民广播一条消息。所有人都能听到,内容完全一致。

在分布式训练中,Broadcast 用于将数据从一个进程复制到所有其他进程。比如,主节点初始化模型参数后,需要把这份参数"广播"给所有工作节点。

// 代码示例:HCCL Broadcast 调用
#include "hccl/hccl.h"

// 第 1 行:初始化 HCCL 通信域
hcclComm_t comm;
hcclCommInitAll(&comm, rank, nRanks);

// 第 2 行:定义广播的根节点(村长)
int root = 0;

// 第 3 行:准备广播的数据缓冲区
float* data = (float*)malloc(bufferSize);
if (rank == root) {
    // 根节点填充数据
    initialize_model_parameters(data);
}

// 第 4 行:执行广播操作
hcclResult_t ret = hcclBroadcast(
    data,                   // 发送/接收缓冲区
    elementCount,           // 元素数量
    HCCL_FLOAT,            // 数据类型
    root,                  // 根节点编号
    comm,                  // 通信域
    stream                 // 昇腾达芬奇架构的流
);

// 第 5 行:同步等待广播完成
hcclCommStreamSynchronize(comm, stream);

逐行解释

  • 第 1 行:初始化 HCCL 通信域,让所有 AICore 加入"喊话群聊",rank 是当前设备的编号,nRanks 是总设备数。
  • 第 2 行:指定根节点(root),就像选村长,只有村长有喇叭。
  • 第 3 行:准备数据缓冲区,根节点的缓冲区里有数据,其他节点的缓冲区是空的(等待接收)。
  • 第 4 行:调用 hcclBroadcast,HCCL 会自动选择最优算法(Ring 或 Mesh),让数据像水波一样从根节点扩散到所有节点。
  • 第 5 行:同步等待,确保所有 AICore 都收到了完整数据,避免"有人还在听,有人已经开始算了"的混乱。

2. AllReduce:全员投票汇总

比喻:村委会开会,每个人手里有一张选票(局部梯度),大家需要把所有选票加起来,得到总票数(全局梯度),然后每个人都拿到这份总和。

AllReduce 是分布式训练中最核心的操作——每个 AICore 计算完本地梯度后,通过 AllReduce 求和,得到全局梯度,再用于参数更新。

// 代码示例:HCCL AllReduce 调用
#include "hccl/hccl.h"

// 第 1 行:假设每个进程都计算完了本地梯度
float* localGrad = get_local_gradients();

// 第 2 行:准备接收全局梯度的缓冲区
float* globalGrad = (float*)malloc(bufferSize);

// 第 3 行:执行 AllReduce 操作
hcclResult_t ret = hcclAllReduce(
    localGrad,              // 发送缓冲区(本地梯度)
    globalGrad,             // 接收缓冲区(全局梯度)
    elementCount,           // 梯度元素数量
    HCCL_FLOAT,            // 数据类型
    HCCL_SUM,              // 归约操作:求和
    comm,                  // 通信域
    stream                 // 昇腾达芬奇架构的流
);

// 第 4 行:同步等待 AllReduce 完成
hcclCommStreamSynchronize(comm, stream);

// 第 5 行:使用全局梯度更新模型参数
update_model_parameters(globalGrad);

逐行解释

  • 第 1 行:每个 AICore 通过反向传播计算出本地梯度,存储在 localGrad 中。
  • 第 2 行:分配全局梯度缓冲区,AllReduce 的结果会写到这里。
  • 第 3 行:调用 hcclAllReduce,HCCL 会使用 Ring 或 Tree 算法,让所有 AICore 的梯度"绕圈"传递并累加,最终每个人都拿到完整的全局梯度。
  • 第 4 行:同步等待,确保所有 AICore 的梯度都计算完毕且通信完成。
  • 第 5 行:用全局梯度更新模型参数,这一步每个 AICore 都会执行,因为大家都拿到了相同的 globalGrad

3. AllGather:拼图游戏

比喻:五个人各自拿着拼图的一部分,大家需要把自己的部分展示给所有人,最终每个人都拥有完整的拼图。

AllGather 用于收集所有进程的张量,并在所有进程上拼接成完整的张量。在张量并行(TP)中,每一层的输出需要 AllGather 来聚合所有 AICore 的计算结果。

// 代码示例:HCCL AllGather 调用
#include "hccl/hccl.h"

// 第 1 行:每个进程持有部分张量
float* localTensor = get_local_tensor_part();

// 第 2 行:准备接收完整张量的缓冲区
float* fullTensor = (float*)malloc(fullBufferSize);

// 第 3 行:执行 AllGather 操作
hcclResult_t ret = hcclAllGather(
    localTensor,            // 发送缓冲区(本地张量部分)
    fullTensor,             // 接收缓冲区(完整张量)
    localElementCount,      // 每个进程的张量大小
    HCCL_FLOAT,            // 数据类型
    comm,                  // 通信域
    stream                 // 昇腾达芬奇架构的流
);

// 第 4 行:同步等待 AllGather 完成
hcclCommStreamSynchronize(comm, stream);

// 第 5 行:使用完整张量进行后续计算
compute_next_layer(fullTensor);

逐行解释

  • 第 1 行:在张量并行中,每一层会被切分成多份,每个 AICore 只计算其中一份,存储在 localTensor 中。
  • 第 2 行:分配完整张量的缓冲区,大小是所有进程张量之和。
  • 第 3 行:调用 hcclAllGather,HCCL 会让每个 AICore 把自己的部分"喊"出来,其他人"听"到后拼接到自己的缓冲区。
  • 第 4 行:同步等待,确保拼接完成。
  • 第 5 行:用完整张量计算下一层,比如在 Transformer 的多头注意力中,AllGather 用于聚合所有头的输出。

4. ReduceScatter:分而治之的逆向操作

比喻:一群人手里都有完整的清单,但需要把清单按照某种规则切分,每个人只保留自己负责的那部分,同时把其他部分"扔"给对应的人。

ReduceScatter 是 AllGather 的逆操作——它先把所有进程的数据归约(求和、求最大值等),然后再散射到各个进程。在分布式优化器中,ReduceScatter 用于切分梯度,让每个 AICore 只更新自己负责的参数部分。

// 代码示例:HCCL ReduceScatter 调用
#include "hccl/hccl.h"

// 第 1 行:每个进程持有完整的梯度张量
float* fullGrad = get_full_gradients();

// 第 2 行:准备接收局部梯度的缓冲区
float* localGradPart = (float*)malloc(localBufferSize);

// 第 3 行:执行 ReduceScatter 操作
hcclResult_t ret = hcclReduceScatter(
    fullGrad,              // 发送缓冲区(完整梯度)
    localGradPart,         // 接收缓冲区(局部梯度)
    localElementCount,     // 每个进程的梯度大小
    HCCL_FLOAT,            // 数据类型
    HCCL_SUM,              // 归约操作:求和
    comm,                  // 通信域
    stream                 // 昇腾达芬奇架构的流
);

// 第 4 行:同步等待 ReduceScatter 完成
hcclCommStreamSynchronize(comm, stream);

// 第 5 行:每个进程只更新自己负责的参数部分
update_local_parameters(localGradPart);

逐行解释

  • 第 1 行:假设通过某种方式(比如 AllGather),每个 AICore 都拿到了完整的梯度张量。
  • 第 2 行:分配局部梯度缓冲区,大小是完整梯度除以进程数。
  • 第 3 行:调用 hcclReduceScatter,HCCL 会先对所有 AICore 的梯度进行归约(比如求和),然后再按照 rank 切分,每个 AICore 拿到自己对应的部分。
  • 第 4 行:同步等待,确保切分完成。
  • 第 5 行:每个 AICore 只用自己拿到的局部梯度更新对应的参数部分,实现参数的分布式更新。

5. AlltoAll:全员交叉换位

比喻:舞会上,每个人都要和所有人交换舞伴。你把手里的花给张三,李四把书给你,王五把帽子给张三……最终每个人都拿到了别人手里的东西。

AlltoAll 是最复杂的通信模式——每个进程都会向其他所有进程发送数据,同时从其他所有进程接收数据。在专家并行(EP)中,AlltoAll 用于在不同 AICore 之间重新分配数据,让每个专家都能处理自己擅长的样本。

// 代码示例:HCCL AlltoAll 调用
#include "hccl/hccl.h"

// 第 1 行:准备发送缓冲区(每个进程要给其他人发送的数据)
float* sendBuf = prepare_send_data();

// 第 2 行:准备接收缓冲区
float* recvBuf = (float*)malloc(recvBufferSize);

// 第 3 行:执行 AlltoAll 操作
hcclResult_t ret = hcclAlltoAll(
    sendBuf,               // 发送缓冲区
    sendCountPerRank,      // 给每个进程发送的元素数量
    HCCL_FLOAT,            // 发送数据类型
    recvBuf,               // 接收缓冲区
    recvCountPerRank,      // 从每个进程接收的元素数量
    HCCL_FLOAT,            // 接收数据类型
    comm,                  // 通信域
    stream                 // 昇腾达芬奇架构的流
);

// 第 4 行:同步等待 AlltoAll 完成
hcclCommStreamSynchronize(comm, stream);

// 第 5 行:使用重新分配后的数据
process_redistributed_data(recvBuf);

逐行解释

  • 第 1 行:在专家并行中,每个 AICore 负责一些专家,需要把输入数据按照专家分配情况发送给对应的 AICore。
  • 第 2 行:分配接收缓冲区,用于存放其他 AICore 发来的数据。
  • 第 3 行:调用 hcclAlltoAll,HCCL 会构建一个通信调度表,让所有 AICore 同时发送和接收数据,就像舞会上的"交叉换位"。
  • 第 4 行:同步等待,确保数据重新分配完成。
  • 第 5 行:用重新分配后的数据进行计算,每个专家现在拿到了自己需要处理的数据。

三种"喊话"算法:Ring、Mesh 与 RHD

HCCL 之所以高效,是因为它内置了多种集合通信算法,会根据通信域大小、消息大小、拓扑结构自动选择最优算法。让我们认识三位"喊话专家":

Ring 算法:击鼓传花

原理:所有进程排成一个环,数据像击鼓传花一样在环上传递。每个进程只和邻居通信,带宽利用率高,适合大规模集群。

适用场景:AllReduce、AllGather 等操作,特别是在进程数较多(>8)时,Ring 算法的延迟低、带宽利用率高。

Rank 0 → Rank 1 → Rank 2 → Rank 3 → Rank 4
  ↑                                   ↓
  ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ←

Mesh 算法:网格式协同

原理:进程按照物理拓扑排列成网格,每个进程和多个邻居同时通信。在多机多卡场景下,Mesh 算法能充分利用机内高速互联和机间网络。

适用场景:小规模集群(≤8 卡),或需要低延迟的场景。

RHD 算法:层级式动态调度

原理:Hierarchical + Dynamic,在多层网络拓扑中(如机内 HCCS + 机间 RoCE),先在每层内部做局部通信,再做跨层通信。同时动态感知网络状态,选择最优路径。

适用场景:超大规模集群(64+ 卡),跨机通信占比高的场景,比如大模型训练。


TP/PP/EP 并行中的 HCCL 配置与优化

在大模型训练中,HCCL 是三种并行策略的通信基础:

张量并行(TP):每个计算层被切分到多个 AICore。核心通信是 AllReduce(每层前向/反向传播后)和 AllGather(聚合多头输出)。通信量随切分粒度线性增长,是 HCCL 性能调优的重点。

流水线并行(PP):不同设备负责模型的不同层。核心通信是 Send/Recv(点对点),但 microbatch 在 PP 中的调度依赖 AllReduce 做梯度同步。

专家并行(EP):MoE 模型中,不同设备负责不同的专家。核心通信是 AlltoAll(输入数据在不同专家间的重新分配)。

大模型训练通信瓶颈分析:

TP(张量并行):AllReduce 频繁,通信带宽要求最高
     ↓
PP(流水线并行):点对点通信为主,但梯度同步依赖 AllReduce
     ↓
EP(专家并行):AlltoAll 频繁,数据重分配开销大
     ↓
混合并行(TP+PP+EP):多种通信模式叠加,HCCL 综合调度

优化建议:在大模型训练中,建议使用 HCCS 或 RoCE 网络(HBM 带宽可达 GB/s 级,远超 PCIe),并开启 HCCL 的拓扑感知,让 Ring 算法优先选择机内通信链路,减少跨机通信。


HCCS/RoCE/PCIe:三条高速公路的自适应选择

HCCL 支持三种物理链路,会根据硬件拓扑自动选择:

  • HCCS(Huawei Collective Communication Service):昇腾自研高速互联总线,机内互联首选,延迟最低。
  • RoCE(RDMA over Converged Ethernet):基于以太网的 RDMA,适合跨机通信,带宽高、成本低。
  • PCIe:通用总线,兼容性好但带宽和延迟都不如前两者。

HCCL 会自动感知物理拓扑,优先使用 HCCS 做机内通信,只在必要时跨机使用 RoCE/PCIe。这种"近者优先"的策略,让通信效率始终保持在最优状态。


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐