news 2026/9/20 11:03:48

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 的 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.sh

3. 编译并运行

bash run.sh

从 run.sh 可以看到脚本的内部逻辑:

  • 若环境变量ASCEND_INSTALL_PATH/ASCEND_HOME_PATH未设置,自动加载 set_sample_env.sh;
  • 强制要求ASCEND_HOME_PATHSOC_VERSIONASCENDC_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; };

四个回调函数(ActiveInitCallbackCancelledInitCallbackActiveFinalizeCallbackCancelledFinalizeCallback)各自对相应计数器自增,并防御性地检查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; }

注册ActiveInitCallbackCancelledInitCallback两个回调后,立即注销后者。这样,真正进入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(&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按照"先设备、后运行时、再逐个注销回调"的逆序执行清理,所有清理步骤的错误都被记录下来但不会中断清理过程:

  1. aclrtResetDeviceForce(kDeviceId)复位 Device 0;
  2. aclFinalizeReference(&session.referenceCount)递减引用计数——由于会话初始计数为 1,本次调用会归零并触发实际去初始化与ActiveFinalizeCallback
  3. 依次注销cancelledFinalizeactiveFinalizecancelledInitactiveInit四个回调。

最终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_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);

初始化完成后,内部引用计数被置为 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在"已初始化"情况下立即补执行回调的逻辑,则保证了晚到的模块也能获得初始化通知。两者结合,为插件式组件在统一生命周期下的优雅接入提供了基础设施。

扩展:如何在真实插件中复用该模式

将样例模式推广到真实插件,建议遵循以下要点:

  1. 注册时机:必须在aclInit之前完成初始化回调注册,才能保证回调在aclInit内部流程中被触发;若错过时机,InitCallbackManager也会在注册时立即补执行;
  2. 注销兜底:无论业务是否出错,都要像样例Cleanup那样按逆序注销所有已注册回调,避免残留回调在进程退出阶段被意外触发;
  3. 引用计数语义:每个"持有 ACL 引用"的模块与一次aclFinalizeReference对应,切勿重复调用(会得到ACL_ERROR_REPEAT_FINALIZE),也勿与aclFinalize混用,二者语义不同;
  4. 回调返回值:回调返回非ACL_SUCCESS会中断aclInit/aclFinalize的后续流程(从NotifyCallbackImpl的实现可以看到失败即返回),因此回调内部要做好参数校验与错误处理;
  5. 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 11:03:15

uBlock Origin 3 分钟装好:免费广告拦截器,装完即用

uBlock Origin 3 分钟装好&#xff1a;免费广告拦截器&#xff0c;装完即用 【免费下载链接】uBlock uBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean. 项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock uBlock Origin 是 Chro…

作者头像 李华
网站建设 2026/9/20 10:58:43

VMware Workstation 17 Pro 离线激活与深度配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华