单算子API调用

单算子API调用方式,是指直接调用单算子API接口,基于C语言的API执行算子。算子工程创建完成后,基于工程代码框架完成算子原型定义、kernel侧算子实现、host侧tiling实现,通过工程编译脚本完成算子的编译部署,之后再进行单算子API的调用。

基本原理

完成自定义算子编译后,会自动生成单算子API,可以直接在应用程序中调用。

单算子API的形式一般定义为“两段式接口”,形如:

1
2
aclnnStatus aclnnXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor);
aclnnStatus aclnnXxx(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);

其中aclnnXxxGetWorkspaceSize/aclnnXxxTensorGetWorkspaceSize为第一段接口,主要用于计算本次API调用过程中需要多少workspace内存,获取到本次计算所需的workspaceSize后,按照workspaceSize申请NPU内存,然后调用第二段接口aclnnXxx执行计算Xxx代表算子原型注册时传入的算子类型。

aclnnXxxGetWorkspaceSize接口的输入输出参数生成规则如下:

当算子原型注册时使用ValueDepend接口标识输入为数据依赖输入时,会额外生成一个API,该API支持值依赖场景输入数据为空的一阶段计算。

1
aclnnStatus aclnnXxxTensorGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor);
在aclnnXxxTensorGetWorkspaceSize中,aclnnXxxGetWorkspaceSize参数的数据类型(aclIntArray、aclFloatArray和aclBoolArray)将被转换为aclTensor数据类型,其他输入输出参数生成规则与aclnnXxxGetWorkspaceSize一致。如下示例中,x0、x1、x2是算子声明为数据依赖的输入,数据类型分别为DT_INT64、DT_BOOL、DT_FLOAT,out是算子输出。
1
2
aclnnStatus aclnnXxxGetWorkspaceSize(const aclIntArray *x0, const aclBoolArray *x1, const aclFloatArray *x2, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor);
aclnnStatus aclnnXxxTensorGetWorkspaceSize(const aclTensor *x0, const aclTensor *x1, const aclTensor *x2, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor);

前置步骤

准备验证代码工程

代码工程目录结构如下,您可以单击LINK,获取样例工程的完整样例:
├── CMakeLists.txt    // 编译规则文件
├── main.cpp    // 单算子调用主体流程实现文件

单算子调用流程

单算子API执行流程如下:

本节以AddCustom自定义算子调用为例,介绍如何编写单算子调用的代码逻辑。其他算子的调用逻辑与Add算子大致一样,请根据实际情况自行修改代码。

以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考,调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。

因为单算子API执行方式,会自动在编译工程的build_out/autogen目录下生成.cpp和.h,编写单算子的调用代码时,要包含自动生成的单算子API执行接口头文件。示例如下:

1
#include "aclnn_add_custom.h"
 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
35
36
37
38
// 1.初始化
CHECK_ACL(aclnnInit(nullptr));

// 2.运行管理资源申请
const int32_t deviceId = 0;
CHECK_ACL(aclrtSetDevice(deviceId));

// 3.申请内存存放算子的输入输出
// ......

// 4.传输数据
CHECK_ACL(aclrtMemcpy(input0DeviceMem, bufferSize, input0HostData.data(), bufferSize, ACL_MEMCPY_HOST_TO_DEVICE));
CHECK_ACL(aclrtMemcpy(input1DeviceMem, bufferSize, input1HostData.data(), bufferSize, ACL_MEMCPY_HOST_TO_DEVICE));

// 5.计算workspace大小并申请内存
uint64_t workspaceSize = 0;
aclOpExecutor* executor = nullptr;
CHECK_ACL(aclnnAddCustomGetWorkspaceSize(input0, input1, output0, &workspaceSize, &executor));
void* workspaceDeviceMem = nullptr;
if (workspaceSize > 0) {
    CHECK_ACL(aclrtMalloc(&workspaceDeviceMem, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST));
}

// 6.执行算子
CHECK_ACL(aclnnAddCustom(workspaceDeviceMem, workspaceSize, executor, stream));

// 7.同步等待
CHECK_ACL(aclrtSynchronizeStream(stream));

// 8.处理执行算子后的输出数据,例如在屏幕上显示、写入文件等,由用户根据实际情况自行实现
// ......

// 9.释放运行管理资源
CHECK_ACL(aclrtResetDevice(deviceId));
// ....

// 10.去初始化
CHECK_ACL(aclnnFinalize());

CMakeLists文件

算子编译后,会生成单算子调用的头文件aclnn_xx.h和动态库libcust_opapi.so。具体路径请参考前置步骤

编译算子调用程序时,需要在头文件的搜索路径include_directories中增加单算子调用的头文件目录,便于找到该头文件;同时需要链接cust_opapi动态库并在库文件的搜索路径link_directories中增加libcust_opapi.so所在目录。

编译与运行

  1. 开发环境上,设置环境变量,配置单算子验证程序编译依赖的头文件与库文件路径,如下为设置环境变量的示例。${INSTALL_DIR}请替换为CANN软件安装后文件存储路径。以root用户安装为例,安装后文件默认存储路径为:/usr/local/Ascend/cann。{arch-os}为运行环境的架构和操作系统,arch表示操作系统架构,os表示操作系统,例如x86_64-linux或aarch64-linux。
    export DDK_PATH=${INSTALL_DIR}
    export NPU_HOST_LIB=${INSTALL_DIR}/{arch-os}/devlib
  2. 编译样例工程,生成单算子验证可执行文件。
    1. 切换到样例工程根目录,然后在样例工程根目录下执行如下命令创建目录用于存放编译文件,例如,创建的目录为“build”
      mkdir -p build
    2. 进入build目录,执行cmake编译命令,生成编译文件

      命令示例如下所示:

      cd build
      cmake ../src -DCMAKE_SKIP_RPATH=TRUE
    3. 执行如下命令,生成可执行文件。
      make

      会在工程目录的output目录下生成可执行文件execute_add_op

  3. 执行单算子
    1. 以运行用户(例如HwHiAiUser)拷贝开发环境中样例工程output目录下的execute_add_op到运行环境任一目录。

      说明: 若您的开发环境即为运行环境,此拷贝操作可跳过。

    2. 在运行环境中,执行execute_add_op文件:
      chmod +x execute_add_op
      ./execute_add_op

      如果有test pass,表明执行成功。