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上的服务端执行对应的通信任务。
所谓合适的时机,取决于用户编排的是先通信后计算的任务,还是先计算后通信的任务。对于这两种场景,简述如下:
当后续无通信任务时,调用Finalize接口,通知服务端后续无通信任务,执行结束后退出,客户端检测并等待最后一个通信任务执行结束。以上介绍的AI Core下发Hccl通信任务的机制示意图如下图所示。
实现AI Core下发一个通信任务的具体步骤如下:
1 2 3 |
Hccl<HCCL_SERVER_TYPE_AICPU> hccl; // 创建Hccl对象 GM_ADDR context = AscendC::GetHcclContext<HCCL_GROUP_ID_0>(); // 获取hccl上下文信息 hccl.Init(context); // hccl对象初始化 |
调用Init初始化接口时需要传入通信上下文信息,可以通过框架提供的获取通信上下文的接口GetHcclContext获取,该接口的函数原型如下;示例中HCCL_GROUP_ID_0为定义的全局变量,可参考表1。该接口返回的数据结构为HcclCombinOpParam,开发者也可以构造该数据结构作为Hccl对象初始化的信息传入,HcclCombinOpParam结构体说明参考表2。
1 2 |
template <uint32_t index> __aicore__ inline __gm__ uint8_t* __gm__ GetHcclContext(void) |
数据类型 |
说明 |
||
---|---|---|---|
HcclCombineOpParam |
Hccl通信上下文。
|
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,对应的参数说明参考表3;参数HCCL_REDUCE_SUM是一种Reduce操作,AllReduce和ReduceScatter规约操作支持的Reduce操作类型参见表4。
数据类型 |
说明 |
||
---|---|---|---|
HcclDataType |
Hccl任务的数据类型。
|
1 2 |
// 等待通信任务执行时机成熟,调用Commit接口通知服务端执行 hccl.Commit(handleId); |
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析构,影响执行较慢的核 // 开发者可根据实际的业务场景,选择调用, , 接口,保证全部核的任务完成后再退出执行 |
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, repeat = 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, repeat = tileCnt); for (int i = 0; i < tileCnt; ++i) { Commit(handleId); auto ret = Wait(handleId); // 执行其他计算逻辑 .... } |
数据类型 |
说明 |
||
---|---|---|---|
MC2_BUFFER_LOCATION |
预留参数。计算和通信中间结果的Buffer存放位置。用户在Tiling侧可设置该字段。
|