基于Ascend C方式实现基础矢量算子核函数的流程如下图所示。
图 1 矢量算子核函数实现流程[object Object][object Object]
- 算子分析:分析算子的数学表达式、输入、输出以及计算逻辑的实现,明确需要调用的Ascend C接口。
- 核函数定义:定义Ascend C算子入口函数。
- 根据实现算子类:完成核函数的内部实现,包括3个基本任务:CopyIn,Compute,CopyOut。
下文以输入为half数据类型且shape的最后一维为32Bytes对齐、在单核上运行的、一次完成计算的Add算子为例,对上述步骤进行详细介绍。
算子分析具体步骤如下:
明确算子的数学表达式及计算逻辑。
Add算子的数学表达式为:
[object Object]计算逻辑是:Ascend C提供的的操作元素都为,输入数据需要先从外部存储(Global Memory)搬运进片上存储(Unified Buffer),然后使用计算接口完成两个输入参数相加,得到最终结果,再搬出到外部存储上。Ascend C Add算子的计算逻辑如下图所示。
图 2 算子计算逻辑[object Object][object Object]
明确输入和输出。
确定核函数名称和参数。
- 您可以自定义核函数名称,本样例中核函数命名为vec_add_custom。
- 根据对算子输入输出的分析,确定核函数有3个参数x,y,z;x,y为输入在Global Memory上的内存地址,z为输出在Global Memory上的内存地址。
确定算子实现所需接口。
通过以上分析,得到Ascend C Add算子的设计规格如下:
算子类型(OpType):Add
算子输入输出:
表 1 Add算子输入输出规格
[object Object][object Object]
[object Object]核函数名称:vec_add_custom
使用的主要接口:
- DataCopy:数据搬移接口
- Add:矢量基础算术接口
- EnQue、DeQue等接口:Queue队列管理接口
算子实现文件名称:vector_add.asc
函数原型定义
本样例中,函数名为vector_add_custom(核函数名称可自定义),根据中对算子输入输出的分析,确定有3个参数x,y,z,其中x,y为输入内存,z为输出内存。根据的规则介绍,函数原型定义如下所示:使用__global__函数类型限定符标识它是一个核函数,可以被<<<>>>调用;使用__vector__函数类型限定符标识该核函数在设备端aicore的Vector Core上执行;为方便起见,统一使用GM_ADDR宏修饰入参,GM_ADDR宏定义请参考。
[object Object]调用算子类的Init和Process函数。
算子类的Init函数,完成内存初始化相关工作,Process函数完成算子实现的核心逻辑,具体介绍参见。
[object Object]根据章节,调用核函数时,除了需要传入参数x,y,z,还需要传入numBlocks(核函数执行的核数),nullptr(保留参数,设置为nullptr),stream(应用程序中维护异步操作执行顺序的stream)来规定核函数的执行配置。
[object Object]
根据上一节介绍,核函数中会调用算子类的Init和Process函数,本节具体讲解如何基于编程范式实现算子类。
根据矢量编程范式对Add算子的实现流程进行设计的思路如下,矢量编程范式请参考,设计完成后得到的Add算子实现流程图参见:
- Add算子的实现流程分为3个基本任务:CopyIn,Compute,CopyOut。CopyIn任务负责将Global Memory上的输入Tensor xGm和yGm搬运至Local Memory,分别存储在xLocal,yLocal,Compute任务负责对xLocal,yLocal执行加法操作,计算结果存储在zLocal中,CopyOut任务负责将输出数据从zLocal搬运至Global Memory上的输出Tensor zGm中。
- CopyIn,Compute任务间通过VECIN队列inQueueX,inQueueY进行同步,Compute,CopyOut任务间通过VECOUT队列outQueueZ进行同步。
- 任务间交互使用到的内存、临时变量的内存统一使用进行管理。
图 3 Add算子实现流程[object Object][object Object]
算子类中主要实现上述流程,包含对外开放的初始化Init函数和核心处理函数Process,Process函数中会对上图中的三个基本任务进行调用;同时包括一些算子实现中会用到的私有成员,比如上图中的GlobalTensor(xGm、yGm、zGm)和VECIN、VECOUT队列等。KernelAdd算子类具体成员如下:
初始化函数主要完成以下内容:
设置输入输出Global Tensor的Global Memory内存地址。
本样例中的分配方案是:数据整体长度TOTAL_LENGTH为1 * 2048,使用GlobalTensor类的接口设定该核上Global Memory的起始地址以及长度。
[object Object]-
比如,为输入x的Queue分配内存,可以通过如下代码段实现:
[object Object]
具体的初始化函数代码如下:
基于矢量编程范式,将核函数的实现分为3个基本任务:CopyIn,Compute,CopyOut。Process函数中通过如下方式调用这三个函数。
根据编程范式上面的算法分析,将整个计算拆分成三个Stage,用户单独编写每个Stage的代码,三阶段流程示意图参见,具体流程如下: