ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HCCL AlltoAllV 集合通信实战:基于 HcclAlltoAllV 接口的单机多卡数据全交换样例解析

HCCL AlltoAllV 集合通信实战:基于 HcclAlltoAllV 接口的单机多卡数据全交换样例解析 HCCL AlltoAllV 集合通信实战基于 HcclAlltoAllV 接口的单机多卡数据全交换样例解析【免费下载链接】hccl集合通信库Huawei Collective Communication Library简称HCCL是基于昇腾AI处理器的高性能集合通信库为计算集群提供高性能、高可靠的通信方案项目地址: https://gitcode.com/cann/hcclAlltoAllV全交换是集合通信中数据交换形态最灵活的算子每个 rank 可以向通信域内所有 rank 发送长度各不相同的数据同时从所有 rank 接收长度各异的数据。本文以 CANN/HCCL 开源仓库中的 AlltoAllV 官方样例 为骨架逐行解析HcclAlltoAllV()的调用方式、通信域初始化流程与编译运行方法并结合 HcclAlltoAllV 接口文档 与 算子源码 讲清接口参数语义、底层执行链路与工程约束让读者能够独立在昇腾环境上运行该样例并迁移到自己的业务中。AlltoAllV 是什么从语义到接口原型算子语义AlltoAllV 是 MPI Alltoallv 在 HCCL 中的对应实现其核心语义为发送侧每个 rank 将本地sendBuf中的数据切分成rankSize块第i块发送给 ranki接收侧每个 rank 从其他所有 rank 接收数据块按序拼接写入recvBuf关键区别与数据量必须相等的 AlltoAll 不同AlltoAllV 允许每个 rank 发给不同对端的数据量各不相同通过sendCounts/recvCounts两个数组精确控制因此它是实现稀疏通信、变长数据交换、AlltoAllVC 等复杂调度的基础原语。从 src/ops/all_to_all_v/all_to_all_v.cc 的源码可以看到HcclAlltoAllV作为对外统一入口会根据设备能力判断是否走OutPlace 新流程IsOutPlaceDevice否则回退到内部兼容流程HcclAlltoAllVInner。函数原型与参数说明接口声明位于 include/hccl.hextern HcclResult HcclAlltoAllV( const void* sendBuf, const void* sendCounts, const void* sdispls, HcclDataType sendType, const void* recvBuf, const void* recvCounts, const void* rdispls, HcclDataType recvType, HcclComm comm, aclrtStream stream);各参数语义依据 HcclAlltoAllV 接口文档参数输入/输出含义sendBuf输入源数据 buffer 地址sendCounts输入uint64 数组sendCounts[i] n表示本 rank 发给 rank i 的数据量为 n 个元素sdispls输入uint64 数组sdispls[i] n表示发给 rank i 的数据在 sendBuf 中的起始偏移以sendType元素为单位sendType输入发送数据类型HcclDataTyperecvBuf输出结果 buffer 地址必须与 sendBuf 不同且内存范围不能重叠AlltoAllV 不支持原地操作recvCounts输入uint64 数组recvCounts[i] n表示本 rank 从 rank i 接收的数据量为 n 个元素rdispls输入uint64 数组rdispls[i] n表示从 rank i 收到的数据在 recvBuf 中的起始偏移以recvType元素为单位recvType输入接收数据类型comm输入通信域句柄stream输入本 rank 使用的任务流语义对照若收发数据类型均为 FP32则sendCounts[i] n表示向 rank i 发送 n 个 float 数据偏移量sdispls[i] n表示该块数据起始位置相对 sendBuf 的偏移为 n 个元素。样例总览功能与目录结构本样例位于 examples/02_collectives/07_alltoallv在**单机多卡单进程多线程**场景下演示完整的 AlltoAllV 流程通过aclrtGetDeviceCount()查询可用设备数量以 rank0 为 root通过HcclGetRootInfo()生成 rootinfo 标识信息包含 Device IP、Device ID 等信息广播给集群内所有 rank 用于初始化通信域在每个线程中基于同一份 rootinfo通过HcclCommInitRootInfo()初始化通信域调用HcclAlltoAllV()完成全交换并打印结果。目录结构如下├── main.cc # 样例源文件 ├── Makefile # 编译/构建配置文件 └── alltoallv # 编译生成的可执行文件环境准备支持的产品与组网本样例支持**单机 N 卡N 2**组网覆盖以下产品与接口文档的产品支持情况一致Ascend 950PR / Ascend 950DTAtlas A3 训练系列产品 / Atlas A3 推理系列产品Atlas A2 训练系列产品Atlas 训练系列产品Atlas 推理系列产品配置 CANN 环境变量编译前需保证 CANN 已正确安装并加载环境变量root 用户默认安装路径为例source /usr/local/Ascend/cann/set_env.shset_env.sh会导出编译与链接必需的ASCEND_HOME_PATH等变量。Makefile 中对此有强校验若未设置ASCEND_HOME_PATHmake会直接报错提示先执行source .../set_env.sh见 Makefile。源码逐段解析从设备检测到结果打印完整源码见 main.cc。代码先定义了两个错误检查宏ACLCHECK与HCCLCHECK分别对 ACLAscendCL接口和 HCCL 接口的返回值做断言失败即打印出错文件与行号并返回这是昇腾编程的标准防御式写法。第一步初始化与设备检测main 函数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)); // 将 rank0 设为 root 设备 void* rootInfoBuf nullptr; ACLCHECK(aclrtMallocHost(rootInfoBuf, sizeof(HcclRootInfo))); HcclRootInfo* rootInfo (HcclRootInfo*)rootInfoBuf; HCCLCHECK(HcclGetRootInfo(rootInfo)); // 生成 rootinfo 标识HcclGetRootInfo()生成的是通信域初始化的种子信息主要包含 Device IP、Device ID 等。它只需在 root rank 上生成一次随后主线程将同一份rootInfo指针共享给所有工作线程等价于完成了广播给集群内所有 rank的动作——在单机单进程多线程场景下所有线程直接复用同一份 rootinfo 即可。第二步启动多线程每个线程绑定一个设备std::vectorstd::thread threads(devCount); std::vectorThreadContext args(devCount); for (uint32_t i 0; i devCount; i) { args[i].rootInfo rootInfo; args[i].device i; args[i].devCount devCount; threads[i] std::thread(Sample, (void*)args[i]); } for (uint32_t i 0; i devCount; i) { threads[i].join(); }ThreadContext结构体封装了每个线程所需的上下文共享的rootInfo、当前线程操作的device即 rank id以及通信域规模devCount即 rankSize。每个线程对应一张卡模拟了多 rank 并行执行集合通信的语义。第三步设置设备、申请内存并准备输入数据Sample 函数ACLCHECK(aclrtSetDevice(static_castint32_t(device))); // 设置当前线程操作的设备 size_t mallocSize count * sizeof(float); ACLCHECK(aclrtMalloc(sendBuf, mallocSize, ACL_MEM_MALLOC_HUGE_ONLY)); ACLCHECK(aclrtMalloc(recvBuf, mallocSize, ACL_MEM_MALLOC_HUGE_ONLY));其中count devCount即每个 rank 的收发缓冲区均能容纳rankSize个 float。接着在 Host 侧构造输入数据——每个 rank 的全部元素初始化为自己的 rank_idvoid* hostBuf nullptr; ACLCHECK(aclrtMallocHost(hostBuf, mallocSize)); float* tmpHostBuff static_castfloat*(hostBuf); for (uint64_t i 0; i count; i) { tmpHostBuff[i] static_castfloat(device); } ACLCHECK(aclrtMemcpy(sendBuf, mallocSize, hostBuf, mallocSize, ACL_MEMCPY_HOST_TO_DEVICE)); ACLCHECK(aclrtFreeHost(hostBuf));即 rank 0 的 sendBuf 内容为[0 0 0 ...]、rank 1 为[1 1 1 ...]、rank i 为[i i i ...]这样便于从输出结果直观验证 AlltoAllV 的数据交换是否正确。第四步初始化通信域HcclComm hcclComm; HCCLCHECK(HcclCommInitRootInfo(rankSize, ctx-rootInfo, device, hcclComm));HcclCommInitRootInfo()基于 rootinfo 为当前设备创建通信域第一个参数rankSize为通信域包含的 rank 总数第二个参数为共享的 rootinfo第三个参数为当前设备号第四个参数输出通信域句柄。所有线程使用同一份 rootInfo从而保证整个通信域视图一致。第五步构造 AlltoAllV 的四个数组并执行算子// 创建任务流 aclrtStream stream; ACLCHECK(aclrtCreateStream(stream)); // 设置收发数据量收发数据量相同 std::vectoruint64_t sendCounts(rankSize, 1); std::vectoruint64_t recvCounts(rankSize, 1); std::vectoruint64_t sdispls(rankSize); std::vectoruint64_t rdispls(rankSize); for (size_t i 0; i rankSize; i) { sdispls[i] i; rdispls[i] i; } HCCLCHECK(HcclAlltoAllV( sendBuf, sendCounts.data(), sdispls.data(), HCCL_DATA_TYPE_FP32, recvBuf, recvCounts.data(), rdispls.data(), HCCL_DATA_TYPE_FP32, hcclComm, stream)); // 阻塞等待任务流中的集合通信任务执行完成 ACLCHECK(aclrtSynchronizeStream(stream));这是整个样例的核心。以 8 卡为例各数组取值为sendCounts[i] 1本 rank 发给 rank i 恰好 1 个 floatrecvCounts[i] 1本 rank 从 rank i 恰好接收 1 个 floatsdispls[i] i发给 rank i 的数据在 sendBuf 中的偏移为 i 个元素rdispls[i] i从 rank i 接收的数据写入 recvBuf 的偏移为 i 个元素。因此rank i 的 sendBuf 布局为[i, i, ..., i]共 rankSize 个 i切分后发给 rank 0..7 各 1 个元素每个 rank 从 rank 0..7 各收 1 个元素按 rdispls 拼接到 recvBuf 中恰好得到[0, 1, 2, 3, 4, 5, 6, 7]。该样例同时是各 rank 间数据量相等的特例每个元素数量均为 1但sendCounts/recvCounts数组本身完全支持每对 rank 数据量可定制的不等量交换。说明HcclAlltoAllV是异步接口算子下发后需通过aclrtSynchronizeStream(stream)阻塞等待执行完成才能保证后续 Host 侧读取的结果有效。第六步回读结果并打印std::this_thread::sleep_for(std::chrono::seconds(device)); // 错峰打印避免输出交错 void* resultBuff; ACLCHECK(aclrtMallocHost(resultBuff, mallocSize)); ACLCHECK(aclrtMemcpy(resultBuff, mallocSize, recvBuf, mallocSize, ACL_MEMCPY_DEVICE_TO_HOST)); float* tmpResBuff static_castfloat*(resultBuff); std::cout rankId: device , output: [; for (uint64_t i 0; i count; i) { std::cout tmpResBuff[i]; } std::cout ] std::endl; ACLCHECK(aclrtFreeHost(resultBuff));将 Device 侧结果回拷到 Host 后打印。sleep_for(device)秒是让各线程按 rank 顺序错峰输出避免多线程 stdout 竞争导致打印交错。第七步资源释放HCCLCHECK(HcclCommDestroy(hcclComm)); // 销毁通信域 ACLCHECK(aclrtFree(sendBuf)); // 释放 Device 侧内存 ACLCHECK(aclrtFree(recvBuf)); // 释放 Device 侧内存 ACLCHECK(aclrtDestroyStream(stream)); // 销毁任务流主线程在所有工作线程 join 后释放 rootinfo 的 Host 内存并调用aclFinalize()完成设备去初始化。资源释放顺序遵循先销毁算子相关资源、再销毁通信域、最后设备去初始化的规范。编译与运行在样例代码目录examples/02_collectives/07_alltoallv下执行make make testmake调用 Makefile 完成编译关键编译选项包括-stdc17C17 标准链接-lhccl -lascendcl链接 HCCL 与 AscendCL 动态库ASCEND_LIB_DIR ${ASCEND_HOME_PATH}/lib64头文件路径-I$(ASCEND_HOME_PATH)/include安全加固选项-fstack-protector-strong、-fPIE -pie、-Wl,-z,relro、-Wl,-z,now、-Wl,-z,noexecstack等。make test则直接运行生成的可执行文件./alltoallv。另有make clean清理构建产物与make help查看帮助目标。算子展开模式可选配置可通过环境变量HCCL_OP_EXPANSION_MODE配置通信算子的展开模式不同产品型号支持的范围不同完整用法参见仓库内环境变量说明 HCCL_OP_EXPANSION_MODE.md。例如将通信算子展开模式设置为 AI CPU 通信引擎export HCCL_OP_EXPANSION_MODEAI_CPU在 all_to_all_v.cc 中HcclGetOpExpansionMode(comm, param)会读取该环境变量并把展开模式写入OpParam.engine随后AlltoAllVExecDispatch依据引擎类型AICPU_TS / AIV / CCU 等分流到不同的执行器。运行结果解读在 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 的输入数据初始化为对应的 rank_id经过 AlltoAllV 后各 rank 的输出均为所有节点输入数据的拼接[0 1 2 3 4 5 6 7]与上文数组推导完全吻合可用于自检算子行为是否正确。源码级原理HcclAlltoAllV 底层执行链路入口与参数校验all_to_all_v.cc 中的HcclAlltoAllV在确认设备支持 OutPlace 新流程后依次执行InitEnvConfig()初始化环境配置CheckAlltoAllVInputPara()逐项校验 comm、sendCounts、sdispls、recvCounts、rdispls、stream 非空并明确拒绝 sendBuf 与 recvBuf 相同AlltoAllV does not support in-place operation返回HCCL_E_PARA见 源码第 542-546 行查询 rankSize、userRank、commName生成操作标签tag ALLTOALLV_ commName并做 tag 合法性检查遍历 sendCounts/recvCounts 求出maxSendRecvCount并做CheckCount与CheckDataType校验通过AlltoAllVEntryLog记录接口交互信息日志sendCounts/recvCounts/sdispls/rdispls 四个数组均会打印调用AlltoAllVOutPlace进入真正的执行流程。参数封装与算法分发AlltoAllVOutPlaceCommon源码第 792-829 行完成 OpParam 构造AlltoAllVConstructOpParam将四个数组打包进连续内存varData按 SEND_COUNT、RECV_COUNT、SEND_DISPL、RECV_DISPL 四个区段排布并依据 sdisplssendCounts 与 rdisplsrecvCounts 计算输入输出总尺寸CalcInputOutputSize用于图模式下的内存安全校验。随后AlltoAllVExecDispatch源码第 730-790 行负责算法级分发rankSize 1 时走SingleRankProc单 rank 处理否则调用Selector依据拓扑信息选择算法AlltoAllV 的自动选择器实现位于 alltoallv_auto_selector.cc从源码目录结构可以推断AlltoAllV 在不同引擎下实现了多套执行器与算法模板AICPU 侧有ins_temp_all_to_all_v_mesh_1D等模板AIV 侧有aiv_temp_all_to_all_v_mesh_1D与 superkernel 实现CCU 侧则有 mesh1d、mesh2die、multi_jetty 等多种拓扑变体见 template 目录 与 executor 目录。与 AlltoAll / AlltoAllVC 的关系从 all_to_all_v.cc 可以看到HcclAlltoAll等量全交换在 OutPlace 新流程中会构造四个等长数组后复用 AlltoAllV 的执行路径HcclAlltoAllVC矩阵式变长全交换同样通过ConvertAlltoAllVCParam将收发矩阵展开为 sendCounts/recvCounts/sdispls/rdispls 后走 AlltoAllV 逻辑。这说明 AlltoAllV 是 HCCL 全交换家族的公共底座理解本样例即可顺带掌握 AlltoAll 与 AlltoAllVC 的执行框架。工程约束与调优建议依据 HcclAlltoAllV 接口文档 的约束说明实际业务落地时需注意不支持原地操作sendBuf 与 recvBuf 必须为不同地址且内存范围不能重叠否则接口返回HCCL_E_PARA性能与缓存区相关AlltoAllV 的性能与 NPU 之间共享数据的缓存区大小有关当通信数据量超过缓存区大小时性能会明显下降。若业务通信数据量较大建议通过环境变量 HCCL_BUFFSIZE 适当增大缓存区以提升性能串行下发约束多个通信域下的所有通信算子在每个 Device 上需要保证串行下发不允许乱序、多线程并发下发也不支持线程重入同一 Device 上、同一通信域内的所有通信算子下发线程需使用相同的 Context产品差异针对 Atlas 训练系列产品AlltoAllV 的通信域需满足 cluster 维度约束单 Server 1p/2p 通信域需在同一 cluster 内4p/8p 与多 Server 场景以 cluster 为基本单位且 Server 间 cluster 选取需一致单 Server 场景下要求网卡状态为 upAtlas 300I Duo 推理卡仅支持单 Server 场景最多 2 张卡共 4 个 NPU数据类型不同型号支持的 HcclDataType 集合不同Ascend 950 系列额外支持 float8-e5m2、float8-e4m3、float8-e8m0、hifloat8 等低比特类型A2/A3 系列支持 bfp16Atlas 训练系列与 300I Duo 则支持 int8/uint8/int16/uint16/int32/uint32/int64/uint64/float16/float32/float64。小结本文围绕 AlltoAllV 官方样例 完整走通了HCCL 环境准备 → 通信域初始化rootinfo HcclCommInitRootInfo→ 四个数组构造 → HcclAlltoAllV 下发 → 结果回读 → 资源释放的端到端链路并结合 HcclAlltoAllV 接口文档 与 all_to_all_v.cc 源码剖析了参数校验、OpParam 构造与算法分发机制。读者可将本样例作为模板把sendCounts/recvCounts/sdispls/rdispls四个数组改为不等长配置即可实现任意变长数据量的全交换通信如需在 PyTorch / TensorFlow 框架中使用该能力可参考仓库内 03_ai_framework 下的框架集成样例。【免费下载链接】hccl集合通信库Huawei Collective Communication Library简称HCCL是基于昇腾AI处理器的高性能集合通信库为计算集群提供高性能、高可靠的通信方案项目地址: https://gitcode.com/cann/hccl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表