昇腾Ascend+C编程入门教程(纯干货)

2023年5月6日,在昇腾AI开发者峰会上,华为正式发布了面向算子开发场景的昇腾Ascend C编程语言。Ascend C原生支持C/C++编程规范,通过多层接口抽象、并行编程范式、孪生调试等技术,极大提高了算子的开发效率,帮助AI开发者低成本完成算子开发和模型调优部署。

1  昇腾AI软硬件基础

和CUDA开发的算子运行在GPU上一样,基于Ascend C开发的算子,可以通过异构计算架构CANN(Compute Architecture for Neural Networks)运行在昇腾AI处理器(可简称NPU)上。CANN是使能昇腾AI处理器的一个软件栈,通过软硬件协同优化,能够充分发挥昇腾AI处理器的强大算力。从下面的架构图可以清楚的看到,使用Ascend C编程语言开发的算子通过编译器编译和运行时调度,最终运行在昇腾AI处理器上。

我们知道,通用计算就是我们常写的一些在CPU上运行的计算,它擅长逻辑控制和串行计算,而AI计算相对通用计算来说,更擅长并行计算,可支持大规模的计算密集型任务。如下面左图所示,做一个矩阵乘,使用CPU计算需要三层for循环,而右图在昇腾AI处理器上使用vector计算单元,只需要两层for循环,最小计算代码能同时计算多个数据的乘加,更近一步,如果使用Cube计算单元,只需要一条语句就能完成一个矩阵乘的计算,这就是我们所说的SIMD(单指令多数据)。因此,我们通常使用AI处理器来进行大量的并行计算。

NPU不能独立运行,需要与CPU协同工作,可以看成是CPU的协处理器,CPU负责整个操作系统运行,管理各类资源并进行复杂的逻辑控制,而NPU主要负责并行计算任务。在基于CPU+NPU的异构计算架构中,NPU与CPU通过PCIe总线连接在一起来协同工作,CPU所在位置称为主机端(host),而NPU所在位置称为设备端(device),示意图如下:

这里再详细介绍一下昇腾AI处理器。昇腾AI处理器有不同的型号和产品形态,小到模块、加速卡,大到服务器、集群。昇腾AI处理器里面最核心的部件是AI Core,有多个,是神经网络加速的计算核心,每一个AI Core就相当于我们大家平时理解的多核cpu里的每个核,使用Ascend C编程语言开发的算子就运行在AI Core上,因为核心的神经网络计算的加速都来源于AI Core的算力。

AI Core内部的并行计算架构抽象如下图所示:

这个并行计算架构抽象核心包含了几个大的部件,AI Core外面有一个Gobal Memory,是多个AI Core共享的,在AI Core内部有一块本地内存Local Memory,因为靠近计算单元,所以它的带宽会非常高,相对的容量就会很小,比如一般是几百K到1M。AI Core内部的核心组件有三个计算单元,标量计算单元、向量计算单元,矩阵计算单元。另外还有一个DMA搬运单元,DMA搬运单元负责在Global Memory和Local Memory之间搬运数据。

AI Core内部的异步并行计算过程:Scalar计算单元读取指令序列,并把向量计算、矩阵计算、数据搬运指令发射给对应单元的指令队列,向量计算单元、矩阵计算单元、数据搬运单元异步并行执行接收到的指令。该过程可以参考上图中蓝色箭头所示的指令流。不同的指令间有可能存在依赖关系,为了保证不同指令队列间的指令按照正确的逻辑关系执行,Scalar计算单元也会给对应单元下发同步指令。各单元之间的同步过程可以参考上图中的橙色箭头所示的同步信号流。

AI Core内部数据处理的基本过程:DMA搬入单元把数据搬运到Local Memory,Vector/Cube计算单元完成数据,并把计算结果写回Local Memory,DMA搬出单元把处理好的数据搬运回Global Memory。该过程可以参考上图中的红色箭头所示的数据流。

2  Ascend C编程模型基础

2.1  Ascend C编程范式

Ascend C编程范式是一种流水线式的编程范式,把算子核内的处理程序,分成多个流水任务,通过队列(Queue)完成任务间通信和同步,并通过统一的内存管理模块(Pipe)管理任务间通信内存。流水编程范式应用了流水线并行计算方法。

若n=3,即待处理的数据被切分成3片,则上图中的流水任务运行起来的示意图如下,从运行图中可以看出,对于同一片数据,Stage1、Stage2、Stage3之间的处理具有依赖关系,需要串行处理;不同的数据切片,同一时间点,可以有多个任务在并行处理,由此达到任务并行、提升性能的目的。

Ascend C分别针对Vector、Cube编程设计了不同的流水任务。开发者只需要完成基本任务的代码实现即可,底层的指令同步和并行调度由Ascend C框架实现,开发者无需关注。

2.2  矢量编程范式

矢量编程范式把算子的实现流程分为3个基本任务:CopyIn,Compute,CopyOut。CopyIn负责搬入操作,Compute负责矢量计算操作,CopyOut负责搬出操作。

我们只需要根据编程范式完成基本任务的代码实现就可以了,底层的指令同步和并行调度由Ascend C框架来实现。

那Ascend C是怎么完成不同任务之间的数据通信和同步的呢?这里Ascend C提供了Queue队列管理的API,主要就是两个队列操作API EnQue、DeQue以及内存的逻辑抽象。

矢量编程中使用到的逻辑位置(QuePosition)定义如下:

1. 搬入数据的存放位置:VECIN;

2. 计算中间变量的位置:VECCALC;

3. 搬出数据的存放位置:VECOUT。

从前面可以看到,矢量编程主要分为CopyIn、Compute、CopyOut三个任务。CopyIn任务中将输入数据从Global内存搬运至Local内存后,需要使用EnQue将LocalTensor放入VECIN的Queue中;Compute任务等待VECIN的Queue中LocalTensor出队之后才可以完成矢量计算,计算完成后使用EnQue将计算结果LocalTensor放入到VECOUT的Queue中;CopyOut任务等待VECOUT的Queue中LocalTensor出队,再将其拷贝到Global内存。这样 ,Queue队列就完成了三个任务间的数据通信和同步。具体流程和流程图如下:

1.  Stage1:CopyIn任务。

使用DataCopy接口将GlobalTensor数据拷贝到LocalTensor。

使用EnQue接口将LocalTensor放入VECIN的Queue中。

2.  Stage2:Compute任务。

使用DeQue接口从VECIN中取出LocalTensor。

使用Ascend C接口完成矢量计算。

使用EnQue接口将计算结果LocalTensor放入到VECOUT的Queue中。

3.  Stage3:CopyOut任务。

使用DeQue接口从VECOUT的Queue中去除LocalTensor。

使用DataCopy接口将LocalTensor拷贝到GlobalTensor上。

    这样我们的kernel实现代码就很清晰了。先初始化内存和队列,然后通过编程范式实现CopyIn、Compute、CopyOut三个Stage就可以了。

2.3  SPMD并行编程-多核

最前面介绍昇腾AI处理器的时候,有介绍过AI Core是有多个的,那我们怎么把多个AI Core充分利用起来呢?常用的并行计算方法中,有一种SPMD(Single-Program Multiple-Data)数据并行的方法,简单说就是将数据分片,每片数据经过完整的一个数据处理流程。这个就能和昇腾AI处理器的多核匹配上了,我们将数据分成多份,每份数据的处理运行在一个核上,这样每份数据并行处理完成,整个数据也就处理完了。Ascend C是SPMD(Single-Program Multiple-Data)编程,多个AI Core共享相同的指令代码,每个核上的运行实例唯一的区别是就是block_idx(内置变量)不同,这样我们就可以通过block_idx来区分不同的核,只要对Global Memory上的数据地址进行切分偏移,就可以让每个核处理自己对应的那部分数据了。

算子被调用时,所有的计算核心都执行相同的实现代码,入口函数的入参也是相同的。每个核上处理的数据地址需要在起始地址上增加block_idx*BLOCK_LENGTH(每个block处理的数据长度)的偏移来获取。这样也就实现了多核并行计算的数据切分。

class KernelAdd {
public:__aicore__ inline KernelAdd() {}__aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z){// get start index for current core, core parallelGM_ADDR xGmOffset = x + BLOCK_LENGTH * GetBlockIdx();GM_ADDR yGmOffset = y + BLOCK_LENGTH * GetBlockIdx();GM_ADDR zGmOffset = z + BLOCK_LENGTH * GetBlockIdx();xGm.SetGlobalBuffer((__gm__ half*)xGmOffset, BLOCK_LENGTH);yGm.SetGlobalBuffer((__gm__ half*)yGmOffset, BLOCK_LENGTH);zGm.SetGlobalBuffer((__gm__ half*)zGmOffset, BLOCK_LENGTH);……}……
}

2.4  Ascend C API介绍

在整个kernel实现中,最最核心的代码就是Add(zLocal, xLocal, yLocal, TILE_LENGTH);通过一个Ascend C提供的API接口完成了所有数据的加法计算,对,没看错,就是这个接口完成了计算。

接下来就介绍下Ascend C提供的API。Ascend C算子采用标准C++语法和一组类库API进行编程,类库API主要包含以下几种,大家可以在核函数的实现中根据自己的需求选择合适的API:

  1. 计算类API,包括标量计算API、向量计算API、矩阵计算API,分别实现调用Scalar计算单元、Vector计算单元、Cube计算单元执行计算的功能。
  2. 数据搬运API,上述计算API基于Local Memory数据进行计算,所以数据需要先从Global Memory搬运至Local Memory,再使用计算接口完成计算,最后从Local Memory搬出至Global Memory。执行搬运过程的接口称之为数据搬移接口,比如DataCopy接口。
  3. 内存管理API,用于分配管理内存,比如AllocTensor、FreeTensor接口。
  4. 任务同步API,完成任务间的通信和同步,比如EnQue、DeQue接口。

        Ascend C API的计算操作数都是Tensor类型:GlobalTensor和LocalTensor。

介绍完Ascend C API种类后,下面来解释下为什么一个Add接口就可以计算所有的数。原来Ascend C编程模型是基于SIMD(单指令多数据)架构的,单条指令可以完成多个数据操作,同时在API内部封装了一些指令的高级功能。

2.5  算子执行基本流程

        前面有提到,在异构计算架构中,NPU与CPU是协同工作的,在Ascend C编程模型中,我们需要实现NPU侧的代码和CPU侧的代码。在NPU侧的代码我们通常叫做Kernel实现代码,CPU侧的代码我们一般叫做Host实现代码,一份完整的Ascend C代码,通常包括Host侧实现代码和Kernel侧实现代码。Ascend C算子执行的基本流程如下:

  1. 初始化Device设备;
  2. 创建Context绑定设备;
  3. 分配Host内存,并进行数据初始化;
  4. 分配Device内存,并将数据从Host上拷贝到Device上;
  5. 用内核调用符<<<>>>调用核函数完成指定的运算;
  6. 将Device上的运算结果拷贝回Host;
  7. 释放申请的资源。
    1. 核函数介绍

上面的流程中,最重要的一步就是调用核函数来进行并行计算任务。核函数(Kernel Function)是Ascend C算子Device侧实现的入口。在核函数中,需要为在AI核上执行的代码规定要进行的数据访问和计算操作。

extern "C" __global__ __aicore__ void add_custom(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z);

上面这个是一个核函数声明的示例,extern "C"表示核函数按照类C的编译和连接规约来编译和连接,__global__函数类型限定符表示它是一个核函数, __aicore__函数类型限定符表示该核函数在device侧的AI Core上执行。参数列表中的变量类型限定符__gm__,表明该指针变量指向Global Memory上某处内存地址,注意这里的入参只能支持指针或C/C++内置数据类型,样例里指针使用的类型为uint8_t,在后续的使用中需要将其转化为实际的指针类型。

Ascend C编程模型中的核函数采用内核调用符<<<...>>>来调用,样例如下:

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

kernel_name即为上面讲的核函数名称,argument list是核函数的函数入参,在<<<>>>中间,有3个参数:

  1. blockDim,规定了核函数将会在几个核上执行,我们可以先设置为1;
  2. l2ctrl,保留参数,暂时设置为固定值nullptr,我们不用关注;
  3. stream,使用aclrtCreateStream创建,用于多线程调度。

3  样例开发讲解

3.1  样例代码结构

|-- CMakeLists.txt  //编译工程文件
|-- cmake  //编译工程文件
|-- data_utils.h  //数据读入写出函数
|-- input  //存放脚本生成的输入数据目录
|-- leakyrelu_custom.cpp  //算子kernel实现
|-- leakyrelu_custom.py  //输入数据和真值数据生成脚本文件
|-- leakyrelu_custom_tiling.h  //host侧tiling函数
|-- main.cpp  //主函数,host侧调用代码,含cpu域及npu域调用
|-- output  //存放算子运行输出数据和标杆数据的目录
|-- readme.md  //执行命令说明
|-- run.sh  //运行脚本 

3.2  主要文件

3.2.1  输入数据和真值数据生成脚本文件:KERNEL_NAME.py。

根据算子的输入输出编写生成输入数据和真值数据的脚本。

本例子生成8 * 200 * 1024大小的fp16数据:

……
def gen_golden_data_simple():total_length_imm = 8 * 200 * 1024tile_num_imm = 8//生成tilling的bin文件total_length = np.array(total_length_imm, dtype=np.uint32)tile_num = np.array(tile_num_imm, dtype=np.uint32)scalar = np.array(0.1, dtype=np.float32)tiling = (total_length, tile_num, scalar)tiling_data = b''.join(x.tobytes() for x in tiling)with os.fdopen(os.open('./input/tiling.bin', WRITE_FILE_FLAGS, PEN_FILE_MODES_640), 'wb') as f:f.write(tiling_data)//生成输入数据input_x = np.random.uniform(-100, 100, [8, 200, 1024]).astype(np.float16)//生成golden数据,功能和LeakyRelu相同golden = np.where(input_x > 0, input_x, input_x * scalar).astype(np.float16)input_x.tofile("./input/input_x.bin")golden.tofile("./output/golden.bin")

3.2.2  编译工程文件:CMakeLists.txt

用于编译cpu侧或npu侧运行的Ascend C算子。主要关注CMakeLists.txt中源文件是否全部列全。

3.2.3  调用算子的应用程序:main.cpp

主要是内存申请,数据拷贝和文件读写等操作,并最终调用算子,相关API的介绍如下:

  1. AscendCL初始化接口aclInit,用于运行时接口AscendCL的初始化,是程序最先调用的接口;aclrtCreateContext和aclrtCreateStream用于创建Context和Stream,主要用于线程相关的资源管理。 
  2. aclrtMallocHost接口,用于在Host上申请内存:

aclError aclrtMallocHost(void **hostPtr, size_t size)

这个函数和C语言中的malloc类似,用于在Host上申请一定字节大小的内存,其中hostPtr是指向所分配内存的指针,size是申请的内存大小,如果需要释放这块内存的话,使用aclrtFreeHost接口释放,这和C语言中的free函数对应。

     3. aclrtMalloc接口,用于在Device上申请内存:

      aclError aclrtMalloc(void **devPtr, size_t size, aclrtMemMallocPolicy policy)

和Host上的内存申请接口相比,多了一个policy参数,用于设置内存分配规则,一般设置成ACL_MEM_MALLOC_HUGE_FIRST就可以了。使用完毕后可以用对应的aclrtFree接口释放内存。

     4. aclrtMemcpy接口,用于Host和Device之间数据拷贝:

前面申请的内存区分了Host内存和Device内存,那就会涉及到数据同步的问题,aclrtMemcpy就是用于Host和Device之间数据通信的接口:

aclError aclrtMemcpy(void *dst, size_t destMax, const void *src, size_t count, aclrtMemcpyKind kind)aclrtMemcpy(void *dst, size_t destMax, const void *src, size_t count, aclrtMemcpyKind kind)

其中src指向数据源,而dst是目标内存地址,destMax 是目的内存地址的最大内存长度,count是拷贝的字节数,其中aclrtMemcpyKind控制复制的方向:ACL_MEMCPY_HOST_TO_HOST、ACL_MEMCPY_HOST_TO_DEVICE、ACL_MEMCPY_DEVICE_TO_HOST和ACL_MEMCPY_DEVICE_TO_DEVICE,像ACL_MEMCPY_HOST_TO_DEVICE就是将Host上数据拷贝到Device上。

    5. 核心函数为CPU侧的调用kernel函数

ICPU_RUN_KF(leakyrelu_custom, blockDim, x, y, usrWorkSpace, tiling);

和NPU侧调用的

leakyrelu_custom_do(blockDim, nullptr, stream, xDevice, yDevice, workspaceDevice, tilingDevice);

完整代码如下:

//This file constains code of cpu debug and npu code.We read data from bin file and write result to file.
#include "data_utils.h"
#include "leakyrelu_custom_tiling.h"
#ifndef __CCE_KT_TEST__
#include "acl/acl.h"
extern void leakyrelu_custom_do(uint32_t coreDim, void* l2ctrl, void* stream, uint8_t* x, uint8_t* y,uint8_t* workspace, uint8_t* tiling);
#else
#include "tikicpulib.h"
extern "C" __global__ __aicore__ void leakyrelu_custom(GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling);
#endifint32_t main(int32_t argc, char* argv[])
{size_t tilingSize = sizeof(LeakyReluCustomTilingData);size_t usrWorkspaceSize = 4096;size_t sysWorkspaceSize = 16 * 1024 * 1024;uint32_t blockDim = 8;
#ifdef __CCE_KT_TEST__   //CPU侧调用//申请内存用于存放workspace和tilling数据uint8_t* usrWorkSpace = (uint8_t*)AscendC::GmAlloc(usrWorkspaceSize);uint8_t* tiling = (uint8_t*)AscendC::GmAlloc(tilingSize);ReadFile("./input/tiling.bin", tilingSize, tiling, tilingSize);size_t inputByteSize = blockDim * 200 * 1024 * sizeof(uint16_t);  // uint16_t represent halfsize_t outputByteSize = blockDim * 200 * 1024 * sizeof(uint16_t);  // uint16_t represent half//申请内存用于存放输入和输出数据uint8_t* x = (uint8_t*)AscendC::GmAlloc(inputByteSize);uint8_t* y = (uint8_t*)AscendC::GmAlloc(inputByteSize);//获取输入数据ReadFile("./input/input_x.bin", inputByteSize, x, inputByteSize);// PrintData(x, 16, printDataType::HALF);//在AIV上执行AscendC::SetKernelMode(KernelMode::AIV_MODE);//调用kernel函数ICPU_RUN_KF(leakyrelu_custom, blockDim, x, y, usrWorkSpace, tiling); // use this macro for cpu debug// PrintData(y, 16, printDataType::HALF);WriteFile("./output/output_y.bin", y, outputByteSize);AscendC::GmFree((void *)x);AscendC::GmFree((void *)y);AscendC::GmFree((void *)usrWorkSpace);AscendC::GmFree((void *)tiling);
#else     //NPU侧调用CHECK_ACL(aclInit(nullptr));aclrtContext context;int32_t deviceId = 0;CHECK_ACL(aclrtSetDevice(deviceId));CHECK_ACL(aclrtCreateContext(&context, deviceId));aclrtStream stream = nullptr;CHECK_ACL(aclrtCreateStream(&stream));uint8_t *xHost, *yHost, *tilingHost, *workspaceHost;uint8_t *xDevice, *yDevice, *tilingDevice, *workspaceDevice;//申请host上tilling内存并读入tilling数据CHECK_ACL(aclrtMallocHost((void**)(&tilingHost), tilingSize));ReadFile("./input/tiling.bin", tilingSize, tilingHost, tilingSize);//申请host上workspace内存CHECK_ACL(aclrtMallocHost((void**)(&workspaceHost), tilingSize));size_t inputByteSize = blockDim * 200 * 1024 * sizeof(uint16_t);  // uint16_t represent halfsize_t outputByteSize = blockDim * 200 * 1024 * sizeof(uint16_t);  // uint16_t represent halfsize_t workspaceByteSize = sysWorkspaceSize + usrWorkspaceSize;//申请host和device上的输入输出内存和device上的workspace和tilling内存CHECK_ACL(aclrtMallocHost((void**)(&xHost), inputByteSize));CHECK_ACL(aclrtMallocHost((void**)(&yHost), inputByteSize));CHECK_ACL(aclrtMallocHost((void**)(&workspaceHost), workspaceByteSize));CHECK_ACL(aclrtMalloc((void**)&xDevice, inputByteSize, ACL_MEM_MALLOC_HUGE_FIRST));CHECK_ACL(aclrtMalloc((void**)&yDevice, inputByteSize, ACL_MEM_MALLOC_HUGE_FIRST));CHECK_ACL(aclrtMalloc((void**)&tilingDevice, tilingSize, ACL_MEM_MALLOC_HUGE_FIRST));CHECK_ACL(aclrtMalloc((void**)&workspaceDevice, workspaceByteSize, ACL_MEM_MALLOC_HUGE_FIRST));ReadFile("./input/input_x.bin", inputByteSize, xHost, inputByteSize);// PrintData(xHost, 16, printDataType::HALF);//从host上拷贝输入数据和tilling数据到deviceCHECK_ACL(aclrtMemcpy(xDevice, inputByteSize, xHost, inputByteSize, ACL_MEMCPY_HOST_TO_DEVICE));CHECK_ACL(aclrtMemcpy(tilingDevice, tilingSize, tilingHost, tilingSize, ACL_MEMCPY_HOST_TO_DEVICE));//调用核函数leakyrelu_custom_do(blockDim, nullptr, stream, xDevice, yDevice, workspaceDevice, tilingDevice);//等待核函数运行完成CHECK_ACL(aclrtSynchronizeStream(stream));//拷回运行结果到hostCHECK_ACL(aclrtMemcpy(yHost, outputByteSize, yDevice, outputByteSize, ACL_MEMCPY_DEVICE_TO_HOST));// PrintData(yHost, 16, printDataType::HALF);WriteFile("./output/output_y.bin", yHost, outputByteSize);//释放资源CHECK_ACL(aclrtFree(xDevice));CHECK_ACL(aclrtFree(yDevice));CHECK_ACL(aclrtFree(workspaceDevice));CHECK_ACL(aclrtFree(tilingDevice));CHECK_ACL(aclrtFreeHost(xHost));CHECK_ACL(aclrtFreeHost(yHost));CHECK_ACL(aclrtFreeHost(workspaceHost));CHECK_ACL(aclrtFreeHost(tilingHost));CHECK_ACL(aclrtDestroyStream(stream));CHECK_ACL(aclrtDestroyContext(context));CHECK_ACL(aclrtResetDevice(deviceId));CHECK_ACL(aclFinalize());
#endifreturn 0;
}

3.2.4  一键式编译运行脚本run.sh

编译和运行应用程序。

cpu侧运行命令:

bash run.sh leakyrelu_custom ascend910B1 VectorCore cpu

npu侧运行命令:

bash run.sh leakyrelu_custom ascend910B1 VectorCore npu

参数含义如下:

bash run.sh <kernel_name> <soc_version> <core_type> <run_mode>

<kernel_name>表示需要运行的算子。
<soc_version>表示算子运行的AI处理器型号。
<core_type>表示在AI Core上或者Vector Core上运行,参数取值为AiCore/VectorCore。
<run_mode>表示算子以cpu模式或npu模式运行,参数取值为cpu/npu。

3.3  kernel 实现

3.3.1  函数原型定义

本样例中,函数名为leakyrelu_custom,根据对算子输入输出的分析,确定有2个参数x,y,其中x为输入内存,y为输出内存。核函数原型定义如下所示:

extern "C" __global__ __aicore__ void leakyrelu_custom(GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling){ }

使用__global__函数类型限定符来标识它是一个核函数,可以被<<<...>>>调用;使用__aicore__函数类型限定符来标识该核函数在设备端AI Core上执行;为方便起见,统一使用GM_ADDR宏修饰入参,GM_ADDR宏定义:

#define GM_ADDR __gm__ uint8_t* __restrict__

3.3.2  获取tilling数据,并调用算子类的Init和Process函数。

算子类的Init函数,完成内存初始化相关工作,Process函数完成算子实现的核心逻辑。

extern "C" __global__ __aicore__ void leakyrelu_custom(GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling)
{GET_TILING_DATA(tilingData, tiling);KernelLeakyRelu op;op.Init(x, y, tilingData.totalLength, tilingData.tileNum, tilingData.scalar);op.Process();
}
      1. 对核函数的调用进行封装

3.3.3  对核函数的调用进行封装

封装后得到leakyrelu_custom_do函数,便于主程序调用。#ifndef __CCE_KT_TEST__表示该封装函数仅在编译运行NPU侧的算子时会用到,编译运行CPU侧的算子时,可以直接调用add_custom函数。调用核函数时,除了需要传入输入输出参数x,y,切分相关参数tiling,还需要传入blockDim(核函数执行的核数), l2ctrl(保留参数,设置为nullptr), stream(应用程序中维护异步操作执行顺序的stream)来规定核函数的执行配置。

#ifndef __CCE_KT_TEST__
// call of kernel function
void leakyrelu_custom_do(uint32_t blockDim, void* l2ctrl, void* stream, uint8_t* x, uint8_t* y,uint8_t* workspace, uint8_t* tiling)
{leakyrelu_custom<<<blockDim, l2ctrl, stream>>>(x, y,  workspace, tiling);
}
#endif

3.3.4  获取 tiling 函数

主要从tilingPointer中获取tiling的参数totalLength(总长度)、tileNum(切分个数,单核循环处理数据次数)和scalar(LeakyRelu计算标量)。

#define GET_TILING_DATA(tilingData, tilingPointer)                                   \LeakyReluCustomTilingData tilingData;                                                \INIT_TILING_DATA(LeakyReluCustomTilingData, tilingDataPointer, tilingPointer);   \(tilingData).totalLength = tilingDataPointer->totalLength;                       \(tilingData).tileNum = tilingDataPointer->tileNum;                               \(tilingData).scalar = tilingDataPointer->scalar;
#endif // LEAKYRELU_CUSTOM_TILING_H

3.3.5  Init 函数

主要获取tiling数据后,设置单核上gm的地址和Buffer的初始化。

__aicore__ inline void Init(GM_ADDR x, GM_ADDR y, uint32_t totalLength, uint32_t tileNum, float scalar){ASSERT(GetBlockNum() != 0 && "block dim can not be zero!");this->blockLength = totalLength / GetBlockNum();this->tileNum = tileNum;this->scalar = static_cast<half>(scalar);ASSERT(tileNum != 0 && "tile num can not be zero!");this->tileLength = this->blockLength / tileNum / BUFFER_NUM;// get start index for current core, core parallelxGm.SetGlobalBuffer((__gm__ half*)x + this->blockLength * get_block_idx(), this->blockLength);yGm.SetGlobalBuffer((__gm__ half*)y + this->blockLength * get_block_idx(), this->blockLength);// pipe alloc memory to queue, the unit is Bytespipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(half));pipe.InitBuffer(outQueueY, BUFFER_NUM, this->tileLength * sizeof(half));}

3.3.6  Process 函数

主要实现三个CopyIn、Compute、CopyOut这三stage。

__aicore__ inline void Process(){// loop count need to be doubled, due to double bufferint32_t loopCount = this->tileNum * BUFFER_NUM;// tiling strategy, pipeline parallelfor (int32_t i = 0; i < loopCount; i++) {CopyIn(i);Compute(i);CopyOut(i);}}

3.3.7  CopyIn 函数

负责从Global Memory拷贝数据到Local Memory,并将数据加入Queue

__aicore__ inline void CopyIn(int32_t progress){// alloc tensor from queue memoryLocalTensor<half> xLocal = inQueueX.AllocTensor<half>();// copy progress_th tile from global tensor to local tensorDataCopy(xLocal, xGm[progress * tileLength], tileLength);// enque input tensors to VECIN queueinQueueX.EnQue(xLocal);}

3.3.8  Computer 函数

负责从Queue中取出数据,进行计算,并将结果放入Queue

 __aicore__ inline void Compute(int32_t progress){// deque input tensors from VECIN queueLocalTensor<half> xLocal = inQueueX.DeQue<half>();LocalTensor<half> yLocal = outQueueY.AllocTensor<half>();// call LeakyRelu instr for computationLeakyRelu(yLocal, xLocal, scalar, tileLength);// enque the output tensor to VECOUT queueoutQueueY.EnQue<half>(yLocal);// free input tensors for reuseinQueueX.FreeTensor(xLocal);}

3.3.9  CopyOut 函数

负责从Queue中将数据取出,并将数据从Local Memory拷贝到Global Memory。

__aicore__ inline void CopyOut(int32_t progress){// deque output tensor from VECOUT queueLocalTensor<half> yLocal = outQueueY.DeQue<half>();// copy progress_th tile from local tensor to global tensorDataCopy(yGm[progress * tileLength], yLocal, tileLength);// free output tensor for reuseoutQueueY.FreeTensor(yLocal);}

3.4  编译和执行

3.4.1  在CPU侧执行

执行结果如下:

可以看到最后的输出结果output_y.bin和标杆数据golden.bin的MD5值相同,说明计算结果相同。

执行完成后,在input下存放输入数据和tiling数据,在output下面存放了输出数据和标杆数据,npuchk目录下是每个核的npu_check执行结果

在当前目录还有一个可执行二进制文件leakyrelu_custom_cpu,如果执行报错,可以通过gdb调试这个可执行文件,具体调试可参考文末官方教程。

3.4.2  在NPU侧执行

在NPU侧执行有两种方式:仿真执行和上板运行,命令都相同,只是编译选项不同,我们可以通过修改编译选项-DASCEND_RUN_MODE为SIMULATOR运行CAModel仿真,设置为 ONBOARD是上板运行。

function compile_and_execute() {# 使用cmake编译cpu侧或者npu侧算子, SIMULATOR or ONBOARDmkdir -p build; cd build;       \cmake ..                        \-Dsmoke_testcase=$1         \-DASCEND_PRODUCT_TYPE=$2    \-DASCEND_CORE_TYPE=$3       \-DASCEND_RUN_MODE="SIMULATOR" \-DASCEND_INSTALL_PATH=$ASCEND_HOME_DIRVERBOSE=1 cmake --build . --target ${1}_${4}……
}

4.  参考资料

总之,学习Ascend C,仅需了解C++编程、理解对列通信与内存申请释放机制、通过调用相应的计算接口与搬运接口,就可以写出运行在昇腾AI处理器上的高性能算子。

了解更多Ascend C学习资源,请访问官方教程:Ascend C编程指南(官方教程)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1621778.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

微信拍卖_简析微信古玩艺术品拍卖平台的崛起

伴随着4G的到来、移动带宽的提升与移动终端的智能化&#xff0c;围绕在手机终端的应用大行其道&#xff0c;就连古玩艺术品也置身于其中。。。。 微信拍卖的兴起 2013年&#xff0c; 国内艺术品网络拍卖可谓风生水起&#xff0c;不仅艺术圈内各机构纷纷试行网络拍卖&#xff0c…

弘辽科技:淘宝店铺装修多少钱一个月?总共要多少钱?

店铺装修是非常重要的一个环节&#xff0c;如果只是抱着试一试的心态&#xff0c;那就可以不必装修店铺&#xff0c;如果想要长期开下去&#xff0c;还是需要做店铺装修的。那么&#xff0c;店铺装修一般是需要多少钱一个月呢? 一、多少钱一个月? 首先&#xff0c;淘宝店铺全…

帝国CMS源码《珠宝网》企业官网大气绿色珠宝玉器奢侈品古玩类展示型商城源码 带手机版

本套源码带非常完美的筛选功能&#xff0c;非常适合做奢侈品&#xff0c;收藏品&#xff0c;古玩等方面的网站。 源码开源&#xff0c;可以二次开发&#xff01; 本模板为展示型商城&#xff0c;适用于企业官网等。 独家升级为帝国CMS7.5版核心

弘辽科技:淘宝店铺搜不到了还能再开吗?为什么搜索不到?

我们在淘宝开店的目的就是为了将产品卖出去赚钱&#xff0c;但是&#xff0c;在开店的过程中&#xff0c;往往会遇到各种各样的问题&#xff0c;如果店铺都搜索不到了&#xff0c;说明开店不是很成功&#xff0c;那么&#xff0c;是否还能再开呢&#xff1f; 一、店铺搜不到了还…

<C++> STL_deque

<c> STL_deque 1.deque的使用 deque(双端队列)&#xff1a;是一种双开口的"连续"空间的数据结构&#xff0c;双开口的含义是&#xff1a;可以在头尾两端进行插入和 删除操作&#xff0c;且时间复杂度为O(1)&#xff0c;与vector比较&#xff0c;头插效率高&a…

基于SSM+Bootstrap+MySQL+JSP的艺术品古玩交易销售系统

项目运行截图 销售首页 热销推荐 古玩详情 用户注册 用户登录 订单订单 商品管理 分类管理 条幅推荐 修改密码 管理员登录 购物车 收货订单 我的订单 个人中心 技术描述 开发工具&#xff1a; Idea/Eclipse 数据库&#xff1a; mysql Jar包仓库&#xff1a; Maven 前段框架&am…

【开题报告】springboot成都市古玩线上服务平台1x2ji计算机毕业设计

本项目包含程序源码数据库LW调试部署环境&#xff0c;文末可获取一份本项目的java源码和数据库参考。 开题报告 研究背景&#xff1a; 随着互联网的快速发展和普及&#xff0c;线上服务平台在各个领域得到了广泛应用。古玩行业作为一种传统文化产业&#xff0c;也需要适应时代…

JVM第三篇 运行时数据区-虚拟机栈和PC程序计数器

目录 1. JAVA中的线程 2. 栈区 2.1 栈帧 2.2 栈可能出现的异常 2.3 设置栈大小 3.程序计数器&#xff08;PC&#xff09; 4. PC和栈发挥的作用 5. 关于栈的常见面试题 虚拟机包含三大部分&#xff0c;类加载子系统&#xff0c;运行时数据区&#xff0c;执行引擎。运行时…

智慧监狱整体解决方案PPT

导读&#xff1a;原文《智慧监狱整体解决方案PPT》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 喜欢文章&#xff0c;您可以点赞评论转发本文&#xff0c;了解更多…

Linux设备驱动之多个同类设备共用一套驱动

1. 应用场景 比如我们的设备上有很多一样的usb接口&#xff0c;这些usb接口都需要有驱动才能工作&#xff0c;那么是每个usb都一套单独的驱动程序么&#xff1f;显然不是的&#xff0c;这些usb接口属于同一类设备&#xff0c;用户对他们的操作方法完全一致&#xff0c;只不过不…

ScheduleJS Crack,新的“信息列”水平滚动功能

ScheduleJS Crack,新的“信息列”水平滚动功能 增加了对Angular 16的支持 新的“信息列”水平滚动功能。 新的“信息列”固定功能。 添加了输入属性以处理组件模板中的偶数和奇数ScheduleRowPlainBackgroundColor以及CSS变量。 改进了“信息列”和角度甘特组件的类型。 Schedul…

Unity3D开发之发布webplayer设置全屏

项目打包出来后会出现一个html文件&#xff0c;使用notepad打开&#xff0c;删除部分代码 并增加一些代码可设置全屏。 <style type"text/css">#unityPlayer {float:left;top: 0px; height: 100%; width: 100%;position: relative;}html,body{overflow-y:hidde…

web player php,unity web player是什么软件

unity web player是一款浏览器运行Unity3D游戏引擎发布的游戏的插件&#xff0c;一般是用户在玩游戏的时候系统自动安装的&#xff1b;通过unity web player插件可以发布web平台的游戏。 本文操作环境&#xff1a;Windows7系统&#xff0c;Dell G3电脑。 推荐&#xff1a;《编程…

软考高级系统架构设计师真题练习(一)计算机组成结构

【原文链接】软考高级系统架构设计师真题练习&#xff08;一&#xff09;计算机组成结构 真题练习 1 答案&#xff1a;B 真题练习 2 答案&#xff1a;C 真题练习 3 答案&#xff1a;A 真题练习 4 答案&#xff1a;A、D 真题练习 5 答案&#xff1a;D 真题练习 …

C语言(第三十天)

1. 什么是bug bug本意是昆虫”或“虫子”&#xff0c;现在一般是指在电脑系统或程序中&#xff0c;隐藏着的一些未被发现的缺陷或问 题&#xff0c;简称程序漏洞。 “Bug” 的创始人格蕾丝赫柏&#xff08;Grace Murray Hopper&#xff09;&#xff0c;她是一位为美国海军工作的…

QQ互联申请及配置

QQ互联申请及配置 今天要说的只是针对QQ互联的操作&#xff0c;其他的互联请参考相关网站。 第一步&#xff1a;需要申请API接口的两码 自行登录QQ互联https://connect.qq.com/index.html&#xff0c;然后按照要求申请就OK啦。 过几天你会收到一封审核通过的邮件&#xff1a; …

C、C++、VC、MFC网页自动注册、登陆、发帖、留言 QQ注册、QQ申请器源码、源代码

查看文章 【转】C、C、VC、MFC网页自动注册、登陆、发帖、留言 QQ注册、QQ申请器源码、源代码 2012-01-11 10:58 转载自 qq316293804 最终编辑 qq316293804 参考资料&#xff1a; 自动登录yahoo邮箱http://blog.csdn.net/suisuibianbian/archive/2005/12/12/550260.aspx VC采集…

激活函数总结(二十一):激活函数补充(APL、Inverse Cubic)

激活函数总结&#xff08;二十一&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 Adaptive piecewise linear&#xff08;APL&#xff09;激活函数2.2 Inverse Cubic激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、…

mozilla美国工程师两秒钟制造百度山寨版

周三下午清华科技园的一个咖啡屋&#xff0c;我和几位国内的blogger–刘兴亮、老杳、詹鹏与mozilla组织的两位美国工程师Aza Raskin 、Dan Mills相聚。两位工程师给我们介绍mozilla全球组织和基金会&#xff0c;还给我们演示了很多超级酷的firefox插件&#xff0c;两个多小时的…

热点故事“百度C2C是“社会化商务”的重大利好”

17号下午&#xff0c;传出了百度C2C的消息&#xff0c;一天之内炸开了锅。 玩聚网象techmeme一样的行动起来&#xff0c;自动语义聚合出来了这个玩聚热点故事- 百度C2C是“社会化商务”的重大利好&#xff0c;一时间麦田的、詹鹏的、wkcow的等一大堆blogger的评论都进来了。百度…