【2023 · CANN训练营第一季】TIK C++算子开发入门

TIK C++算子开发入门

一、TIK C++基本概念

TIK C++是一种使用**C/C++**作为前端语言的算子开发工具,通过**四层接口抽象**、**并行编程范式**、**孪生调试**等技术,极大提高算子开发效率,助力AI开发者低成本完成算子开发和模型调优部署。

二、核函数

1.基本定义

核函数(Kernel Function)是TIK C++算子**设备侧**的入口。TIK C++允许用户使用核函数这种C/C++函数的语法扩展来管理设备侧的运行代码,用户在核函数中实现算子逻辑的编写,例如自定义算子类及其成员函数以实现该算子的所有功能。

核函数是直接在设备侧执行的代码。在核函数中,需要为在**一个核**上执行的代码规定要进行的**数据访问**和**计算操作**,当核函数被调用时,**多个核**将并行执行同一个计算任务。

2.编写规范

```c++

__global__ __aicore__ void kernel_name(argument list);

```

a.使用函数限定符

使用**`__global__`**函数类型限定符来标识它是一个核函数,可以被**`<<<...>>>`**调用;使用**`__aicore__`**函数类型限定符来标识该核函数在设备侧**`AI Core`**上执行。

b.使用变量类型限定符

指针入参变量统一的类型定义为**`__gm__ uint8_t*`**。用户可统一使用**`uint8_t`**类型的指针,并在使用时转化为实际的指针类型;亦可直接传入实际的指针类型。

c.其他规则

必须具有**void**返回类型;使用**extern "C"**;仅支持入参为指针类型或C/C++内置数据类型(Primitive Data Types),如:`half* s0`、`float* s1`、`int32_t c`。

3.调用方式

a.核函数使用内核调用符**`<<<...>>>`**这种语法形式,来规定核函数的执行配置。

```c++

kernel_name<<<blockDim, l2ctrl, stream>>>(argument list);

```

- **blockDim**,规定了核函数将会在几个核上执行,每个执行该核函数的核会被分配一个逻辑`ID`,表现为内置变量`block_idx`,编号从0开始,可为不同的逻辑核定义不同的行为,可以在算子实现中使用。

- **`l2ctrl`**,保留参数,暂时设置为固定值`nullptr`。

- **`stream`**,类型为`aclrtStream`,`stream`是一个任务队列,应用程序通过`stream`来管理任务的并行。

b.核函数的调用是**异步**的,核函数的调用结束后,控制权立刻返回给主机侧。

强制主机侧程序等待所有核函数执行完毕的API(阻塞应用程序运行,直到指定Stream中的所有任务都完成,同步接口)为**`aclrtSynchronizeStream`**。

```

aclError aclrtSynchronizeStream(aclrtStream stream);

```

三、TIK C++`演示样例`

```c++

/*

* Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.

*/

#ifdef __CCE_KT_TEST__

// 运行CPU模式包含的头文件

#include "tikicpulib.h"

#define __aicore__

#else

// 运行NPU模式包含的头文件

#include "acl/acl.h"

#define __aicore__ [aicore]

#endif

// 核函数的定义

extern "C" __global__ __aicore__ void HelloWorld(__gm__ uint8_t* foo) {}

int32_t main(int32_t argc, char* argv[])

{

    // 申请的数据内存大小Bytes

    size_t fooSize = 256;

    // 执行核函数的逻辑核个数

    uint32_t blockDim = 8;

// 内置宏(主机侧执行CPU模式逻辑)

#ifdef __CCE_KT_TEST__

    uint8_t *foo  = (uint8_t *)tik2::GmAlloc(fooSize);

    // 执行宏(主机侧执行CPU模式逻辑)

    ICPU_RUN_KF(HelloWorld, blockDim, foo);

    // 释放CPU模式下的内存空间

    tik2::GmFree((void *)foo);

// 内置宏(主机侧执行NPU模式逻辑)

#else

    // AscendCL初始化

    aclInit(nullptr);

    // 运行管理资源申请

    aclrtStream stream = nullptr;

    aclrtCreateStream(&stream);

    uint8_t *fooDevice;

    aclrtMalloc((void**)&fooDevice, fooSize, ACL_MEM_MALLOC_HUGE_FIRST);

// 执行任务并等待

    HelloWorld<<<blockDim, nullptr, stream>>>(fooDevice);

    aclrtSynchronizeStream(stream);

// 运行管理资源释放

    aclrtFree(fooDevice);

    aclrtDestroyStream(stream);

    // AscendCL去初始化

    aclFinalize();

#endif

    return 0;

}

```

`四`、TIK C++数据结构

**`GlobalTensor`**用来存放**`Global Memory`**(外部存储)的全局数据;**`LocalTensor`**用于存放核上**`Local Memory`**(内部存储)的数据。

五、多层级API封装

1.基本概念

矢量计算指令接口,能够启动AI Core中的`Vector`单元执行计算。为了降低开发者的使用门槛,指令按照由易到难,分成了3级到0级接口。其中3级接口最为简单,0级接口最为复杂。

a. **3级接口**,运算符重载,支持`+, -, *, /,  |, &, ^, >, < , >=, <=,!=,==`实现2级接口的简化表达。

b. **2级接口**,针对源操作数`srcLocal`的连续`COUNT`个数据进行计算,并连续写入目的操作数`dstLocal`,提供了一维`Tensor`的连续`COUNT`个数据的计算支持。

c. **1级接口**还未发布。

d. **0级接口**,是最底层的开发接口,可以完整发挥硬件优势的计算API,可以进行非连续的计算该功能可以充分发挥CANN系列芯片的强大功能指令,支持对每个操作数的`Block stride`,`Repeat stride`,`MASK`的操作,允许用户使用诸多的通用参数来定制化所需要的操作。

2.使用方法

```

// int16_t数据类型, dstLocal长度为512个int16_t

// 0级接口样例-mask连续模式

uint64_t mask = 128;

// repeatTimes = 4, 一次迭代计算128个数, 共计算512个数

// dstBlkStride, src0BlkStride, src1BlkStride = 1, 单次迭代内数据连续读取和写入

// dstRepStride, src0RepStride, src1RepStride = 8, 相邻迭代间数据连续读取和写入

Add(dstLocal, src0Local, src1Local, mask, 4, { 1, 1, 1, 8, 8, 8 });

// 0级接口样例-mask逐bit模式

uint64_t mask[2] = { UINT64_MAX, UINT64_MAX };

// repeatTimes = 4, 一次迭代计算128个数, 共计算512个数

// dstBlkStride, src0BlkStride, src1BlkStride = 1, 单次迭代内数据连续读取和写入

// dstRepStride, src0RepStride, src1RepStride = 8, 相邻迭代间数据连续读取和写入

Add(dstLocal, src0Local, src1Local, mask, 4, { 1, 1, 1, 8, 8, 8 });

// 2级接口样例

Add(dstLocal, src0Local, src1Local, 512);

// 3级接口样例

dstLocal = src0Local + src1Local;

```

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容