CANN ops-cv 算子 API 样例编译与运行实战:以 GridSample(aclnnGridSampler2D)为例
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
本文是 CANN ops-cv 图像算子库中算子 API 样例的完整编译与运行指南。文章以GridSample 算子(aclnnGridSampler2D)为主线,系统讲解从环境准备、示例代码与 CMake 脚本编写,到环境变量配置、编译、运行及结果验证的全过程;同时结合 op_api/aclnn_grid_sampler2d.cpp 等仓库源码,说明两段式接口的调用原理与参数校验逻辑,帮助开发者在本地复现算子 API 的调用流程并快速排查运行期错误。
1. 前提说明:编译运行算子 API 所需的基础环境
在编译和执行算子 API 之前,请确保基础环境已搭建完成,主要包括:
- 驱动与固件:AI 处理器对应的驱动和固件已正确安装并生效;
- CANN 软件包:包含 Runtime、算子库等核心组件,安装后可提供
set_env.sh环境脚本; - ops 包(算子二进制包):包含算子编译后的二进制 kernel 库,缺少该包时 API 内部会报“未加载到算子的二进制 kernel 库”类错误(对应返回码
ACLNN_ERR_INNER_OPP_KERNEL_PKG_NOT_FOUND,见 docs/zh/context/aclnn_return_code.md); - 编译工具链:
g++(支持 C++11)、cmake(最低版本 3.14)、make。
说明:算子 API 的调用流程和编译运行操作的完整官方说明,可参见《应用开发(C&C++)》中“单算子调用 > 单算子API执行 > 调用aclnn接口示例代码”章节。本文的实战步骤与仓库内 image/grid_sample 模块的实际代码一一对应。
2. 编译前准备:以 GridSample 算子为例
本文以开发和运行环境合设场景为例,即带 AI 处理器的机器既作为开发环境又作为运行环境,代码开发和代码运行在同一台机器上。以 GridSample 算子为例,其他算子的调用逻辑、流程、编译脚本与 GridSample 算子大致一样,请根据实际情况自行修改 API 调用脚本(*.cpp)和编译脚本(CMakeLists)。
2.1 准备示例代码
GridSample 算子的功能是:提供一个输入 tensor 以及一个对应的 grid 网格,然后根据 grid 中每个位置提供的坐标信息,将 input 中对应位置的像素值填充到网格指定的位置,得到最终的输出。
示例代码可从 image/grid_sample/docs/aclnnGridSampler2D.md 的“调用示例”一节获取,将代码文件命名为test_grid_sampler2_d.cpp。仓库中已存在可直接使用的完整样例 image/grid_sample/examples/test_aclnn_grid_sample2_d.cpp,代码整体分为 7 个步骤:
int main() { // 1. (固定写法)device/stream初始化,参考acl API手册 int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); // 2. 构造输入与输出,需要根据API的接口自定义构造 int64_t interpolationMode = 0; // 0: bilinear int64_t paddingMode = 0; // 0: zeros bool alignCorners = false; std::vector<int64_t> inputShape = {1, 1, 5, 8}; std::vector<int64_t> gridShape = {1, 3, 3, 2}; std::vector<int64_t> outShape = {1, 1, 3, 3}; // 3. 调用CANN算子库API,需要修改为具体的Api名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnGridSampler2D第一段接口 ret = aclnnGridSampler2DGetWorkspaceSize(input, grid, interpolationMode, paddingMode, alignCorners, out, &workspaceSize, &executor); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); } // 调用aclnnGridSampler2D第二段接口 ret = aclnnGridSampler2D(workspaceAddr, workspaceSize, executor, stream); // 4. (固定写法)同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); // 5. 获取输出的值,将device侧内存上的结果复制至host侧 // 6. 释放aclTensor // 7. 释放Device资源 return 0; }代码中几个要点与仓库源码相互印证:
- 两段式接口调用:
aclnnGridSampler2DGetWorkspaceSize是第一段接口,负责完成参数校验、构图并计算出计算所需 workspace 大小;aclnnGridSampler2D是第二段接口,真正在指定 stream 上执行计算。这与仓库文档 docs/zh/context/two_phase_api.md 描述的“两段式”机制一致:第一段接口形如aclxxXxxGetWorkspaceSize(const aclTensor *src, ..., uint64_t *workspaceSize, aclOpExecutor **executor),第二段接口形如aclxxXxx(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)。注意第二段接口不能重复调用,否则会出现异常。 - workspace 的概念:workspace 是指除输入/输出外,算子在 NPU 上完成计算所需要的临时内存,workspaceSize 表示临时内存的大小。只有第一段接口计算出的
workspaceSize > 0时才需要调用aclrtMalloc申请 Device 侧内存。 - 参数校验在第一段完成:从 op_api/aclnn_grid_sampler2d.cpp 源码可见,
aclnnGridSampler2DGetWorkspaceSize内部依次执行空指针检查(CheckNotNull)、数据类型检查(CheckDtypeValid)、属性取值检查(CheckAttrValid,其中interpolationMode与paddingMode的取值范围均为 0~2)、shape 匹配关系检查(CheckShape),全部通过后才进入构图流程;而第二段接口 aclnn_grid_sampler2d.cpp 仅通过CommonOpExecutorRun完成计算下发。
2.2 编写 CMakeLists 文件
CMake 文件示例如下(与示例代码放在同一目录),请根据实际情况修改:
# Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved. # CMake lowest version requirement cmake_minimum_required(VERSION 3.14) # 设置工程名 project(ACLNN_EXAMPLE) # Compile options add_compile_options(-std=c++11) # 设置编译选项 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "./bin") set(CMAKE_CXX_FLAGS_DEBUG "-fPIC -O0 -g -Wall") set(CMAKE_CXX_FLAGS_RELEASE "-fPIC -O2 -Wall") # 设置可执行文件名(如opapi_test),并指定待运行算子文件*.cpp所在目录 add_executable(opapi_test test_grid_sampler2_d.cpp) # 设置ASCEND_PATH(CANN软件包目录,请根据实际路径修改)和INCLUDE_BASE_DIR(头文件目录) if(NOT "$ENV{ASCEND_CUSTOM_PATH}" STREQUAL "") set(ASCEND_PATH $ENV{ASCEND_CUSTOM_PATH}) else() set(ASCEND_PATH "/usr/local/Ascend/cann") endif() set(INCLUDE_BASE_DIR "${ASCEND_PATH}/include") include_directories( ${INCLUDE_BASE_DIR} ${INCLUDE_BASE_DIR}/aclnn ) # 设置链接的库文件路径 target_link_libraries(opapi_test PRIVATE ${ASCEND_PATH}/lib64/libacl_rt.so ${ASCEND_PATH}/lib64/libnnopbase.so ${ASCEND_PATH}/lib64/libopapi_math.so ${ASCEND_PATH}/lib64/libopapi_cv.so) # 可执行文件在CMakeLists文件所在目录的bin目录下 install(TARGETS opapi_test DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})对上述脚本的逐项说明:
| 配置项 | 说明 |
|---|---|
cmake_minimum_required(VERSION 3.14) | 最低 CMake 版本要求,低于 3.14 的版本可能无法正确解析脚本 |
project(ACLNN_EXAMPLE) | 工程名,可自定义 |
add_compile_options(-std=c++11) | 算子 API 示例代码使用 C++11 标准,与仓库示例代码的语法一致 |
CMAKE_RUNTIME_OUTPUT_DIRECTORY "./bin" | 编译产物输出到当前目录的 bin 子目录 |
ASCEND_PATH | CANN 软件包安装目录。优先读取环境变量ASCEND_CUSTOM_PATH,未设置时默认/usr/local/Ascend/cann,请按实际安装路径修改 |
include_directories | 引入${ASCEND_PATH}/include与${ASCEND_PATH}/include/aclnn,用于找到acl/acl.h、aclnnop/aclnn_grid_sampler2d.h等头文件 |
libacl_rt.so | ACL Runtime 库,提供aclInit、aclrtMalloc、aclrtMemcpy、aclrtCreateStream等运行时接口 |
libnnopbase.so | 算子基础库(OPBASE) |
libopapi_math.so/libopapi_cv.so | 算子 API 库,其中libopapi_cv.so承载图像处理类算子 API,GridSample 的aclnnGridSampler2D即位于该库中 |
提示:若算子 API 的调用代码使用了
aclGetRecentErrMsg(错误信息获取接口),该接口由libacl_rt.so提供,无需额外链接其他库。
2.3 MC2 算子的特殊编译要求
对于集合通信和 MatMul 计算融合、并行的算子,统称为通算融合算子(简称 MC2 算子),包括AllGatherMatmul、AlltoAllAllGatherBatchMatMul、BatchMatMulReduceScatterAlltoAll、MatmulAllReduce、MatmulAllReduceAddRmsNorm、MatmulReduceScatter等。
调用该类算子 API 时,一般会涉及多线程和 HCCL(Huawei Collective Communication Library,集合通信库),因此 CMake 文件需要额外导入如下内容,否则无法成功编译:
# 设置链接的库文件路径 find_package(Threads REQUIRED) target_link_libraries(opapi_test PRIVATE ${ASCEND_PATH}/lib64/libacl_rt.so ${ASCEND_PATH}/lib64/libnnopbase.so ${ASCEND_PATH}/lib64/libopapi_math.so ${ASCEND_PATH}/lib64/libopapi_cv.so ${ASCEND_PATH}/lib64/libhccl.so # 集合通信库文件 ${CMAKE_THREAD_LIBS_INIT}) # 多线程依赖的库文件其中find_package(Threads REQUIRED)是 CMake 用于查找线程库的命令,可自动链接线程库依赖的头文件或间接依赖的库文件。${CMAKE_THREAD_LIBS_INIT}为查找到的线程库(Linux 下通常为-lpthread)。
3. 编译与运行
3.1 步骤一:准备代码与编译脚本
提前准备好算子的调用代码(*.cpp)和编译脚本(CMakeLists.txt),两者位于同一目录。
3.2 步骤二:配置环境变量
安装 CANN 软件后,使用 CANN 运行用户登录环境,执行如下命令生效环境变量:
source ${INSTALL_DIR}/set_env.sh其中${INSTALL_DIR}为 CANN 软件安装后文件存储路径,请根据实际情况替换(例如/usr/local/Ascend/cann)。环境变量生效后,ASCEND_CUSTOM_PATH等变量即被设置,CMake 脚本中if(NOT "$ENV{ASCEND_CUSTOM_PATH}" STREQUAL "")分支会命中,自动使用该路径作为ASCEND_PATH。
3.3 步骤三:编译
进入 CMakeLists.txt 所在目录,执行如下命令,新建 build 目录存放生成的编译文件:
mkdir -p build进入 build 目录,执行 cmake 命令编译,再执行 make 命令生成可执行文件:
cd build cmake ../ -DCMAKE_CXX_COMPILER=g++ -DCMAKE_SKIP_RPATH=TRUE make参数说明:
-DCMAKE_CXX_COMPILER=g++:显式指定 C++ 编译器为 g++;-DCMAKE_SKIP_RPATH=TRUE:跳过 RPATH 设置,避免在可执行文件中写入绝对路径,确保运行时依赖按系统动态库搜索路径(LD_LIBRARY_PATH)解析。
编译成功后,会在 build 目录的 bin 文件夹下生成opapi_test可执行文件。
3.4 步骤四:运行并查看结果
进入 bin 目录,运行可执行文件 opapi_test:
cd bin ./opapi_test以 GridSample 算子的运行结果为例,运行后的结果示例如下:
resultData[0] is: 0.250000 resultData[1] is: 2.250000 resultData[2] is: 2.000000 resultData[3] is: 8.500000 resultData[4] is: 20.500000 resultData[5] is: 12.000000 resultData[6] is: 8.250000 resultData[7] is: 18.250000 resultData[8] is: 10.000000说明:样例输入
inputShape={1,1,5,8}(共 40 个元素)、gridShape={1,3,3,2}、outShape={1,1,3,3},输出共 9 个元素,与上述打印一一对应。该组输入/输出数据同样出现在仓库单元测试 image/grid_sample/tests/ut/op_host/op_api/test_aclnn_grid_sampler2d.cpp 的case_1中(输入值 0~39、grid 为 3×3 的 9 个采样点、alignCorners=false、bilinear + zeros),可用于交叉验证算子计算结果的正确性。
4. 运行报错排查:使用 aclGetRecentErrMsg 获取错误信息
若执行结果报错,未出现预期结果,可以使用aclGetRecentErrMsg接口获取报错具体信息。以调用aclnnGridSampler2DGetWorkspaceSize报错(input 为空指针)为例,示例代码如下:
// input is nullptr ret = aclnnGridSampler2DGetWorkspaceSize( input, grid, interpolationMode, paddingMode, alignCorners, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnGridSampler2DGetWorkspaceSize failed. ERROR: %d.\n[ERROR msg]%s", ret, aclGetRecentErrMsg()); return ret);上述构造空指针问题获取到的报错信息示例如下:
aclnnGridSampler2DGetWorkspaceSize failed. ERROR: 161001 [ERROR msg][PID:xxxx] xxx(timestamp) AclNN_Parameter_Error(EZ1001): Expected a proper Tensor but got null for argument input.对错误码的解读:
- 错误码
161001对应返回码ACLNN_ERR_PARAM_NULLPTR,含义为“参数校验错误,参数中存在非法的 nullptr”; - 错误信息中的
AclNN_Parameter_Error(EZ1001)与Expected a proper Tensor but got null for argument input明确指出了出问题的参数是input。
这一行为与第一段接口源码中的参数校验顺序完全对应:在 op_api/aclnn_grid_sampler2d.cpp 中,CheckParams首先执行CheckNotNull(input, grid, out),任一参数为空即返回ACLNN_ERR_PARAM_NULLPTR(错误码 161001)。
常见返回码速查(详见 docs/zh/context/aclnn_return_code.md):
| 状态码名称 | 状态码值 | 状态码说明 |
|---|---|---|
| ACLNN_SUCCESS | 0 | 成功 |
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 参数校验错误,参数中存在非法的 nullptr |
| ACLNN_ERR_PARAM_INVALID | 161002 | 参数校验错误,如输入的两个数据类型不满足输入类型推导关系 |
| ACLNN_ERR_RUNTIME_ERROR | 361001 | API 内部调用 npu runtime 的接口异常 |
| ACLNN_ERR_INNER_XXX | 561xxx | API 内部发生异常,如 561003 表示未找到 kernel(可能因算子二进制包未安装) |
针对 aclnnGridSampler2D,第一段接口入参校验阶段常见的错误码还包括(同样定义在 image/grid_sample/docs/aclnnGridSampler2D.md 的返回值一节):
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 input、grid 或 out 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | input、grid、out 的数据类型不在支持范围之内或数据类型不一致;interpolationMode 或 paddingMode 的值不在支持范围内;interpolationMode 为 bicubic 时数据类型不是 FLOAT32 或 FLOAT16;input、grid、out 的维度关系不匹配;input 最后两维为空 |
5. 算子 API 背后:从 aclnn 接口到 NPU 计算
为便于理解样例代码的行为,这里结合仓库源码简要梳理 aclnnGridSampler2D 的调用链路:
- 第一段接口
aclnnGridSampler2DGetWorkspaceSize(op_api/aclnn_grid_sampler2d.cpp):- 创建
OpExecutor(CREATE_EXECUTOR()); - 依次执行空指针、数据类型、属性取值、shape 关系校验(见上文);
- 空 tensor 场景直接返回
workspaceSize = 0; - 通过
l0op::Contiguous将 input、grid 转为连续 tensor; - 根据硬件平台选择执行路径:AI Core 上优先使用
l0op::GridSample(必要时做 NCHW→NHWC 转置与 FP16/FP32 的 Cast),AI CPU 路径使用l0op::GridSampler2D,均不满足时报ACLNN_ERR_PARAM_INVALID; - 通过
l0op::ViewCopy将计算结果拷贝到输出 out,最后调用uniqueExecutor->GetWorkspaceSize()取得 workspace 大小并返回。
- 创建
- 第二段接口
aclnnGridSampler2D(op_api/aclnn_grid_sampler2d.cpp):接收 workspace、executor 与 stream,直接调用CommonOpExecutorRun完成计算下发与执行。 - 算子定义层:op_host/grid_sample_def.cpp 中通过
OP_ADD(GridSample)注册算子,定义了 x、grid 输入与 y 输出(FLOAT16/FLOAT32/BFLOAT16,ND 格式),以及interpolation_mode(默认 "bilinear")、padding_mode(默认 "zeros")、align_corners(默认 false)、channel_last(默认 false)、scheduler_mode(默认 1)等属性,并为不同芯片(ascend910b、ascend910_93、ascend950、ascend310p、ascend310b、kirinx90、kirin9030)配置了不同的计算核心配置。
通过aclnnGridSampler2DGetWorkspaceSize返回的executor会携带完整的算子计算流程,因此样例代码中无需自行组织 kernel 启动细节,只需按两段式模式申请 workspace 后调用第二段接口即可完成 NPU 上的计算,这也是单算子 API 调用方式相比图模式(通过算子 IR 构图,见 image/grid_sample/op_graph/grid_sample_proto.h)更轻量、更直接的体现。
6. 常见问题与注意事项
- 未配置环境变量导致编译失败:执行
source ${INSTALL_DIR}/set_env.sh后,CMake 才能通过ASCEND_CUSTOM_PATH找到 CANN 软件包路径;否则会回退到默认路径/usr/local/Ascend/cann,请确保该路径真实存在。 - 链接库缺失:示例代码用到的
aclnnGridSampler2D在libopapi_cv.so中,aclrtMalloc、aclrtMemcpy等在libacl_rt.so中,缺少任一库都会导致链接失败;MC2 算子还需额外链接libhccl.so与线程库。 - 运行时报 kernel 未找到:错误码
561003(ACLNN_ERR_INNER_FIND_KERNEL_ERROR)通常表示算子二进制包(ops 包)未安装,请检查基础环境中的 ops 包是否就绪。 - 两段式接口使用约束:第二段接口
aclnnGridSampler2D(...)不能重复调用,每次执行需重新走一遍两段式流程(参见 docs/zh/context/two_phase_api.md)。 - 换用其他算子时的改动点:替换
test_grid_sampler2_d.cpp中的 API 名称、输入/输出 shape 与 host 数据,同时把 CMakeLists 中add_executable的源文件换成对应的 *.cpp;若新算子的 API 位于其他库(如libopapi_math.so),保持现有链接项即可覆盖绝大多数图像与数学算子。
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考