目录

OrangePi AIpro 香橙派 昇腾 Ascend C算子开发 - HelloWorld

OrangePi AIpro 香橙派 昇腾 Ascend C算子开发 - HelloWorld

flyfish

Ascend C算子编程SPMD(Single-Program Multiple-Data)编程

假设,从输入数据到输出数据需要经过3个阶段任务的处理(T1、T2、T3)。如下图所示,SPMD会启动一组进程,并行处理待处理的数据。对待处理数据切分,把切分后数据分片分发给不同进程处理,每个进程对自己的数据分片进行3个任务的处理。

具体到Ascend C编程模型中的应用,是将需要处理的数据被拆分并同时在多个计算核心(类比于上文介绍中的多个进程)上运行,从而获取更高的性能。多个AI Core共享相同的指令代码,每个核上的运行实例唯一的区别是block_idx不同,每个核通过不同的block_idx来识别自己的身份。block的概念类似于上文中进程的概念,block_idx就是标识进程唯一性的进程ID。并行计算过程的示意图如下图所示。

核函数(Kernel Function)Ascend C算子设备侧实现的入口。在核函数中,需要为在一个核上执行的代码规定要进行的数据访问和计算操作,当核函数被调用时,多个核都执行相同的核函数代码,具有相同的参数,并行执行。

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

一个Hello World例子展示Ascend C核函数(设备侧实现的入口函数)的基本写法和如何被调用的流程。

hello_world.cpp

cpp 复制代码
#include "kernel_operator.h"

extern "C" __global__ __aicore__ void hello_world()
{
    AscendC::printf("Hello World!!!\n");
}

void hello_world_do(uint32_t blockDim, void *stream)
{
    hello_world<<<blockDim, nullptr, stream>>>();
}

main.cpp

cpp 复制代码
#include "acl/acl.h"
extern void hello_world_do(uint32_t coreDim, void *stream);

int32_t main(int argc, char const *argv[])
{
    aclInit(nullptr);
    int32_t deviceId = 0;
    aclrtSetDevice(deviceId);
    aclrtStream stream = nullptr;
    aclrtCreateStream(&stream);

    constexpr uint32_t blockDim = 8;
    hello_world_do(blockDim, stream);
    aclrtSynchronizeStream(stream);

    aclrtDestroyStream(stream);
    aclrtResetDevice(deviceId);
    aclFinalize();
    return 0;
}

HelloWorldSample例子

下载地址

进入源码目录执行

cpp 复制代码
 bash run.sh -v Ascend310B4

结果

cpp 复制代码
opType=hello_world, DumpHead: AIV-0, CoreType=, block dim=8, total_block_num=8, block_remain_len=1048424, block_initial_space=1048576, rsv=0, magic=5aa5bccd
CANN Version: 901005402, TimeStamp: 20240821
Hello World!!!
opType=hello_world, DumpHead: AIV-1, CoreType=, block dim=8, total_block_num=8, block_remain_len=1048424, block_initial_space=1048576, rsv=0, magic=5aa5bccd
CANN Version: 901005402, TimeStamp: 20240821
Hello World!!!
opType=hello_world, DumpHead: AIV-2, CoreType=, block dim=8, total_block_num=8, block_remain_len=1048424, block_initial_space=1048576, rsv=0, magic=5aa5bccd
CANN Version: 901005402, TimeStamp: 20240821
Hello World!!!
opType=hello_world, DumpHead: AIV-3, CoreType=, block dim=8, total_block_num=8, block_remain_len=1048424, block_initial_space=1048576, rsv=0, magic=5aa5bccd
CANN Version: 901005402, TimeStamp: 20240821
Hello World!!!
opType=hello_world, DumpHead: AIV-4, CoreType=, block dim=8, total_block_num=8, block_remain_len=1048424, block_initial_space=1048576, rsv=0, magic=5aa5bccd
CANN Version: 901005402, TimeStamp: 20240821
Hello World!!!
opType=hello_world, DumpHead: AIV-5, CoreType=, block dim=8, total_block_num=8, block_remain_len=1048424, block_initial_space=1048576, rsv=0, magic=5aa5bccd
CANN Version: 901005402, TimeStamp: 20240821
Hello World!!!
opType=hello_world, DumpHead: AIV-6, CoreType=, block dim=8, total_block_num=8, block_remain_len=1048424, block_initial_space=1048576, rsv=0, magic=5aa5bccd
CANN Version: 901005402, TimeStamp: 20240821
Hello World!!!
opType=hello_world, DumpHead: AIV-7, CoreType=, block dim=8, total_block_num=8, block_remain_len=1048424, block_initial_space=1048576, rsv=0, magic=5aa5bccd
CANN Version: 901005402, TimeStamp: 20240821
Hello World!!!
extern "C" __global__ __aicore__ void hello_world()

核函数时需要遵循以下规则

使用函数类型限定符

除了需要按照C/C++函数声明的方式定义核函数之外,还要为核函数加上额外的函数类型限定符,包含__global__和__aicore__。

使用__global__函数类型限定符来标识它是一个核函数,可以被<<<...>>>调用;
使用__aicore__函数类型限定符来标识该核函数在设备端AI Core上执行:

cpp 复制代码
__global__ __aicore__ void kernel_name(argument list);

编程中使用到的函数可以分为三类:核函数(device侧执行)host侧执行函数device侧执行函数(除核函数之外的)。三者的调用关系如下图所示:

host侧执行函数可以调用同类的host执行函数,也就是通用C/C++编程中的函数调用;也可以通过<<<>>>调用核函数。

device侧执行函数(除核函数之外的)可以调用同类的device侧执行函数。

核函数可以调用device侧执行函数(除核函数之外的)。

核函数(device侧执行)、host侧执行函数、device侧执行函数(除核函数之外的)调用关系图

使用变量类型限定符

指针入参变量需要增加变量类型限定符__gm__。表明该指针变量指向Global Memory上某处内存地址。

其他规则或建议

规则:核函数必须具有void返回类型。

规则:仅支持入参为指针或C/C++内置数据类型(Primitive data types),如:half* s0、float* s1、int32_t c

建议:为了统一表达,建议使用GM_ADDR宏来修饰入参,GM_ADDR宏定义如下:

cpp 复制代码
#define GM_ADDR __gm__ uint8_t*

使用GM_ADDR修饰入参的样例如下:

cpp 复制代码
extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z)

这里统一使用uint8_t类型的指针,在后续的使用中需要将其转化为实际的指针类型。

代码解释 extern "C"

extern "C" 告诉编译器,不要对这些函数名进行C++的名称修饰,这样C语言的代码就可以正确地调用这些函数。

如果有一部分代码是用C编写的,另一部分代码是用C++编写的,extern "C" 会确保 my_c_function 以C语言的方式进行链接,C++编写的代码能够调用C语言编写的函数。

名称修饰是编译器生成唯一符号名的一种机制,目的是支持C++的高级功能,如函数重载。通过名称修饰,编译器确保每个函数或变量在链接阶段具有唯一性,避免命名冲突。

举个例子

考虑以下两个函数:

cpp 复制代码
int add(int a, int b);
double add(double a, double b);

在C语言中,由于函数名称不能重载,这两个函数将会引起命名冲突。但是在C++中,编译器会将这两个函数分别转换为不同的符号名,例如(符号名称是编译器生成的,具体表示可能会不同):

cpp 复制代码
_add_int_int
_add_double_double

这些修饰后的名称在编译后的二进制文件中存储,使得它们可以在链接时区分开来。

使用 extern "C" 避免名称修饰如果希望C++函数能够被C代码调用,或者希望C++代码调用C语言的函数,需要使用extern "C"来告诉编译器不要对这些函数进行名称修饰。例如:

cpp 复制代码
extern "C" void myFunction(int a);

在这种情况下,myFunction 的名字在编译后的二进制文件中将保持为 myFunction,而不会被修饰。

代码解释 extern void hello_world_do(uint32_t coreDim, void *stream);中的extern

在C++中,extern 关键字用于声明一个变量或函数是由其他文件定义的,而不是在当前文件中定义的。它告诉编译器这个函数的定义在另一个编译单元(例如另一个源文件)中,而不是在当前文件中。在提供的 main.cpp 文件中,extern void hello_world_do(uint32_t coreDim, void *stream); 这一行的作用是声明 hello_world_do 函数的存在,使得 main.cpp 文件可以调用这个函数,而不需要在 main.cpp 中定义它。

原理:

  1. 函数定义在另一个文件中
    hello_world_do 函数实际上是在 hello_world.cpp 文件中定义的。为了在 main.cpp 中使用这个函数,编译器需要知道这个函数的签名(返回类型、参数类型等)。通过使用 extern,告诉编译器"这个函数在别的地方定义了,只需要知道它的签名就可以了"。

  2. 链接阶段的作用

    编译器在编译 main.cpp 时,不需要知道 hello_world_do 函数的具体实现,只需要知道它的签名。而在链接阶段,链接器会把 hello_world.cpp 中的 hello_world_do 函数实现与 main.cpp 中的调用关联起来。

执行配置由3个参数决定:

blockDim,规定了核函数将会在几个核上执行。每个执行该核函数的核会被分配一个逻辑ID,即block_idx,可以在核函数的实现中调用GetBlockIdx来获取block_idx;

l2ctrl,保留参数,暂时设置为固定值nullptr,开发者无需关注;

stream,类型为aclrtStream,stream用于维护一些异步操作的执行顺序,确保按照应用程序中的代码调用顺序在device上执行。

计算单元包括了三种基础计算资源:Cube计算单元、Vector计算单元和Scalar计算单元。

存储单元包括内部存储和外部存储:

AI Core的内部存储,统称为Local Memory,对应的数据类型为LocalTensor。由于不同芯片间硬件资源不固定,可以为UB、L1、L0A、L0B等。

AI Core能够访问的外部存储称之为Global Memory,对应的数据类型为GlobalTensor。

DMA(Direct Memory Access)搬运单元:负责在Global Memory和Local Memory之间搬运数据

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
博云技术社区7 天前
昇腾+DeepSeeK | 博云联合昇腾打造满血版一体机
昇腾·博云·deepseek·ai一体机
林泽毅7 天前
SwanLab硬件监控:英伟达、昇腾、寒武纪
python·深度学习·昇腾·英伟达·swanlab·寒武纪·训练实战
Zain Lau1 个月前
MindIE 基于昇腾910B2 aarch64环境profile
人工智能·python·昇腾
泰洋睿兔3 个月前
OPI4A,目标检测,口罩检测,mnn,YoloX
人工智能·目标检测·香橙派·mnn·opi4a
被制作时长两年半的个人练习生3 个月前
【AscendC】tiling方案设计不当引起的一个时隐时现的bug
人工智能·bug·算子开发·ascendc
哦豁灬3 个月前
CANN 学习——基于香橙派 KunpengPro(1)
学习·昇腾·cann
华为云开发者联盟3 个月前
开源flux适配昇腾NPU分享,体验120亿参数AI文生图模型
ai·大模型·flux·昇腾·npu
华为云开发者联盟3 个月前
读懂华为开发者空间第一课,让云上开发如此简单
python·鸿蒙·codearts·软件开发·昇腾·云主机
plmm烟酒僧3 个月前
香橙派5Plus启动报错bug: spinlock bad magic on cpu#6, systemd-udevd/443
linux·bug·rk3588·kernel·香橙派·orangepi5plus
yuanlulu3 个月前
mindie推理大语言模型问题及解决方法汇总
人工智能·华为·自然语言处理·nlp·大语言模型·昇腾