当前位置: 首页 > news >正文

CANN Runtime 异步任务调度:Stream 与 Event 的执行哲学

推理框架的代码里,aclrtLaunchJob(提交算子)和aclrtSynchronizeStream(等算子执行完)这两类调用占了大部分。不夸张地说,Runtime 的异步调度机制直接决定了推理服务的吞吐上限。

CANN Runtime 的核心调度模型是 Stream + Event。Stream 让操作并发,Event 让并发有序。这篇文章从执行链路入手,讲清楚 Runtime 是怎么调度任务推进的。


为什么 AI 推理需要异步执行

推理一个模型的典型步骤:

  1. CPU 预处理输入数据
  2. 把预处理结果拷到 NPU 显存(H2D)
  3. NPU 执行模型推理
  4. 把结果拷回 CPU 内存(D2H)
  5. CPU 后处理结果

如果这 5 步是串行的——CPU 等着 NPU 算完才做下一步——NPU 执行期间 CPU 在空等,CPU 做预处理时 NPU 在空等。推理服务的吞吐浪费了一半。

异步执行的核心是把这些步骤重叠。CPU 在处理第 N 个请求的预处理时,NPU 在同时跑第 N-1 个请求的推理。理想状态下 CPU 和 NPU 100% 并行工作,互不等待。


Runtime 如何调度任务

Runtime 不直接暴露给应用层——应用层调的是 AscendCL,AscendCL 内部调用 Runtime 的接口。以aclmdlExecute为例,Runtime 内部做的事情:

  1. 解析模型句柄。根据 modelId 找到对应的 OM 模型内存映射,获取 GE 优化后的 Task 列表
  2. Task 拆解。每个 Task 对应一个算子或融合算子。Runtime 为每个 Task 创建 Task 描述(算子的 Kernel 句柄、输入输出地址、执行配置)
  3. Stream 分发。Task 被推送到指定 Stream 的队列中
  4. 硬件提交。Runtime 的调度器从 Stream 队列取 Task,通过驱动提交到 AI Core 执行

整个过程耗时约 5-15μs。对于单次推理来说微不足道,但对于解码阶段每步都要 Launch 几十个 Task 的场景,调度开销累加起来可能占推理延迟的 10-20%。


Stream 为什么重要

Stream 是 Runtime 调度的核心抽象。一个 Stream 是一个 FIFO 的任务队列——所有提交到该 Stream 的 Task 按顺序执行。

单 Stream 模型:

提交: Task1 → Task2 → Task3 → Task4 → Task5 执行: [Task1] → [Task2] → [Task3] → [Task4] → [Task5]

严格串行,N 个 Task 的总延迟 = 各 Task 延迟之和。

多 Stream 模型(无依赖):

Stream A: [Task1] → [Task3] → [Task5] Stream B: [Task2] → [Task4]

Task1 和 Task2 可以同时执行——如果 NPU 有足够的执行单元,总延迟可能从 5 个单位降到 3 个单位。

推理框架利用多 Stream 的典型做法是三条流水线:

aclrtStream preprocess_stream,compute_stream,postprocess_stream;// 每条 Stream 处理不同阶段的任务aclrtLaunchOp(preprocess_op,...,preprocess_stream);// CPU 预处理aclrtLaunchOp(compute_op,...,compute_stream);// NPU 推理aclrtLaunchOp(postprocess_op,...,postprocess_stream);// 后处理

Runtime 的硬件调度器会尽可能让这 3 条 Stream 的 Task 在不同执行单元上并行——preprocess_stream 在 DVPP 上跑,compute_stream 在 AI Core 上跑,postprocess_stream 在 Vector Unit 上跑。


Event 如何同步任务

多 Stream 的问题在于:不同 Stream 的任务之间可能有依赖关系。推理的计算必须在预处理完成之后开始,后处理必须在推理完成之后开始。

Event 就是来解决这个同步问题的。

aclrtEvent preprocess_done;aclrtCreateEvent(&preprocess_done);// 在预处理 Stream 上提交预处理,并在完成后设置 EventaclrtLaunchOp(preprocess_op,...,preprocess_stream);aclrtRecordEvent(preprocess_done,preprocess_stream);// 在推理 Stream 上等待预处理完成后再开始推理aclrtStreamWaitEvent(compute_stream,preprocess_done);aclrtLaunchOp(compute_op,...,compute_stream);

aclrtRecordEvent在指定 Stream 中插入一个 Event 标记——当 Stream 执行到这个标记时,Event 变为完成状态。aclrtStreamWaitEvent让另一个 Stream 在继续执行前等待这个 Event。

Event 的同步是在硬件层面完成的——不经过 CPU 中断或轮询。当 compute_stream 的调度器看到它依赖的 Event 还没完成时,不去启动待运行的 Task,而是去调度其他不依赖这个 Event 的 Task。


Memory 管理与 Stream 的关联

Runtime 的内存管理也跟 Stream 紧密相关。每个 Stream 有自己的内存分配上下文。两个 Stream 之间共享显存池,但各自维护独立的分配指针。

aclrtMalloc默认从进程级显存池分配。aclrtMallocFromStream可以从指定 Stream 的上下文中分配——Stream 退出时自动回收该内存。

// 从 compute_stream 的上下文中分配显存// Stream 销毁时自动 Freevoid*buf=aclrtMallocFromStream(size,compute_stream);

这个机制在流水线推理中很有用——预处理 Stream 分配的临时 Buffer 在推理 Stream 消费完后自动回收,不需要手动调用aclrtFree


昇腾 Runtime 的执行链路

把完整链路串起来:

应用层调用 aclmdlExecute 或 aclmdlExecuteAsync ↓ AscendCL 解析模型 → 获取 GE 优化后的 Task 列表 ↓ Runtime 创建 Task 描述(50-100 个 Task) ↓ Task 按 Stream 分配到队列 ↓ 硬件调度器从各 Stream 队列取 Task ↓ HW Scheduler 检查 Event 依赖 ↓ 就绪的 Task 派发给 AI Core / Vector Unit / DVPP ↓ Task 完成 → 触发 Event(如果有注册) ↓ 依赖该 Event 的 Stream 继续执行

Runtime 的硬件调度器是整个链路中最被低估的组件。它不是简单的 FIFO 调度器——它会动态调整不同 Stream 的 Task 执行顺序来最大化硬件利用率。一个 Stream 的 Task 如果因为等待 Event 被阻塞,调度器不会空转,而是去运行另一个不依赖该 Event 的 Stream 的 Task。

继续学习

Runtime 的 Stream 和 Event 机制是 CANN 推理异步调度的核心。理解了这两层,就能理解如何通过多 Stream 流水线把 NPU 利用率从 40% 拉到 80%+。进一步学习 GE 的图执行机制,可以了解 Task 在提交给 Runtime 之前已经做了哪些编排。

CANN Runtime 仓库

Graph Executor 图执行引擎


内存池的 Stream 亲和性

Runtime 的内存池对不同 Stream 的亲和性不同。aclrtMalloc从全局池分配——所有 Stream 共享。aclrtMallocFromStream从 Stream 的本地池分配——该 Stream 独享,分配和回收不需要锁。

// 全局分配——所有 Stream 都可访问,但需要加锁void*buf=aclrtMalloc(size,ACL_MEM_MALLOC_HUGE_FIRST);// Stream 本地分配——只有 compute_stream 可快速访问void*local=aclrtMallocFromStream(size,compute_stream);

Stream 本地池的分配速度比全局池快约 20%,因为不需要锁争用。但本地池的内存只能被该 Stream 访问。如果是多 Stream 共享的 Tensor(比如模型的权重),必须用全局分配。


Runtime 调试技巧

推理性能不达预期时,第一步是检查 Runtime 的调度效率:

# 开启 Runtime 的 Trace 日志exportACL_RUNTIME_TRACE=1

Trace 日志会输出每个 Task 的提交时间、执行开始时间、完成时间。从日志中可以看到:

  • Task 之间有没有空档(Task 结束到下一个 Task 开始之间 NPU 空闲的时间)
  • Stream 之间有没有等待(一个 Stream 等另一个 Stream 的 Event)
  • Memory 分配有没有持锁等待(多 Stream 竞争全局池)

常见问题:如果日志显示同一 Stream 内连续 Task 之间有空档,说明数据搬运没跟上计算——需要优化 Double Buffer 或加大 Tile 尺寸。

参考仓库

CANN Runtime
Graph Executor 图执行引擎

http://www.jsqmd.com/news/855694/

相关文章:

  • 杭州书法艺考机构哪家强?2026浙江书法联考培训机构推荐:杭州专业书法高考工作室+杭州口碑好书法高考培训机构合集 - 栗子测评
  • c#笔记之面向对象
  • ArduPilot SITL进阶:在Ubuntu 22.04上配置多旋翼/固定翼/小车模拟与自动化测试
  • Netcap 性能优化秘籍:7个技巧提升网络分析处理速度 [特殊字符]
  • git diff 从入门到精通
  • 为什么选择snnTorch?5个理由让你爱上这个脉冲神经网络框架
  • 别再瞎调PID了!手把手教你用STM32 HAL库搞定电机速度闭环(附完整代码)
  • Tere跨平台部署指南:在Linux、Windows和macOS上的终极安装配置教程
  • 3步实战Windows风扇控制:FanControl深度配置指南
  • 《Windows Sysinternals实战指南》PsTools 学习笔记(7.5):PsExec 的备用凭据与安全基线
  • 2026番茄罐头供应商怎么选?番茄酱供应厂家-恒钧隆实力解析 - 栗子测评
  • 现在怎么去学习AI,在哪里去学习?
  • PyTorch-FCN扩展开发指南:添加新数据集和网络架构的完整流程
  • torchtitan-npu:在昇腾集群上训练大模型
  • Lumia设备深度定制突破:Windows Phone Internals核心技术解密与实战指南
  • 避坑指南:VirtualBox中CentOS虚拟机网络配置的5个常见错误(附ifcfg-enp0s8文件详解)
  • 2026水果罐头源头厂家指南必看!甜玉米罐头批发厂家全梳理 - 栗子测评
  • 基于ssm的支教志愿者招聘系统(10069)
  • CANN AscendC反量化缓冲区API
  • 如何在Windows系统上免费恢复WannaCry加密文件?内存密钥恢复工具实战指南
  • 基于ssm框架的博客系统(10070)
  • MODBUS调试助手开发全解析:从协议原理到实战避坑指南
  • 告别臃肿PDF!用Ghostscript命令行批量压缩/拆分/合并的保姆级教程
  • rebar3与Hex.pm集成指南:Erlang包管理的完整解决方案
  • 《Windows Sysinternals实战指南》PsTools 学习笔记(7.7):进程性能选项——优先级、CPU 亲和性与稳定落地
  • HTML会代替Markdown吗?为什么?
  • 2026年口碑好的南京报警腕表/社区矫正腕表批量采购厂家推荐 - 品牌宣传支持者
  • 终极GTA5游戏增强菜单:YimMenu全方位安全防护指南
  • 别再死记命令了!用eNSP模拟真实办公室,手把手带你搞定华为AC+AP无线组网
  • 新能源充电桩厂家有哪些?2026新能源充电桩厂家优选:权威电动汽车充电桩厂家+电动汽车充电桩品牌榜单 - 栗子测评