使用说明
Ascend C提供一组Hccl高阶API,方便算子Kernel开发用户在AI Core侧灵活编排集合通信任务。相比于一般算子,使用Hccl高阶API的算子在开发和执行过程中的注意事项请参考通算融合算子。
Hccl为集合通信任务客户端,主要对外提供了集合通信原语接口(以下统称为Prepare接口),对标集合通信C++接口,详细可参见HCCL接口参考,当前支持AllReduce、AllGather、ReduceScatter、AlltoAll接口等。本章的所有接口运行在AI Core上,且不执行通信任务,而是由用户调用Prepare接口将对应类型的通信任务信息发送给AI CPU服务端,并在合适的时机通过Commit接口通知AI CPU上的服务端执行对应的通信任务。
所谓合适的时机,取决于用户编排的是先通信后计算的任务,还是先计算后通信的任务。对于这两种场景,简述如下:
- 先通信后计算的任务:典型的如AllGather通信+MatMul计算任务编排。此场景下,用户在调用AllGather接口下发通信任务之后,通过AllGather接口返回的该通信任务标识handleId,可立即调用Commit接口通知服务端执行该handleId对应的任务,同时用户调用Wait阻塞接口等待服务端通知handleId对应的通信任务执行结束,待该通信任务结束后,再执行计算任务。
- 先计算后通信的任务:典型的如MatMul计算+AllReduce通信任务编排。此场景下,用户可以先调用AllReduce接口通知服务端先下发通信任务,再调用MatMul计算接口进行计算,这样AllReduce任务的组装和任务下发过程可以被MatMul的计算流水所掩盖,待计算任务完成后,调用Commit接口通知服务端执行AllReduce任务,无须调用Wait接口等待通信任务执行结束。
当后续无通信任务时,调用Finalize接口,通知服务端后续无通信任务,执行结束后退出,客户端检测并等待最后一个通信任务执行结束。以上介绍的AI Core下发Hccl通信任务的机制示意图如下图所示。
实现AI Core下发一个通信任务的具体步骤如下:
- 创建Hccl对象,并调用初始化接口。当前支持两种方式调用初始化接口Init,推荐使用传initTiling地址的Init接口调用方式。
- 传initTiling地址的Init接口调用方式:
1 2 3 4 5 6 7 8
// 传initTiling地址的调用方式,推荐使用该方式 auto tiling = (__gm__ AllGatherCustomTilingData*)tilingGM; // AllGatherCustomTilingData为对应算子头文件定义的结构体 Hccl hccl; GM_ADDR contextGM = GetHcclContext<0>(); // AscendC自定义算子kernel中,通过此方式获取Hccl context __gm__ void *mc2InitTiling = (__gm__ void *)(&tiling->mc2InitTiling); hccl.Init(contextGM, mc2InitTiling);
当使用传入initTiling地址的方式调用Init接口时,必须使用标准C++语法定义TilingData结构体的开发方式,具体请参考使用标准C++语法定义TilingData。如上示例代码中的tilingGM为host侧传入的、作为核函数入参的算子TilingData的GM地址,将该地址强转为在kernel侧对应算子头文件中自定义的结构体指针类型。完整示例请参考8.13.1.2-调用示例。
- 不传initTiling地址的Init接口调用方式:
1 2 3 4
// 不传initTiling地址的调用方式 Hccl<HCCL_SERVER_TYPE_AICPU> hccl; // 创建Hccl对象 GM_ADDR context = AscendC::GetHcclContext<0>(); // 获取hccl上下文信息 hccl.Init(context); // hccl对象初始化
在上述两种调用方式中,调用Init初始化接口时需要传入通信上下文信息,可以通过框架提供的获取通信上下文的接口GetHcclContext获取,该接口的函数原型如下。
1 2
template <uint32_t index> __aicore__ inline __gm__ uint8_t* __gm__ GetHcclContext(void)
- 传initTiling地址的Init接口调用方式:
- 设置对应通信算法的Tiling地址(传initTiling地址的Init接口调用方式需要该步骤)。通过SetCcTiling接口设置对应通信算法的Tiling地址,调用Commit接口后,该地址被发送到服务端由服务端解析。SetCcTiling接口必须与传initTiling地址的Init接口配合使用。如果Init接口的调用方式为不传initTiling地址的方式,则不需要调用该接口,示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 13
// 传initTiling地址的调用方式 auto tiling = (__gm__AllGatherCustomTilingData*)tilingGM; Hccl hccl; GM_ADDR contextGM1 = GetHcclContext<0>(); // AscendC自定义算子kernel中,通过此方式获取Hccl context __gm__ void *mc2InitTiling = (__gm__ void *)(&tiling->mc2InitTiling); __gm__ void *mc2CcTiling = (__gm__ void *)(&(tiling->mc2CcTiling)); hccl.Init(contextGM, mc2InitTiling); auto ret = SetCcTiling(mc2CcTiling); if (ret) { return; }
- 用户通过对应的Prepare接口异步下发对应类型的通信任务,并获取到该任务的标识handleId,服务端接收到后开始通信任务的展开和下发,示例如下。
1 2 3 4 5 6 7 8
auto handleId = hccl.ReduceScatter<false>(aGM, cGM, recvCount, AscendC::HCCL_DATA_TYPE_FP16, HCCL_REDUCE_SUM, strideCount, 1); // 对于Prepare接口,在调试时可增加异常值校验和PRINTF打印 // if (handleId == INVALID_HANDLE_ID) { // PRINTF("[ERROR] call ReduceScatter failed, handleId is -1."); // return; // }
示例的Prepare接口为ReduceScatter,其他接口可参考后续章节的内容。其中的参数AscendC::HCCL_DATA_TYPE_FP16是Hccl任务的数据类型,其数据结构为HcclDataType,对应的参数说明参考表1;参数HCCL_REDUCE_SUM是一种Reduce操作,AllReduce和ReduceScatter规约操作支持的Reduce操作类型参见表2。
表1 HcclDataType参数说明 数据类型
说明
HcclDataType
Hccl任务的数据类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
enum HcclDataType { HCCL_DATA_TYPE_INT8 = 0, /* int8 */ HCCL_DATA_TYPE_INT16 = 1, /* int16 */ HCCL_DATA_TYPE_INT32 = 2, /* int32 */ HCCL_DATA_TYPE_FP16 = 3, /* half或float16 */ HCCL_DATA_TYPE_FP32 = 4, /* float32 */ HCCL_DATA_TYPE_INT64 = 5, /* int64 */ HCCL_DATA_TYPE_UINT64 = 6, /* uint64 */ HCCL_DATA_TYPE_UINT8 = 7, /* uint8 */ HCCL_DATA_TYPE_UINT16 = 8, /* uint16 */ HCCL_DATA_TYPE_UINT32 = 9, /* uint32 */ HCCL_DATA_TYPE_FP64 = 10, /* float64 */ HCCL_DATA_TYPE_BFP16 = 11, /* bfloat16 */ HCCL_DATA_TYPE_RESERVED /* *< reserved */ }
- 用户调用Commit接口通知服务端执行handleId对应的通信任务。
1 2
// 等待通信任务执行时机成熟,调用Commit接口通知服务端执行 hccl.Commit(handleId);
- 用户调用Wait阻塞接口,等待服务端执行完对应的通信任务。
1 2 3 4 5 6 7 8 9
auto ret = hccl.Wait(handleId); // 对于Wait和Query接口,在调试时可增加异常值校验和PRINTF打印 // if (ret == HCCL_FAILED) { // PRINTF("[ERROR] call Wait for handleId[%d] failed.", handleId); // return; // } // 调用核间同步接口,防止部分核执行较快退出,触发hccl析构,影响执行较慢的核 // 开发者可根据实际的业务场景,选择调用, , 接口,保证全部核的任务完成后再退出执行
- 用户调用Finalize接口,通知服务端后续无通信任务,执行结束后退出;客户端检测并等待最后一个通信任务执行结束。
1
hccl.Finalzie();
注意:调用步骤2到步骤5的接口前,必须指定接口代码运行在AI Cube核或者AI Vector核上,实现时如下代码所示。
1 2 3 4 5 |
// 通过g_coreType来判断AI Cube核(即AIC核)或者AI Vector核(即AIV核) if (g_coreType == AIV) { // if (g_coreType == AIC) { 调用Hccl接口 } |
基于以上对单个通信任务下发的了解,介绍各Prepare接口中repeat参数的灵活使用方式。每个handleId对应的通信任务,其Commit接口和Wait接口的调用次数,与Prepare接口的repeat入参的值相等。以图2 ReduceScatter通信示例进行说明,假设共4张卡,每张卡上源数据首先按照rankId均匀分成4份,每份数据被切分成3份,最终被切分后的每份数据的个数为TiliLen,每次ReduceScatter通信仅通信一组切分数据(如图中数据0-0、1-0、2-0、3-0为一组切分数据),因此需要做3次ReduceScatter操作,全部数据才能通信完。
注意:数据的切分数量不要超过16块,否则会影响算子性能或者可能导致部分资源不足。一个通信域内,Prepare接口的总调用次数不能超过32次,否则通信任务会下发失败,Prepare接口返回INVALID_HANDLE_ID,即返回-1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const size_t rankSize = 4U; // 4张卡 const size_t tileCnt = 3U; // 卡上的数据按照rankId均匀分成rankSize份,且每份又被切分成3份 const size_t TileLen = 100U;// 被切分后的每份数据个数 // for循环中生成了3个handleId,每个handleId只调用了repeat=1次Commit和Wait接口 for (int i = 0; i < tileCnt; ++i) { auto handleId = ReduceScatter(sendBuf, recvBuf, TileLen, HCCL_DATA_TYPE_FP32 , HCCL_REDUCE_SUM, TileLen*tileCnt, 1); Commit(handleId); auto ret = Wait(handleId); // 执行其他计算逻辑 .... // 更新ReduceScatter的收发地址 constexper uint32_t kSizeOfFloat32 = 4U; sendBuf += TileLen * kSizeOfFloat32; recvBuf += TileLen * kSizeOfFloat32; } |
由于每张卡上3份数据的源地址SendBuf是连续的,且每张卡中目的地址recvBuf用来存储3份通信结果数据的内存也是连续的,因此以上代码可以优化,将ReduceScatter接口中的repeat参数设置为3,从而调用1次ReduceScatter接口,达到下发3个通信任务的效果。此时,只有1个handleId的任务,但是需要调用3次Commit和Wait接口,对应代码片段如下。
1 2 3 4 5 6 7 8 9 10 11 |
const size_t rankSize = 4U; // 4张卡 const size_t tileCnt = 3U; // 卡上的数据按照rankId均匀分成rankSize份,且每份又被切分成3份 const size_t TileLen = 100U;// 被切分后的每份数据个数 auto handleId = ReduceScatter(sendBuf, recvBuf, TileLen, HCCL_DATA_TYPE_FP32, HCCL_REDUCE_SUM, TileLen*tileCnt, tileCnt); for (int i = 0; i < tileCnt; ++i) { Commit(handleId); auto ret = Wait(handleId); // 执行其他计算逻辑 .... } |
数据类型 |
说明 |
||
---|---|---|---|
MC2_BUFFER_LOCATION |
预留参数。计算和通信中间结果的Buffer存放位置。用户在Tiling侧可设置该字段。
|
提示:调试含有Hccl高阶API的算子时,在算子编译工程中增加编译选项-DASCENDC_DEBUG,可以使能异常场景拦截的能力,具体内容请参考并使用assert接口。