写点什么

一文教你如何调用 Ascend C 算子

作者:EquatorCoco
  • 2024-05-30
    福建
  • 本文字数:4370 字

    阅读完需:约 14 分钟

Ascend C 是 CANN 针对算子开发场景推出的编程语言,原生支持 C 和 C++标准规范,兼具开发效率和运行性能。基于 Ascend C 编写的算子程序,通过编译器编译和运行时调度,运行在昇腾 AI 处理器上。使用 Ascend C,开发者可以基于昇腾 AI 硬件高效实现自定义的创新算法。


本文重点介绍基于 Ascend C 算子编程语言完成自定义算子的开发和部署后,如何调用自定义算子验证算子功能。


三种常见的算子调用方式


目前,Ascend C 算子有三种常见的调用方式:


  • Kernel 直调:完成算子核函数开发和 Tiling 实现后,可基于内核调用符方式进行完成算子的调用,用来快速验证算法逻辑。

  • 单算子调用:相比于 Kernel 直调,单算子调用是一种较为标准的调用方式。开发者在完成所有算子交付件开发、编译部署之后,一般通过单算子调用方式验证单算子功能以满足交付条件,包括两种调用方式:

  • 单算子 API 执行:基于 C 语言的 API 执行算子,直接调用单算子 API 接口,无需提供单算子描述文件进行离线模型的转换。

  • 单算子模型执行:基于图 IR 执行算子,先编译算子(例如,使用 ATC 工具将 Ascend IR 定义的单算子描述文件编译成算子 om 模型文件),再调用 AscendCL 接口加载算子模型,最后调用 AscendCL 接口执行算子。

  • 在 PyTorch、ONNX、TensorFlow 等三方框架中调用算子:需要完成框架适配开发,即可从第三方框架实现算子调用。


当然,除了可以调用自定义算子进行功能验证外,开发者也可以通过单算子调用方式直接调用昇腾算子库中预制的算子,使用昇腾算力。


通过单算子 API 执行方式调用算子


通过单算子 API 执行方式调用算子,是算子交付阶段最重要的一种调用方式,也是 Ascend C 算子开发人员必须掌握的算子调用手段,下面做重点讲解。开发者若想了解其他方式,可以移至文末查阅“Ascend C 一站式学习资源”[1]。


Ascend C 算子开发并编译部署完成后,会在算子包安装目录下的 op_api 目录下会自动生成单算子 API,以默认安装场景为例,单算子调用的头文件.h 和动态库 libcust_opapi.so 所在的目录结构为:

├── opp    //算子库目录 │   ├── vendors     //自定义算子所在目录 │       ├── config.ini │       └── vendor_name1   // 存储对应厂商部署的自定义算子,此名字为编译自定义算子安装包时配置的vendor_name,若未配置,默认值为customize │           ├── op_api │           │   ├── include │           │   │  └── aclnn_xx.h │           │   └── lib │           │       └── libcust_opapi.so ...
复制代码


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

aclnnStatus aclnnXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, uint64_t workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnXxx(void* workspace, int64 workspaceSize, aclOpExecutor* executor, aclrtStream stream);
复制代码


单算子 API 可以直接在应用程序中调用,大致过程为:


  1. 使用第一段接口 aclnnXxxGetWorkspaceSize 计算本次 API 调用计算过程中需要多少的 workspace 内存

  2. 获取到本次 API 计算需要的 workspace 大小后,按照 workspaceSize 大小申请 Device 侧内存

  3. 调用第二段接口 aclnnXxx,调用对应的单算子二进制执行计算


完整调用流程如下:



下面提供单算子调用的关键代码示例,供开发者参考:

// 1.AscendCL初始化 aclRet = aclInit("../scripts/acl.json");  // 2.运行管理资源申请 int deviceId = 0; aclRet = aclrtSetDevice(deviceid); // 获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等) aclrtRunMode runMode; bool g_isDevice = false; aclError aclRet = aclrtGetRunMode(&runMode); g_isDevice = (runMode == ACL_DEVICE);  // 3.申请内存存放算子的输入输出 // ......  // 4.传输数据 if (aclrtMemcpy(devInputs_[i], size, hostInputs_[i], size, kind) != ACL_SUCCESS) {     return false; }  // 5.计算workspace大小并申请内存 size_t workspaceSize = 0; aclOpExecutor *handle = nullptr; auto ret = aclnnAddCustomGetWorkspaceSize(inputTensor_[0], inputTensor_[1], outputTensor_[0],                                           &workspaceSize, &handle); // ... void *workspace = nullptr; if (workspaceSize != 0) {     if (aclrtMalloc(&workspace, workspaceSize, ACL_MEM_MALLOC_NORMAL_ONLY) != ACL_SUCCESS) {         ERROR_LOG("Malloc device memory failed");     } }  // 6.执行算子 if (aclnnAddCustom(workspace, workspaceSize, handle, stream) != ACL_SUCCESS) {     (void)aclrtDestroyStream(stream);     ERROR_LOG("Execute Operator failed. error code is %d", static_cast<int32_t>(ret));     return false; }  // 7.同步等待 aclrtSynchronizeStream(stream);  // 8.处理执行算子后的输出数据,例如在屏幕上显示、写入文件等,由用户根据实际情况自行实现 // ......  // 9.释放运行管理资源 aclRet = aclrtResetDevice(deviceid); // ....  // 10.AscendCL去初始化 aclRet = aclFinalize();
复制代码


运行一个完整的算子调用程序


昇腾的 gitee 仓中提供了完整的样例工程LINK,工程目录结构如下:

├──input                                                 // 存放脚本生成的输入数据目录 ├──output                                                // 存放算子运行输出数据和真值数据的目录 ├── inc                           // 头文件目录  │   ├── common.h                 // 声明公共方法类,用于读取二进制文件  │   ├── operator_desc.h          // 算子描述声明文件,包含算子输入/输出,算子类型以及输入描述与输出描述  │   ├── op_runner.h              // 算子运行相关信息声明文件,包含算子输入/输出个数,输入/输出大小等  ├── src  │   ├── CMakeLists.txt    // 编译规则文件 │   ├── common.cpp         // 公共函数,读取二进制文件函数的实现文件 │   ├── main.cpp    // 单算子调用应用的入口 │   ├── operator_desc.cpp     // 构造算子的输入与输出描述  │   ├── op_runner.cpp   // 单算子调用主体流程实现文件 ├── scripts │   ├── verify_result.py    // 真值对比文件 │   ├── gen_data.py    // 输入数据和真值数据生成脚本文件 │   ├── acl.json    // acl配置文件
复制代码


步骤 1 增加头文件引用。


安装部署完成后,会在算子包安装目录下的 op_api 目录生成单算子调用的头文件 aclnn_xx.h 和动态库 libcust_opapi.so,编写单算子的调用代码时,要包含自动生成的单算子 API 执行接口头文件:

#include "aclnn_add_custom.h"
复制代码


步骤 2 修改 CMakeLists 文件。


编译算子调用程序时,需要在头文件的搜索路径 include_directories 中增加算子包安装目录下的 op_api/include 目录,便于找到该头文件;同时需要链接 cust_opapi 动态库。


  • 设置 CUST_PKG_PATH 变量为算子包安装目录下的 op_api 目录,以下样例仅为参考,请根据算子包部署的实际目录位置进行设置。

if (NOT DEFINED ENV{DDK_PATH})     set(INC_PATH "/usr/local/Ascend/ascend-toolkit/latest")     message(STATUS "set default INC_PATH: ${INC_PATH}") else ()     message(STATUS "env INC_PATH: ${INC_PATH}") endif() set(CUST_PKG_PATH "${INC_PATH}/opp/vendors/customize/op_api")
复制代码


  • 在头文件的搜索路径 include_directories 中增加算子包安装目录下的 op_api/include 目录。

include_directories(     ${INC_PATH}/runtime/include     ${INC_PATH}/atc/include     ../inc     ${CUST_PKG_PATH}/include )
复制代码


  • 链接 cust_opapi 链接库。

target_link_libraries(execute_add_op     ascendcl     cust_opapi     acl_op_compiler     nnopbase     stdc++ )
复制代码


步骤 3 生成测试数据。


在样例工程目录下,执行如下命令:

python3 scripts/gen_data.py
复制代码


会在工程目录下 input 目录中生成两个 shape 为(8,2048),数据类型为 float16 的数据文件 input_0.bin 与 input_1.bin,用于进行 AddCustom 算子的验证。代码样例如下:

import numpy as np a = np.random.randint(100, size=(8, 2048,)).astype(np.float16) b = np.random.randint(100, size=(8, 2048,)).astype(np.float16) a.tofile('input_0.bin') b.tofile('input_1.bin')
复制代码


步骤 4 程序编译与运行。


1. 开发环境上,设置环境变量,配置 AscendCL 单算子验证程序编译依赖的头文件与库文件路径,如下为设置环境变量的示例。${INSTALL_DIR}表示 CANN 软件安装目录,例如,$HOME/Ascend/ascend-toolkit/latest。{arch-os}为运行环境的架构和操作系统,arch 表示操作系统架构,os 表示操作系统,例如 x86_64-linux。

export DDK_PATH=${INSTALL_DIR} export NPU_HOST_LIB=${INSTALL_DIR}/{arch-os}/lib64
复制代码


2. 编译样例工程,生成单算子验证可执行文件。


a. 切换到样例工程根目录,然后在样例工程根目录下执行如下命令创建目录用于存放编译文件,例如,创建的目录为“build”。

mkdir -p build
复制代码


b. 进入 build 目录,执行 cmake 编译命令,生成编译文件,命令示例如下所示:

cd build cmake ../src
复制代码


c. 执行如下命令,生成可执行文件。

make
复制代码


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


3. 执行单算子。


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


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


b. 在运行环境中,执行 execute_add_op:

chmod +x execute_add_op ./execute_add_op
复制代码


会有如下屏显信息:

[INFO]  Set device[0] success [INFO]  Get RunMode[1] success [INFO]  Init resource success [INFO]  Set input success [INFO]  Copy input[0] success [INFO]  Copy input[1] success [INFO]  Create stream success [INFO]  Execute aclnnAddCustomGetWorkspaceSize success, workspace size 0 [INFO]  Execute aclnnAddCustom success [INFO]  Synchronize stream success [INFO]  Copy output[0] success [INFO]  Write output success [INFO]  Run op success [INFO]  Reset Device success [INFO]  Destory resource success
复制代码


如果有 Run op success,表明执行成功,会在 output 目录下生成输出文件 output_z.bin。


4. 比较真值文件。


切换到样例工程根目录,然后执行如下命令:

python3 scripts/verify_result.py output/output_z.bin output/golden.bin
复制代码


会有如下屏显信息:

test pass
复制代码


可见,AddCustom 算子验证结果正确。


文章转载自:华为云开发者联盟

原文链接:https://www.cnblogs.com/huaweiyun/p/18219771

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
一文教你如何调用Ascend C算子_人工智能_EquatorCoco_InfoQ写作社区