CANN opbase 算子执行器复用机制解析:aclSetAclOpExecutorRepeatable 使用指南
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
本篇文章聚焦 CANN opbase 基础框架库(README)中面向单算子 API 的执行器复用能力,围绕aclSetAclOpExecutorRepeatable展开:它解决了"同一份 aclOpExecutor 反复执行算子、避免重复构建执行上下文"的性能诉求。读完本文,你将掌握该 API 的调用时机、底层实现原理、适用与禁用场景,以及如何与aclSetDynamicInputTensorAddr、aclDestroyAclOpExecutor等配套 API 组合出可复用的算子执行流程。
一、背景:两阶段单算子 API 与 aclOpExecutor
在 CANN 的 AclNN 单算子调用模型(single-operator API)中,算子执行被拆分为两阶段:
- 第一阶段(GetWorkspaceSize 阶段):调用形如
aclnnXxxGetWorkspaceSize的 API,传入输入/输出张量与属性,框架完成算子解析、图构建、内核(Kernel)选择与上下文组装,输出workspaceSize和aclOpExecutor*执行器指针; - 第二阶段(执行阶段):调用
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,必须在第一阶段 API
aclnnXxxGetWorkspaceSize执行之后立即调用本 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,其核心动作是:
- 将执行器的
repeatFlag置为true,标记进入复用模式; - 调用
NnopbaseExecutorFixCache固定(fix)执行器相关缓存,确保复用期间缓存不被重建或失效; - 若该算子内部挂载了非连续(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 调用下的禁用场景
- 使用了 Host 到 Device、Device 到 Device 拷贝类 L0 API(如
CopyToNpu、CopyNpuToNpu、CopyToNpuSync)时,不能复用。这类拷贝场景与执行器内部的存储/缓存 fix 机制冲突; - 使用 L0 ViewCopy API 且源地址与目的地址相同时,不能复用;
- 不能在算子 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 中
aclSetAclOpExecutorRepeatable与aclDestroyAclOpExecutor相邻声明,二者作为执行器生命周期的一对接口配套使用。 - 实现入口: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),仅供参考