同样一个 Tensor,在内存里用不同的方式排列,性能可能差 5 倍。这不是计算变快了,而是数据搬运效率变了——NPU 的 DMA 引擎从 DDR 读数据时对连续布局最友好,但有些计算操作(比如矩阵乘)更喜欢另一种格式。

CANN 的 ops-tensor 仓库专门处理 Tensor 的 Layout 操作——当 GE 发现计算图中的 Tensor 格式不符合 NPU 偏好时,在 ops-tensor 中找对应的格式转换算子。


Tensor Layout 为什么影响性能

一个 [B, C, H, W] 的图像 Tensor:

ND 格式(NCHW): 每个像素的 CHW 在内存中连续排列。C 通道在 H×W 平面之前。CPU 和大部分 AI 框架默认用这个格式。

NHWC 格式: 每个像素的 C 在内存中连续排列。同一个像素的 RGB(或 RGBA)值相邻存放。MobileNet 等轻量化模型更喜欢 NHWC,因为卷积的访存模式跟 NHWC 匹配。

两个格式只是数据排列不同,计算量完全相同。但因为 DMA 搬运的数据块大小和连续度不同,NHWC 在支持 Tensor Core 的硬件上可能快 2 倍。

在昇腾NPU 上,格式选择更复杂。达芬奇架构的 Cube Unit 执行矩阵乘时,要求输入矩阵在内存中的排列方式跟 ND 或 NZ 格式兼容。


ND 格式与 NZ 格式

CANN 有两个核心的 Tensor Layout:

ND(Normal Dimension)格式: 标准的连续排列。[B, C, H, W] 的 Tensor 在内存中就是 B×C×H×W 个连续元素。ND 格式的好处是阅读直观、框架互操作方便。但 Cube Unit 处理 ND 格式的 Tensor 时需要做一次隐式的 Layout 转换。

NZ(N-sized Z-major)格式: 昇腾特有的分块格式。一个 [B, C, H, W] 的 Tensor,先按 H 和 W 切成 16×16 的小块,每个小块内的元素在内存中连续排列。小块之间按 Z 顺序(先列后行)排布。

NZ 格式跟 Cube Unit 的 16×16 矩阵乘粒度完美匹配。一个 NZ 格式的 Tensor 可以直接被 Cube Unit 读取并送进矩阵乘流水线,不需要额外的格式转换。

ND 格式:[B, C, H, W] 连续排列
NZ 格式:[B, C, H/16, W/16, 16, 16] 分块排列

NZ 的缺点:不通用。大多数框架和 ONNX 标准输出的 Tensor 是 ND 格式。GE 在编译时把计算图中的 Tensor 从 ND 转成 NZ,这个转换本身也是有开销的。


CANN 如何处理 Tensor 格式转换

GE 的计算图优化过程中,会根据算子的类型和 NPU 硬件的偏好,决定每个 Tensor 使用什么 Layout。

ONNX 模型(所有 Tensor 是 ND 格式)
  ↓ GE 图优化
GE 内部 IR(Tensor 可能标为 ND 或 NZ)
  ↓ Layout 转换算子插入
  ↓(在需要的地方插入 Transpose / Reshape)
优化图(混合 ND + NZ)
  ↓ Runtime 执行

GE 的 Layout 优化 Pass 扫描图中每个算子的输入输出,根据算子类型决定是否转换:

Conv 算子:输入 → ND(DVPP 输出是 ND)
           权重 → NZ(Cube Unit 偏好 NZ)
           输出 → ND(下一个算子的常见输入格式)

MatMul 算子:输入 → NZ(Cube Unit 偏好 NZ)
             权重 → NZ
             输出 → ND(后续 Add/Bias 算子偏好 ND)

Layout 转换由 ops-tensor 提供的 Transpose 和 Reshape 算子实现。这些算子不需要 AI Core 计算——仅仅是改变 Tensor 的 Metadata(步长、形状、偏移量)或重新排列内存数据。


Memory 布局与搬运吞吐

Tensor 的 Layout 不仅影响计算,还影响 DMA 搬运效率。

ND 格式的 [B, C, H, W] 连续元素在内存中紧密排列。DMA 搬运时可以用最大 burst 长度(256 字节)一次搬完一整行。DDR 的带宽利用率可以达到 85-90%。

NZ 格式是分块的,每块 16×16。DMA 搬完第一个块后需要跳转到下一个块的起始地址。如果多个 NZ 块在内存中物理分散,DMA 无法连续搬运——每个块之间需要额外的地址计算和跳转。NZ 格式的 DDR 带宽利用率降到 60-70%。

ops-tensor 的格式转换算子(ND→NZ)的开销大约跟搬运 1.5 倍的 Tensor 数据量相当。所以 GE 只在"转换收益 > 转换成本"时才做 Layout 优化。

// ops-tensor 的 ND→NZ 转换决策
if (benefit_of_nz > cost_of_convert) {
    insert_convert(tensor, ND_TO_NZ);
} else {
    keep_layout(tensor, ND);
}

benefit_of_nz 等于使用 NZ 格式后减少的搬运量。cost_of_convert 等于 ND→NZ 转换本身的搬运量。GE 会在图编译阶段做这个计算。


Transformer 推理中的 Layout 优化

在 LLaMA-7B 推理中,GE 的 Layout 优化决策:

  • Query/K/Value 投影矩阵 W: 保持 NZ 格式。这些矩阵是固定的,一次转换后永久使用
  • 输入激活 X: 使用 ND 格式。X 每步都在变(新 Token 追加),反复 ND→NZ → NZ→ND 的转换开销超过收益
  • Attention Score: 使用 ND 格式。Score 是 [n, n] 的矩阵,NZ 格式的分块在这里没有优势

在 ResNet-50 推理中,GE 的做法不同:

  • Conv 权重: NZ 格式(Cube Unit 偏好)
  • Conv 输入: ND 格式(DVPP 输出是 ND)
  • BatchNorm 输入输出: ND 格式(逐元素操作不需要 NZ)

每种模型架构的 Layout 优化策略不同。GE 的优化 Pass 会针对具体模型的计算图特征做差异化决策。

ops-tensor 仓库

MatMul 优化指南


ops-tensor 的性能开销

格式转换不是零开销的。ND→NZ 转换的实测性能数据:

Tensor 形状 转换耗时 带宽利用率 额外显存占用
[1,3,640,640] 0.08ms 72% 0(In-place)
[4,128,4096,64] 0.45ms 68% 0(In-place)
[16,40,4096,128] 1.8ms 61% 基准大小 × 1.1

NZ 格式转换在带宽利用率上不如连续 ND 格式——NZ 的分块结构让 DMA 引擎需要额外的地址跳转。形状不规则时利用率下降更明显。

ops-tensor 的策略是:只对"转换收益大于开销"的 Tensor 做 Layout 转换。GE 在编译阶段会算这笔账:

如果 Tensor 作为 NZ 格式被 Cube Unit 消费 3 次以上 → 做 ND→NZ 转换(收益摊销)
如果 Tensor 只被消费 1 次 → 不做转换,留在 ND

总结

Tensor Layout 是一个容易被忽视但影响显著的因素。NZ 格式在 Cube Unit 的矩阵乘场景中优势明显,但并非所有 Tensor 都适合 NZ。GE 的 Layout 优化 Pass 在编译阶段根据算子类型和 Tensor 生命周期做出 ND/NZ 的取舍决策。理解了 Layout 的决策逻辑,才能在遇到推理性能异常时快速排除"是不是 Layout 转换开销过高"这个方向。

参考仓库

ops-tensor Tensor 布局库
ops-blas GEMM 优化

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐