Kernel侧算子实现
===========

在[算子实现](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/cannkit-operator-implementation-overview)章节已经介绍了kernel侧算子核心的实现方法，本章节侧重于介绍接入DDK框架时编程模式和API的使用。

#### 自动生成kernel侧算子实现模板

在算子工程目录下的“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: 用户实现内核逻辑
}

**简要说明：**

算子原型定义中的输入和输出同名的情况下，自动生成的核函数中，输出参数增加ref后缀予以区分。示例如下。

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获取Tiling参数

提供[GET\_TILING\_DATA](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/cannkit-get-tiling-data)，用于获取算子kernel入口函数传入的tiling信息，并填入注册的Tiling结构体中，此函数会以宏展开的方式进行编译。注意，对应的算子host实现中需要定义TilingData结构体，实现并注册计算TilingData的Tiling函数。具体请参考[Host侧Tiling实现](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/cannkit-tiling-implementation-on-the-host)。

核函数中调用[GET\_TILING\_DATA](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/cannkit-get-tiling-data)获取TilingData的样例如下。

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();
    }
}

#### 核函数内推导输入数据类型和格式

算子工程在核函数内提供了DTYPE\_<Arg>、ORIG\_DTYPE\_<Arg>、FORMAT\_<Arg>三种宏用于推导核函数入参的数据类型、原始数据类型和数据格式。其中<Arg>会自动大写。样例如下。

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) {
        // ...
    }
}

#### 输出shape依赖计算的算子kernel侧实现

某些算子，比如NonZero（统计tensor中非零值的个数），计算完成前无法得知算子输出的shape信息，算子计算完成后才能获取。该类算子在原型定义时，需要使用[OutputShapeDependOnCompute](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/cannkit-outputshapedependoncompute)接口进行标识，同时在算子核函数中将实际输出shape写入到出参中，便于框架侧基于该信息进行输出内存的管理。

在核函数所有输出的最后增加一个GM\_ADDR类型的输出参数，并在核函数计算完成后，将输出shape信息写入到该出参中。shape信息的排布格式如下，大小为**n \* (8 + 1)**，每个元素的数据类型为**uint64\_t**。其中n表示待刷新shape信息的输出个数，每个输出的shape信息都通过第1个元素来保存实际的shape维度(dim)，后续的8个元素来保存具体每个维度的shape信息。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/e7/v3/5ZXaDfbfR0Kpr4cgO2DCIQ/zh-cn_image_0000002594829472.png?HW-CC-KV=V1&HW-CC-Date=20260612T073658Z&HW-CC-Expire=86400&HW-CC-Sign=8ABDEE144942782C5C22DB21059D4FA0D350D5079826ED4037F9F49920754768)

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/17/v3/RPVVRNg7SNuJAW6oVRDpQw/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260612T073658Z&HW-CC-Expire=86400&HW-CC-Sign=A3168D7FEE05A4AECBE6ED7552DEE565EF3C0FFB8B0952BD76D266F507A92ED1)

*   输出的顺序和原型定义中输出的顺序保持一致。
    
*   对于uint64\_t的输出数据类型（对于tensor而言），需要将dim的uint32\_t的高位设置为1，表示以uint64\_t类型解析该tensor。
    

*   如下示例中，算子中有一个输出依赖计算得出，输出tensor的数据类型为uint32\_t，计算完成后，得到输出的shape为（32, 64），出参shape\_out用于存放该shape信息，值为（2, 32, 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;
         // 输出数据为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);
     // ...
     }
    
*   如下示例中，算子中有一个输出依赖计算得出，输出tensor的数据类型为uint64\_t，计算完成后，得到输出的shape为（1, 64, 128, 128），出参shape\_out用于存放该shape信息，值为（0x0000000010000000 | 2, 32, 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);
     // ...
     }
    
*   如下示例中，算子中有两个输出依赖计算得出，输出tensor的数据类型为uint64\_t，计算完成后，得到输出的shape为（16, 32）和 （1, 16, 16, 32），出参shape\_out用于存放该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) {
         // ...
         // 有两个输出需要刷新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);
         // ...
     }