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

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* s0float* s1int32_t c

3.调用方式

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

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

  • l2ctrl,保留参数,暂时设置为固定值nullptr

  • stream,类型为aclrtStreamstream是一个任务队列,应用程序通过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 strideRepeat strideMASK的操作,允许用户使用诸多的通用参数来定制化所需要的操作。

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;

你可能感兴趣的:(c++,华为)