在算子实现章节已经介绍了kernel侧算子核心的实现方法,本章节侧重于介绍接入CANN框架时编程模式和API的使用。
在算子工程目录下的“op_kernel/xxx.cpp”文件中实现算子的核函数。核函数的定义模板已通过msOpGen工具自动生成,样例如下所示。注意这里参数的顺序按照“输入、输出、workspace、tiling”的顺序排布,开发者不要调整其顺序。
#include "kernel_operator.h" extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, GM_ADDR workspace, GM_ADDR tiling) { GET_TILING_DATA(tiling_data, tiling);// 获取Tiling参数,详见下文介绍 // TODO: user kernel impl }
extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR x_ref, GM_ADDR workspace, GM_ADDR tiling) { ... }
提供GET_TILING_DATA,用于获取算子kernel入口函数传入的tiling信息,并填入注册的Tiling结构体中,此函数会以宏展开的方式进行编译。注意,对应的算子host实现中需要定义TilingData结构体,实现并注册计算TilingData的Tiling函数。具体请参考Host侧tiling实现。
extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, GM_ADDR workspace, GM_ADDR tiling) { GET_TILING_DATA(tilingData, tiling); KernelAdd op; op.Init(x, y, z, tilingData.totalLength, tilingData.tileNum); if (TILING_KEY_IS(1)) { op.Process(); } }
template<class T> func() {} extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, GM_ADDR workspace, GM_ADDR tiling) { DTYPE_X temp; func<DTYPE_Z>(); if (FORMAT_Y == FORMAT_ND) { ... } }
某些算子,比如NonZero(统计tensor中非零值的个数),计算完成前无法得知算子输出的shape信息,算子计算完成后才能获取。该类算子在原型定义时,需要使用OutputShapeDependOnCompute接口进行标识,同时在算子核函数中将实际输出shape写入到出参中,便于框架侧基于该信息进行输出内存的管理。
在核函数所有输出的最后增加一个GM_ADDR类型的输出参数,并在核函数计算完成后,将输出shape信息写入到该出参中。shape信息的排布格式如下,大小为n * (8 + 1),每个元素的数据类型为uint64_t。其中n表示待刷新shape信息的输出个数,每个输出的shape信息都通过第1个元素来保存实际的shape维度(dim),后续的8个元素来保存具体每个维度的shape信息。
extern "C" __global__ __aicore__ void xxx_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, GM_ADDR shape_out, GM_ADDR workspace, GM_ADDR tiling) { ... constexpr uint32_t SHAPEOUT_SIZE = 9; // 输出数据为2维([32, 64]),tensor类型为uint32_t GlobalTensor<uint64_t> shapeoutGlobal_uint32; shapeoutGlobal_uint32.SetGlobalBuffer((__gm__ uint64_t*)shape_out, SHAPEOUT_SIZE); shapeoutGlobal_uint32.SetValue(0, 2); shapeoutGlobal_uint32.SetValue(1, 32); shapeoutGlobal_uint32.SetValue(2, 64); ... }
extern "C" __global__ __aicore__ void xxx_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, GM_ADDR shape_out, GM_ADDR workspace, GM_ADDR tiling) { ... constexpr uint32_t SHAPEOUT_SIZE = 9; // 输出数据为4维([1, 64, 128, 128]),tensor类型为uint64_t GlobalTensor<uint64_t> shapeoutGlobal_uint64; shapeoutGlobal_uint64.SetGlobalBuffer((__gm__ uint64_t*)shape_out, SHAPEOUT_SIZE); shapeoutGlobal_uint64.SetValue(0, 0x0000000010000000 | 4); shapeoutGlobal_uint64.SetValue(1, 1); shapeoutGlobal_uint64.SetValue(2, 64); shapeoutGlobal_uint64.SetValue(3, 128); shapeoutGlobal_uint64.SetValue(4, 128); ... }
extern "C" __global__ __aicore__ void xxx_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, GM_ADDR shape_out, GM_ADDR workspace, GM_ADDR tiling) { ... // 有两个输出需要刷新shape,一个维度为2维[16, 32],一个维度为4维[1, 16, 16, 32] // tensor类型为uint64_t constexpr uint32_t SHAPEOUT_SIZE_2 = 18; GlobalTensor<uint64_t> shapeoutGlobal_uint64_2; shapeoutGlobal_uint64_2.SetGlobalBuffer((__gm__ uint64_t*)shape_out, SHAPEOUT_SIZE_2 ); shapeoutGlobal_uint64_2.SetValue(0, 0x0000000010000000 | 2); shapeoutGlobal_uint64_2.SetValue(1, 16); shapeoutGlobal_uint64_2.SetValue(2, 32); // index[3]~index[8]数据为占位 shapeoutGlobal_uint64_2.SetValue(9, 0x0000000010000000 | 4); shapeoutGlobal_uint64_2.SetValue(10, 1); shapeoutGlobal_uint64_2.SetValue(11, 16); shapeoutGlobal_uint64_2.SetValue(12, 16); shapeoutGlobal_uint64_2.SetValue(13, 32); ... }