- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
导读
本文以 CANN asc-devkit 仓库中 01_acl_invocation 样例 为核心,系统讲解在完成自定义算子开发部署后,如何通过aclnn 单算子 API 执行(基于 C 语言的两段式接口,免离线模型转换)与aclop 单算子模型调用(基于atc --singleop生成的单算子离线模型)两种方式在 Host 侧驱动昇腾 AI 处理器执行自定义算子。读完本文,你将掌握aclnnAddCustomGetWorkspaceSize/aclnnAddCustom与aclopExecuteV2的完整调用流程、工程编译配置、单算子描述文件编写以及结果验证方法,可直接套用到自己的自定义算子验证场景。
背景:为什么要区分 aclnn 与 aclop 两种调用方式
在 CANN 生态中,算子开发完成后并不等于可以直接被上层框架使用,还需要在 Host 侧通过 ACL(Ascend Computing Language)运行时接口把算子"发下去"执行。面向不同的集成场景,CANN 提供了两种单算子执行路径,本样例的父级文档 99_acl_based 目录 将其归纳为:
| 目录名称 | 功能描述 |
|---|---|
| 00_acl_compilation | 自定义算子编译、打包和部署的实现方法 |
| 01_acl_invocation | Aclnn 和 Aclop 算子调用的实现方法 |
其中 01_acl_invocation 正是本文的主体,它包含两个子样例:
| 目录名称 | 功能描述 | 支持的产品 |
|---|---|---|
| aclnn_invocation | 介绍 Aclnn 算子调用(单算子 API 执行)的实现方法 | Ascend 950PR/Ascend 950DT、Atlas A3/A2 训练与推理系列产品、Atlas 200I/500 A2 推理产品、Atlas 推理系列产品 |
| aclop_invocation | 介绍 Aclop 算子调用(单算子模型执行)的实现方法 | 与 aclnn 样例相同 |
两个样例都以自定义的AddCustom算子(z = x + y,输入输出 shape 均为[8, 2048],float16,ND 格式)为对象,分别演示两种调用路径的完整代码,是理解单算子调用的最小可运行范本。
两种调用方式的原理差异
aclnn:单算子 API 执行(免离线模型转换)
单算子 API 执行是基于 C 语言 API 直接执行算子的方式:无需提供单算子描述文件、无需经过离线模型转换,只要自定义算子工程已完成编译、打包和部署,Host 程序即可直接链接算子侧生成的 op_api 库并调用对应的aclnnOpType接口。
以本样例为例,对应算子生成的接口是aclnnAddCustom,采用两段式调用:
aclnnAddCustomGetWorkspaceSize:获取本次计算所需 workspace 大小,并在 Device 上申请对应内存;aclnnAddCustom:真正下发算子计算。
这种"先查 workspace、再执行"的模式是 aclnn 系列接口的统一约定:workspace 是算子执行过程中 Kernel 侧所需的临时存储空间,由框架根据算子实现动态计算,Host 侧需要按其返回值申请显存后传入。
aclop:单算子模型调用(离线模型驱动)
单算子模型调用走的是离线模型路径:在 Host 应用运行前,需要先用atc --singleop工具将单算子描述文件(add_custom.json)转换为单算子离线模型,然后在应用中通过aclopSetModelDir指定模型目录,再调用aclopExecuteV2执行。该路径不需要生成aclnnOpType形式的算子专用接口,而是以"算子类型名(OpType)+ 输入输出描述 + 数据 Buffer"的通用形式驱动模型执行。
两种方式的取舍可归纳为:aclnn 免模型转换、接口语义更贴近具体算子(参数即为张量),适合快速验证算子功能与算子侧自研集成;aclop 需要提前用 atc 生成模型,但接口更通用,适合以 OpType 为键的框架级调度场景。
前置条件:先编译、打包、部署自定义算子工程
两个调用样例的 README 都明确要求:运行前必须先进入 自定义算子工程样例 完成编译、打包和部署。该工程内同时提供AddCustom、AddCustomTemplate、AddCustomTilingSink、LeakyRelu四个算子实现,其中被本样例调用的AddCustom基于 Ascend C 的矢量计算接口Add实现,tiling 侧使用totalLength、tileNum两个参数控制数据切分。
部署命令(在该工程根目录下执行):
mkdir -p build && cd build cmake .. && make -j binary package ./custom_opp_*.run执行结果出现SUCCESS即部署成功。部署产物会安装到ASCEND_OPP_PATH(默认${install_path}/cann/opp)下的vendors/customize目录,这正是后续 aclnn 样例链接cust_opapi库、aclop 样例查找模型文件时依赖的环境。
环境变量配置
根据 CANN 安装方式 配置环境变量:
source ${install_path}/cann/set_env.sh说明:
${install_path}为 CANN 包安装目录,未指定安装目录时默认安装至/usr/local/Ascend下。编译脚本中使用的ASCEND_HOME_PATH、ASCEND_OPP_PATH均由该脚本注入。
aclnn 样例详解:aclnnAddCustom 两段式调用
目录结构
aclnn_invocation/ ├── CMakeLists.txt // 编译工程文件 ├── main.cpp // 算子调用主程序 ├── README.md // 样例说明文档 └── scripts/ └── gen_data.py // 测试数据生成脚本数据准备:gen_data.py
gen_data.py 使用 numpy 生成 shape 为(8, 2048)的测试数据:
input0.bin:全 1.1 的 float16 矩阵input1.bin:全 2.2 的 float16 矩阵golden.bin:(input0 + input1)的 float16 结果,作为验证基准
数据以二进制格式写入input/与output/目录,供主程序ReadFile读取。
主程序核心流程
aclnn_invocation/main.cpp 完整演示了三段式生命周期,可拆解为以下五步:
Step 1:初始化 ACL 环境
const int32_t deviceId = 0; aclrtStream stream = nullptr; CHECK_ACL(aclnnInit(nullptr)); // 初始化 aclnn(替代传统 aclInit) CHECK_ACL(aclrtSetDevice(deviceId)); // 指定设备 CHECK_ACL(aclrtCreateStream(&stream)); // 创建流注意 aclnn 路径使用aclnnInit/aclnnFinalize而非aclInit/aclFinalize,这是 aclnn 接口族的使用约定。
Step 2:创建 aclTensor 并准备 Device 数据
先申请 Device 显存,再用aclCreateTensor将显存包装为带 shape、dtype、format 语义的张量描述:
void* input0DeviceMem = nullptr; CHECK_ACL(aclrtMalloc(&input0DeviceMem, bufferSize, ACL_MEM_MALLOC_HUGE_FIRST)); aclTensor* input0 = aclCreateTensor( shape.data(), shape.size(), ACL_FLOAT16, nullptr, 0, ACL_FORMAT_ND, shape.data(), shape.size(), input0DeviceMem);其中 shape 为{8, 2048}、bufferSize = 8 * 2048 * sizeof(aclFloat16),三个张量(input0、input1、output0)同构创建。随后通过aclrtMemcpy以ACL_MEMCPY_HOST_TO_DEVICE方向将 Host 数据拷入 Device:
CHECK_ACL(aclrtMemcpy(input0DeviceMem, bufferSize, input0HostData.data(), bufferSize, ACL_MEMCPY_HOST_TO_DEVICE));Step 3:两段式调用——先查 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)); } CHECK_ACL(aclnnAddCustom(workspaceDeviceMem, workspaceSize, executor, stream));这段代码体现了 aclnn 两段式接口的标准用法:第一段返回workspaceSize(可能为 0,即算子无需额外 workspace)和复用的executor句柄;第二段把 workspace 显存与流传入执行。workspaceSize 为 0 时不能申请 0 字节显存,因此代码用if (workspaceSize > 0)做了保护。
Step 4:同步与结果回拷
CHECK_ACL(aclrtSynchronizeStream(stream)); // 等待算子异步执行完成 CHECK_ACL(aclrtMemcpy(output0HostData.data(), bufferSize, output0DeviceMem, bufferSize, ACL_MEMCPY_DEVICE_TO_HOST));Step 5:结果校验与资源释放
程序打印前 10 个结果元素,并用VerifyResult与 golden 数据比对(float16 先转 float,容差1e-3F):
printf("\ntest %s\n", VerifyResult(output0HostData, goldenData) ? "pass" : "failed");释放顺序为:aclDestroyTensor→aclrtFree(含 workspace)→aclrtDestroyStream→aclrtResetDevice→aclnnFinalize,与申请顺序严格对称。
编译配置:CMakeLists.txt
aclnn_invocation/CMakeLists.txt 的关键点在于:除 ACL 运行时头文件与库外,还要链接部署自定义算子包时生成的 op_api 产物:
target_include_directories(execute_add_op PRIVATE $ENV{ASCEND_HOME_PATH}/include $ENV{ASCEND_OPP_PATH}/vendors/customize/op_api/include ) target_link_directories(execute_add_op PRIVATE $ENV{ASCEND_HOME_PATH}/lib64 $ENV{ASCEND_OPP_PATH}/vendors/customize/op_api/lib ) target_link_libraries(execute_add_op PRIVATE cust_opapi // 自定义算子生成的 aclnn 接口库 nnopbase // 算子基础库 acl_rt // ACL 运行时 )cust_opapi正是aclnnAddCustom等接口的载体——这也是为什么必须先部署自定义算子工程再编译本样例。
编译运行
mkdir -p build && cd build python3 ../scripts/gen_data.py # 生成 input/ 与 output/ 下的测试数据 cmake .. && make -j ./execute_add_op执行结果:
result is: 3.3 3.3 3.3 3.3 3.3 3.3 3.3 3.3 3.3 3.3 test pass(1.1 + 2.2 = 3.3,符合z = x + y预期。)
aclop 样例详解:aclopExecuteV2 单算子模型执行
目录结构
aclop_invocation/ ├── add_custom.json // 单算子描述文件(atc 模型转换输入) ├── CMakeLists.txt // 编译工程文件 ├── main.cpp // 算子调用主程序 ├── README.md // 样例说明文档 └── scripts/ └── gen_data.py // 测试数据生成脚本与 aclnn 样例相比,多了一个add_custom.json,它是离线模型转换的输入描述文件。
单算子描述文件 add_custom.json
add_custom.json 声明了算子类型、输入输出描述:
[ { "op": "AddCustom", "input_desc": [ { "name": "x", "param_type": "required", "format": "ND", "shape": [8, 2048], "type": "float16" }, { "name": "y", "param_type": "required", "format": "ND", "shape": [8, 2048], "type": "float16" } ], "output_desc": [ { "name": "z", "param_type": "required", "format": "ND", "shape": [8, 2048], "type": "float16" } ] } ]其中op必须与算子工程注册的 OpType(AddCustom)一致;input_desc/output_desc的name需与算子 Host 侧注册的输入输出名对应(本算子为 x、y、z),shape、type、format决定 atc 生成固定 shape 模型的输入输出规格。
主程序核心流程
aclop_invocation/main.cpp 使用经典的 ACL C 接口,流程如下:
Step 1:初始化并指定模型目录
CHECK_ACL(aclInit(nullptr)); // aclop 路径使用 aclInit/aclFinalize CHECK_ACL(aclrtSetDevice(deviceId)); CHECK_ACL(aclrtCreateStream(&stream)); CHECK_ACL(aclopSetModelDir(".")); // 指定 atc 生成的单算子模型所在目录注意与 aclnn 样例的区别:这里调用aclInit,且执行前必须aclopSetModelDir指向模型目录(本例模型输出到.,即 build 目录)。
Step 2:用 TensorDesc + DataBuffer 描述输入输出
与 aclnn 的aclTensor不同,aclop 路径使用"描述(aclCreateTensorDesc)+ 数据(aclCreateDataBuffer)"分离的模型:
const char* opType = "AddCustom"; auto* input0 = aclCreateTensorDesc(dataType, shape.size(), shape.data(), format); // input1、output0 同理,format 为 ACL_FORMAT_ND,dataType 为 ACL_FLOAT16 void* input0DeviceMem = nullptr; CHECK_ACL(aclrtMalloc(&input0DeviceMem, bufferSize, ACL_MEM_MALLOC_HUGE_FIRST)); auto* input0Buffer = aclCreateDataBuffer(input0DeviceMem, bufferSize);TensorDesc 放入inputDesc/outputDesc两个std::vector,DataBuffer 放入inputBuffers/outputBuffers,保持下标一一对应。
Step 3:执行 aclopExecuteV2
CHECK_ACL(aclopExecuteV2(opType, inputDesc.size(), inputDesc.data(), inputBuffers.data(), outputDesc.size(), outputDesc.data(), outputBuffers.data(), opAttr, stream));opAttr由aclopCreateAttr()创建(本算子无属性,传入空属性即可),opType字符串即单算子描述文件中的"op"值,ACL 依据该 OpType 在模型目录中查找对应模型并执行。
Step 4:同步、回拷与校验
CHECK_ACL(aclrtSynchronizeStream(stream)); CHECK_ACL(aclrtMemcpy(output0HostData.data(), bufferSize, output0DeviceMem, bufferSize, ACL_MEMCPY_DEVICE_TO_HOST));校验逻辑与 aclnn 样例相同(容差1e-3F的逐元素比对),释放资源时额外调用aclDestroyDataBuffer、aclDestroyTensorDesc、aclopDestroyAttr。
编译配置:CMakeLists.txt
aclop_invocation/CMakeLists.txt 不需要链接cust_opapi,只需 ACL 基础库:
target_link_libraries(execute_add_op PRIVATE acl_op_executor // aclop 单算子模型执行库 acl_mdl // 模型加载/执行库 acl_rt // ACL 运行时 )这从工程层面印证了两条路径的本质差异:aclnn 依赖算子工程产物cust_opapi,aclop 依赖通用模型执行库。
编译运行:先 atc 生成单算子离线模型
mkdir -p build && cd build python3 ../scripts/gen_data.py # 使用 atc 模型转换工具生成单算子离线模型 atc --singleop=../add_custom.json --output=. --soc_version=${soc_version} cmake .. && make -j ./execute_add_op其中${soc_version}为 AI 处理器型号,获取方式如下:
- 针对Atlas A2 训练/推理系列产品、Atlas 200I/500 A2 推理产品、Atlas 推理系列产品:在安装昇腾 AI 处理器的服务器执行
npu-smi info查询,取Name信息,实际配置值为 AscendName(例如Name取值为 xxxyy,实际配置值为 Ascendxxxyy); - 针对Ascend 950PR/Ascend 950DT、Atlas A3 训练/推理系列产品:执行
npu-smi info -t board -i <id> -c <chip_id>查询,取Chip Name与NPU Name,实际配置值为Chip Name_NPU Name(例如 Chip Name 为 Ascendxxx、NPU Name 为 1234,则配置Ascendxxx_1234)。其中id为设备 id(通过npu-smi info -l查出的 NPU ID),chip_id为芯片 id(通过npu-smi info -m查出的 Chip ID)。
说明:基于同系列的 AI 处理器型号创建的算子工程,其基础功能(算子开发、编译和部署)通用。
atc命令在 CANN 包安装目录(默认/usr/local/Ascend)的cann/atc/bin下,环境变量配置后可直接使用。
执行结果与 aclnn 样例一致,出现test pass即验证通过。
两种调用方式对比与选型建议
| 对比维度 | aclnn 单算子 API 执行 | aclop 单算子模型执行 |
|---|---|---|
| 前置步骤 | 仅需部署自定义算子包 | 部署算子包 +atc --singleop生成离线模型 |
| 模型转换 | 不需要 | 需要(输入add_custom.json) |
| 关键接口 | aclnnOpTypeGetWorkspaceSize/aclnnOpType(两段式) | aclopSetModelDir/aclopExecuteV2 |
| 数据对象 | aclTensor(描述与数据一体) | aclTensorDesc+aclDataBuffer(分离) |
| 初始化 | aclnnInit/aclnnFinalize | aclInit/aclFinalize |
| 链接依赖 | cust_opapi+nnopbase+acl_rt | acl_op_executor+acl_mdl+acl_rt |
| 适用场景 | 算子功能快速验证、算子侧集成 | 框架级以 OpType 为键的通用调度 |
选择建议:如果你的目标是验证刚开发的自定义算子功能是否正确,aclnn 路径更直接——部署算子包后即可调用,且接口参数即张量,错误定位直观;如果你需要模拟真实推理/训练框架加载算子模型的路径,或在同一应用中按 OpType 动态调度多个算子,则 aclop 路径更贴近生产形态。
延伸阅读
- 算子调用样例总览:99_acl_based/README.md
- 前置的自定义算子工程编译、打包、部署流程:00_acl_compilation/custom_op/README.md
- CANN 开发套件包安装与环境准备:docs/zh/quick_start.md
- 若需以 git 方式获取源码,可执行
git clone https://gitcode.com/cann/asc-devkit后进入examples/01_simd_cpp_api/02_features/99_acl_based/01_acl_invocation目录复现本文全部步骤。
- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
相关推荐
Security-101 入门网络安全课程指南:八模块课纲、多语言分发机制与学习路径(基于保加利亚语版 README 解读)
Security 101 入门网络安全课程指南:八模块课纲、多语言分发机制与学习路径(基于保加利亚语版 README 解读) 本文基于 Security 101
人工智能深度学习算子库CANNAscendCANN ops-math ReduceSum 算子全解析:aclnn 单算子调用与 GE IR 图模式实战指南
CANN ops math ReduceSum 算子全解析:aclnn 单算子调用与 GE IR 图模式实战指南 本指南以 CANN ops math 开源仓库
算子库人工智能CANNCANN ops-nn WeightQuantBatchMatmulExperiment 算子 aclnn 单算子调用样例深度解析
CANN ops nn WeightQuantBatchMatmulExperiment 算子 aclnn 单算子调用样例深度解析 本指南以 experimen
人工智能算子库深度学习CANNAscend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考