runtime 运行时:昇腾NPU的“操作系统内核”
后来帮一个朋友排查 FlashAttention 算子执行慢的问题,发现 runtime 才是真正的“幕后黑手”——算子再快,runtime 调度不当,NPU 算力也跑不满。如果你的模型只需要做归一化,用 AIPP 就够了,别浪费 DVPP 资源。FlashAttention 本身不调 DVPP,但如果你的模型是 Vision Transformer(ViT),输入图像就得先过 DVPP 做预处理
runtime 运行时:昇腾NPU的“操作系统内核”
刚接触 CANN 那会,我以为 runtime 就是个“运行时库”,调几个 API 就完事。后来帮一个朋友排查 FlashAttention 算子执行慢的问题,发现 runtime 才是真正的“幕后黑手”——算子再快,runtime 调度不当,NPU 算力也跑不满。
昇腾NPU 上跑模型,runtime 是绕不开的基础设施。CANN 把 runtime 放在第五层架构的“执行层”,负责把编译好的算子真正扔到 NPU 硬件上跑。这次把 runtime 的架构拆清楚,以后排查性能问题就不会再迷路。
环境准备:理解 runtime 在 CANN 中的位置
在开始拆解 runtime 架构之前,先得搞清楚它跟其他组件的关系。
runtime 的定位:
- CANN 五层架构中的第4层(昇腾计算执行层):它位于图编译器之下,驱动之上。
- 上层对接:Graph Compiler(图编译器)、BiSheng/ATC(编译器)。
- 下层对接:Driver(驱动)、NPU 硬件(达芬奇架构)。
依赖关系:
你的 PyTorch 模型
↓ (框架适配层)
Graph Compiler(图编译)
↓ (编译成执行计划)
runtime(运行时)
↓ (调用底层接口)
driver(驱动)
↓ (真正干活)
昇腾NPU 达芬奇架构硬件
FlashAttention 的执行链路:ops-transformer 的 FlashAttention 算子 →\rightarrow→ 调用 Ascend C 编程接口 →\rightarrow→ runtime 把算子调度到 AI Core →\rightarrow→ 在 NPU 上执行矩阵分块计算。
如果 runtime 调度不当(比如任务分配不均、数据搬运没重叠),FlashAttention 的延迟就会上去。
逐步推进:runtime 的核心模块拆解
runtime 不是单一模块,而是一组协同工作的子系统。结合 CANN 8.0+(当前时间 2026 年 5 月的主流版本)的特性,核心模块有 5 个:
1. Runtime 运行时(核心调度器)
这是 runtime 的“大脑”,负责:
- 设备管理:检测 NPU 设备、分配设备上下文。
- 任务调度:把算子任务分配给多个 AI Core 并行执行。
- 内存管理:管理 NPU 上的 GM(全局内存)、L1、L0。
关键 API(AscendCL 接口):
import acl
# 初始化运行时
acl.init()
# 申请 NPU 设备
device_id = 0
acl.rt.set_device(device_id)
# 分配内存(GM 上)
data_size = 1024 * 1024 # 1MB
data_ptr, _ = acl.rt.malloc(data_size, acl.rt.MemType.MEM_TYPE_NORMAL)
# 释放设备
acl.rt.reset_device(device_id)
acl.finalize()
⚠️ 踩坑预警:acl.rt.malloc 申请的是 NPU 的 GM 内存,不是主机内存。如果你把主机指针传给 NPU 算子,会报“非法内存访问”错误。正确做法是先用 acl.rt.malloc 申请 NPU 内存,再用 acl.rt.memcpy 把数据从主机拷到 NPU。
2. Graph Executor(图执行器)
如果你的模型是用 CANN 的图开发接口构建的(比如用了 GE 图引擎),Graph Executor 负责:
- 图解析:把编译好的图(.om 文件)解析成执行计划。
- 节点调度:按拓扑顺序执行图中的算子节点。
- 数据流转:管理算子之间的中间数据(不写回主机,直接在 NPU 上传递)。
FlashAttention 如果跑在图模式下,Graph Executor 会把 Q、K、V 的算子融合成一个子图,减少数据搬运次数。
验证 Graph Executor 是否工作:
# 检查图执行日志
cat /var/log/npu/log/runtime.log | grep "GraphExecutor"
如果看到 “GraphExecutor: Load graph success”,说明图加载成功。
3. HCCL(集合通信库)
如果你做的是分布式训练/推理(比如 Megatron + 昇腾NPU),HCCL 负责:
- 集合通信:AllReduce、AllGather、ReduceScatter 等。
- 多卡协同:多个 NPU 之间的数据同步。
FlashAttention 在分布式场景下,需要 HCCL 做 Ring Attention(把序列维度切分到多个 NPU 上并行算)。
⚠️ 踩坑预警:HCCL 的初始化很慢(第一次要建通信组)。如果你在测试用例里频繁初始化/销毁 HCCL,性能会很差。正确做法是进程启动时初始化一次,整个生命周期复用。
4. DVPP(数字视觉预处理)
如果你的模型输入是图像/视频,DVPP 负责:
- 图像解码:JPEG/PNG 解码成 YUV/RGB。
- 预处理:Resize、Crop、Color Space Conversion。
- 零拷贝:预处理完直接扔给 NPU 算子,不写回主机。
FlashAttention 本身不调 DVPP,但如果你的模型是 Vision Transformer(ViT),输入图像就得先过 DVPP 做预处理。
验证 DVPP 是否工作:
# 检查 DVPP 占用率
npu-smi info -t dvpp
如果 DVPP 占用率是 0%,说明预处理没走 NPU,而是在 CPU 上做的(性能会差很多)。
5. AIPP(AI 预处理)
AIPP 是 DVPP 的补充,负责:
- 图像归一化:Mean/Std 归一化。
- 格式转换:RGB → BGR、YUV → RGB。
- 融合到算子:AIPP 的配置可以融合到算子执行计划里,不单独跑。
⚠️ 踩坑预警:AIPP 和 DVPP 都能做图像预处理,但 AIPP 更轻量(不占 DVPP 硬件单元)。如果你的模型只需要做归一化,用 AIPP 就够了,别浪费 DVPP 资源。
踩坑实录:runtime 使用中的常见错误
踩坑 1:内存泄漏——忘了释放 NPU 内存
# 错误写法(会泄漏)
data_ptr, _ = acl.rt.malloc(size, acl.rt.MemType.MEM_TYPE_NORMAL)
# ... 用完没释放 ...
# 正确写法
data_ptr, _ = acl.rt.malloc(size, acl.rt.MemType.MEM_TYPE_NORMAL)
try:
# ... 使用 data_ptr ...
pass
finally:
acl.rt.free(data_ptr) # 确保释放
踩坑 2:设备上下文没设置——算子执行失败
# 错误写法(没设置设备上下文)
acl.init()
# ... 直接调算子 ... # 会报"设备未初始化"错误
# 正确写法
acl.init()
acl.rt.set_device(0) # 先设置设备上下文
# ... 调算子 ...
acl.rt.reset_device(0)
acl.finalize()
踩坑 3:Host→Device 数据搬运没同步——数据没搬完就开始算
# 错误写法(没同步)
acl.rt.memcpy(dev_ptr, host_data, size, acl.rt.MemcpyKind.MEMCPY_HOST_TO_DEVICE)
# ... 立刻调算子 ... # 数据可能还没搬完
# 正确写法(加同步)
acl.rt.memcpy(dev_ptr, host_data, size, acl.rt.MemcpyKind.MEMCPY_HOST_TO_DEVICE)
acl.rt.synchronize() # 等数据搬运完成
# ... 再调算子 ...
验证环节:runtime 是否正常工作
1. 检查 NPU 设备状态
# 查看 NPU 设备列表
npu-smi info
# 查看运行时日志
cat /var/log/npu/log/runtime.log | tail -50
如果 runtime 初始化成功,日志里会有 “Runtime initialized successfully”。
2. 跑一个最简单的算子,验证 runtime 调度
你可以参考 cann-samples 仓库中的示例代码。
import acl
import numpy as np
# 初始化
acl.init()
acl.rt.set_device(0)
# 申请内存
size = 1024
host_data = np.random.randn(size).astype(np.float32)
dev_ptr, _ = acl.rt.malloc(size * 4, acl.rt.MemType.MEM_TYPE_NORMAL)
# 数据搬运(Host → Device)
acl.rt.memcpy(dev_ptr, host_data.tobytes(), size * 4, acl.rt.MemcpyKind.MEMCPY_HOST_TO_DEVICE)
acl.rt.synchronize()
# ... 这里调你的算子 ...
# 数据搬运(Device → Host)
output_host = np.zeros(size, dtype=np.float32)
acl.rt.memcpy(output_host.tobytes(), dev_ptr, size * 4, acl.rt.MemcpyKind.MEMCPY_DEVICE_TO_HOST)
acl.rt.synchronize()
# 释放
acl.rt.free(dev_ptr)
acl.rt.reset_device(0)
acl.finalize()
如果这个流程能跑通,说明 runtime 基本正常工作。
3. 性能验证:runtime 调度是否跑满 NPU
# 跑模型时,实时监控 NPU 利用率
npu-smi info -t aicore -i 0 -c 0
如果 AI Core 利用率长期低于 60%,说明 runtime 调度有问题(任务分配不均、数据搬运没重叠)。这时候得去查:
- 算子是不是没切分好(大算子占满一个 AI Core,其他 AI Core 闲着)。
- 数据搬运是不是同步的(没用异步搬运,计算和数据搬运串行)。
下一步行动建议
- 读 runtime 源码:从
acl.rt接口看起,理解内存管理/任务调度的实现。 - 跑 CANN 的 runtime 示例:
cann-samples仓库里有现成的调用代码。 - 用 npu-smi 监控 runtime 状态:学会看 AI Core 利用率、内存占用、通信带宽。
- 排查 FlashAttention 性能问题:如果 FlashAttention 跑不满 NPU,先查 runtime 调度,再查算子实现。
参考仓库:
- cann-samples(运行时调用示例):https://atomgit.com/cann/cann-samples
- driver(运行时下层对接的驱动):https://atomgit.com/cann/driver
更多推荐




所有评论(0)