Ascend C算子开发入门
一、Ascend C基本概念
Ascend C是一种使用C/C++作为前端语言的算子开发工具,通过四层接口抽象、并行编程范式、孪生调试等技术,极大提高算子开发效率,助力AI开发者低成本完成算子开发和模型调优部署。
二、核函数
1.基本定义
核函数(Kernel Function)是Ascend C算子设备侧的入口。Ascend C允许用户使用核函数这种C/C++函数的语法扩展来管理设备侧的运行代码,用户在核函数中实现算子逻辑的编写,例如自定义算子类及其成员函数以实现该算子的所有功能。
核函数是直接在设备侧执行的代码。在核函数中,需要为在一个核上执行的代码规定要进行的数据访问和计算操作,当核函数被调用时,多个核将并行执行同一个计算任务。
2.编写规范
__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.核函数使用内核调用符<<<...>>>
这种语法形式,来规定核函数的执行配置。
kernel_name<<>>(argument list);
blockDim
,规定了核函数将会在几个核上执行,每个执行该核函数的核会被分配一个逻辑ID
,表现为内置变量block_idx
,编号从0开始,可为不同的逻辑核定义不同的行为,可以在算子实现中使用。
l2ctrl
,保留参数,暂时设置为固定值nullptr
。
stream
,类型为aclrtStream
,stream
是一个任务队列,应用程序通过stream
来管理任务的并行。
b.核函数的调用是异步的,核函数的调用结束后,控制权立刻返回给主机侧。
强制主机侧程序等待所有核函数执行完毕的API(阻塞应用程序运行,直到指定Stream中的所有任务都完成,同步接口)为aclrtSynchronizeStream
。
aclError aclrtSynchronizeStream(aclrtStream stream);
三、Ascend 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<<>>(fooDevice);
aclrtSynchronizeStream(stream);
// 运行管理资源释放
aclrtFree(fooDevice);
aclrtDestroyStream(stream);
// AscendCL去初始化
aclFinalize();
#endif
return 0;
}
四、Ascend 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;