前言:昇腾AI处理器的算子开发增加了一种新的方式,称之为TIK2,正式名称是TIK C++算子开发。不同于采用Python的DSL和TIK方式,TIK C++使用C/C++作为前端语言的算子开发工具,通过四层接口抽象、并行编程范式、孪生调试等技术,极大提高了算子的开发效率,助力AI开发者低成本完成算子开发和模型调优部署。为了帮助开发者快速掌握这一新的技术,2023 CANN训练营第一季同步开设了相关课程,总共有三节课。

        本次是第一节课,讲述了核函数的定义及开发。

课程地址:CANN训练营2023年第一季_TIK2算子开发入门

课程视频:发布在B站“昇腾AI开发者”:

第1次课:【2023  CANN训练营第一季】-TIKC++算子开发入门(上)

https://www.bilibili.com/video/BV1ha4y1V7vK

第2次课:【2023  CANN训练营第一季】-TIKC++算子开发入门(中)

https://www.bilibili.com/video/BV1Pa4y157RG/

第3次课:【2023  CANN训练营第一季】-TIKC++算子开发入门(下)

https://www.bilibili.com/video/BV1yM411g7nw

技术文档:“文档首页>CANN社区版>6.3.RC2.alpha001>算子开发>TIK C++算子开发>TIK C++简介”https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/63RC2alpha001/operatordevelopment/tik2opdevg/atlastik2_10_0001.html

        本次课的内容要点如下:

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子

一、TIK C++基本概念

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

    使用TIK C++进行自定义算子开发的突出优势有:

    1、C/C++原语编程

    2、编程模型屏蔽硬件差异,编程范式提高开发效率

    3、多层级API封装,从简单到灵活,兼顾易用与高效

    4、孪生调试,CPU侧模拟NPU侧的行为,可现在CPU侧调试

    当前:

1、当前TIK C++支持的AI处理器型号为昇腾310P AI处理器、昇腾910 AI处理器,其他型号暂不支持。

2、当前支持用户使用g++等C/C++编译器编译在cpu侧执行的TIK C++算子,并使用gdb单步调试;支持用户使用CCEC编译器编译在npu侧执行的TIK C++算子,实现加速计算。

二、核函数

        核函数(Kernel Function)是TIK C++算子kernel侧实现的入口。TIK C++允许用户使用核函数这种C/C++函数的语法扩展来管理设备端的运行代码,用户在核函数中进行算子类对象的创建和其成员函数的调用,由此实现该算子的所有功能。核函数是主机端和设备端连接的桥梁。

一)、核函数定义:

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

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_02

1、使用extern "C"

2、函数类型限定符

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_03

3、必须具有void返回类型

4、变量类型限定符

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_04

         为了方便:指针入参变量统一的类型定义为__gm__ uint8_t*。用户统一使用uint8_t类型的指针,并在使用时转换为实际的指针类型;亦可直接传入实际的指针类型。
二)、核函数的调用

        核函数的调用语句是C/C++函数调用语句的一种扩展。不同于常见的function_name(argument list)函数调用方式,核函数使用内核调用符<<<...>>>这种语法形式,来规定核函数的执行配置:

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_05

1、内核调用符这种调用方式,仅可在NPU侧编译时调用,CPU侧编译无法识别该符号。

2、核函数的调用是异步的,核函数的调用结束后,控制权立刻返回给主机端,可以调用aclrtSynchronizeStream函数来强制主机端程序等待所有核函数执行完毕。

3、算子执行的不同模式

TIK C++算子可用CPU模式或NPU模式执行

CPU模式:算子功能调试用,可以模拟在NPU上的计算行为,不需要依赖昇腾设备

NPU模式:算子功能/性能调试,可以使用NPU的强大算力进行运算加速

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_06

4、代码里使用内置宏 __CCE_KT_TEST__标识被宏包括的代码在CPU或NPU模式下编译。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_07

        老师在课上讲解了一个helloworld的例子,以下为课件截图:

1、代码

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_08

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_09

 2、编译与运行

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_10

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_11

 四、常用数据定义

一)、GlobalTensor

文档首页>CANN社区版>6.3.RC2.alpha001>算子开发>TIK C++算子开发>API参考>TIK C++ API参考>数据定义>GlobalTensorhttps://www.hiascend.com/document/detail/zh/CANNCommunityEdition/63RC2alpha001/operatordevelopment/tik2opdevg/atlastik2_api_07_0007.html

1、功能说明:GlobalTensor用来存放Global Memory(外部存储)的全局数据。

2、定义原型:

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_12

3、函数说明:

 类型T支持所有数据类型,但需要遵循使用此GlobalTensor的指令的数据类型支持情况。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_13

4、示例:

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_14

 二)、LocalTensor

文档首页>CANN社区版>6.3.RC2.alpha001>算子开发>TIK C++算子开发>API参考>TIK C++ API参考>数据定义>LocalTensorhttps://www.hiascend.com/document/detail/zh/CANNCommunityEdition/63RC2alpha001/operatordevelopment/tik2opdevg/atlastik2_api_07_0006.html

1、功能说明:LocalTensor用于存放AI Core中Local Memory(内部存储)的数据,支持QuePosition为VECIN、VECOUT、A1、A2、B1、B2、CO1、CO2。

2、定义原型:

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_15

3、函数说明:类型T支持所有数据类型,但需要遵循使用此LocalTensor的指令的数据类型支持情况。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_16

 4、示例:

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_17

五、多层级API接口(本次课讲解的是矢量Vector运算单元相关的接口)

        文档首页>CANN社区版>6.3.RC2.alpha001>算子开发>TIK C++算子开发>API参考>TIK C++ API参考>简介https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/63RC2alpha001/operatordevelopment/tik2opdevg/atlastik2_api_07_0003.html

一)简介

        多层级API封装指的是:指令按照由易到难,分成了3级到0级接口。其中3级接口最为简单,0级接口最为复杂,(1级接口暂未发布)

        这样做的主要作用是:

        降低复杂指令的使用难度

        跨代兼容性保障

        保留最大灵活度的可能

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_18

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_19

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_20

二)API的3级接口

        运算符重载,支持+, -, *, /, |, &, <, >, <=, >=, ==, !=,实现1级指令的简化表达。允许用户使用形如:dst = src0 * src1,针对整个Tensor进行计算,一下指令API拥有3级接口:

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_21

三)API的2级接口

         针对源操作数srcLocal的连续COUNT个数据进行计算,并连续写入目的操作数dstLocal,解决一维tensor的连续计算问题。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_22

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_23

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

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_24

1、重复迭代次数-Repeat times 

        矢量计算单元,每次读取连续的8个block(每个block32 Bytes,共256 Bytes)数据进行计算,为完成对输入数据的处理,必须通过多次迭代(repeat)才能完成所有数据的读取与计算。Repeat times表示迭代的次数。

        如下图所示,待处理数据大小为16个block(512Bytes),每次迭代处理8个block(256Bytes),需要两次迭代完成计算,Repeat times应设置为2。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_25

2、相邻迭代间相同block的地址步长 

        当Repeat times大于1,需要多次迭代完成矢量计算时,您可以根据不同的使用场景合理设置相邻迭代间相同block的地址步长Repeat stride的值。

连续计算场景:假设定义一个Tensor供目的操作数和源操作数同时使用(即地址重叠),Repeat stride取值为8。此时,矢量计算单元第一次迭代读取连续8个block,第二轮迭代读取下一个连续的8个block,通过多次迭代即可完成所有输入数据的计算。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_26

非连续计算场景:Repeat stride取值大于8(如取10)时,则相邻迭代间矢量计算单元读取的数据在地址上不连续,出现2个block的间隔。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_27

 反复计算场景:Repeat stride取值为0时,矢量计算单元会对首个连续的8个block进行反复读取和计算。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_28

部分重复计算:Repeat stride取值大于0且小于8时,相邻迭代间部分数据会被矢量计算单元重复读取和计算,此种情形一般场景不涉及。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_29

 3、同一迭代内不同block的地址步长

        如果需要控制单次迭代内,数据处理的步长,可以通过设置同一迭代内不同block的地址步长Block stride来实现。

连续计算,Block stride 设置为1,对同一迭代内的8个block数据连续进行处理。

非连续计算,Block stride值大于1(如取2),同一迭代内不同block之间在读取数据时出现一个block的间隔,如下图所示。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_30

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_31

4、Mask参数

        mask用于控制每次迭代内参与计算的元素。可通过连续模式和逐比特模式两种方式进行设置。

连续模式:表示前面连续的多少个元素参与计算。数据类型为uint64_t。取值范围和操作数的数据类型有关,数据类型不同,每次迭代内能够处理的元素个数最大值不同(当前数据类型单次迭代时能处理的元素个数最大值为:256 / sizeof(数据类型))。当操作数的数据类型占比特位16位时(如half,uint16_t),mask∈[1, 128];当操作数为32位时(如float, int32_t),mask∈[1, 64]。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_32

逐bit模式:可以按位控制哪些元素参与计算,bit位的值为1表示参与计算,0表示不参与。参数类型为长度为2的uint64_t类型数组。参数取值范围和操作数的数据类型有关,数据类型不同,每次迭代内能够处理的元素个数最大值不同。当操作数为16位时,mask[0]、mask[1]∈[0, 264-1];当dst/src为32位时,mask[1]为0,mask[0]∈[0, 264-1]。

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_CANN_33

【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课_Ascend C算子_34