SIMT BuiltIn关键字
函数执行空间限定符
函数执行空间限定符(Function Execution Space Qualifier)指示函数是在Host侧执行还是在Device侧执行,以及能被调用的空间范围。
函数执行空间限定符 |
执行空间 |
调用函数空间 |
||
|---|---|---|---|---|
Host |
Device |
Host |
Device |
|
__host__, 无限定符 |
√ |
x |
√ |
x |
__aicore__ |
x |
√ |
x |
√ |
__global__ |
x |
√ |
√ |
x |
__global__修饰的函数是核函数入口,有以下使用约束:
- 函数返回类型必须为void,不能是class、struct或者union的成员函数。
- 不支持递归调用。
- 对__global__函数的调用是异步的,调用后即返回Host侧的主机线程。
- 只能被Host侧函数调用,在Device上执行。
__aicore__修饰的函数只能在Device侧执行,只能被__global__函数,或者其他__aicore__函数调用。
__host__修饰的函数只能在Host侧被调用和执行。
内存空间限定符
使用内存空间限定符__ubuf__来表示动、静态内存,静态内存的大小在编译期是确定的,动态内存的大小在核函数执行时确定。
当前版本暂未支持动、静态内存,请关注后续版本。
- 静态内存通过数组分配:
1__ubuf__ half staticBuf[1024];
- 动态内存通过以下方式申请使用:
1extern __ubuf__ half dynamicBuf[];
动态内存的实际内存大小需要在核函数启动时配置,详见核函数配置。
内置变量
- dim3
用于指定和获取线程网格(grid)、线程块(block)在x、y、z维度上的内置结构体。
dim3由3个无符号整数组成,结构体定义为{dimx,dimy,dimz},用于指定3个不同维度的大小,三维总数为dimx * dimy * dimz。开发者可以通过如下方式创建dim3结构。1 2 3
dim3(x); // 创建一维结构,dimy和dimz为默认值1 dim3(x, y); // 创建二维结构,dimz为默认值1 dim3(x, y, z); // 创建三维结构
当前提供了以下仅在Device上可用的dim3结构的内置变量:
- blockDim
内置全局变量,在核函数中可以直接使用,用于获取线程块中配置的线程的三维层次结构,即启动VF时配置的dim3结构体实例值。blockDim.x,blockDim.y,blockDim.z分别表示线程块中三个维度的线程数。
- gridDim
内置全局变量,只能在核函数中使用,表示整个计算任务在各个维度上分别由多少个线程块构成。
- gridDim.x是x维度上的线程块数量。
- gridDim.y是y维度上的线程块数量,目前只能返回1。
- gridDim.z是z维度上的线程块数量,目前只能返回1。
- blockIdx
内置全局变量,只能在核函数中使用,用于获取块索引。表示当前线程所在的线程块在整个网格中的位置坐标。
- blockIdx.x的范围是0到gridDim.x - 1。
- blockIdx.y的范围是0到gridDim.y - 1,目前只能返回0。
- blockIdx.z的范围是0到gridDim.z - 1,目前只能返回0。
- threadIdx
内置全局变量,在核函数中可以直接使用,用于获取当前线程在线程块内部的索引。threadIdx.x,threadIdx.y,threadIdx.z分别表示当前线程在3个维度的索引,threadIdx.x的范围为[0, blockDim.x),threadIdx.y的范围为[0, blockDim.y),threadIdx.z的范围为[0, blockDim.z)。线程块内线程的索引与线程ID对应关系如下:
- 对于一维线程块,其线程ID为threadIdx.x。
- 对于二维线程块,其线程ID为(threadIdx.x + threadIdx.y * blockDim.x)。
- 对于三维线程块,其线程ID为(threadIdx.x + threadIdx.y * blockDim.x + threadIdx.z * blockDim.x * blockDim.y)。
当前提供了以下仅在Device上可用的int类型的内置变量:
内置数据类型
类型 |
数据类型 |
描述 |
Size(bit) |
取值范围 |
布尔型 |
bool |
类型,占8比特,全0时代表false,否则代表true。 |
8 |
true, flase |
整形 |
uint8 |
unsigned char |
8 |
[0, 255] |
int8 |
signed char |
8 |
[-128, 127] |
|
uint16 |
unsigned short |
16 |
[0, 65535] |
|
int16 |
signed short |
16 |
[-32768, 32767] |
|
uint32 |
unsigned int |
32 |
[0, 4294967295] |
|
int32 |
signed int |
32 |
[-2147483648, 2147483647] |
|
uint64 |
unsigned long |
64 |
[0,18446744073709551615] |
|
int64 |
signed long |
64 |
[-9223372036854775808, 9223372036854775807] |
|
浮点型 |
float8_e4m3 |
符号位宽1,指数位宽4,尾数位宽3 |
8 |
[26 - 29, 29 - 26] |
float8_e5m2 |
符号位宽1,指数位宽5,尾数位宽2 |
8 |
[213 - 216, 216 - 213] |
|
hifloat8 |
符号位宽1,点域位宽2,指数与尾数位宽由点域编码决定 |
8 |
点域编码决定数据精度与取值范围 |
|
half |
符号位宽1,指数位宽5,尾数位宽10 |
16 |
[25 - 216, 216 - 25] |
|
bfloat16 |
符号位宽1,指数位宽8,尾数位宽7 |
16 |
[2120 - 2128, 2128 - 2120] |
|
float |
符号位宽1,指数位宽8,尾数位宽23 |
32 |
[2104 - 2128, 2128 - 2104] |
元素数据类型 |
Vector X2 |
Vector X3 |
Vector X4 |
unsigned char |
ucharx2 |
ucharx3 |
ucharx4 |
signed char |
charx2 |
charx3 |
charx4 |
unsigned short (16bit) |
ushortx2 |
ushortx3 |
ushortx4 |
signed short (16bit) |
shortx2 |
shortx3 |
shortx4 |
unsigned int |
uintx2 |
uintx3 |
uintx4 |
signed int |
intx2 |
intx3 |
intx4 |
无符号的长整型 (64bit) |
ulonglongx2 |
ulonglongx3 |
ulonglongx4 |
有符号的长整型 (64bit) |
longlongx2 |
longlongx3 |
longlongx4 |
无符号的长整型 (32bit) |
ulongx2 |
ulongx3 |
ulongx4 |
有符号的长整型 (32bit) |
longx2 |
longx3 |
longx4 |
浮点型,1符号位,2指数位,1尾数位 |
float4_e2m1x2 |
- |
- |
浮点型,1符号位,1指数位,2尾数位 |
float4_e1m2x2 |
- |
- |
浮点型,1符号位,4指数位,3尾数位 |
float8_e4m3x2 |
- |
- |
浮点型,1符号位,5指数位,2尾数位 |
float8_e5m2x2 |
- |
- |
浮点型 hif8 |
hifloat8x2 |
- |
- |
浮点型,1符号位,5指数位,10尾数位 |
halfx2 |
- |
- |
浮点型,1符号位,8指数位,7尾数位 |
bfloat16x2 |
- |
- |
浮点型,1符号位,8指数位,23尾数位 |
floatx2 |
floatx3 |
floatx4 |
数据类型 |
内存大小(字节) |
地址对齐(字节) |
charx2, ucharx2 |
2 |
2 |
charx3, ucharx3, charx4, ucharx4 |
4 |
4 |
shortx2, ushortx2 |
4 |
4 |
shortx3, ushortx3,shortx4, ushortx4 |
8 |
8 |
intx2, uintx2 |
8 |
8 |
intx3, uintx3, intx4, uintx4 |
16 |
16 |
longx2,ulongx2 |
8 |
8 |
longx3,ulongx3,longx4,ulongx4 |
16 |
16 |
longlongx2,ulonglongx2 |
16 |
16 |
longlongx3,ulonglongx3,longlongx4,ulonglongx4 |
32 |
32 |
floatx2 |
8 |
8 |
floatx3,floatx4 |
16 |
16 |
float4_e2m1x2, float4_e1m2x2 |
1 |
1 |
float8_e4m3x2,float8_e5m2x2、 hifloat8x2 |
2 |
2 |
halfx2,bfloat16x2 |
4 |
4 |
运算符
类别 |
运算符 |
bool |
int8_t/uint8_t/int16_t/uint16_t/int32_t/uint32_t/int64_t/uint64_t |
half/bfloat16_t/float |
half2/bfloat16x2_t |
hifloat8_t |
|---|---|---|---|---|---|---|
算术运算符 |
+ |
x |
√ |
√ |
√ |
x |
- |
x |
√ |
√ |
√ |
x |
|
* |
x |
√ |
√ |
√ |
x |
|
/ |
x |
√ |
√ |
√ |
x |
|
% |
x |
√ |
x |
x |
x |
|
++ |
x |
√ |
√ |
√ |
x |
|
-- |
x |
√ |
√ |
√ |
x |
|
+ (取正) |
x |
√ |
√ |
√ |
x |
|
- (取反) |
x |
√ |
√ |
√ |
x |
|
比较运算符 |
< |
x |
√ |
√ |
x |
x |
<= |
x |
√ |
√ |
x |
x |
|
> |
x |
√ |
√ |
x |
x |
|
>= |
x |
√ |
√ |
x |
x |
|
== |
x |
√ |
√ |
x |
x |
|
!= |
x |
√ |
√ |
x |
x |
|
位运算符 |
& |
x |
√ |
x |
x |
x |
| |
x |
√ |
x |
x |
x |
|
^ |
x |
√ |
x |
x |
x |
|
~ |
x |
√ |
x |
x |
x |
|
<< |
x |
√ |
x |
x |
x |
|
>> |
x |
√ |
x |
x |
x |
|
逻辑运算符 |
&& |
√ |
√ |
√ |
x |
x |
|| |
√ |
√ |
√ |
x |
x |
|
! |
√ |
√ |
√ |
x |
x |
|
条件运算符 |
a ? b : c |
√ |
√ |
√ |
√ |
x |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 加法运算 res[idx] = x[idx] + y[idx]; // 取反运算 x[idx] = (-x[idx]); // 比较运算 if (x[idx] > y[idx]) { res[idx] = x[idx]; } else { res[idx] = y[idx]; } // 按位与运算 res[idx] = x[idx] & y[idx]; // 逻辑或运算 if (x[idx] || y[idx]) { res[idx] = 1; } // 条件运算 res[idx] = x[idx] > y[idx] ? x[idx] : y[idx]; |
核函数配置
在调用__global__限定符修饰的函数时必须指定执行配置。执行配置通过在函数名后带括号的参数列表之间插入,形如:
1 | <<<grid_dim, block_dim, dynamic_mem_size, stream>>> |
其中:
- grid_dim:int或dim3类型,用于指定网格(grid)的维度与规模,grid_dim.x * grid_dim.y * grid_dim.z等于启动的线程块总数。
- block_dim:int或dim3类型,用于指定每个线程块(block)的维度与规模,block_dim.x * block_dim.y * block_dim.z等于每个线程块包含的线程数。
- dynamic_mem_size:size_t类型,该参数指定除静态分配的内存外,本次调用为每个线程块动态分配的共享内存字节数。
- stream:aclrtStream类型指针,指定关联的流,用于维护异步操作的执行顺序。
1 2 3 4 | // 声明 __global__ void add_custom(float* x, float* y, float* z, uint64_t total_length); // 调用 add_custom<<<block_num, thread_num_per_block, dyn_ubuf_size, stream>>>(x, y, z, 1024); |
在执行函数之前,会先对上述配置参数进行校验。如果grid_dim或block_dim超出设备的最大允许规模,或dynamic_smem_bytes超过分配静态内存后剩余的可用共享内存,该函数将会执行失败。
在多线程并发执行时,每个线程使用较少的寄存器可以让更多的线程和线程块驻留在AI处理器上,从而提升性能。因此,编译器会采用启发式算法,将寄存器溢出(register spilling)和指令数量控制在最低水平,同时尽量减少寄存器的使用量。应用程序可以通过在__global__函数定义中使用__launch_bounds__()限定符来限制启动边界(launch bounds),提供附加信息辅助编译器优化这一过程,这属于可选配置。
- __launch_bounds__(N)是函数标记宏,在SIMT VF入口函数上可选配置,用于在编译期指定SIMT VF启动的最大线程数。若未配置__launch_bounds__,最大线程数默认为1024。参数N需要满足:
- N >= dimx * dimy * dimz;dimx,dimy,dimz为表示线程的dim3结构体。
- N的取值范围为1到2048。
最大线程数决定了每个线程可分配的寄存器数量,具体对应关系请见下表,寄存器用于存储线程中的局部变量,若局部变量的个数超出寄存器个数,容易出现栈溢出等问题。建议最大线程数与启动VF任务的dim3线程数保持一致。
表5 __launch_bounds__的Thread数量与每个Thread可用寄存器数 Thread的个数(个)
每个Thread可用寄存器个数(个)
1025~2048
16
513~1024
32
257~512
64
1~256
127
配置SIMT函数最大线程数为512,示例如下:
1__simt_vf__ __launch_bounds__(512) inline void add(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z)