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

ascend · 浏览次数 : 0

小编点评

```c++ #include "aclnn_add_custom.h" int main() { // Initialize AscendCL context aclnnInit(); // Set device ID int deviceid = 0; // Get workspace size size_t workspaceSize = 0; ret = aclnnAddCustomGetWorkspaceSize(inputTensor_[0], inputTensor_[1], outputTensor_[0], &workspaceSize, &handle); // Execute operator if (aclnnAddCustom(workspace, workspaceSize, handle, stream) != ACL_SUCCESS) { ERROR_LOG("Execute Operator failed. error code is %d", static_cast(ret)); return false; } // Synchronize stream aclrtSynchronizeStream(stream); // Process output data // ... // Release resources aclrtDestroyStream(stream); aclnnResetDevice(deviceid); aclFinalize(); return 0; } ```

正文

本文分享自华为云社区《一文教你如何调用Ascend C算子》,作者: 昇腾CANN。

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.png

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

// 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算子验证结果正确。

更多学习资源

[1]Ascend C一站式学习资源:https://www.hiascend.com/ascend-c

点击关注,第一时间了解华为云新鲜技术~

 

与一文教你如何调用Ascend C算子相似的内容:

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

本文分享自华为云社区《一文教你如何调用Ascend C算子》,作者: 昇腾CANN。 Ascend C是CANN针对算子开发场景推出的编程语言,原生支持C和C++标准规范,兼具开发效率和运行性能。基于Ascend C编写的算子程序,通过编译器编译和运行时调度,运行在昇腾AI处理器上。使用Ascend

gRPC入门学习之旅(十)

gRPC是一个高性能、通用的开源远程过程调用(RPC)框架,基于底层HTTP/2协议标准和协议层Protobuf序列化协议开发, gRPC 客户端和服务端可以在多种环境中运行和交互。你可以用Java创建一个 gRPC 服务端,用 Go、Python、C# 来创建客户端。本系统文章详细描述了如何创建一...

gRPC入门学习之旅(九)

gRPC是一个高性能、通用的开源远程过程调用(RPC)框架,基于底层HTTP/2协议标准和协议层Protobuf序列化协议开发, gRPC 客户端和服务端可以在多种环境中运行和交互。你可以用Java创建一个 gRPC 服务端,用 Go、Python、C# 来创建客户端。本系统文章详细描述了如何创建一...

gRPC入门学习之旅(八)

gRPC是一个高性能、通用的开源远程过程调用(RPC)框架,基于底层HTTP/2协议标准和协议层Protobuf序列化协议开发, gRPC 客户端和服务端可以在多种环境中运行和交互。你可以用Java创建一个 gRPC 服务端,用 Go、Python、C# 来创建客户端。本系统文章详细描述了如何创建一...

[Cmake Qt]找不到文件ui_xx.h的问题?有关Qt工程的问题,看这篇文章就行了。

前言 最近在开发一个组件,但是这个东西是以dll的形式发布的界面库,所以在开发的时候就需要上层调用。 如果你是很懂CMake的话,ui_xx.h的文件目录在 $ 下 然后除了有关这个ui_xx.h,还有一些别的可以简单聊聊的 一、父子工程组织,或者说依赖关系 在使用CMake进行开发的时候,一般可以

还不知道如何在java中终止一个线程?快来,一文给你揭秘

简介 工作中我们经常会用到线程,一般情况下我们让线程执行就完事了,那么你们有没有想过如何去终止一个正在运行的线程呢? 今天带大家一起来看看。 Thread.stop被禁用之谜 问道怎么终止一个线程,可能大多数人都知道可以调用Thread.stop方法。 但是这个方法从jdk1.2之后就不推荐使用了,

5分钟教你搭建邮件服务器的实用指南

今天我写了一篇实用的文章,重点是教你如何免费搭建一个邮件服务器,这个服务器不仅可以用于发送邮件,还可以供我的待办机器人使用。一开始我试图找一些免费的 API 接口来实现这个功能,但遗憾的是,并没有找到合适的。对于程序员来说,能自己动手实现绝对是最好的选择,幸运的是,我有一台空闲的服务器可以利用。如果...

Docker 中的 .NET 异常了怎么抓 Dump

## 一:背景 ### 1. 讲故事 有很多朋友跟我说,在 Windows 上看过你文章知道了怎么抓 Crash, CPU爆高,内存暴涨 等各种Dump,为什么你没有写在 Docker 中如何抓的相关文章呢?瞧不上吗? 哈哈,在DUMP的分析旅程中,跑在 Docker 中的 .NET 占比真的不多,

每日一库:cobra 简介

当你需要为你的 Go 项目创建一个强大的命令行工具时,你可能会遇到许多挑战,比如如何定义命令、标志和参数,如何生成详细的帮助文档,如何支持子命令等等。为了解决这些问题,github.com/spf13/cobra 就可以派上用场。 github.com/spf13/cobra 是一个用于构建强大的命

netty系列之: 在netty中使用 tls 协议请求 DNS 服务器

简介 在前面的文章中我们讲过了如何在netty中构造客户端分别使用tcp和udp协议向DNS服务器请求消息。在请求的过程中并没有进行消息的加密,所以这种请求是不安全的。 那么有同学会问了,就是请求解析一个域名的IP地址而已,还需要安全通讯吗? 事实上,不加密的DNS查询消息是很危险的,如果你在访问一