CANN Runtime 模型运行实例(Model RI)实战:用 aclrtSwitchStream 与 aclrtActiveStream 实现图内 Stream 条件跳转与激活
2026/9/19 3:44:33 网站建设 项目流程

CANN Runtime 模型运行实例(Model RI)实战:用 aclrtSwitchStream 与 aclrtActiveStream 实现图内 Stream 条件跳转与激活

【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime

导读

本篇文章基于 CANN Runtime 开源仓库中的2_model_switch样例,深入讲解如何通过aclmdlRIBuildBegin系列接口构建"模型运行实例"(Model Runtime Instance,Model RI),并在图任务中利用aclrtSwitchStream(条件 Stream 跳转)与aclrtActiveStream(Stream 激活)实现多分支控制流。读完本文,你将掌握 Model RI 从创建、绑定 Stream、下发任务到执行、解绑的完整生命周期,理解图内条件分支与并行分支的编排方式,并能在自己的算子任务中复现这套"数据依赖驱动分支"的编程范式。

一、背景:为什么需要"模型运行实例"与图内控制流

常规的算子执行方式是把单个算子或一组算子按顺序下发到 Stream 上串行执行。但真实推理/训练场景中,图(Graph)内部往往存在条件分支(根据某个值决定走哪条计算路径)与并行激活(一个 Stream 完成后激活另一条 Stream 开始执行)。CANN Runtime 提供了两套互补的能力:

  • 模型运行实例(Model RI):通过aclmdlRI*系列接口,可以在**不依赖离线模型文件(.om)**的情况下,把一组绑定的 Stream 及下发到其上的任务"打包"成一个可重复执行的模型实例,再通过aclmdlRIExecuteAsync异步执行整张图;
  • 图内控制流原语aclrtSwitchStream在任务执行期间根据 Device 侧数据的比较结果决定跳转到哪条 Stream;aclrtActiveStream在一条 Stream 执行到某一点时激活另一条 Stream,形成并行执行关系。

2_model_switch样例正是把两者结合:先构建一个绑定 5 条 Stream 的模型实例,在其中编排 2 条条件跳转分支,再连续执行两次(分别命中不同分支),验证控制流正确性。

二、样例概述与产品支持情况

样例位于 example/2_advanced_features/model_ri/2_model_switch,目录结构如下:

2_model_switch/ ├── CMakeLists.txt # 构建配置(链接 libascendcl / libnnopbase / libopapi) ├── README.md # 中文说明 ├── README_en.md # 英文说明 ├── main.cpp # 样例主程序 └── run.sh # 编译 + 运行脚本

关键接口在不同产品上的支持情况如下(来自 README_en.md):

接口Ascend 950PR / Ascend 950DTAtlas A3 训练/推理系列Atlas A2 训练/推理系列
aclmdlRIBuildBegin支持支持支持
aclmdlRIBindStream支持支持支持
aclmdlRIEndTask支持支持支持
aclmdlRIBuildEnd支持支持支持
aclmdlRIUnbindStream支持支持支持
aclmdlRIExecuteAsync支持支持支持
aclrtSwitchStream支持不支持支持
aclrtActiveStream支持不支持支持

注意:aclrtSwitchStreamaclrtActiveStream在 Atlas A3 系列上不支持,移植该样例到 A3 系列产品前需确认硬件与 CANN 版本支持情况。

三、环境准备与编译运行

3.1 前置条件

  • 已安装 CANN 软件包(默认安装在/usr/local/Ascend,下文以${install_root}指代安装根目录);
  • 已克隆本仓库,样例根目录为example/
  • 具备编译工具链与 CMake(CMakeLists.txt要求最低版本 3.16.0)。

3.2 编译运行步骤

# 1. 加载 CANN 运行环境(${install_root} 替换为实际安装根目录) source ${install_root}/cann/set_env.sh # 2. 自动识别 SOC_VERSION 与 ASCENDC_CMAKE_DIR source ${git_clone_path}/example/set_sample_env.sh # 3. 编译并运行(编译产物安装到 ./out,运行日志输出到 output_msg.txt) bash run.sh

run.sh 内部依次执行:加载setenv.bashcmake -B build(传入ASCEND_CANN_PACKAGE_PATH)→cmake --build build -jcmake --install build→ 运行./build/main | tee output_msg.txt,日志同时打印到终端并落盘到output_msg.txt

3.3 构建配置要点

CMakeLists.txt 展示了本例的链接依赖:

  • include(${ASCENDC_CMAKE_DIR}/ascendc.cmake):引入 AscendC 构建宏(SOC_VERSION 与 ASCENDC_CMAKE_DIR 由set_sample_env.sh注入环境变量);
  • ascendc_library(kernels STATIC ../../../kernel_func/easy_OP.cpp):把样例共用的算子 kernel 源文件编译成静态库;
  • target_link_libraries(main ... libascendcl.so libnnopbase.so libopapi.so):链接 AscendCL 运行时、NN 算子底座与算子 API 库;
  • 编译目标mainmain.cpp与上一级目录的 model_utils.cpp 共同构成。

四、核心 API 全景解析

样例同时覆盖"模型实例生命周期"与"图内控制流"两类接口,全部声明于 include/external/acl/acl_rt.h。

4.1 模型运行实例(Model RI)接口

接口作用关键参数
aclmdlRIBuildBegin(aclmdlRI* modelRI, uint32_t flag)开始构建模型运行实例flag为保留参数,必须传0(acl_rt.h L4904)
aclmdlRIBindStream(modelRI, stream, flag)将模型实例与 Stream 绑定flag取值ACL_MODEL_STREAM_FLAG_HEAD(入口流)或ACL_MODEL_STREAM_FLAG_DEFAULT(acl_rt.h L4915)
aclmdlRIEndTask(modelRI, stream)标记任务下发结束(EndGraph)一条模型只能有一个 EndTask,样例中只放在汇聚流 endStream 上
aclmdlRIBuildEnd(modelRI, void* reserve)结束构建,通知 Runtime 模型已就绪reserve必须传nullptr(acl_rt.h L4935)
aclmdlRIUnbindStream(modelRI, stream)解除模型实例与 Stream 的绑定见 acl_rt.h L4945
aclmdlRIExecuteAsync(modelRI, stream)异步执行模型推理执行流可与绑定流分离
aclmdlRIDestroy(modelRI)销毁模型运行实例须在解绑所有 Stream 之后调用

在 src/runtime/feature/model/model.cc 中可以看到这些接口的构建期校验逻辑:调用aclmdlRIBuildEnd之前必须先调用aclmdlRIBindStream绑定 Stream,且必须先调用aclmdlRIEndTask标记任务下发结束,否则会返回RT_ERROR_MODEL_NOT_END(错误码 EE1018)等构建错误。

4.2 图内控制流接口

aclrtSwitchStream—— 根据条件跳转 Stream(acl_rt.h L4342):

aclError aclrtSwitchStream(void* leftValue, aclrtCondition cond, void* rightValue, aclrtCompareDataType dataType, aclrtStream trueStream, aclrtStream falseStream, aclrtStream stream);
  • leftValue/rightValue:参与比较的左右值,均为Device 侧内存地址(比较在 Device 上执行);
  • cond:比较条件,取aclrtCondition枚举(acl_rt.h L727-L734):
typedef enum { ACL_RT_EQUAL = 0, // 相等 ACL_RT_NOT_EQUAL, // 不相等 ACL_RT_GREATER, // 大于 ACL_RT_GREATER_OR_EQUAL, // 大于等于 ACL_RT_LESS, // 小于 ACL_RT_LESS_OR_EQUAL // 小于等于 } aclrtCondition;
  • dataType:比较值的数据类型,取aclrtCompareDataType(acl_rt.h L736-L739):ACL_RT_SWITCH_INT32 = 0ACL_RT_SWITCH_INT64 = 1
  • trueStream:条件成立时跳转的目标 Stream;falseStream保留参数,必须传nullptrstream:下发该跳转任务的 Stream。

底层实现在 src/acl/aclrt_impl/stream.cpp L374-L394:aclrtSwitchStreamImpl会对leftValuerightValuetrueStreamstream做非空校验,并通过ACL_CHECK_INVALID_PARAM_NO_VALUE(falseStream == nullptr, ...)强制falseStream必须为nullptr(当前版本未开放 false 分支),随后调用rtsSwitchStream下探到 Runtime 驱动层。

aclrtActiveStream—— 激活一条 Stream(acl_rt.h L4327):

aclError aclrtActiveStream(aclrtStream activeStream, aclrtStream stream);

stream上插入一个"激活任务":当stream执行到该点时,激活activeStream开始执行,形成并行执行关系。底层aclrtActiveStreamImpl(stream.cpp L361-L372)对两个入参做非空校验后调用rtsActiveStream

使用提示:aclrtActiveStream的两个 Stream应都已绑定到同一个模型实例,否则激活语义不受图调度保护(详见样例代码注释)。

4.3 配套基础接口

样例还演示了完整的初始化/资源管理链路:

  • 初始化:aclInit/aclFinalize
  • Device:aclrtSetDevice/aclrtResetDeviceForce
  • Context:aclrtCreateContext/aclrtDestroyContext
  • Stream:aclrtCreateStream/aclrtCreateStreamWithConfig(如ACL_STREAM_PERSISTENT)/aclrtSynchronizeStream/aclrtDestroyStream/aclrtDestroyStreamForce
  • 内存:aclrtMalloc/aclrtFree
  • 数据传输:aclrtMemcpy/aclrtMemcpyAsync(支持 H2D、D2D、D2H)。

五、样例主程序逐段解析

完整源码见 example/2_advanced_features/model_ri/2_model_switch/main.cpp,下面按逻辑分段说明。

5.1 数据准备:三个 aclnnAdd 算子任务

样例定义了三组 shape 为{4, 2}的 float 输入,alpha = 1.2,调用aclnnAddGetWorkspaceSize获取 workspace 并分配 Device 内存,随后通过ModelUtils::CreateAclTensor创建aclTensor(该工具函数在 model_utils.cpp 中完成aclrtMalloc+ stride 计算 +aclCreateTensor):

// out = self + other * alpha // stream1: out1 = self1 + other1 * alpha self1={1} other1={2} → 3.4 // stream2: out2 = self2 + other2 * alpha self2={2} other2={2} → 4.4 // stream3: out3 = self3 + other3 * alpha self3={3} other3={2} → 5.4 vector<float> selfHostData1 = {1, 1, 1, 1, 1, 1, 1, 1}; vector<float> otherHostData1 = {2, 2, 2, 2, 2, 2, 2, 2}; // ... self2/other2、self3/other3 同理 float alphaValue = 1.2f;

aclrtMalloc统一使用ACL_MEM_MALLOC_HUGE_FIRST大页优先策略。

5.2 构造跳转条件:Device 侧比较值

aclrtSwitchStream的比较发生在 Device 侧,因此需要把比较值先拷贝到 Device 内存(main.cpp L97-L115):

int32_t rightValue1 = 1; // 分支条件:numDevice == 1 int32_t rightValue2 = 2; // 分支条件:numDevice == 2 aclrtCondition condition = ACL_RT_EQUAL; aclrtCompareDataType dataType = ACL_RT_SWITCH_INT32; // aclrtMalloc + aclrtMemcpy(HOST_TO_DEVICE) 拷贝 rightValue1/rightValue2 // 再申请 numDevice(uint32_t),初值为 0

图内部还会执行一次ACL_MEMCPY_DEVICE_TO_DEVICE拷贝,把targetValDev_1(值为 1 或 2,可被 Host 侧改写)写入numDevice,作为 SwitchStream 的实际判断依据——这就是"运行期动态决定分支"的关键设计。

5.3 创建 Stream 并构建模型实例

创建 5 条持久化工作流 + 1 条独立拷贝流(main.cpp L125-L130):

aclrtCreateStreamWithConfig(&stream1, 0x00U, ACL_STREAM_PERSISTENT); // stream2、stream3、stream4、endStream 同理 aclrtCreateStream(&copyStream); // 独立拷贝流,不绑定模型,避免流状态异常

随后aclmdlRIBuildBegin开始构建实例,并将 5 条流全部绑定,stream1标记为入口流(HEAD),其余为 DEFAULT(main.cpp L132-L137):

aclmdlRIBuildBegin(&modelRI, 0x00U); aclmdlRIBindStream(modelRI, stream1, ACL_MODEL_STREAM_FLAG_HEAD); aclmdlRIBindStream(modelRI, stream2, ACL_MODEL_STREAM_FLAG_DEFAULT); aclmdlRIBindStream(modelRI, stream3, ACL_MODEL_STREAM_FLAG_DEFAULT); aclmdlRIBindStream(modelRI, stream4, ACL_MODEL_STREAM_FLAG_DEFAULT); aclmdlRIBindStream(modelRI, endStream, ACL_MODEL_STREAM_FLAG_DEFAULT);

5.4 编排各条 Stream 上的任务

stream1(入口流):拷贝 self1/other1 → 执行 add1 → D2D 拷贝把 1 写入numDevice(main.cpp L139-L147):

aclrtMemcpyAsync(selfDevice1, size, selfHostData1.data(), size, ACL_MEMCPY_HOST_TO_DEVICE, stream1); // otherDevice1 同理 aclnnAdd(addWorkspaceAddr1, addWorkspaceSize1, addExecutor1, stream1); aclrtMemcpyAsync(numDevice, sizeof(uint32_t), targetValDev_1, sizeof(uint32_t), ACL_MEMCPY_DEVICE_TO_DEVICE, stream1);

stream2 / stream3:各执行一个 add,并在完成后激活汇聚流 endStream(main.cpp L149-L153):

aclnnAdd(addWorkspaceAddr2, addWorkspaceSize2, addExecutor2, stream2); aclrtActiveStream(endStream, stream2); // stream2 完成后激活 endStream aclnnAdd(addWorkspaceAddr3, addWorkspaceSize3, addExecutor3, stream3); aclrtActiveStream(endStream, stream3); // stream3 完成后激活 endStream

stream1 上的两条条件跳转(main.cpp L156-L157):

// numDevice == 1 → 跳转到 stream3 aclrtSwitchStream(numDevice, condition, rightDevice1, dataType, stream3, nullptr, stream1); // numDevice == 2 → 跳转到 stream4 aclrtSwitchStream(numDevice, condition, rightDevice2, dataType, stream4, nullptr, stream1);

stream4:激活 stream2(两个流并行执行,且都已绑定模型实例,main.cpp L159):

aclrtActiveStream(stream2, stream4);

EndTask 收束:只在汇聚流 endStream 上标记任务下发结束(一个模型只能有一个 EndGraph),然后结束构建(main.cpp L160-L163):

aclmdlRIEndTask(modelRI, endStream); aclmdlRIBuildEnd(modelRI, NULL);

5.5 执行模型并回拷结果

aclmdlRIExecuteAsync使用独立的executeStream异步执行整张图,aclrtSynchronizeStream阻塞等待完成;结果的 D2H 回拷使用未绑定模型的copyStream,避免与图内流互相干扰(main.cpp L164-L173)。

5.6 第二次执行:验证另一条分支

第二次执行前,Host 侧通过copyStreamtargetValDev_1的值从 1 改写为 2,并把上一次的 Host 结果清零,再次执行同一模型实例(main.cpp L183-L199)。由于图内 D2D 拷贝会把 2 写入numDevice,本次执行会命中第二条跳转分支——同一个模型实例无需重建即可按不同数据走不同路径,这是 Model RI 相比一次性下发任务的显著优势。

5.7 资源回收

样例严格按逆序释放资源(main.cpp L207-L256):aclmdlRIUnbindStream逐条解绑 →aclmdlRIDestroy销毁实例 → 销毁全部 Stream →aclDestroyTensor/aclDestroyScalaraclrtFree全部内存 →aclrtDestroyContextaclrtResetDeviceForceaclFinalize

六、执行路径推演与示例输出解读

6.1 两种分支路径

  • 路径 1(numDevice == 1)stream1(add1=3.4)→ SwitchStream 命中 →stream3(add3=5.4,激活 endStream)→endStreamEndTask。stream2stream4从未被激活,因此out2 保持 0.0
  • 路径 2(numDevice == 2)stream1(add1=3.4)→ SwitchStream 命中 →stream4(激活 stream2)→stream2(add2=4.4,激活 endStream)→endStreamEndTask。stream3不被执行,因此out3 保持 0.0

6.2 示例输出

[INFO] After executing, print data1. [INFO] The vector data is: 3.4000 3.4000 3.4000 3.4000 3.4000 3.4000 3.4000 3.4000 [INFO] After executing, print data2. [INFO] The vector data is: 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 [INFO] After executing, print data3. [INFO] The vector data is: 5.4000 5.4000 5.4000 5.4000 5.4000 5.4000 5.4000 5.4000 ... [INFO] After second execution, print data2. [INFO] The vector data is: 4.4000 4.4000 4.4000 4.4000 4.4000 4.4000 4.4000 4.4000

数值与公式out = self + other × alpha完全吻合(3.4 = 1 + 2×1.2,5.4 = 3 + 2×1.2,4.4 = 2 + 2×1.2),且两次执行分别验证了"data2 为 0"(分支未命中,stream2 未执行)与"data2 为 4.4"(分支命中)两种状态,说明条件跳转与 Stream 激活均按预期工作。README 中...省略号之后的部分对应第二次执行的 data1/data2/data3 全部打印(实际 main.cpp 会完整打印三组数据)。

七、关键注意事项与最佳实践

  1. falseStream 必须为 nullptraclrtSwitchStream的 false 分支参数为保留项,底层实现会强制校验(见 stream.cpp L386-L387),误传非空值会返回参数错误。
  2. 比较值必须在 Device 侧leftValue/rightValue指向的是 Device 内存地址,Host 侧数据需先用aclrtMemcpy(H2D)或图内aclrtMemcpyAsync(D2D)就位。
  3. 一条模型只能有一个 EndTask:多个分支汇聚后,仅在汇聚流上调用一次aclmdlRIEndTaskaclmdlRIBuildEndreserve参数必须传nullptr
  4. 执行流与绑定流分离是安全的aclmdlRIExecuteAsyncexecuteStream不必是模型绑定的流;同理,独立的copyStream用于数据搬运,可避免把拷贝任务混入图调度导致状态异常。
  5. aclrtActiveStream的双方流应绑定同一模型实例:否则并行激活语义不受图级调度保护。
  6. 产品兼容性aclrtSwitchStream/aclrtActiveStream在 Atlas A3 系列上不支持(见第二节表格),跨产品移植前务必核对。
  7. 接口顺序约束aclmdlRIBindStream必须在aclmdlRIBuildEnd之前完成,aclmdlRIEndTask也必须在aclmdlRIBuildEnd之前调用(源码构建校验见 src/runtime/feature/model/model.cc),否则构建失败并报错。

八、延伸阅读

  • 样例根目录环境说明:example/README_en.md;
  • 样例环境变量脚本:example/set_sample_env.sh;
  • 模型运行实例管理接口说明:docs/zh/api_ref/15_model_running_instance_management.md;
  • 同目录下其他 Model RI 样例:0_simple_model(基础创建/执行)、1_model_update(模型更新)、3_cond_model(条件模型)、4_model_sync_external(外部同步)、5_reusable_buffer_reset(复用缓冲区重置);
  • ACL Graph 捕获与任务组机制可参考:docs/zh/dev_guide/04_ACL-Graph.md。

九、已知问题

当前版本该样例无已知问题(Known Issues: None)。

【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime

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

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

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

立即咨询