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之间搬运数据

相关推荐
zjun30211 个月前
什么是CANN和Ascend C
c++·人工智能·华为·昇腾·cann·ascendc
Seal软件2 个月前
在昇腾Ascend 910B上运行Qwen2.5推理
ai·大模型·llm·昇腾·genai
华为云开发者联盟2 个月前
1024 | 码客聚会,云上跃迁,探秘华为云和他的开发者朋友们的故事
人工智能·鸿蒙·昇腾·1024程序员节
plmm烟酒僧2 个月前
qemu模拟arm64环境-构建6.1内核以及debian12
linux·debian·qemu·虚拟机·香橙派·aarch64
叶庭云3 个月前
了解华为计算产品线,昇腾的业务都有哪些?
人工智能·华为·昇腾·计算产品线·ai 大模型时代
华为云开发者联盟3 个月前
技术解读:华为云如何携手昇腾、鸿蒙等根生态,助力开发者技术创新
大模型·华为云·鸿蒙·鲲鹏·昇腾
西西弗Sisyphus3 个月前
OrangePi AIpro 香橙派 昇腾 Ascend C 算子开发 与 调用
香橙派·昇腾·orangepi aipro·ascendcl
honeysuckle_luo4 个月前
香橙派入手第一天
香橙派
西西弗Sisyphus4 个月前
昇腾 - AscendCL C++应用开发 推理部分 模型执行
c++·昇腾·ascendcl