CANN Runtime 生命周期回调与引用计数去初始化实战:从插件回调注册到引用归零
【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime
导读
本文围绕 CANN runtime 开源仓库中的5_runtime_lifecycle_callbacks样例,讲解应用插件如何参与 ACL 初始化(aclInit)与去初始化(aclFinalize)流程。你将掌握aclInitCallbackRegister/aclFinalizeCallbackRegister等回调注册、注销接口的正确用法,理解aclFinalizeReference引用计数去初始化的底层语义,并学会用一个单 Device 业务场景验证"有效回调各执行一次、已注销回调均不执行、引用计数最终归零"这一完整生命周期。文中所有接口说明均以仓库头文件与实现源码为准,可直接对照 样例目录 复现实验。
样例背景:插件如何"感知" ACL 生命周期
CANN 的 ACL(AscendCL)运行时在进程内只允许初始化一次、去初始化一次。对于模型、算子、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-L5112:
typedef 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 0 |
aclrtGetDevice(&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_cast<CallbackCounts*>(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。
初始化运行时并验证 Device
int 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: active=1, cancelled=0."); CHECK_ERROR(aclrtSetDevice(kDeviceId)); session.deviceSet = true; int32_t currentDevice = -1; CHECK_ERROR(aclrtGetDevice(¤tDevice)); 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 0;aclFinalizeReference(&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_init=1, cancelled_init=0, active_finalize=1, " "cancelled_finalize=0, reference=0.");示例输出
运行成功后,程序输出如下(同时写入output_msg.txt):
[INFO] Start to run 0_reference_counted_plugin_lifecycle sample. [INFO] Initialization callbacks verified: active=1, cancelled=0. [INFO] Device 0 selected and verified. [INFO] Plugin lifecycle verified: active_init=1, cancelled_init=0, active_finalize=1, cancelled_finalize=0, reference=0. [INFO] Run the 0_reference_counted_plugin_lifecycle sample successfully.源码级原理:回调管理与引用计数是如何实现的
样例演示的接口行为,可以在仓库源码中找到完整实现依据。
回调注册/注销:单例 InitCallbackManager
aclInitCallbackRegister等四个对外接口的*Impl实现位于 src/acl/aclrt_impl/callback_api.cpp,它们全部委托给单例acl::InitCallbackManager:
aclError 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::multimap<aclRegisterCallbackType, std::pair<回调函数, userData>>保存初始化与去初始化两组回调(RegInitCallback); - 同类型去重:对于
ACL_REG_TYPE_OTHER之外的类型,同一类型只允许注册一次,重复注册返回ACL_ERROR_INTERNAL_ERROR;ACL_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);初始化完成后,内部引用计数被置为 1(aclInitRefCount = 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(),完成真正的资源回收,并在此过程中通知仍有效的去初始化回调。
aclFinalizeInternal(src/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.h(
aclInit见 L1099,aclFinalizeReference见 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),仅供参考