Multi-Stream:昇腾推理的异步并行执行
摘要:本文介绍了AI推理中单Stream与Multi-Stream的差异。单Stream串行执行导致硬件利用率不足50%,而Multi-Stream通过预处理、推理、后处理三阶段流水线并行,利用率可提升至85%以上。文章详细解析了CANN Runtime中Stream的并行执行机制,包括任务队列管理、事件同步和硬件调度策略,并特别说明了解码阶段多Stream并发执行的实现方式。通过Stream间任
单 Stream 推理的流程:预处理 → H2D 搬运 → 推理 → D2H 搬运 → 后处理。每步串行执行,NPU 做推理时 CPU 在等,CPU 做预处理时 NPU 在等。硬件利用率不到 50%。
Multi-Stream 让不同阶段在不同的 Stream 上并行执行——预处理 Stream 在准备下一批数据时,推理 Stream 同时在 NPU 上算当前批。硬件利用率可以拉到 85% 以上。
为什么 AI 推理需要多 Stream
推理管线天然适合用多 Stream 并行。三段流水线:
Stream A(预处理): [预处理 N] [预处理 N+1] [预处理 N+2]
Stream B(推理): [推理 N-1] [推理 N] [推理 N+1]
Stream C(后处理): [后处理 N-2] [后处理 N-1] [后处理 N]
第 N 批推理的结果在后处理时,第 N+1 批正在推理,第 N+2 批正在预处理。三个 Stream 流水线执行——NPU 和 CPU 几乎 100% 并行。
Stream 如何并行执行
CANN Runtime 中的 Stream 是硬件调度器管理的任务队列。不同 Stream 的任务可以同时提交到不同的执行单元上。
// 三段 Stream 并行推理
aclrtStream pre_stream, inf_stream, post_stream;
aclrtCreateStream(&pre_stream);
aclrtCreateStream(&inf_stream);
aclrtCreateStream(&post_stream);
// 提交三阶段任务到不同的 Stream
for (int i = 0; i < num_batches; i++) {
// 预处理(DVPP 在 Stream A 上执行)
dvpp_decode(frame[i], pre_stream);
dvpp_resize(decoded, pre_stream);
// 推理(AI Core 在 Stream B 上执行)
// Event 保证推理在预处理完成后开始
aclrtStreamWaitEvent(inf_stream, pre_done_event);
aclmdlExecuteAsync(model, input, output, inf_stream);
// 后处理(CPU Vector 在 Stream C 上执行)
aclrtStreamWaitEvent(post_stream, inf_done_event);
postprocess(output, post_stream);
}
aclrtStreamWaitEvent 是同步点——推理 Stream 在等预处理 Stream 的事件完成时,不会阻塞预处理 Stream 继续提交下一批任务。事件完成前推理 Stream 也不空转——硬件调度器会去执行其他 Stream 的就绪任务。
Runtime 如何调度 Stream
Runtime 的硬件调度器维护一个 Stream 队列池。每个 Stream 是一个 FIFO 队列。调度器循环检查所有 Stream:
- 从 Stream i 的队列头部取一个 Task
- 检查 Task 的 Event 依赖是否就绪
- 如果就绪:分配执行单元并提交
- 如果未就绪:跳到 Stream i+1
这个循环在硬件中执行,不经过 CPU。调度器遍历所有 Stream 一次的时间约 1-2μs——远小于单个 Task 的执行时间(几十到几千 μs)。
Multi-Stream 从流水线到并发解码
流水线并行适合处理器不同的阶段。解码阶段的多 Stream 玩法不一样。
解码阶段的 NPU 每步只算一个 Token。计算量很小——Cube Unit 的利用率很低。多 Stream 的玩法是把多个独立请求的解码放在不同的 Stream 上并发执行:
Stream A:Token 5 → Token 6 → Token 7 → ...
Stream B:Token 2 → Token 3 → Token 4 → ...
两个请求在同一个 NPU 上并发解码。硬件调度器在 Stream A 和 Stream B 的 Task 之间交替执行——Stream A 的 MatMul 在 Cube Unit 上算时,Stream B 的 Q 投影 DMA 可以同时搬运数据。
Stream 与 Task 的映射
Runtime 中 Stream 跟 Task 不是一一对应的。一个 Stream 包含多个 Task,Task 在 Stream 中按序执行。不同 Stream 的 Task 之间没有顺序约束——硬件调度器可以任意调整执行顺序。
但 Task 之间有数据依赖——Task B 需要使用 Task A 的输出 Tensor。这种依赖通过 Event 来约束。如果 GE 在生成 Task 时分析出 Task B 依赖 Task A 的输出,会在 Task A 完成时记录 Event,Task B 等待这个 Event。
解码阶段的多 Stream
解码阶段的多 Stream 需要比流水线并行更精细的调度。每个 request 的解码进度不同——request A 出第 5 个 Token 时 request B 才出第 2 个。Stream 按 request 分配——A 走 Stream 0,B 走 Stream 1。
连续 Batch(Continuous Batching)中多个 request 的解码进程完全独立——A 和 B 的 Token 长度不同、计算步数不同。Runtime 的调度器在 Stream 之间公平分配资源——不会因为 A 的计算步数多就把 B 的 Stream 长时间挂起。
参考仓库
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐



所有评论(0)