HCCL AlltoAllVC 集合通信样例实战:从 RootInfo 初始化到自定义数据量矩阵的完整解析
2026/9/18 22:09:53 网站建设 项目流程

HCCL AlltoAllVC 集合通信样例实战:从 RootInfo 初始化到自定义数据量矩阵的完整解析

【免费下载链接】hccl集合通信库(Huawei Collective Communication Library,简称HCCL)是基于昇腾AI处理器的高性能集合通信库,为计算集群提供高性能、高可靠的通信方案项目地址: https://gitcode.com/cann/hccl

本文基于 HCCL(Huawei Collective Communication Library,昇腾集合通信库)开源仓库中的 AlltoAllVC 样例,完整讲解如何在一个单机多卡进程中完成通信域初始化并调用HcclAlltoAllVC()算子执行"可定制每对 rank 数据量"的全交换通信。读完本文,你将掌握 AlltoAllVC 与 AlltoAllV 的参数差异、sendCountMatrix数据量矩阵的含义、样例源码的逐段实现逻辑,以及编译运行所需的环境配置与性能调优环境变量。

样例概述:AlltoAllVC 要解决什么问题

AlltoAllVC(AlltoAll Variable Count)是一种"全交换"集合通信操作:通信域内的每个 rank 都会向其他所有 rank 发送数据,同时从所有 rank 接收数据,并且每对 rank 之间的数据量可以由调用方独立指定。相比 AlltoAllV 需要分别提供 send/recv 两侧的 counts 与 displs 四个数组,AlltoAllVC 通过一个[rankSize][rankSize]的二维数据量矩阵sendCountMatrix即可同时描述发送与接收规模,参数更简洁。

仓库中的样例位于 examples/02_collectives/08_alltoallvc,其 样例说明文档 明确了本样例覆盖的功能点:

  • 调用aclrtGetDeviceCount()检测设备,查询可用设备数量;
  • 调用HcclGetRootInfo()并以rank 0作为 root rank,生成 rootinfo 标识信息。rootinfo 中包含设备 IP 地址和设备 ID,该信息必须广播到集群内所有 rank,用于初始化通信域;
  • 在每个线程中调用HcclCommInitRootInfo()基于 rootinfo 初始化通信域;
  • 调用HcclAlltoAllVC()将输入数据沿特定维度切分成若干块、按顺序发送给其他 rank,接收来自其他 rank 的数据后按序拼接,并打印结果。

样例目录结构如下:

examples/02_collectives/08_alltoallvc/ ├── main.cc # 样例源文件 ├── Makefile # 编译/构建配置文件 ├── README.md # 中文说明文档 ├── README_en.md # 英文说明文档 └── alltoallvc # make 编译后生成的可执行文件

环境准备

硬件与组网要求

根据 样例说明文档,本样例支持**单机 N 卡(N >= 2)**组网下的以下产品:

  • Ascend 950PR / Ascend 950DT
  • Atlas A3 训练系列产品 / Atlas A3 推理系列产品
  • Atlas A2 训练系列产品

加载 CANN 环境变量

# 加载 CANN 环境变量,以下以 root 用户默认安装路径为例 source /usr/local/Ascend/cann/set_env.sh

ASCEND_HOME_PATH是该脚本设置的关键变量之一,Makefile 依赖它来定位头文件与链接库。

编译与运行

在样例代码目录下执行:

make make test

样例 Makefile 的关键内容值得逐一看懂:

# 未加载 CANN 环境变量时直接报错,提示先执行 source set_env.sh ifndef ASCEND_HOME_PATH $(error "ASCEND_HOME_PATH is not set, please ensure CANN is properly installed and \ source environment variables by running `source /path/to/Ascend/cann/set_env.sh`") endif CXXFLAGS := -std=c++17 \ -Werror \ -fstack-protector-strong \ -fPIE -pie \ -O2 \ ... SOURCES = main.cc ASCEND_INC_DIR = ${ASCEND_HOME_PATH}/include ASCEND_LIB_DIR = ${ASCEND_HOME_PATH}/lib64 LIBS = -L$(ASCEND_LIB_DIR) -lhccl -lascendcl INCS = -I$(ASCEND_INC_DIR) TARGET = alltoallvc

从 Makefile 可以看出:样例以 C++17 标准编译,链接-lhccl(HCCL 集合通信库)与-lascendcl(昇腾计算图运行时库,提供aclrt*系列设备接口)两个库,头文件目录为$ASCEND_HOME_PATH/include。除默认的all目标外,还定义了test(运行可执行文件)、cleanhelp目标。

通信算子展开模式环境变量 HCCL_OP_EXPANSION_MODE

样例文档特别提示,可通过环境变量HCCL_OP_EXPANSION_MODE配置通信算子的展开模式:

# 设置通信算子的展开模式为 AI CPU 通信引擎 export HCCL_OP_EXPANSION_MODE=AI_CPU

关于该环境变量的完整取值,可参阅仓库中的 HCCL_OP_EXPANSION_MODE 文档。要点如下:

  • Ascend 950PR / 950DT:支持AI_CPU(AI CPU 展开,默认调度器)、AICPU_TS(默认值,STARS 调度器)、AICPU_CacheDisable(关闭算子结果缓存)、AIV(Vector Core 展开,仅对称组网/推理特性)、CCU_MSCCU_SCHED(CCU 展开)等取值;其中AI_CPUAICPU_TSAIV均支持 AlltoAllVC 算子。文档同时注明AI_CPU将在后续版本废弃,由AICPU_TS取代,当前两者功能完全一致;
  • Atlas A3 / Atlas A2 系列:支持范围与默认值有所不同,配置了不支持的取值时会回退默认值,具体以该文档中对应产品小节为准。

不同产品型号支持的取值范围存在差异,配置前请先确认自己的硬件型号对应小节,避免设置不被支持的模式。

样例源码逐段解析

main.cc 采用"主线程准备 rootinfo + 每卡一个线程执行通信"的经典单机多卡编程模型,以下按执行顺序拆解。

1. 主线程:设备探测与 rootinfo 生成

// 设备资源初始化 ACLCHECK(aclInit(NULL)); // 查询设备数量 uint32_t devCount; ACLCHECK(aclrtGetDeviceCount(&devCount)); std::cout << "Found " << devCount << " NPU device(s) available" << std::endl; int32_t rootRank = 0; ACLCHECK(aclrtSetDevice(rootRank)); // 生成 Root 节点信息,各线程使用同一份 RootInfo void* rootInfoBuf = nullptr; ACLCHECK(aclrtMallocHost(&rootInfoBuf, sizeof(HcclRootInfo))); HcclRootInfo* rootInfo = (HcclRootInfo*)rootInfoBuf; HCCLCHECK(HcclGetRootInfo(rootInfo));

HcclGetRootInfo()必须在某个已aclrtSetDevice()的设备上下文上调用,样例固定在rank 0设备上生成HcclRootInfo。该结构承载设备 IP 与设备 ID 等 root 节点标识信息,在单机场景中直接共享给所有线程;在跨节点部署时,这一份 rootinfo 需要通过框架侧机制广播到集群内所有 rank,才能完成通信域初始化——这正是文档中强调"此信息需广播至集群内所有 rank"的原因。

2. 线程上下文与设备内存准备

每个线程通过ThreadContext共享 rootinfo 并持有自己负责的device编号:

struct ThreadContext { HcclRootInfo* rootInfo; uint32_t device; uint32_t devCount; };

线程入口Sample()先绑定设备,再申请收发缓冲:

// 设置当前线程操作的设备 ACLCHECK(aclrtSetDevice(static_cast<int32_t>(device))); // 申请集合通信操作的 Device 内存 ACLCHECK(aclrtMalloc(&sendBuf, mallocSize, ACL_MEM_MALLOC_HUGE_ONLY)); ACLCHECK(aclrtMalloc(&recvBuf, mallocSize, ACL_MEM_MALLOC_HUGE_ONLY)); // 申请 Host 内存用于存放输入数据:每个 rank 的数据初始化为该 rank 的 rankId float* tmpHostBuff = static_cast<float*>(hostBuf); for (uint64_t i = 0; i < count; ++i) { tmpHostBuff[i] = static_cast<float>(device); } ACLCHECK(aclrtMemcpy(sendBuf, mallocSize, hostBuf, mallocSize, ACL_MEMCPY_HOST_TO_DEVICE));

注意两点:ACL_MEM_MALLOC_HUGE_ONLY表示申请大页显存,适合通信缓冲这类大块内存;输入数据整体填充为device值(即 rankId),这是后面结果验证的依据——若 AlltoAllVC 正确执行,每个 rank 的输出应为[0 1 2 ... N-1]的完整拼接。

3. 通信域初始化

// 初始化集合通信域 HcclComm hcclComm; HCCLCHECK(HcclCommInitRootInfo(rankSize, ctx->rootInfo, device, &hcclComm));

HcclCommInitRootInfo(rankSize, rootInfo, device, &hcclComm)rankSize等于devCount(单机场景下 rank 数与卡数一一对应),device为该线程绑定的物理设备号。这一步会基于 rootinfo 与本地设备建立 rank 间连接并分配 HCCL_BUFFSIZE 相关的缓存区,因此每个线程必须各自调用一次,各自持有独立的HcclComm句柄。

4. 构造数据量矩阵并调用 HcclAlltoAllVC

// 设置收发数据量,收发数据量相同 std::vector<uint64_t> sendCountMatrix(rankSize * rankSize); for (uint32_t i = 0; i < rankSize; ++i) { for (uint32_t j = 0; j < rankSize; ++j) { sendCountMatrix[i * rankSize + j] = count / rankSize; } } // 执行 AlltoAllVC,向通信域内所有 rank 发送相同数据量的数据, // 并从所有 rank 接收相同数据量的数据,可定制数据量 HCCLCHECK(HcclAlltoAllVC( sendBuf, sendCountMatrix.data(), HCCL_DATA_TYPE_FP32, recvBuf, HCCL_DATA_TYPE_FP32, hcclComm, stream)); // 阻塞等待任务流中的集合通信任务执行完成 ACLCHECK(aclrtSynchronizeStream(stream));

sendCountMatrix[rankSize][rankSize]的二维uint64数组,按行主序平铺为一维向量:sendCountMatrix[i * rankSize + j] = n表示rank i 发送给 rank j 的数据量为 n 个元素。样例中每个 rank 总共持有count = devCount个 float 元素,均分后每对 rank 之间交换count / rankSize个元素。AlltoAllVC 会隐式推导出接收侧规模:rank i 从 rank j 接收sendCountMatrix[j][i]个元素,并按源 rank 顺序在 recvBuf 中拼接。

HcclAlltoAllVC()的函数原型定义在 include/hccl.h:

extern HcclResult HcclAlltoAllVC( const void* sendBuf, const void* sendCountMatrix, HcclDataType sendType, const void* recvBuf, HcclDataType recvType, HcclComm comm, aclrtStream stream);

各参数含义如下(详见 HcclAlltoAllVC 接口文档):

参数名输入/输出描述
sendBuf输入源数据 buffer 地址
sendCountMatrix输入二维uint64数组,形状[rankSize][rankSize]sendCountMatrix[i][j] = n表示 rank i 发给 rank j 的数据量为 n(单位:元素个数)
sendType输入发送数据类型(如HCCL_DATA_TYPE_FP32),不同型号支持的数据类型不同
recvBuf输出目的 buffer 地址,通信结果输出至此;recvBuf 与 sendBuf 地址不能相同,内存范围不能重叠
recvType输入接收数据类型
comm输入本 rank 的通信域句柄
stream输入本 rank 使用的任务流

可能返回的错误码包括:HCCL_SUCCESS(成功)、HCCL_E_PTR(comm、sendBuf、recvBuf、stream、sendCountMatrix 等指针为空)、HCCL_E_PARA(参数无效,如 sendBuf 与 recvBuf 地址相同)、HCCL_E_NOT_SUPPORT(数据类型非法或当前型号不支持)、HCCL_E_INTERNAL(内部错误)。不同产品的支持数据类型:950PR/950DT 支持 int8/uint8/int16/uint16/int32/uint32/int64/uint64 各整型、float8 系列、hifloat8、float16、float32、float64、bfp16;Atlas A3 与 Atlas A2 训练系列支持 int8~uint64 整型、float16、float32、float64、bfp16。

5. 结果回读与资源释放

// 将 Device 侧集合通信任务结果拷贝到 Host,并打印结果 void* resultBuff; ACLCHECK(aclrtMallocHost(&resultBuff, mallocSize)); ACLCHECK(aclrtMemcpy(resultBuff, mallocSize, recvBuf, mallocSize, ACL_MEMCPY_DEVICE_TO_HOST)); std::cout << "rankId: " << device << ", output: ["; for (uint64_t i = 0; i < count; ++i) { std::cout << " " << tmpResBuff[i]; } std::cout << " ]" << std::endl; // 释放资源 HCCLCHECK(HcclCommDestroy(hcclComm)); // 销毁通信域 ACLCHECK(aclrtFree(sendBuf)); // 释放 Device 侧内存 ACLCHECK(aclrtFree(recvBuf)); ACLCHECK(aclrtDestroyStream(stream)); // 销毁任务流

主线程在全部工作线程join()后释放 rootinfo 的 Host 内存并调用aclFinalize()完成去初始化。样例中主线程用ACLCHECK/HCCLCHECK宏统一封装错误检查:接口返回非成功码时打印文件名、行号与错误码,便于快速定位失败点。

从源码结构看,样例中std::this_thread::sleep_for(std::chrono::seconds(device))一行只是让不同 rank 的打印结果交错错开、便于肉眼核对输出顺序,与 AlltoAllVC 语义本身无关,移植到自己业务时可去除。

运行结果验证

样例文档给出的期望输出(以 8 卡为例):

Found 8 NPU device(s) available rankId: 0, output: [ 0 1 2 3 4 5 6 7 ] rankId: 1, output: [ 0 1 2 3 4 5 6 7 ] rankId: 2, output: [ 0 1 2 3 4 5 6 7 ] rankId: 3, output: [ 0 1 2 3 4 5 6 7 ] rankId: 4, output: [ 0 1 2 3 4 5 6 7 ] rankId: 5, output: [ 0 1 2 3 4 5 6 7 ] rankId: 6, output: [ 0 1 2 3 4 5 6 7 ] rankId: 7, output: [ 0 1 2 3 4 5 6 7 ]

验证逻辑很直接:每个 rank 的输入整体填充为自己的 rankId(rank i 的 8 个元素全是 i),经过 AlltoAllVC 后每个 rank 从其余 7 个 rank 各收到 1 个元素并按源 rank 顺序拼接,因此无论哪个 rank,输出都应是0 1 2 3 4 5 6 7。若某行出现缺失或顺序错乱,说明该 rank 与其他 rank 的数据交换不完整。

约束说明与性能调优

接口文档 HcclAlltoAllVC 约束说明 指出三条必须遵守的规则:

  1. 缓存区与性能:AlltoAllVC 的性能与通信域共享数据缓存区大小有关,当通信数据量超过缓存区大小时性能将明显下降。数据量大时建议通过环境变量HCCL_BUFFSIZE适当增大缓存区;
  2. 算子串行下发:多个通信域下,所有通信算子在每个 Device 上必须串行下发,不允许乱序、多线程并发下发,也不支持线程重入;
  3. Context 一致性:同一 Device 上、同一通信域内所有通信算子的下发线程需使用相同的 Context。

关于HCCL_BUFFSIZE,参阅 HCCL_BUFFSIZE 文档:该变量控制通信域共享数据缓存区大小,取值为大于等于 1 的整数,单位 MB,默认值 200。关键约束:每个通信域实际独占2 * HCCL_BUFFSIZE的内存(收发各一份),且该内存为 HCCL 独占、不可与业务内存复用;若集群内通信域较多,过大的取值会挤占模型数据存储,反之通信数据量大的场景则建议调大。配置示例:

export HCCL_BUFFSIZE=200

此外,若初始化通信域时通过HcclCommConfighcclBufferSize参数指定了缓存区大小,则以通信域粒度的配置优先于环境变量。

小结

本文以 examples/02_collectives/08_alltoallvc 样例为主线,完整覆盖了 AlltoAllVC 实战的四个环节:环境准备(CANN 环境变量 + 支持的 950PR/950DT、A3、A2 训练产品)、make && make test编译运行流程、源码中aclrtGetDeviceCount → HcclGetRootInfo → 每线程 HcclCommInitRootInfo → HcclAlltoAllVC的核心调用链,以及基于[rankSize][rankSize]数据量矩阵定制每对 rank 交换规模的用法。在需要非对称数据量交换的场景(例如 MoE 路由后的专家分片交换)中,只需修改sendCountMatrix的填充逻辑,即可在不改变其余框架代码的前提下实现自定义的 AlltoAllVC 通信模式。

【免费下载链接】hccl集合通信库(Huawei Collective Communication Library,简称HCCL)是基于昇腾AI处理器的高性能集合通信库,为计算集群提供高性能、高可靠的通信方案项目地址: https://gitcode.com/cann/hccl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询