本文基于昇腾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。


参考仓库

Runtime 运行时

GE 图引擎

Ascend C 算子编程语言

CANN 学习中心

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐