开发者
资源
[object Object]
[object Object]

基于SIMD与SIMT混合编程方式实现矢量算子核函数的流程如下图所示。

图 1 SIMD与SIMT混合核函数实现流程[object Object][object Object]

  • 算子分析:分析算子的输入、输出、数学表达式和计算逻辑。
  • 核函数开发:定义并实现Ascend C算子入口函数。
  • SIMD VF函数开发:定义并实现SIMD VF入口函数。
  • SIMT VF函数开发:定义并实现SIMT VF入口函数。

以下内容以从长度为10万的一维向量中提取指定索引的8192个数据,并对提取的数据分别执行加1运算的gather & adds算子为例,对上述步骤进行详细说明。本样例中介绍的算子完整代码请参见

[object Object]

算子分析具体步骤如下:

  1. 明确算子的输入和输出。

    • gather & adds算子有两个输入input与index,input是原始数据,index是要获取的数据在input中的索引;输出为output。
    • 本样例中算子的输入input支持的数据类型为float,输入index支持的数据类型为uint32_t,输出output的数据类型与输入input的数据类型相同。
    • 算子输入input支持的shape为[100000];输入index支持的shape为[8192],且index数据取值在[0, 100000)范围内;输出output的shape与输入index的shape相同。
    • 算子输入支持的为:ND。
  2. 明确算子的数学表达式及计算逻辑。

    gather & adds算子输出output中第i个数据为:

    [object Object]

    计算逻辑如下:

    • 使用SIMT编程方式从输入input(Global Memory)中获取指定索引的数据,存储到Unified Buffer上。
    • 使用SIMD编程方式在片上存储(Unified Buffer)做数据加1运算。
    • 将Unified Buffer上的计算结果搬出到外部存储(Global Memory)上。

    图 2 算子计算逻辑[object Object][object Object]

    [object Object]
  3. 确定核函数名称和参数。

    • 本样例中核函数命名为gather_and_adds_kernel。
    • 根据对算子输入输出的分析,确定核函数有5个参数input,index,output,input_total_length,index_total_length;input,index为输入在Global Memory上的内存地址,output为输出在Global Memory上的内存地址,input_total_length是input的数据长度,index_total_length是index的数据长度,也是output的数据长度。
  4. 明确分核策略、SIMT线程配置和SIMD Reg矢量计算API循环调用次数。

    本例中算子输入index的形状为8192,可设置核数为8,每个核处理数据量为1024。

    对于SIMT实现,可设置线程数为1024,每个线程处理1个数据,单个核只需调用1次simt_gather函数即可完成gather运算。

    对于SIMD Reg矢量计算实现,单核处理数据量为1024,Reg矢量计算API单次处理的数据长度one_repeat_size为/sizeof(float),API的循环调用次数repeat_times为1024/one_repeat_size。

  5. 确定SIMT VF函数名称和参数。

    • 本样例中SIMT VF函数命名为simt_gather。
    • 根据SIMT线程配置策略,确定SIMT VF函数有6个参数input,index,gather_output,input_total_length,index_total_length,output_total_length;input,index为输入在Global Memory上的内存地址,gather_output为输出在Unified Buffer上的内存地址,input_total_length是input的数据长度,index_total_length是index的数据长度,output_total_length是单核上gather_output的数据长度。
  6. 确定SIMD VF函数名称和参数

    • 本样例中SIMD VF函数命名为simd_adds。
    • 根据上述SIMD策略,确定SIMD VF函数有5个参数output,input,count,one_repeat_size,repeat_times;output为输出在Unified Buffer上的内存地址,input为输入在Unified Buffer上的内存地址,count是单核处理的数据总量,one_repeat_size是单次循环处理的数据量,repeat_times是Reg矢量计算API循环调用次数。

通过以上分析,得到Ascend C gather & adds算子的设计规格如下:

  • 算子类型(OpType):Gather_Adds

  • 算子输入输出:

    表 1 gather & adds算子输入输出规格

    [object Object][object Object]

    [object Object]
  • 核数:8

  • SIMT线程数:1024

  • 核函数名称:gather_and_adds_kernel

  • SIMT VF函数名称:simt_gather

  • SIMD VF函数名称:simd_adds

  • 算子实现文件名称:gather_and_adds.asc

[object Object]

根据中介绍的规则进行核函数的定义。

  1. 函数原型定义

    本样例中,函数名为gather_and_adds_kernel(核函数名称可自定义),根据上述分析,函数原型定义如下:

    [object Object]
  2. 启动SIMT VF函数simt_gather,从input中获取指定索引的数据。

    1. 计算单核应处理的数据量。数据总量为index_total_length,除以核数即可得到单核应处理的数据量。

      [object Object]
    2. 使用接口申请Unified Buffer内存空间,并将该Tensor作为simt_gather函数的输出。

    3. 使用接口启动SIMT_VF函数simt_gather。第一个参数为结构,代表线程的三维层次结构,本例中初始化为dim3(1024),使用一维定义方式,线程总数为1024。

    [object Object]
  3. 启动SIMD VF函数simd_adds,对Unified Buffer上的数据做加1计算。

    1. 使用接口申请Unified Buffer内存空间,并将该Tensor作为simt_adds函数的输出。
    2. 使用接口除以单个数据长度,计算Reg矢量计算API单次处理的数据量one_repeat_size。使用单核应处理的数据量index_total_length_per_block除以单次处理数据量one_repeat_size,计算Reg矢量计算API循环调用次数。
    3. 使用接口启动SIMD VF函数simd_adds。
    [object Object]
  4. 使用接口将结果数据搬运到Global Memory。

    [object Object]
[object Object]
  1. 定义函数原型。

    根据上述对SIMT VF函数的参数分析,定义SIMT VF函数原型。使用函数类型限定符标识SIMT VF核函数入口,使其可以被asc_vf_call调用。

    [object Object]
    [object Object]
  2. 实现函数。

    simt_gather函数实现从输入input(Global Memory)中获取指定索引的数据。基于上述数据切分策略,首先计算线程应处理数据的索引,然后通过赋值操作将数据存储到Unified Buffer上。

    本例中核数设置为8,线程的层次结构为{1024, 1, 1},数据总量为8192(8 *1024)。每个线程只需处理一个数据,应处理数据在index中的索引计算逻辑为:当前核id*每核线程数+当前线程的id,代码如下:

    [object Object]

    用于获取当前核id。blockDim用于获取线程三维层次结构{x, y, z},本例中为{1024, 1, 1},其中第2,3维度均为1,使用一维层次结构,因此线程数可写作blockDim.x。用于获取三维线程索引{x, y, z},本例中仅使用第1维x,可通过threadIdx.x获取当前线程的id。

    [object Object]
[object Object]
  1. 定义函数原型。

    根据上述对SIMD VF函数的参数分析,定义SIMD VF函数原型。使用__simd_vf__函数类型限定符标识SIMD VF入口函数,使其可以被asc_vf_call关键字调用。

    [object Object]
  2. 循环调用repeat_times次Reg矢量计算API完成加1运算。

    1. 使用接口将数据从Unified Buffer搬运到Reg矢量计算基本单元
    2. 使用接口完成将数据加1运算。
    3. 使用接口将数据从RegTensor搬运到Unified Buffer。
    [object Object]