CANN opbase 算子执行器复用机制解析:aclSetAclOpExecutorRepeatable 使用指南
2026/9/19 9:39:47 网站建设 项目流程

CANN opbase 算子执行器复用机制解析:aclSetAclOpExecutorRepeatable 使用指南

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

本篇文章聚焦 CANN opbase 基础框架库(README)中面向单算子 API 的执行器复用能力,围绕aclSetAclOpExecutorRepeatable展开:它解决了"同一份 aclOpExecutor 反复执行算子、避免重复构建执行上下文"的性能诉求。读完本文,你将掌握该 API 的调用时机、底层实现原理、适用与禁用场景,以及如何与aclSetDynamicInputTensorAddraclDestroyAclOpExecutor等配套 API 组合出可复用的算子执行流程。

一、背景:两阶段单算子 API 与 aclOpExecutor

在 CANN 的 AclNN 单算子调用模型(single-operator API)中,算子执行被拆分为两阶段

  1. 第一阶段(GetWorkspaceSize 阶段):调用形如aclnnXxxGetWorkspaceSize的 API,传入输入/输出张量与属性,框架完成算子解析、图构建、内核(Kernel)选择与上下文组装,输出workspaceSizeaclOpExecutor*执行器指针;
  2. 第二阶段(执行阶段):调用aclnnXxx,传入 workspace 内存地址与第一阶段得到的executor,在指定 stream 上真正下发算子计算任务。

其中aclOpExecutor是框架定义的算子执行器,本质是算子计算执行的容器,用户无需了解其内部实现即可直接使用。相关接口声明位于 include/nnopbase/aclnn/acl_meta.h。

常规流程下,每次执行算子都要重新走一遍两阶段调用,执行器的构建开销会随调用次数累积。aclSetAclOpExecutorRepeatable正是为打破这一模式而设计:把一个已经构建好的 executor 标记为可复用,此后仅需更新张量设备地址即可反复执行同一算子,省去重复构建。

二、函数原型与参数说明

aclnnStatus aclSetAclOpExecutorRepeatable(aclOpExecutor *executor)
参数输入/输出说明
executor输入待设置为可复用的 aclOpExecutor,由第一阶段 APIaclnnXxxGetWorkspaceSize创建

返回值:成功返回 0(ACL_SUCCESS);失败返回错误码,详见 Common API Return Codes(中文对照见 常用接口返回值说明)。

错误码 561103 的含义

文档明确指出一种典型失败场景:

  • 若返回错误码561103,说明executor空指针

从源码可印证该错误码的来源。src/nnopbase/common/api/acl_op_api.cpp 中,函数入口首先执行:

NNOPBASE_ASSERT_NULLPTR_WITH_RETURN(executor, ACLNN_ERR_INNER_NULLPTR);

即对空指针入参直接返回ACLNN_ERR_INNER_NULLPTR(即 561103),随后才进入基于"魔数"(magic number)的分发逻辑。

三、调用时机:必须在第一阶段之后立即调用

文档对调用时机有严格要求:

如果想复用已有的 aclOpExecutor,必须在第一阶段 APIaclnnXxxGetWorkspaceSize执行之后立即调用本 API 开启复用,之后才能多次调用第二阶段 APIaclnnXxx执行算子。

原因在于:executor 在构建时内部已包含算子图、内核句柄、workspace 规划等资源;一旦在第二阶段执行后,部分缓存与资源状态会发生变化,此时再开启复用将无法保证后续执行的正确性。因此开启复用的"窗口"在第一阶段结束之后、第二阶段首次执行之前

四、底层实现原理:魔数分发与两类执行器

从源码实现看,aclSetAclOpExecutorRepeatable通过检查 executor 内存首部的魔数来区分执行器类型,进而走不同的设置路径:

if (*magicNum == NNOPBASE_EXECUTOR_MAGIC_NUMBER) { return NnopbaseSetRepeatable(executor); } else if (executor->GetMagicNumber() == K_EXECUTOR_MAGIC_NUMBER) { return executor->SetRepeatable(); } return ACLNN_ERR_INNER;

对应两条主要实现路径:

1. Nnopbase 执行器路径(AI CPU / AI Core 单算子)

当 executor 属于 NNOPBASE 体系时,调用 src/nnopbase/individual_op/executor/indv_executor.cpp 中的NnopbaseSetRepeatable,其核心动作是:

  1. 将执行器的repeatFlag置为true,标记进入复用模式;
  2. 调用NnopbaseExecutorFixCache固定(fix)执行器相关缓存,确保复用期间缓存不被重建或失效;
  3. 若该算子内部挂载了非连续(non-contiguous)辅助执行器(inUnContExe,例如自动插入的 Contiguous 算子),会递归调用NnopbaseSetUnContiguousExecutorRepeatable,将 Contiguous 算子及其内部的 ViewCopy 执行器一并设置为可复用,保证辅助算子与主算子行为一致。

这解释了文档中"AI CPU 和 AI Core 计算单元支持 aclOpExecutor 复用"的限制——只有这两类计算单元的执行器体系支持上述 fix 与复用链路。

2. 通用算子执行器路径(OpExecutor)

当 executor 属于通用执行器体系时,调用 src/nnopbase/composite_op/aclnn_engine/op_executor.cpp 中的OpExecutorImpl::SetRepeatable,它在置位repeatMode_ = RepeatMode::Repeat之前会做一系列可复用性前置校验,任何一项不满足都会返回失败:

  • 执行模式校验:若repeatMode_已是Unrepeatable,拒绝开启(日志中可搜索关键字MarkOpCacheInvalid定位原因);
  • 大页内存校验:若执行器已使用大页(huge page)内存(hugeMemPoolIndex_有效),不能设置为可复用;
  • 张量关系校验tensorRelation_中记录的张量关系必须成对(中间张量 -> 输出张量的存储关系),否则拒绝;
  • 内核级校验:遍历所有KernelLauncher,逐个调用CheckRepeatable检查内核是否支持复用,任一内核不支持即返回ACLNN_ERR_INNER

校验通过后,repeatMode_置为Repeat,并释放执行器缓存管理器与已建缓存(ReleaseOpExecCacheManager/RemoveExecCache),避免复用期间命中失效缓存。执行阶段则会通过UpdateStorageAddr依据张量关系自动把输入地址的更新传播到输出存储(见 op_executor.cpp)。

五、限制与禁用场景(务必逐条核对)

开启复用前,请确认算子与场景满足以下全部条件:

支持范围

  • AI CPU 与 AI Core计算单元的算子支持 aclOpExecutor 复用。

单算子 API 调用下的禁用场景

  1. 使用了 Host 到 Device、Device 到 Device 拷贝类 L0 API(如CopyToNpuCopyNpuToNpuCopyToNpuSync)时,不能复用。这类拷贝场景与执行器内部的存储/缓存 fix 机制冲突;
  2. 使用 L0 ViewCopy API 且源地址与目的地址相同时,不能复用;
  3. 不能在算子 API 内部创建 Device Tensor——复用场景下只允许使用外部(Host 侧创建)的张量,因为复用的前提是张量存储地址由外部通过地址设置接口统一更新。

资源释放要求

  • 被设置为可复用的 executor,在执行完第二阶段 API 后不会自动清理执行器资源,必须配合 aclDestroyAclOpExecutor(中文版:aclDestroyAclOpExecutor)显式销毁,否则会产生资源泄漏。

aclDestroyAclOpExecutor的销毁逻辑同样基于魔数分发(acl_op_api.cpp):NNOPBASE 执行器走NnopbaseResetExecutor(清理辅助执行器并清空缓存,见 indv_executor.cpp),通用执行器直接delete,缓存包装对象则删除OpExecCacheWrap。因此复用流程的结束必须由销毁 API 兜底。

六、完整使用示例(复用以 AddCustom 算子为例)

以下代码改编自官方文档示例(原文见 aclSetAclOpExecutorRepeatable.md),仅供理解流程,实际运行需按真实环境补齐设备初始化、数据搬运与错误处理:

// 1. 创建输入输出张量(aclTensor 与 aclTensorList) std::vector<int64_t> shape = {1, 2, 3}; aclTensor tensor1 = aclCreateTensor(shape.data(), shape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), nullptr); aclTensor tensor2 = aclCreateTensor(shape.data(), shape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), nullptr); aclTensor tensor3 = aclCreateTensor(shape.data(), shape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), nullptr); aclTensor output = aclCreateTensor(shape.data(), shape.size(), aclDataType::ACL_FLOAT, nullptr, 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), nullptr); aclTensor *list[] = {tensor1, tensor2}; auto tensorList = aclCreateTensorList(list, 2); uint64_t workspaceSize = 0; aclOpExecutor *executor; // 2. 第一阶段:AddCustom 算子有两个输入(aclTensorList 与 aclTensor)、一个输出(aclTensor) aclnnAddCustomGetWorkspaceSize(tensorList, tensor3, output, &workspaceSize, &executor); // 3. 紧接第一阶段之后,将执行器设置为可复用 aclSetAclOpExecutorRepeatable(executor); // 4. 复用期间:每次执行前更新张量的设备地址 // 例如更新输入 tensor list 中第 0、1 个 tensor 的设备地址 void *addr; aclSetDynamicInputTensorAddr(executor, 0, 0, tensorList, addr); // 更新第 1 个输入地址 aclSetDynamicInputTensorAddr(executor, 0, 1, tensorList, addr); // 更新第 2 个输入地址 // ... 可重复多次更新地址并多次执行 // 5. 第二阶段:多次调用算子执行(需配套申请 workspace 内存) aclnnAddCustom(workspace, workspaceSize, executor, stream); // 6. 复用结束,显式销毁执行器,释放资源 aclDestroyAclOpExecutor(executor);

流程要点:

  • 第 3 步是关键aclSetAclOpExecutorRepeatable必须在aclnnAddCustomGetWorkspaceSize之后、首次aclnnAddCustom之前调用;
  • 第 4 步是复用的价值所在:借助 aclSetDynamicInputTensorAddr、aclSetInputTensorAddr 等地址设置接口(声明见 acl_meta.h),仅更新张量存储地址即可让同一 executor 作用于新的数据,从而规避重复构建执行上下文的开销;
  • 第 6 步是资源安全的保证:可复用 executor 不会自动回收,必须显式调用aclDestroyAclOpExecutor

七、源码与测试佐证

  • 接口声明:include/nnopbase/aclnn/acl_meta.h 中aclSetAclOpExecutorRepeatableaclDestroyAclOpExecutor相邻声明,二者作为执行器生命周期的一对接口配套使用。
  • 实现入口:src/nnopbase/common/api/acl_op_api.cpp 完成空指针校验与魔数分发;NNOPBASE 路径实现在 indv_executor.cpp,通用执行器路径实现在 op_executor.cpp。
  • 单测覆盖:测试用例TEST_F(AclOpApiTest, aclSetAclOpExecutorRepeatable)(tests/nnopbase/ut/composite_op/test_acl_op_api.cpp)验证了正常置位、地址随张量关系联动更新、以及非法执行器(OpExecCacheWrap伪装对象)返回ACLNN_ERR_INNER等行为;tests/nnopbase/ut/individual_op/api_ext_utest.cpp 等用例则验证了 NNOPBASE 执行器在复用模式下重复执行算子的正确性。

八、实践建议小结

场景建议
循环中反复调用同一算子、仅数据地址变化优先启用 executor 复用,配合aclSetDynamicInputTensorAddr更新地址
算子涉及 CopyToNpu / CopyNpuToNpu / ViewCopy(同址)关闭复用,走常规两阶段流程
算子内部需要创建 Device Tensor不可复用,改为外部创建张量
复用结束后必须调用aclDestroyAclOpExecutor释放资源,防止泄漏
设置失败检查返回值 561103(空指针),并在日志中检索MarkOpCacheInvalid、huge page 等关键字定位原因

本文所描述的行为均以当前仓库源码与文档为准,各 API 的具体错误码定义与算子支持情况,请结合 Common API Return Codes 与对应算子的 AclNN 接口文档 进一步核对。

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询