DMA:NPU 上的数据搬运为什么比 CPU 快几十倍
本文基于昇腾CANN和昇腾NPU,围绕 DMA 数据搬运技术展开。
Tensor搬运在 AI 推理里占的时间比大多数人想的都大。以 LLaMA-7B 推理为例,一次前向要搬运约 30GB 的 Tensor 数据(权重 + 中间结果)。CPU 做搬运的极限速度是 PCIe 带宽——32GB/s。但 NPU 上的 DMA 引擎走的是内部总线,带宽 200GB/s,差了 6 倍。
DMA(Direct Memory Access)是 NPU 内部专门的硬件引擎——它搬数据的时候 Core 继续算,互不阻塞。CPU 的 memcpy 是阻塞式的——搬 1GB 数据 CPU 就不能干别的。DMA 在启动后立刻返回——数据在后台搬,Core 同时算当前的 Tile。
DMA 的完整链路
一次 DDR→L1 的 Tensor搬运 经过以下硬件路径:
DDR (HBM) → 内存控制器 → 内部互联总线 → L1 Buffer
DMA 引擎配置:
源地址: DDR 上的偏移量
目标地址: L1 Buffer 的本地地址
传输长度: tile_size × sizeof(fp16)
传输方向: DDR→L1(读)或 L1→DDR(写)
延迟构成:
启动延迟: 2-5μs(DMA 描述符配置 + 总线仲裁)
传输时间: data_size / bandwidth(200GB/s)
完成中断: 1-2μs(通知 Scalar Unit 数据就绪)
典型 Tile(128×32 FP16 = 8KB)搬运:
启动 3μs + 传输 0.04μs + 中断 1μs ≈ 4μs
DDR→L1 的 DMA 和 L1→DDR 的 DMA 是独立的硬件通道——可以同时进行。读和写互不阻塞。
Memory访问 的优化:为什么 DMA 比 Core 自己搬快
Core 自己搬数据意味着 Scalar Unit 逐条执行 Load/Store 指令——每条指令读 16 字节(FP16×8),一个 128×32 的 Tile 要 512 条 Load 指令。每条 Load 走一遍地址计算和数据读取,耗时约 2-5 cycles。512 条 × 3 cycles ≈ 1500 cycles。
DMA 不逐条走——描述符配置好后,DMA 引擎内部走 Burst 传输,一次 Burst 搬 256 字节。128×32×2=8KB 数据,32 次 Burst 搬完。每次 Burst 约 5-10 cycles——总共 200 cycles。快了约 7 倍。
Runtime 如何调度数据流
CANN Runtime 的 GE 图引擎在编译期标记了每个 Kernel 的输入/输出 Buffer。Kernel 执行时,Runtime 在 Kernel 启动前配置好 DMA 描述符——源地址从 Buffer Pool 取,目标地址是 L1 上分配的临时或持久区域。
Runtime 层的 DMA 调度:
1. Kernel Launch 前:
- 从 Buffer Pool 分配输入 Buffer 的 DDR 地址
- 从 L1 分配临时 Buffer
- 配置 DMA 描述符:源→目标,长度,方向
2. Kernel 执行中:
- Scalar Unit 触发 DMA 启动
- DMA 后台搬运,Cube/Vector 继续运算
- Scalar Unit 检查 DMA 完成中断
3. Kernel 结束后:
- 输出 Buffer 写回 DDR(DMA Write)
- 回收 L1 临时 Buffer
大模型推理中的 Tensor 流转
LLaMA-7B 推理,一次 Attention 前向的 Tensor 流转量:
| Tensor | 大小 | 搬运方向 | 方式 |
|---|---|---|---|
| 权重 W_Q/W_K/W_V | 128MB | DDR→L1 | DMA(一次性) |
| Q/K/V 激活 | 32MB | DDR→L1 | DMA |
| Score 矩阵 | 32MB | L1→DDR | DMA |
| FFN 权重 W1/W2 | 256MB | DDR→L1 | DMA |
一次 Attention 层的 DMA 搬运总量约 500MB。按 200GB/s 算,理论搬运时间 2.5ms。实际因为 Cube 计算覆盖了大部分 DMA 时间(双缓冲),DMA 对延迟的净贡献约 0.8ms——不到 1/3。
参考仓库
更多推荐

所有评论(0)