CANN Runtime 生命周期回调与引用计数去初始化实战:从插件回调注册到引用归零

发布时间:2026/9/20 11:03:57
CANN Runtime 生命周期回调与引用计数去初始化实战:从插件回调注册到引用归零 CANN Runtime 生命周期回调与引用计数去初始化实战从插件回调注册到引用归零【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime导读本文围绕 CANN runtime 开源仓库中的5_runtime_lifecycle_callbacks样例讲解应用插件如何参与 ACL 初始化aclInit与去初始化aclFinalize流程。你将掌握aclInitCallbackRegister/aclFinalizeCallbackRegister等回调注册、注销接口的正确用法理解aclFinalizeReference引用计数去初始化的底层语义并学会用一个单 Device 业务场景验证有效回调各执行一次、已注销回调均不执行、引用计数最终归零这一完整生命周期。文中所有接口说明均以仓库头文件与实现源码为准可直接对照 样例目录 复现实验。样例背景插件如何感知 ACL 生命周期CANN 的 ACLAscendCL运行时在进程内只允许初始化一次、去初始化一次。对于模型、算子、TDT 通道等上层插件模块它们往往需要在 ACL 初始化时完成自身的初始化动作、在去初始化时完成资源回收。为此ACL 提供了一套回调注册机制插件在调用aclInit之前先注册回调ACL 在初始化/去初始化内部流程的特定阶段统一触发这些回调。5_runtime_lifecycle_callbacks主题下的 0_reference_counted_plugin_lifecycle 样例正是为这类插件开发者设计先注册有效与待取消的初始化回调注销后者再执行aclInit与单 Device 业务再注册有效与待取消的去初始化回调注销后者并通过aclFinalizeReference引用计数方式完成去初始化样例自动校验有效回调各执行一次、已注销回调均不执行、Device 0 设置生效、最终引用计数为 0。产品支持情况根据样例说明本样例涉及的全部接口在以下产品上静态支持产品是否支持Atlas A2 训练系列产品/Atlas A2 推理系列产品√Atlas A3 训练系列产品/Atlas A3 推理系列产品√Ascend 950PR/Ascend 950DT√编译与运行1. 进入样例目录将仓库样例代码下载到已安装 CANN 的环境中切换到样例目录cd ${git_clone_path}/example/0_quickstart/5_runtime_lifecycle_callbacks/0_reference_counted_plugin_lifecycle其中${git_clone_path}为仓库克隆路径。2. 设置环境变量# ${install_root} 替换为 CANN 安装根目录 source ${install_root}/set_env.sh source ${git_clone_path}/example/set_sample_env.sh3. 编译并运行bash run.sh从 run.sh 可以看到脚本的内部逻辑若环境变量ASCEND_INSTALL_PATH/ASCEND_HOME_PATH未设置自动加载 set_sample_env.sh强制要求ASCEND_HOME_PATH、SOC_VERSION、ASCENDC_CMAKE_DIR三个变量就绪否则报错退出在build目录执行 CMake 配置、编译与安装最后运行./build/main并将输出同时写入终端和output_msg.txt。CMakeLists.txt 通过${ASCENDC_CMAKE_DIR}/ascendc.cmake引入构建规则将 main.cpp 与 plugin_lifecycle.cpp 编译为可执行文件main并链接${ASCEND_CANN_PACKAGE_PATH}/lib64/libascendcl.so。关键接口全景样例涉及的关键接口均声明于 include/external/acl/acl_rt.h按功能可分为三类。初始化回调管理接口作用aclInitCallbackRegister(type, cbFunc, userData)注册插件初始化动作在aclInit执行时被触发声明aclInitCallbackUnRegister(type, cbFunc)注销不应执行的初始化动作声明初始化回调的函数签名typedef为typedef aclError (*aclInitCallbackFunc)(const char* configStr, size_t len, void* userData);configStr/len为调用aclInit(configPath)时传入的配置文件内容userData为注册时透传的用户数据指针。ACL 生命周期管理接口作用aclInit(configPath)执行 ACL 初始化触发仍有效的初始化回调声明aclFinalizeCallbackRegister(type, cbFunc, userData)注册插件去初始化动作声明aclFinalizeCallbackUnRegister(type, cbFunc)注销不应执行的去初始化动作声明aclFinalizeReference(refCount)每次调用递减一次内部引用计数归零时执行实际资源清理并触发去初始化回调声明去初始化回调的函数签名typedef为typedef aclError (*aclFinalizeCallbackFunc)(void* userData);回调类型枚举回调按类型分组注册枚举定义于 include/external/acl/acl_rt.h#L5102-L5112typedef enum aclRegisterCallbackType { ACL_REG_TYPE_ACL_MODEL, ACL_REG_TYPE_ACL_OP_EXECUTOR, ACL_REG_TYPE_ACL_OP_CBLAS, ACL_REG_TYPE_ACL_OP_COMPILER, ACL_REG_TYPE_ACL_TDT_CHANNEL, ACL_REG_TYPE_ACL_TDT_QUEUE, ACL_REG_TYPE_ACL_DVPP, ACL_REG_TYPE_ACL_RETR, ACL_REG_TYPE_OTHER 0xFFFF, } aclRegisterCallbackType;样例选用ACL_REG_TYPE_OTHER见 plugin_lifecycle.cpp 中kCallbackType的定义。Device 业务验证接口作用aclrtSetDevice(deviceId)选择样例使用的 Device 0aclrtGetDevice(deviceId)回读当前 Device 并校验选择结果aclrtResetDeviceForce(deviceId)复位 Device 0 并回收设备资源样例代码结构解析样例程序入口 main.cpp 只做一件事调用RunPluginLifecycle()并依据返回值打印成功或失败信息。核心逻辑全部位于 plugin_lifecycle.cpp。会话状态与回调计数代码首先定义了一个PluginLifecycleSession会话结构用于贯穿整个流程struct CallbackCounts { int activeInit 0; // 有效初始化回调执行次数 int cancelledInit 0; // 已注销初始化回调执行次数 int activeFinalize 0; // 有效去初始化回调执行次数 int cancelledFinalize 0; // 已注销去初始化回调执行次数 }; struct PluginLifecycleSession { CallbackCounts counts; bool activeInitRegistered false; bool cancelledInitRegistered false; bool runtimeInitialized false; bool deviceSet false; bool activeFinalizeRegistered false; bool cancelledFinalizeRegistered false; uint64_t referenceCount 1U; };四个回调函数ActiveInitCallback、CancelledInitCallback、ActiveFinalizeCallback、CancelledFinalizeCallback各自对相应计数器自增并防御性地检查userData是否为空。以初始化回调为例aclError ActiveInitCallback(const char*, size_t, void* userData) { if (userData nullptr) { return ACL_ERROR_INVALID_PARAM; } static_castCallbackCounts*(userData)-activeInit; return ACL_SUCCESS; }回调通过userData拿到session.counts因此可以在业务侧事后核验哪个回调执行过、执行了几次。主流程五步走RunPluginLifecycle()把整个流程组织为注册初始化回调 → 初始化运行时 → 注册去初始化回调 → 清理 → 校验结果五步int RunPluginLifecycle() { PluginLifecycleSession session; int result RegisterInitCallbacks(session); if (result 0) { result InitializeRuntime(session); } if (result 0) { result RegisterFinalizeCallbacks(session); } Cleanup(session, result); return result 0 ? VerifyLifecycle(session) : result; }这种注册 业务 清理 校验的结构正是插件接入 ACL 生命周期时应遵循的推荐范式。注册与注销初始化回调int RegisterInitCallbacks(PluginLifecycleSession session) { CHECK_ERROR(aclInitCallbackRegister(kCallbackType, ActiveInitCallback, session.counts)); session.activeInitRegistered true; CHECK_ERROR(aclInitCallbackRegister(kCallbackType, CancelledInitCallback, session.counts)); session.cancelledInitRegistered true; CHECK_ERROR(aclInitCallbackUnRegister(kCallbackType, CancelledInitCallback)); session.cancelledInitRegistered false; return 0; }注册ActiveInitCallback与CancelledInitCallback两个回调后立即注销后者。这样真正进入aclInit时只有ActiveInitCallback会生效。这里的CHECK_ERROR宏来自 example/utils.h任一 ACL 调用返回非ACL_SUCCESS都会打印错误码并返回-1。初始化运行时并验证 Deviceint InitializeRuntime(PluginLifecycleSession session) { CHECK_ERROR(aclInit(nullptr)); // 触发仍有效的初始化回调 session.runtimeInitialized true; if (session.counts.activeInit ! 1 || session.counts.cancelledInit ! 0) { ERROR_LOG(Unexpected initialization callback counts: active%d, cancelled%d., ...); return -1; } INFO_LOG(Initialization callbacks verified: active1, cancelled0.); CHECK_ERROR(aclrtSetDevice(kDeviceId)); session.deviceSet true; int32_t currentDevice -1; CHECK_ERROR(aclrtGetDevice(currentDevice)); if (currentDevice ! kDeviceId) { ERROR_LOG(Unexpected current Device: expected%d, actual%d., kDeviceId, currentDevice); return -1; } INFO_LOG(Device %d selected and verified., currentDevice); return 0; }aclInit(nullptr)不传配置文件kDeviceId 0通过aclrtSetDevice/aclrtGetDevice回读校验 Device 0 选择生效。注册与注销去初始化回调int RegisterFinalizeCallbacks(PluginLifecycleSession session) { CHECK_ERROR(aclFinalizeCallbackRegister(kCallbackType, ActiveFinalizeCallback, session.counts)); session.activeFinalizeRegistered true; CHECK_ERROR(aclFinalizeCallbackRegister(kCallbackType, CancelledFinalizeCallback, session.counts)); session.cancelledFinalizeRegistered true; CHECK_ERROR(aclFinalizeCallbackUnRegister(kCallbackType, CancelledFinalizeCallback)); session.cancelledFinalizeRegistered false; return 0; }与初始化阶段对称注册有效与待取消的去初始化回调然后注销后者。清理与最终校验Cleanup按照先设备、后运行时、再逐个注销回调的逆序执行清理所有清理步骤的错误都被记录下来但不会中断清理过程aclrtResetDeviceForce(kDeviceId)复位 Device 0aclFinalizeReference(session.referenceCount)递减引用计数——由于会话初始计数为 1本次调用会归零并触发实际去初始化与ActiveFinalizeCallback依次注销cancelledFinalize、activeFinalize、cancelledInit、activeInit四个回调。最终VerifyLifecycle校验五条断言全部成立if (counts.activeInit ! 1 || counts.cancelledInit ! 0 || counts.activeFinalize ! 1 || counts.cancelledFinalize ! 0 || session.referenceCount ! 0U) { ... return -1; } INFO_LOG(Plugin lifecycle verified: active_init1, cancelled_init0, active_finalize1, cancelled_finalize0, reference0.);示例输出运行成功后程序输出如下同时写入output_msg.txt[INFO] Start to run 0_reference_counted_plugin_lifecycle sample. [INFO] Initialization callbacks verified: active1, cancelled0. [INFO] Device 0 selected and verified. [INFO] Plugin lifecycle verified: active_init1, cancelled_init0, active_finalize1, cancelled_finalize0, reference0. [INFO] Run the 0_reference_counted_plugin_lifecycle sample successfully.源码级原理回调管理与引用计数是如何实现的样例演示的接口行为可以在仓库源码中找到完整实现依据。回调注册/注销单例 InitCallbackManageraclInitCallbackRegister等四个对外接口的*Impl实现位于 src/acl/aclrt_impl/callback_api.cpp它们全部委托给单例acl::InitCallbackManageraclError aclInitCallbackRegisterImpl(aclRegisterCallbackType type, aclInitCallbackFunc cbFunc, void* userData) { return acl::InitCallbackManager::GetInstance().RegInitCallback(type, cbFunc, userData); }InitCallbackManager的实现位于 src/acl/aclrt_impl/init_callback_manager.cpp核心要点如下按类型分组存储内部用std::multimapaclRegisterCallbackType, std::pair回调函数, userData保存初始化与去初始化两组回调RegInitCallback同类型去重对于ACL_REG_TYPE_OTHER之外的类型同一类型只允许注册一次重复注册返回ACL_ERROR_INTERNAL_ERRORACL_REG_TYPE_OTHER是兜底类型可多次注册注册即生效的兜底RegInitCallback中若检测到 ACL 已经初始化GetAclInitFlag()为真会立即以当前配置字符串执行该回调避免初始化之后才注册导致回调丢失线程安全注册、注销、通知均持std::recursive_mutex互斥锁保护。注销逻辑UnregisterCallbackImpl在对应类型的回调链中按函数指针匹配删除匹配不到或类型不存在时返回ACL_ERROR_INTERNAL_ERROR。aclInit 触发时机与顺序在 src/acl/aclrt_impl/acl.cpp 的初始化内部流程中各类型回调按固定顺序被通知其中其他类型回调在最后触发ret acl::InitCallbackManager::GetInstance().NotifyInitCallback(ACL_REG_TYPE_OTHER, cfgStr, cfgLen);初始化完成后内部引用计数被置为 1aclInitRefCount 1UL。这也是样例中session.referenceCount初始值为 1 的由来——它模拟了一个持有 ACL 引用的插件视角。aclFinalize 的引用计数语义aclFinalizeReferenceImpl是理解整个样例的关键src/acl/aclrt_impl/acl.cpp#L648-L683调用即持锁并读取内部引用计数aclInitRefCount若refCount非空先把当前引用计数写回计数大于 1仅自减不触发任何资源清理返回ACL_SUCCESS计数小于 1判定为重复去初始化或未初始化就调用返回ACL_ERROR_REPEAT_FINALIZE计数等于 1执行aclFinalizeInternal()完成真正的资源回收并在此过程中通知仍有效的去初始化回调。aclFinalizeInternalsrc/acl/aclrt_impl/acl.cpp#L547-L628会依次完成日志模块收尾、资源统计、Profiling 收尾然后按ACL_REG_TYPE_ACL_OP_COMPILER → ACL_REG_TYPE_ACL_MODEL → ACL_REG_TYPE_ACL_DVPP → ACL_REG_TYPE_OTHER的顺序通知去初始化回调最后将内部引用计数清零。这正是样例中只有ActiveFinalizeCallback被执行一次、引用计数归零的底层来源。引用计数与多模块协作从aclFinalizeReference的实现可以看出ACL 的引用计数机制天然支持多插件模块共享一次初始化的场景每个模块在aclInit后各自持有一份引用退出时各自调用一次aclFinalizeReference只有当最后一个引用被释放计数归 1 → 执行内部清理时去初始化回调才会真正被触发。而aclInitCallbackRegister在已初始化情况下立即补执行回调的逻辑则保证了晚到的模块也能获得初始化通知。两者结合为插件式组件在统一生命周期下的优雅接入提供了基础设施。扩展如何在真实插件中复用该模式将样例模式推广到真实插件建议遵循以下要点注册时机必须在aclInit之前完成初始化回调注册才能保证回调在aclInit内部流程中被触发若错过时机InitCallbackManager也会在注册时立即补执行注销兜底无论业务是否出错都要像样例Cleanup那样按逆序注销所有已注册回调避免残留回调在进程退出阶段被意外触发引用计数语义每个持有 ACL 引用的模块与一次aclFinalizeReference对应切勿重复调用会得到ACL_ERROR_REPEAT_FINALIZE也勿与aclFinalize混用二者语义不同回调返回值回调返回非ACL_SUCCESS会中断aclInit/aclFinalize的后续流程从NotifyCallbackImpl的实现可以看到失败即返回因此回调内部要做好参数校验与错误处理userData 生命周期回调在注册后可能被延迟触发userData指向的内存必须保证存活到注销回调为止。相关资源索引主题总览example/0_quickstart/5_runtime_lifecycle_callbacks/README.md样例说明中/英0_reference_counted_plugin_lifecycle/README.md、README_en.md样例源码plugin_lifecycle.cpp、main.cpp构建与运行CMakeLists.txt、run.sh接口头文件include/external/acl/acl_rt.haclInit见 L1099aclFinalizeReference见 L1128回调类型与注册接口见 L5102-L5164实现源码src/acl/aclrt_impl/callback_api.cpp、src/acl/aclrt_impl/init_callback_manager.cpp、src/acl/aclrt_impl/acl.cpp引用计数去初始化见 L648-L683【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考