ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

HCCL Scatter 集合通信算子实战:HcclScatter 接口详解与单机多卡样例解析

HCCL Scatter 集合通信算子实战:HcclScatter 接口详解与单机多卡样例解析 HCCL Scatter 集合通信算子实战HcclScatter 接口详解与单机多卡样例解析【免费下载链接】hccl集合通信库Huawei Collective Communication Library简称HCCL是基于昇腾AI处理器的高性能集合通信库为计算集群提供高性能、高可靠的通信方案项目地址: https://gitcode.com/cann/hccl本篇技术文章围绕 HCCLHuawei Collective Communication Library昇腾集合通信库的 Scatter 集合通信算子展开基于仓库中的官方样例 examples/02_collectives/09_scatter/README.md 及其配套源码 main.cc完整讲解如何调用HcclScatter()接口把 root 节点的数据均分并散布至通信域内其他 rank。读完本文你将掌握 Scatter 算子的接口语义、参数约束、单机多卡组网下的完整开发流程设备检测、rootinfo 生成、通信域初始化、算子下发、结果校验以及编译执行样例所需的环境与命令细节。1. Scatter 算子功能概述Scatter 是集合通信六大基础算子之一通信域内的 root 节点持有一份完整数据调用 Scatter 后这份数据被均分为与通信域大小相等的若干份并散布到每个 rank 的接收缓冲区中——每个 rank 只拿到属于自己序号的那一份。它常用于数据分发场景例如把某张卡上准备好的初始数据、词表或梯度切片分发到集群各 rank。Scatter 操作与 AllGather、ReduceScatter 的关系可以简单理解为AllGather 是各发各的一份收全量Scatter 是其逆操作root 发全量各收一份。本样例支持的功能点与 README 一致设备检测通过aclrtGetDeviceCount()接口查询可用设备数量单机 N 卡N 2组网root 节点信息生成将 rank0 作为 root 节点通过HcclGetRootInfo()接口生成 root 节点的 rootinfo 标识信息。rootinfo 主要包含 Device IP、Device ID 等信息需广播至集群内所有 rank 用来初始化通信域通信域初始化每个线程中基于同一份 rootinfo通过HcclCommInitRootInfo()接口初始化通信域执行 Scatter调用HcclScatter()接口将 root 节点的数据均分并散布至其他 rank并打印结果。2.HcclScatter接口定义接口声明位于仓库头文件 include/hccl.hextern HcclResult HcclScatter( void* sendBuf, void* recvBuf, uint64_t recvCount, HcclDataType dataType, uint32_t root, HcclComm comm, aclrtStream stream);接口文档详见 docs/zh/api_ref/comm_op_interface/HcclScatter.md。2.1 参数说明参数名输入/输出描述sendBuf输入源数据 buffer 地址仅 root 节点必须有效非 root 节点可为空recvBuf输出目的数据 buffer 地址集合通信结果输出至此 buffer 中recvCount输入参与 scatter 操作的 recvBuf 的数据个数比如只有一个 int32 数据参与则 recvCount 1dataType输入Scatter 操作的数据类型HcclDataType类型不同产品型号支持的数据类型不同root输入作为 scatter root 的 rank idcomm输入集合通信操作所在的通信域stream输入本 rank 所使用的任务流stream2.2 各产品型号支持的数据类型产品支持的数据类型Ascend 950PR / Ascend 950DTint8、uint8、int16、uint16、int32、uint32、int64、uint64、float8-e5m2、float8-e4m3、float8-e8m0、hifloat8、float16、float32、float64、bfp16Atlas A3 训练系列 / Atlas A3 推理系列int8、uint8、int16、uint16、int32、uint32、int64、uint64、float16、float32、float64、bfp16Atlas A2 训练系列 / Atlas A2 推理系列int8、uint8、int16、uint16、int32、uint32、int64、uint64、float16、float32、float64、bfp16Atlas 训练系列int8、uint8、int16、uint16、int32、uint32、int64、uint64、float16、float32、float642.3 返回值返回值说明HCCL_SUCCESS接口调用成功HCCL_E_PTR传入的指针参数为空如 comm、recvBuf 等为 nullptrroot 节点的 sendBuf 也不能为 nullptrHCCL_E_PARA传入的参数无效如 count 超过上限、root 越界等HCCL_E_NOT_SUPPORT操作不被支持如 dataType 非法或当前型号不支持、混合组网不支持 Scatter 等HCCL_E_INTERNAL内部错误2.4 约束说明重点所有 rank 的recvCount、dataType、root均应相同全局只能有 1 个 root 节点非 root 节点的 sendBuf 可以为空root 节点的 sendBuf 不能为空——这也是样例中 sendBuf 只在 root 分支申请的原因多个通信域下的所有通信算子在每个 Device 上需要保证串行下发不允许乱序、多线程并发下发也不支持线程重入在同一 Device 上同一通信域内的所有通信算子的下发线程需要使用相同的 Context。关于多线程并发下发的约束要注意样例中虽然每张卡各起一个线程但每个线程操作的是不同的 Device且对各自 Device 的算子下发是串行的因此不违反单 Device 串行下发的约束。3. 样例目录结构├── main.cc # 样例源文件 ├── Makefile # 编译/构建配置文件 └── scatter # 编译生成的可执行文件对应仓库路径分别为 examples/02_collectives/09_scatter/main.cc 与 examples/02_collectives/09_scatter/Makefile。4. 环境准备4.1 环境要求本样例支持以下产品组网为单机 N 卡N 2Ascend 950PR / Ascend 950DTAtlas A3 训练系列产品 / Atlas A3 推理系列产品Atlas A2 训练系列产品Atlas 训练系列产品4.2 配置环境变量设置 CANN 环境变量以 root 用户默认安装路径为例source /usr/local/Ascend/cann/set_env.shMakefile 中通过ASCEND_HOME_PATH定位头文件与库路径若未设置该变量会直接报错提示先 source 环境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 ASCEND_INC_DIR ${ASCEND_HOME_PATH}/include ASCEND_LIB_DIR ${ASCEND_HOME_PATH}/lib64 LIBS -L$(ASCEND_LIB_DIR) -lhccl -lascendcl即编译链接libhccl与libascendcl两个库C 标准为 C17并开启了-Werror、-fstack-protector-strong及一组加固链接选项relro、now、noexecstack等。5. 编译与执行样例在本样例代码目录下执行make # 编译生成可执行文件 scatter make test # 运行 ./scatter注意可通过设置HCCL_OP_EXPANSION_MODE环境变量配置通信算子的展开模式不同产品型号支持的范围可参考仓库中的环境变量说明文档 HCCL_OP_EXPANSION_MODE.md。例如# 设置通信算子的展开模式为 AI CPU 通信引擎 export HCCL_OP_EXPANSION_MODEAI_CPU6. 源码逐段解析下面结合 main.cc 完整走读样例实现。6.1 错误检查宏样例定义了两个宏把 ACL 与 HCCL 接口的返回值检查统一化出错时打印文件、行号与错误码后立即返回#define ACLCHECK(ret) \ do { \ if (ret ! ACL_SUCCESS) { \ printf(acl interface return err %s:%d, retcode: %d \n, __FILE__, __LINE__, ret); \ return ret; \ } \ } while (0) #define HCCLCHECK(ret) \ do { \ if (ret ! HCCL_SUCCESS) { \ printf(hccl interface return err %s:%d, retcode: %d \n, __FILE__, __LINE__, ret); \ return ret; \ } \ } while (0)每个线程上下文结构体保存该 rank 所需的设备与 root 信息struct ThreadContext { HcclRootInfo* rootInfo; // 全体 rank 共享的 root 节点标识信息 uint32_t rootRank; // root rank本例为 0 uint32_t device; // 本线程操作的 NPU 设备号 uint32_t devCount; // 通信域大小设备总数 };6.2 主流程设备检测与 rootinfo 生成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)); // 生成 Root 节点信息各线程使用同一份 RootInfo void* rootInfoBuf nullptr; ACLCHECK(aclrtMallocHost(rootInfoBuf, sizeof(HcclRootInfo))); HcclRootInfo* rootInfo (HcclRootInfo*)rootInfoBuf; HCCLCHECK(HcclGetRootInfo(rootInfo));关键点aclInit(NULL)完成 ACL 运行时初始化aclrtGetDeviceCount()获取本机 NPU 数量 devCount对应输出 Found 8 NPU device(s) available先aclrtSetDevice(rootRank)切到 rank0 设备再调用HcclGetRootInfo(rootInfo)生成 rootinfo——其中封装了 Device IP、Device ID 等信息是各 rank 初始化通信域的依据rootinfo 只需生成一次之后所有线程共享同一份HcclRootInfo这正是rootinfo 需广播至集群内所有 rank在单机多线程模型下的落地方式多进程场景下则需要通过进程间方式传递。随后按设备数启动等量线程std::vectorstd::thread threads(devCount); std::vectorThreadContext args(devCount); for (uint32_t i 0; i devCount; i) { args[i].rootInfo rootInfo; args[i].rootRank static_castuint32_t(rootRank); 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(); }每个线程模拟一个 rankrank id 即设备号主线程join()全部线程后释放 rootinfo 并aclFinalize()收尾。6.3 每个 rank 的执行逻辑Sample 函数Sample()是单个 rank 的完整生命周期数据量为sendCount devCount个 float、recvCount 1个 float即 root 持有 N 个 floatScatter 后每 rank 收到 1 个uint64_t sendCount ctx-devCount; uint64_t recvCount 1U; size_t sendSize sendCount * sizeof(float); size_t recvSize recvCount * sizeof(float); // 设置当前线程操作的设备 ACLCHECK(aclrtSetDevice(static_castint32_t(device))); // 申请 Device 内存用于接收 Scatter 结果 ACLCHECK(aclrtMalloc(recvBuf, recvSize, ACL_MEM_MALLOC_HUGE_ONLY)); // 在 Root 节点申请 Device 内存用于存放发送数据初始化为 0,1,2,… 递增序列 if (device rootRank) { ACLCHECK(aclrtMalloc(sendBuf, sendSize, ACL_MEM_MALLOC_HUGE_ONLY)); void* hostBuf nullptr; ACLCHECK(aclrtMallocHost(hostBuf, sendSize)); float* tmpHostBuf static_castfloat*(hostBuf); for (uint32_t i 0; i sendCount; i) { tmpHostBuf[i] static_castfloat(i); } // 将 Host 侧输入数据拷贝到 Device 侧 ACLCHECK(aclrtMemcpy(sendBuf, sendSize, hostBuf, sendSize, ACL_MEMCPY_HOST_TO_DEVICE)); ACLCHECK(aclrtFreeHost(hostBuf)); }从源码结构可以看出两个与接口约束严格对应的细节sendBuf只在device rootRank分支内申请非 root rank 的 sendBuf 保持nullptr正对应HcclScatter 约束中非 root 节点的 sendBuf 可以为空root 节点的 sendBuf 不能为空数据先写入 Host 内存再ACL_MEMCPY_HOST_TO_DEVICE拷贝到 Device 侧保证 Scatter 输入位于 Device 内存。接着是通信域初始化、任务流创建与算子下发// 初始化集合通信域 HcclComm hcclComm; HCCLCHECK(HcclCommInitRootInfo(ctx-devCount, ctx-rootInfo, device, hcclComm)); // 创建任务流 aclrtStream stream; ACLCHECK(aclrtCreateStream(stream)); // 执行 Scatter将通信域内 Root 节点的数据均分并散布至其他 Rank HCCLCHECK(HcclScatter(sendBuf, recvBuf, recvCount, HCCL_DATA_TYPE_FP32, rootRank, hcclComm, stream)); // 阻塞等待任务流中的集合通信任务执行完成 ACLCHECK(aclrtSynchronizeStream(stream));调用链上值得注意的三件事HcclCommInitRootInfo(devCount, rootInfo, device, hcclComm)传入通信域大小、共享的 rootinfo、本设备号返回本 rank 的通信域句柄所有 rank 用相同参数初始化从而构成同一个通信域HcclScatter下发到指定 stream 后是异步的必须aclrtSynchronizeStream(stream)阻塞等待集合通信任务执行完成后 recvBuf 中结果才有效同一 Device 上的算子必须串行下发样例中每个线程只对一个设备下发一次 Scatter天然满足该约束。结果回传与打印// 将 Device 侧集合通信任务结果拷贝到 Host并打印结果 std::this_thread::sleep_for(std::chrono::seconds(device)); void* resultHostBuf; ACLCHECK(aclrtMallocHost(resultHostBuf, recvSize)); ACLCHECK(aclrtMemcpy(resultHostBuf, recvSize, recvBuf, recvSize, ACL_MEMCPY_DEVICE_TO_HOST)); float* tmpResultBuf static_castfloat*(resultHostBuf); std::cout rankId: device , output: [; for (uint32_t i 0; i recvCount; i) { std::cout tmpResultBuf[i]; } std::cout ] std::endl; ACLCHECK(aclrtFreeHost(resultHostBuf));其中std::this_thread::sleep_for(std::chrono::seconds(device))让 rank0、rank1、rank2……依次延后 device 秒打印使多卡结果按 rank 顺序稳定输出、避免日志交叉注意这会把样例总耗时拉长实际业务中不应保留该等待。最后是完整的资源释放序列顺序与申请顺序严格对应HCCLCHECK(HcclCommDestroy(hcclComm)); // 销毁通信域 if (sendBuf ! nullptr) { ACLCHECK(aclrtFree(sendBuf)); // 释放 Device 侧内存仅 root 有效 } ACLCHECK(aclrtFree(recvBuf)); // 释放 Device 侧内存 ACLCHECK(aclrtDestroyStream(stream)); // 销毁任务流 ACLCHECK(aclrtResetDevice(device)); // 重置设备7. 结果示例root 节点的内容初始化为 0~7经过 Scatter 操作后通信域内 root 节点的数据被均分并散布至其他 rank。8 卡场景下输出为Found 8 NPU device(s) available rankId: 0, output: [ 0 ] rankId: 1, output: [ 1 ] rankId: 2, output: [ 2 ] rankId: 3, output: [ 3 ] rankId: 4, output: [ 4 ] rankId: 5, output: [ 5 ] rankId: 6, output: [ 6 ] rankId: 7, output: [ 7 ]即 rootrank0上的序列[0,1,2,3,4,5,6,7]按 rank 序号均分rank i 恰好收到第 i 个元素验证了 Scatter 均分并散布 的语义。8. 开发要点小结与延伸阅读接口层面Scatter 只有recvCount而没有sendCountroot 侧发送数据量隐式等于recvCount × 通信域大小写代码时务必保证 root 的 sendBuf 按此规格分配否则会触发HCCL_E_PARA或读到非法内存参数一致性所有 rank 必须传入相同的recvCount、dataType、root全局仅一个 root内存与同步输入/输出 buffer 需位于 Device 内存aclrtMalloc算子下发后需同步 stream 再读结果资源生命周期HcclCommDestroy→ 释放 Device 内存 → 销毁 stream →aclrtResetDevice多 rank 场景建议每线程独立走一遍该序列。延伸阅读均在当前仓库内接口参考文档HcclScatter、集合通信算子总览头文件定义include/hccl.h同类算子样例AllGather、ReduceScatter环境变量说明HCCL_OP_EXPANSION_MODE、HCCL 环境变量总览【免费下载链接】hccl集合通信库Huawei Collective Communication Library简称HCCL是基于昇腾AI处理器的高性能集合通信库为计算集群提供高性能、高可靠的通信方案项目地址: https://gitcode.com/cann/hccl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表