
CANN ATVC ReduceSum 自定义算子单算子 API 调用AclNNInvocationNaive 工程实战解析【免费下载链接】atvcATVCAscend C Templates for Vector Compute是为基于Ascend C开发的典型Vector算子封装的一系列模板头文件的集合可帮助用户快速开发典型Vector算子。项目地址: https://gitcode.com/cann/atvc本文以 CANN atvc 仓库中 examples/ops_aclnn/reduce_sum/AclNNInvocationNaive 工程样例为主体系统讲解基于 ATVCAscend C Templates for Vector Compute开发的 ReduceSum 自定义算子如何通过aclnn 单算子 API两段式接口在应用程序中完成端到端调用。读完本文你将掌握单算子 API 的调用原理、main.cpp的完整代码流程、编译脚本与运行脚本的配置要点以及从算子编译部署到结果验证的完整实操路径。一、工程概述什么是 AclNNInvocationNaiveAclNNInvocationNaive 是 ATVC 仓库中用于aclnn 单算子调用验证的最小化样例工程。相比于标准版 AclNNInvocation 工程它简化了工程配置——去掉了冗余的构建选项与目录层级仅保留三个文件让开发者把注意力集中在单算子 API 的调用逻辑本身examples/ops_aclnn/reduce_sum/AclNNInvocationNaive ├── CMakeLists.txt // 编译规则文件 ├── main.cpp // 单算子调用应用的入口 └── run.sh // 编译运行算子的脚本该样例依赖同目录父工程 examples/ops_aclnn/reduce_sum 中基于 ATVC 开发的 ReduceSumCustom 自定义算子。ReduceSum 是对输入 tensor 的指定轴进行规约累加并输出结果的 Reduce 类算子本样例的规格为输入x形状8 * 2048float、ND 格式输出y形状1 * 2048float、ND 格式即对第 0 维dim0做归约求和核函数名为reduce_sum_custom。前置说明运行本样例前需先完成 ReduceSumCustom 算子的编译与部署具体流程参见 examples/ops_aclnn/reduce_sum/README.md本文第四节会同步给出关键步骤。二、单算子 API 调用原理两段式接口完成自定义算子的开发部署后可以通过单算子调用方式来验证单算子功能。单算子 API 执行是基于 C 语言的 API 执行算子无需提供单算子描述文件进行离线模型的转换直接调用单算子 API 接口即可。自定义算子编译部署后系统会自动生成单算子 API即aclnn_xxx接口可以在应用程序中直接调用。算子 API 的形式一般定义为两段式接口本样例对应ReduceSumCustom算子生成的两段接口为// 第一段获取算子执行所需的 workspace 空间大小 aclnnStatus aclnnReduceSumCustomGetWorkspaceSize(const aclTensor *x, const aclIntArrat *dim, const aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); // 第二段执行算子 aclnnStatus aclnnReduceSumCustom(void *workspace, int64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);调用流程要点aclnnReduceSumCustomGetWorkspaceSize为第一段接口主要功能是计算本次 API 调用计算过程中需要多少workspace内存同时返回一个aclOpExecutor执行器对象获取到本次 API 计算需要的workspaceSize之后开发者需要按该大小通过aclrtMalloc申请Device 侧内存然后调用第二段接口aclnnReduceSumCustom执行计算计算完成后还需显式释放 workspace 与执行器相关资源。注意原型描述中的dim参数对应算子 JSON 原型ReduceSumCustom.json中声明的attr——一个list_int类型的归约轴列表x、y则对应其中的input_desc/output_desc支持float32与int32两种数据类型。这就是 aclnn 接口自动生成机制中原型驱动接口的直接体现。三、main.cpp 代码实现深度解析main.cpp 是单算子 API 执行的完整示例。其执行流程可分为六个阶段下面结合源码逐一展开。3.1 宏与工具函数文件开头定义了两个调试宏和两个辅助函数#define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0)CHECK_RET对每个 API 的返回值做检查失败时执行给定的返回表达式通常是打印错误日志并销毁资源保证了整个调用链的健壮性GetShapeSize根据 shape 向量累乘计算元素总数用于推导内存字节数VerifyResults将算子输出与期望结果逐元素比较示例中使用std::equal并打印前 10 个元素用于人工核对最后输出test pass或test failed。3.2 阶段一初始化 Device 与 Stream固定代码int Init(int32_t deviceId, aclrtStream *stream) { auto ret aclInit(nullptr); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclInit failed. ERROR: %d\n, ret); return 1); ret aclrtSetDevice(deviceId); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSetDevice failed. ERROR: %d\n, ret); return 1); ret aclrtCreateStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtCreateStream failed. ERROR: %d\n, ret); return 1); return 0; }Init完成 acl 运行时的三步初始化aclInit初始化 ACL、aclrtSetDevice绑定计算设备示例默认deviceId 0实际使用时需改为自己的设备号、aclrtCreateStream创建执行流。这段代码对所有单算子调用样例是通用的。3.3 阶段二构造输入输出 aclTensorstd::vectorint64_t inputXShape {8, 2048}; std::vectorint64_t outputYShape {1, 2048}; ... InitializeData(inputXHostData, outputYHostData, goldenData, inputXShape, outputYShape);InitializeData将输入x全部初始化为1.0由于是对 8 个元素求和期望输出golden为8.0输出y初始化为0.0。随后通过模板函数CreateAclTensor完成 Host 数据到 Device 张量的转换template typename T int CreateAclTensor(const std::vectorT hostData, const std::vectorint64_t shape, void **deviceAddr, aclDataType dataType, aclTensor **tensor) { auto size GetShapeSize(shape) * sizeof(T); auto ret aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); // 1. 申请Device内存 ret aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); // 2. Host数据拷贝到Device *tensor aclCreateTensor(shape.data(), shape.size(), dataType, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); // 3. 创建aclTensor return 0; }其内部依次执行aclrtMalloc申请 Device 侧内存 →aclrtMemcpy完成 Host 到 Device 的拷贝 →aclCreateTensor创建aclTensor对象格式为ACL_FORMAT_ND与算子原型声明的 ND 格式一致。样例中分别创建了inputXACL_FLOAT与outputYACL_FLOAT。此外样例还通过aclCreateIntArray构造了归约轴数组std::vectorint64_t dim{0}; aclIntArray* dimOut aclCreateIntArray(dim.data(), dim.size());这里dim {0}表示对第 0 维归约与8×2048 → 1×2048的形状变化严格对应。3.4 阶段三两段式调用自定义算子库 API核心uint64_t workspaceSize 0; aclOpExecutor *executor; // 第一段计算workspace大小 ret aclnnReduceSumCustomGetWorkspaceSize(inputX, dimOut, outputY, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnReduceSumCustomGetWorkspaceSize failed. ERROR: %d\n, ret); ...); void *workspaceAddr nullptr; if (workspaceSize 0U) { ret aclrtMalloc(workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); // 按workspaceSize申请Device内存 CHECK_RET(ret ACL_SUCCESS, ...); } // 第二段执行算子 ret aclnnReduceSumCustom(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnReduceSumCustom failed. ERROR: %d\n, ret); ...);这里完整复现了前面介绍的两段式接口调用规范先取 workspace 大小再按需aclrtMalloc注意当workspaceSize 0时无需申请最后传入 workspace、executor 与 stream 执行算子。这段代码即为单算子 API 调用的标准模板开发者只需按自己算子的接口签名调整张量参数即可复用。3.5 阶段四、五、六同步、取回结果与资源释放// 4. 同步等待任务完成 ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, ...); // 5. 结果从Device内存拷回Host auto size GetShapeSize(outputYShape); std::vectorfloat resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outputYDeviceAddr, size * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret ACL_SUCCESS, ...); // 6. 释放资源 DestroyResources(tensors, deviceAddrs, stream, deviceId, workspaceAddr);aclrtSynchronizeStream阻塞等待 stream 上的算子任务执行完成是异步计算模型下获取结果的必要同步点随后用ACL_MEMCPY_DEVICE_TO_HOST方向把输出拷回 Host 内存DestroyResources统一释放aclTensoraclDestroyTensor、Device 内存aclrtFree含 workspace、销毁 stream、重置设备并aclFinalize避免资源泄漏。最后调用VerifyResults(goldenData, resultData)校验结果全部元素为8.0时输出test pass并返回 0否则输出test failed并返回 -1。四、编译与运行4.1 前置准备编译部署 ReduceSumCustom 算子运行本样例前需要先完成算子的编译与部署完整步骤参见 examples/ops_aclnn/reduce_sum/README.md核心过程为导入 ATVC 环境变量不导入则默认使用./atvc/include路径export ATVC_PATH${atvc}/include调用 install.sh 生成并编译算子工程SOC_VERSION支持Ascend910B1/Ascend910B2/Ascend910B3/Ascend910B4可通过在装有昇腾 AI 处理器的服务器上执行npu-smi info查询在 Name 前增加Ascend前缀得到cd ./examples/ops_aclnn/reduce_sum bash install.sh -v [SOC_VERSION]脚本内部会调用msopgen gen -i ReduceSumCustom.json -c ai_core-${SOC_VERSION} -lan cpp -out CustomOp生成算子工程框架再拷贝 op_host/reduce_sum_custom.cpp 与 op_kernel/reduce_sum_custom.cpp 到工程对应目录同时删除 msopgen 自动生成的 tiling 头文件改用 ATVC 自身的 tiling 定义最后调用CustomOp/build.sh编译。成功后在CustomOp/build_out下生成安装包custom_opp_target os_target architecture.run如custom_opp_ubuntu_x86_64.run。部署自定义算子包先确认ASCEND_OPP_PATH环境变量存在若无则source [ASCEND_INSTALL_PATH]/bin/setenv.bash然后执行cd CustomOp/build_out ./custom_opp_target os_target architecture.run算子包将部署到ASCEND_OPP_PATH指向的vendors/customize目录中部署完成后系统即自动生成单算子 APIaclnnReduceSumCustom*及其头文件aclnn_reduce_sum_custom.h。提示安装脚本会每次删除并重新生成CustomOp目录切勿在生成目录内直接修改算子代码避免丢失。4.2 修改编译文件路径进入样例目录并将 CMakeLists.txt 内的/usr/local/Ascend/ascend-toolkit/latest替换为 CANN 软件包安装后的实际路径cd atvc/examples/ops_aclnn/reduce_sum/AclNNInvocationNaive例如改为eg:/home/HwHiAiUser/Ascend/ascend-toolkit/latest。该路径在 CMake 中作为默认INC_PATH头文件搜索路径并进一步推导出自定义算子 API 头文件与库的搜索位置set(INC_PATH $ENV{DDK_PATH}) if (NOT DEFINED ENV{DDK_PATH}) set(INC_PATH /usr/local/Ascend/ascend-toolkit/latest) ... endif() set(CUST_PKG_PATH ${INC_PATH}/opp/vendors/customize/op_api) # 单算子API头文件与lib所在目录 ... include_directories(${INC_PATH}/include ${CUST_PKG_PATH}/include) link_directories(${LIB_PATH} ${CUST_PKG_PATH}/lib)其中LIB_PATH默认指向ascend-toolkit/latest/${arch}-${os}/devlibstub 动态库仅用于编译期链接也可通过环境变量NPU_HOST_LIB覆盖。最终生成可执行文件execute_reduce_sum_op并链接以下库库名作用ascendclACL 运行时基础库aclInit/aclrtMalloc/aclCreateTensor等接口cust_opapi自定义算子单算子 API 库包含aclnnReduceSumCustom*接口实现acl_op_compiler算子编译与执行支撑库nnopbase神经网络算子基础库stdcC 标准库从链接库列表可以看出单算子 API 调用需要同时依赖 ACL 基础库与自定义算子 API 库cust_opapi这也解释了为何必须先部署自定义算子包、再编译调用程序。4.3 一键编译运行参考 run.sh 脚本执行编译与运行bash run.shrun.sh内部做了四件事确定 CANN 安装路径按ASCEND_INSTALL_PATH→ASCEND_HOME_PATH→$HOME/Ascend/ascend-toolkit/latest→/usr/local/Ascend/ascend-toolkit/latest的优先级自动探测导入环境source $_ASCEND_INSTALL_PATH/bin/setenv.bash并显式导出DDK_PATH供 CMake 使用与NPU_HOST_LIB指向${arch}-${os}/lib64运行库目录编译cmake -B build -DCMAKE_SKIP_RPATHTRUE后cmake --build build -j产物输出到工程根目录运行在build目录下设置LD_LIBRARY_PATH为$_ASCEND_INSTALL_PATH/opp/vendors/customize/op_api/lib确保能找到libcust_opapi.so随后执行./execute_reduce_sum_op。由于run.sh已自动完成环境变量探测与导出大多数场景下直接执行bash run.sh即可仅当 CMake 默认路径与实际安装路径不一致时才需要手工修改 CMakeLists.txt。4.4 运行结果验证程序运行后将打印输出前 10 个元素均为8.0并输出判定结果result is: 8.0 8.0 8.0 8.0 8.0 8.0 8.0 8.0 8.0 8.0 test pass输出test pass即代表ReduceSumCustom算子在8×2048 → 1×2048、dim0 的规格下正确完成了归约求和验证了 ATVC 框架开发的算子通过单算子 API 可被正常调用。五、ATVC 在算子实现侧的配合AclNNInvocationNaive 之所以能如此简洁地完成调用离不开 ATVC 对算子 host/kernel 侧的模板化封装。在本样例依赖的 op_host/reduce_sum_custom.cpp 中通过ATVC::OpTraitsATVC::OpInputsfloat, ATVC::OpOutputsfloat声明编译态算子描述并在TilingFunc中调用ATVC::Host::CalcReduceTilingReduceOpTraitsFloat(shapeIn, dim, policy, tiling)自动完成 tiling 计算随后context-SetBlockDim(tiling-tilingData.coreNum)设置核数归约策略由ATVC::ReducePolicy自动推导tiling-policyId policy.getID()写入运行态参数供 kernel 侧分支。在 op_kernel/reduce_sum_custom.cpp 中核函数依据param.policyId选择不同的ATVC::Kernel::ReduceOpTemplateATVC::ReduceSumComputeReduceOpTraits, ATVC::REDUCE_POLICYx模板实例执行。这里没有使用TILING_KEY_IS做分支判断而是用policyId运行时判断是因为 Reduce 的 policy 分支较多使用tilingKey判断存在爆栈风险。由此可见ATVC 把 tiling 计算、数据搬运、核间调度等固定逻辑封装在模板内部开发者只需聚焦归约计算本身ReduceSumCompute即可快速产出可被 aclnn 单算子 API 调用的高质量自定义算子。更多 ATVC 的模板能力Elementwise/Reduce/Broadcast/Pool 及CalcReduceTiling、ReduceOpTemplate等接口可参考 docs/02_developer_guide.md 与 docs/01_quick_start.md。六、小结AclNNInvocationNaive 样例以最小化工程呈现了 ATVC 自定义算子的单算子 API 调用全流程先通过两段式接口GetWorkspaceSize Execute在宿主程序中驱动算子在 Device 上执行再以同步、拷回、校验三个收尾步骤完成闭环验证。其工程结构清晰、代码可直接套用是开发者快速验证基于 ATVC 开发的 Vector 算子是否可被上层应用正确调用的首选模板——只需替换输入输出张量、attr 参数与接口名称即可推广到其他 ATVC 算子如 Add、Broadcast 等参见 examples/ops_aclnn/README.md。【免费下载链接】atvcATVCAscend C Templates for Vector Compute是为基于Ascend C开发的典型Vector算子封装的一系列模板头文件的集合可帮助用户快速开发典型Vector算子。项目地址: https://gitcode.com/cann/atvc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考