本章节将以Gather类算子为例,介绍SIMT算子实现的基本流程,如下图所示:

下文将对上述步骤进行详细介绍。完整的算子实现请参考pure_simt_gather算子实现样例。
算子分析具体步骤如下:
gather算子的功能为从输入张量中获取指定索引行的数据,即从形状为M * N的二维向量input中获取指定索引的m行数据,这m行的行索引由输入index指定。算子输出output第i行数据的计算公式为:
output[i] = input[index[i]]
模板参数名 |
模板参数类型 |
参数定义 |
|---|---|---|
type_data |
typename |
输入输出的数据类型 |
type_idx |
typename |
index的数据类型 |
函数入参定义如下:
参数名 |
参数类型 |
参数定义 |
|---|---|---|
input |
type_data* |
输入数据在Global Memory上的内存地址 |
index |
type_idx* |
索引数据在Global Memory上的内存地址 |
gather_output |
type_data* |
输出数据在Global Memory上的内存地址 |
in_width |
uint32_t |
输入数据第二维的长度(列宽) |
index_total_length |
uint32_t |
index数据的总长度 |
通过以上分析,得到SIMT Gather算子的设计规格如下:
name |
shape |
data type |
format |
|---|---|---|---|
input(输入) |
(M, N) |
float/half/int32_t |
ND |
index(输入) |
(m), m < M |
uint32_t |
ND |
output(输出) |
(m, N) |
float/half/int32_t |
ND |
核函数定义如下:
1 2 3 4 5 6 7 8 9 | constexpr uint32_t MAX_THREAD_COUNT = 2048; template <typename type_data, typename type_idx> __global__ __launch_bounds__(MAX_THREAD_COUNT) void gather_custom( type_data* input, type_idx* index, type_data* gather_output, uint32_t in_width, uint32_t index_total_length) |
在定义核函数时,使用__launch_bounds__(MAX_THREAD_COUNT)来指定最大线程数。最大线程数的设置范围为1到2048。设置的最大线程数越大,支持启用的线程越多,性能越好,但每个线程可使用的内部寄存器数量会减少。若未设置,最大线程数默认值为1024。在上述分析中已明确计算不需要过多寄存器,因此设置最大线程数为2048。在实际的算子开发过程中,应根据具体的算子实现来调整该值。
本样例以简单的均匀切分方案介绍如何实现动态切分参数的计算。
1 2 3 4 | uint32_t real_core_num = 0; const auto& platformInfoMgr = platform_ascendc::PlatformAscendCManager::GetInstance(); real_core_num = platformInfoMgr->GetCoreNumAiv(); block_num = real_core_num; // block_num为初始gridDim |
根据输入index的长度index_total_length、初始gridDim计算一个线程块启用的的线程个数blockDim。
// thread_num_per_block为blockDim值 thread_num_per_block = (index_total_length + block_num - 1) / block_num;
若blockDim超出最大线程数限制,调整blockDim值为最大线程数值。
1 2 3 | if (thread_num_per_block > MAX_THREAD_COUNT) { thread_num_per_block = MAX_THREAD_COUNT; } |
重新计算gridDim,确保gridDim * blockDim > index_total_length,即确保所有启用的线程能够处理完指定行数的数据。
1 | block_num = (index_total_length + thread_num_per_block - 1) / thread_num_per_block; |
完整的切分计算代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | constexpr uint32_t MAX_THREAD_COUNT = 2048; constexpr uint32_t MAX_BLOCK_COUNT = 65535; bool block_split(uint32_t index_total_length, uint32_t &block_num, uint32_t &thread_num_per_block) { uint32_t real_core_num = 0; const auto& platformInfoMgr = platform_ascendc::PlatformAscendCManager::GetInstance(); if (platformInfoMgr == nullptr) { std::cout << "[ERROR] Get platform info failed, please check device status."<< std::endl; return false; } real_core_num = platformInfoMgr->GetCoreNumAiv(); block_num = real_core_num; thread_num_per_block = (index_total_length + block_num -1) / block_num; if (thread_num_per_block > MAX_THREAD_COUNT) { thread_num_per_block = MAX_THREAD_COUNT; block_num = (index_total_length + thread_num_per_block - 1) / thread_num_per_block; if (block_num > MAX_BLOCK_COUNT) { std::cout << "[ERROR] index_total_length: "<< index_total_length << " can not be bigger than " << MAX_THREAD_COUNT * MAX_BLOCK_COUNT<< "."<< std::endl; return false; } } return true; } |
在本算子中,仅使用gridDim、blockDim等线程维度的第一维,因此计算偏移量时只需考虑x维信息。如下代码所示,threadIdx表示线程在其所在线程块内的索引,blockDim表示一个线程块中设置的线程数,而blockIdx表示线程块的索引。
1 2 | // 计算线程索引 int32_t out_row = blockIdx.x * blockDim.x + threadIdx.x; |
1 2 3 4 5 6 7 8 | uint32_t in_row = index[out_row]; int input_idx = in_row * in_width; int output_idx = out_row * in_width; for (int32_t col = 0; col < in_width; col++) { gather_output[output_idx] = input[input_idx]; input_idx += 1; output_idx += 1; } |
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 | constexpr uint32_t MAX_THREAD_COUNT = 2048; constexpr uint32_t MAX_BLOCK_COUNT = 65535; template <typename type_data, typename type_idx> __global__ __launch_bounds__(MAX_THREAD_COUNT) void gather_custom( type_data* input, type_idx* index, type_data* gather_output, uint32_t in_width, uint32_t index_total_length) { // Calculate global thread ID int32_t out_row = blockIdx.x * blockDim.x + threadIdx.x; // Maps to the row index of output tensor if (out_row >= index_total_length) { return; } // Single thread processes entire row (all columns) - enables coalesced memory access uint32_t in_row = index[out_row]; int input_idx = in_row * in_width; int output_idx = out_row * in_width; for (int32_t col = 0; col < in_width; col++) { gather_output[output_idx] = input[input_idx]; input_idx += 1; output_idx += 1; } } |
核函数即算子Kernel程序开发完成后,即可编写Host侧的核函数调用程序,实现从Host侧的APP程序调用算子,进行运行验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | std::vector<float> gather(std::vector<float>& input, const uint32_t* in_shape, std::vector<uint32_t>& index) { ... // 计算切分参数,设置动态UB内存 uint32_t block_num = 0; uint32_t thread_num_per_block = 0; block_split(index_total_length, block_num, thread_num_per_block)) ... // 计算切分参数,设置动态UB内存 uint32_t dyn_ubuf_size = 0; // No need to alloc dynamic memory. // 用内存调用符<<<...>>>调用核函数完成指定的运算 gather_custom<<<block_num, thread_num_per_block, dyn_ubuf_size, stream>>>( input_device, index_device, output_device, in_shape[1], index_total_length); ... } |