开发者
资源
[object Object]

本入门教程,将会引导你完成以下任务,体验Ascend C SIMD算子开发基本流程。

  1. 算子分析,明确数学表达式和计算逻辑等内容;
  2. Add算子核函数开发;
  3. 算子核函数运行验证。

在正式的开发之前,还需要先完成环境准备工作,开发Ascend C算子的基本流程如下图所示:

图 1 开发Ascend C算子的基本流程[object Object][object Object]

[object Object]
[object Object]
  • CANN软件安装

    开发算子前,需要先准备好开发环境和运行环境,开发环境和运行环境的介绍和具体的安装步骤可参见

  • 环境变量配置

    安装CANN软件后,使用CANN运行用户进行编译、运行时,需要以CANN运行用户登录环境,执行source {INSTALL_DIR}/set_env.sh命令设置环境变量。_\{INSTALL\_DIR\}_/set\_env.sh命令设置环境变量。{INSTALL_DIR}请替换为CANN软件安装后文件存储路径。以root用户安装为例,安装后文件默认存储路径为:/usr/local/Ascend/cann。

[object Object]

主要分析算子的数学表达式、输入输出的数量、Shape范围以及计算逻辑的实现,明确需要调用的Ascend C接口。下文以Add算子为例,介绍具体的分析过程。

  1. 明确算子的数学表达式及计算逻辑。

    Add算子的数学表达式为:

    计算逻辑是:从外部存储Global Memory搬运数据至内部存储Local Memory,然后使用Ascend C计算接口完成两个输入参数相加,得到最终结果,再搬运到Global Memory上。

  2. 明确输入和输出。

    • Add算子有两个输入:x与y,输出为z。
    • 本样例中算子输入支持的数据类型为float,算子输出的数据类型与输入数据类型相同。
    • 算子输入支持的shape为(8,2048),输出shape与输入shape相同。
    • 算子输入支持的为:ND。
  3. 确定核函数名称和参数。

    • 本样例中核函数命名为add_custom。
    • 根据对算子输入输出的分析,确定核函数有3个参数x,y,z;x,y为输入参数,z为输出参数。
  4. 确定算子实现所需接口。

    • 实现涉及外部存储和内部存储间的数据搬运,查看Ascend C API参考中的数据搬运接口,需要使用来实现数据搬移。
    • 本样例只涉及矢量计算的加法操作,查看Ascend C API参考中的矢量计算接口,初步分析可使用Add接口实现x+y。
    • 计算中使用到的Tensor数据结构,使用进行申请和释放。
    • 并行流水任务之间使用Queue队列完成同步,会使用到等接口。

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

表 1 Ascend C Add算子设计规格

[object Object][object Object]

[object Object][object Object]

完成环境准备和初步的算子分析后,即可开始Ascend C核函数的开发。开发之前请先从获取样例代码,以下样例代码在add_custom.asc中实现。

本样例中使用多核并行计算,即把数据进行分片,分配到多个核上进行处理。Ascend C核函数是在一个核上的处理函数,所以只处理部分数据。分配方案是:假设共启用8个核,数据整体长度为8 * 2048个元素,平均分配到8个核上运行,每个核上处理的数据大小为2048个元素。对于单核上的处理数据,也可以进行数据切块,实现对数据的流水并行处理。

  1. 根据分配方案设计一个结构体AddCustomTilingData,用于保存并行数据切分相关的参数。AddCustomTilingData定义了两个参数:totalLength、tileNum。totalLength指待处理的数据总大小为(8 * 2048)个元素,tileNum指每个核需要计算的数据块个数。

    [object Object]
  2. 根据中介绍的规则进行核函数的定义,并在核函数中调用算子类的Init和Process函数,算子类实现在后续步骤中介绍。

    [object Object]
    • 使用__global__函数类型限定符来标识它是一个核函数,可以被<<<>>>调用;使用__aicore__函数类型限定符来标识该核函数在设备端AI Core上执行。指针入参变量需要增加变量类型限定符__gm__,表明该指针变量指向Global Memory上某处内存地址。为了统一表达,使用GM_ADDR宏来修饰入参,GM_ADDR宏定义如下:

      [object Object]
    • 算子类的Init函数,完成内存初始化相关工作,Process函数完成算子实现的核心逻辑。

  3. 根据矢量编程范式实现算子类,本样例中定义KernelAdd算子类,其具体成员如下:

    [object Object]

    内部函数的调用关系示意图如下:

    图 2 核函数调用关系图[object Object][object Object]

    由此可见除了Init函数完成初始化外,Process中完成了对流水任务“搬入、计算、搬出”的调用,开发者可以重点关注三个流水任务的实现。

  4. 初始化函数Init主要完成以下内容:设置输入输出Global Tensor的Global Memory内存地址,通过TPipe内存管理对象为输入输出Queue分配内存。

    上文我们介绍到,本样例将数据切分成8块,平均分配到8个核上运行,每个核上处理的数据大小为2048个元素。那么我们是如何实现这种切分的呢?

    每个核上处理的数据地址需要在起始地址上增加GetBlockIdx() * blockLength(每个block处理的数据长度)的偏移来获取。这样也就实现了多核并行计算的数据切分。

    以输入x为例,x + blockLength * GetBlockIdx()即为单核处理程序中x在Global Memory上的内存偏移地址,获取偏移地址后,使用GlobalTensor类的接口设定该核上Global Memory的起始地址以及长度。具体示意图如下。

    图 3 多核并行处理示意图[object Object][object Object]

    上面已经实现了多核数据的切分,那么单核上的处理数据如何进行切分?

    对于单核上的处理数据,可以进行数据切块(Tiling),在本示例中,仅作为参考,将数据切分成8块(并不意味着8块就是性能最优)。切分后的每个数据块再次切分成2块,即可开启,实现流水线之间的并行。

    这样单核上的数据(2048个数)被切分成16块,每块tileLength(128)个数据。TPipe为inQueueX分配了两块大小为tileLength * sizeof(float)个字节的内存块,每个内存块能容纳tileLength(128)个float类型数据。数据切分示意图如下。

    图 4 单核数据切分示意图[object Object][object Object]

    具体的初始化函数代码如下:

    [object Object]
  5. 基于矢量编程范式,将核函数的实现分为3个基本任务:CopyIn,Compute,CopyOut。Process函数中通过如下方式调用这三个函数。

    [object Object]
    1. CopyIn函数实现。

      1. 使用接口将GlobalTensor数据拷贝到LocalTensor。
      2. 使用将LocalTensor放入VecIn的Queue中。
      [object Object]
    2. Compute函数实现。

      1. 使用从VecIn中取出LocalTensor。
      2. 使用Ascend C接口完成矢量计算。
      3. 使用将计算结果LocalTensor放入到VecOut的Queue中。
      4. 使用将释放不再使用的LocalTensor。
      [object Object]
    3. CopyOut函数实现。

      1. 使用接口从VecOut的Queue中取出LocalTensor。
      2. 使用接口将LocalTensor拷贝到GlobalTensor上。
      3. 使用将不再使用的LocalTensor进行回收。
      [object Object]
[object Object]

完成Kernel侧核函数开发后,即可编写Host侧的核函数调用程序。实现从Host侧的APP程序调用算子,执行计算过程。

  1. Host侧应用程序框架的编写。

    [object Object]
  2. 编写通过<<<...>>>内核调用符调用算子的代码。

    图 5 调用步骤[object Object][object Object]

    如下示例中的acl API使用方法请参考

    [object Object]
  3. CMake编译配置如下:

    [object Object]
  4. 编译和运行步骤如下

    [object Object]
    [object Object]
[object Object]

如果您对教程中的多核并行、流水编程等概念不了解,导致阅读过程有些吃力,可以参考学习基本概念,再来回顾本教程;如果您已经了解相关概念,并跑通了该样例,您可以参考了解Ascend C矢量编程中的更多细节。