SIMD BuiltIn关键字和API
预定义宏
如其他语言一样,会提供一些内置的宏方便用户编写程序。预定义宏一节着重介绍一些用户在做异构编程时会经常用到的宏,以及宏的解释。
- __NPU_ARCH__
__NPU_ARCH__是Device侧AI Core代码中的预处理宏,用于标识AI处理器的架构版本。该宏由四位数字组成,其中前三位数字用于标识AI Core的IP核(Intellectual Property Core)类型,第四位数字标识该AI Core同一个IP核的配置版本。通过该宏,开发者可以针对不同AI处理器,差异化进行代码适配和优化。昇腾AI处理器型号和__NPU_ARCH__对应关系如下表所示:以下为通过__NPU_ARCH__控制在不同AI处理器上算子输出值舍入模式的示例。
1 2 3 4 5 6 7 8 9 10 11 12
__aicore__ static inline void CopyOut(uint64_t mulLen) { #if __NPU_ARCH__ == 2002 Cast(dstLocal, srcLocal, RoundMode::CAST_NONE, mulLen); // CAST_NONE表示舍入模式在转换有精度损失时使用CAST_RINT模式,在不涉及精度损失时不进行舍入 #elif __NPU_ARCH__ == 2201 Cast(dstLocal, srcLocal, RoundMode::CAST_RINT, mulLen); // CAST_RINT表示舍入模式为四舍六入五成双舍入 #endif event_t eventVToMTE3 = static_cast<event_t>(GetTPipePtr()->FetchEventID(HardEvent::V_MTE3)); SetFlag<HardEvent::V_MTE3>(eventVToMTE3); WaitFlag<HardEvent::V_MTE3>(eventVToMTE3); CommonCopyOut<float>(dstLocal, mulLen); // 拷贝LocalTensor至GlobalTensor }
- ASCEND_IS_AIV、ASCEND_IS_AIC
ASCEND_IS_AIV和ASCEND_IS_AIC是通过C++宏实现的条件判断语句,用于在__aicore__修饰的函数中实现代码的条件编译。基于分离模式(AIC核和AIV核分离)开发融合算子时,算子逻辑中同时涉及AIV核和AIC核的处理逻辑,并需要进行核间同步,此时需要通过ASCEND_IS_AIV/ ASCEND_IS_AIC进行AIV和AIC核代码的隔离。
当使用高阶API Matmul时,其内部已通过REGIST_MATMUL_OBJ宏方式实现了AIV与AIC核代码的隔离,用户无需再使用该宏进行处理。
以MatmulNzCustom算子为例,该算子在分离模式下需要分别在AIV核和AIC核上实现不同的逻辑。具体而言,AIV核负责将矩阵数据搬入Unified Buffer,完成数据的重排(将矩阵数据转换为NZ格式),并将其写入Global Memory。而AIC核则直接从Global Memory读取已经重排好的NZ格式数据,并执行矩阵乘法(Matmul)计算。由于AIV核和AIC核的代码逻辑不同,需要通过ASCEND_IS_AIV和ASCEND_IS_AIC宏进行代码隔离,确保在编译时分别生成适用于AIV核和AIC核的代码。
示例伪码如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
template <typename AType, typename BType, typename CType, typename BiasType> __aicore__ inline void MatmulKernel<AType, BType, CType, BiasType>::Process(AscendC::TPipe *pipe) { // 利用AIV核的Vector计算单元实现ND2NZ格式转换。如下代码中MatrixBtoNZ为将B矩阵进行ND2NZ格式转换的函数。 if ASCEND_IS_AIV { pipe->InitBuffer(ubBuf, TOTAL_UB_SIZE); MatrixBtoNZ<typename B_TYPE::T>(tempGM, bGMNZ, tiling, isTransB, ubBuf, tiling.baseK, tiling.baseN); // Vector侧实现的ND2NZ函数 SyncAll(); // AIC核和AIV核同步 AscendC::CrossCoreSetFlag<0x2, PIPE_MTE3>(0x4); return; } if ASCEND_IS_AIC { AscendC::CrossCoreWaitFlag(0x4); // 等待AIV核完成ND2NZ格式转换 } ... ... // 设置左矩阵A、右矩阵B、Bias。 matmulObj.SetTail(tailM, tailN); matmulObj.SetTensorA(aGlobal, false); matmulObj.SetTensorB(bGlobal, false); if (tiling.isBias) { matmulObj.SetBias(biasGlobal); } // 完成矩阵乘操作 matmulObj.IterateAll(cGlobal); // 结束矩阵乘操作 matmulObj.End(); }
- ASCENDC_CUBE_ONLY
ASCENDC_CUBE_ONLY是通过C++宏实现的条件判断语句,用于在__aicore__修饰的函数中实现代码的条件编译。
基于分离模式开发非融合算子时,在只有矩阵计算的算子场景下,可以通过设置ASCENDC_CUBE_ONLY,使能纯Cube模式完成Matmul计算,减少消息通信的性能开销,提升算子性能。
ASCENDC_CUBE_ONLY宏必须在#include "lib/matmul_intf.h"之前设置。
以matmul_custom算子为例,高阶API Matmul默认使用MIX模式,即用户从AIV侧发起消息,通过消息通信框架中转消息后,在AIC侧执行Matmul计算。这套消息处理机制会带来额外的Scalar性能开销。相较于MIX模式,纯Cube模式可以直接跳过消息通信框架,完成Matmul计算,提升算子性能。
示例伪码如下:
1 2 3 4 5 6 7 8
#define ASCENDC_CUBE_ONLY #include "lib/matmul_intf.h" using A_TYPE = AscendC::MatmulType<AscendC::TPosition::GM, CubeFormat::ND, AType>; using B_TYPE = AscendC::MatmulType<AscendC::TPosition::GM, CubeFormat::ND, BType>; using C_TYPE = AscendC::MatmulType<AscendC::TPosition::GM, CubeFormat::ND, CType>; using BIAS_TYPE = AscendC::MatmulType<AscendC::TPosition::GM, CubeFormat::ND, BiasType>; AscendC::Matmul<A_TYPE, B_TYPE, C_TYPE, BIAS_TYPE, CFG_NORM> matmulObj;
函数执行空间限定符
函数执行空间限定符(Function Execution Space Qualifier)指示函数是在Host侧执行还是在Device侧执行,以及它是否可从Host侧或Device侧调用。
- __global__
__global__执行空间限定符声明一个Kernel函数。Kernel函数有如下性质:在Device上执行;只能被Host侧函数调用;__global__只是表示这是Device侧函数的入口,并不表示具体的设备类型,具体的设备类型由__aicore__标记。具有如下使用约束:
- __aicore__
__aicore__执行空间限定符声明一个函数,它具有如下属性:
- 在Device侧执行
- 只能被__global__函数,或者其他__aicore__函数调用
// Only callable from device functions with same kind // of execution space __aicore__ void bar() {} // Define a kernel function execute on AI Core device __global__ __aicore__ void foo() { bar(); // OK. }
- __host__
__host__执行空间限定符声明一个函数,它具有如下属性:
- 只能在Host侧执行
- 只能被Host侧函数调用
- __global__ 和__host__不能一起使用
__host__限定符是可选项,无函数执行空间限定符定义的函数,默认是host函数。
__aicore__ int f() {} // defines a host side function int foo() {} // defines a host side function __host__ int bar() { f(); // Error. foo(); // OK. } // Error. __global__ __host__ void kfunc() {}
- __inline__
__inline__限定符声明一个函数,它具有如下属性:
- 标识Device侧函数强制内联,可以减少函数频繁调用产生的指令压栈、出栈的开销,但可能会导致算子二进制增加。
- 和C++函数修饰符inline的主要区别是Device侧__inline__是强制内联,C++的inline则是根据编译器优化选择性内联。
- AI Core对函数嵌套深度有限制,一般推荐嵌套深度不超过4层。使用强制内联可以减少调用层次。
- __cube__
标识该核函数仅在Cube核执行。针对耦合模式的硬件架构,该修饰符不生效。
__vector__ __global__ __aicore__ void add_custom(){}
- __vector__
标识该核函数仅在Vector核执行。针对耦合模式的硬件架构,该修饰符不生效。
- __mix__(cube, vec)
标识该核函数同时在Cube核和Vector核上执行。(cube, vec)分别表示核函数启动的Cube核和Vector核的配比,支持的配比为(1, 0),(0, 1),(1, 1), (1, 2)。针对耦合模式的硬件架构,该修饰符不生效。
地址空间限定符
AI Core具备多级独立片上存储,各个地址空间独立编址,具备各自的访存指令,根据架构差异,有些存储空间具备统一地址空间(Generic Address Space),有些则没有。设备侧编程基于语法扩展允许地址空间作为合法的类型限定符,以提供针对不同地址空间的访问能力和地址空间合法性检查。
|
地址空间限定符 |
AI Core物理存储空间 |
|---|---|
|
__gm__ |
设备侧内存GM |
|
__ubuf__ |
Vector Unified Buffer |
|
__ca__ |
Cube L0A Buffer |
|
__cb__ |
Cube L0B Buffer |
|
__cc__ |
Cube L0C Buffer |
|
__cbuf__ |
Cube L1 Buffer |
|
__fbuf__ |
Fixpipe Buffer |
地址空间限定符可以在变量声明中使用,用于指定对象分配的区域。如果对象的类型被地址空间名称限定,那么该对象将被分配在指定的地址空间中。同样地,对于指针,指向的类型可以通过地址空间进行限定,以指示所指向的对象所在的地址空间。
// declares a pointer p in the __gm__ address space that
// points to an object(has int type) in the __gm__ address space
__gm__ int *p;
__global__ __aicore__ void foo(...)
{
// declares an array of 4 floats in the private address space.
float x[4];
}
地址空间限定符不能用于非指针返回类型,非指针函数参数,函数类型,同一个类型上不允许使用多个地址空间限定符。
// OK.
__aicore__ int f() {...}
// Error. Address space qualifier cannot be used with a non-pointer return type.
__ubuf__ int f() { ... }
// OK. Address space qualifier can be used with a pointer return type.
__ubuf__ int *f() { ... }
// Error. Multiple address spaces specified for a type.
__ubuf__ __gm__ int i;
// OK. The first address space qualifies the object pointed to and the second
// qualifies the pointer.
__ubuf__ int * __gm__ ptr;
重要:不同地址空间指针的大小可能不同。例如,不能认为 sizeof(__gm__ int *)总是等于sizeof(__ubuf__ int *),譬如编译器或许可能在某些系统上以32bit存储__ubuf__指针。
- private地址空间
private地址空间是大多数变量的默认地址空间,特别是局部变量。
// m is in a specific kernel parameter address space, // it's physical location is implementation determined. __global__ __aicore__ void foo(int m) { // OK. i is an int variable allocated in private address space int i; } __aicore__ void bar(int k) { //OK. k is in private address space // OK. i is an int variable allocated in private address space int i; }
- __gm__地址空间
__gm__地址空间限定符用来表示分配于设备侧全局内存的对象,全局内存对象可以声明为标量、用户自定义结构体的指针。
__gm__ int *var; // var point to an array of int elements typedef struct { float a[3]; int b[2]; } foo_t; __gm__ foo_t *info; // info point to an array of foo_t elements
- __ubuf__地址空间
__ubuf__地址空间用来描述存储于AI Core核内UB存储空间的变量。
__global__ __aicore__ void foo() { // ptr is in private address space, point to __ubuf__ __ubuf__ int *ptr; }
- __ca__, __cb__, __cc__, __cbuf__地址空间
上述几个地址空间主要用于特定的DMA指令访问,不具备标量直接访问能力。
class ObjTy{ ObjTy(){...} void print(){...} private: int a; int b; }; __global__ __aicore__ void foo(__ca__ int * ptr) { // Error. Cannot have __ca__ // qualifier in kernel arguments // OK __ca__ int *ptr; }
内置常量
|
常量名 |
取值 |
功能 |
|---|---|---|
|
constexpr int32_t g_coreType |
|
常量值由框架自动设置,AIC核下,配置为AscendC::AIC,AIV核下,配置为AscendC::AIV。 可以通过对该常量值的判断,来实现了AIV与AIC核代码的区分和隔离。功能等同于直接使用ASCEND_IS_AIV、ASCEND_IS_AIC。 |
内置变量
|
变量名 |
对应API |
功能 |
|---|---|---|
|
block_num |
当前任务配置的核数,用于代码内部的多核逻辑控制等。 |
|
|
block_idx |
当前核的索引,用于代码内部的多核逻辑控制及多核偏移量计算等。 |
通常,建议用户使用内置变量对应的API获取所需值,不建议用户直接使用内置变量。因为内置变量反映的是单个硬件资源的配置信息,对于软件栈整合硬件资源、扩展硬件的功能,内置变量的值与实际语义可能不符。
例如,在
BuiltIn API
具体API列表请参见《CCE Intrinsic开发接口》。