- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
导读
本文面向使用 Ascend C(asc-devkit 算子开发套件)进行 Cube(矩阵计算)算子开发的开发者,系统讲解昇腾 AI 处理器矩阵计算单元为何要求输入输出数据必须符合"分形存储格式",以及如何用"大Y小x"命名法快速识别 Zz、Zn、Nz 等关键格式并正确完成数据搬运与分块。读完本文,你将掌握分形格式的物理成因、各格式在不同 Buffer 中的分布、转换过程的核心公式,并能据此在Mmad类接口开发中正确选择数据排布方式。
本文主体基于 背景与核心概念,并深度参考同目录的 关键分形格式详解 与 辅助矩阵分形格式详解,结合仓库源码与样例交叉验证。
一、为什么需要分形存储:从分块计算到内存访问瓶颈
昇腾 AI 处理器的矩阵计算单元(Cube)采用分块计算逻辑,例如可并行处理 16×16 的矩阵分片。分块计算意味着硬件每次以"块"为单位取数、计算、累加,这与通用 CPU 以"元素流"方式处理数据有本质区别。
在传统的线性存储布局中(如 row major(行主序)或 column major(列主序)),数据按行或列连续存放。此时若需要读取一个 16×16 的矩形数据块,硬件必须访问多个不连续的内存地址,逐行"收集"数据:
- 行主序下,同一列的数据在内存中相隔整整一行,地址大幅跳跃;
- 每读一行都要发起一次新的寻址,16 行就要寻址 16 次;
- 这种非连续访问会显著降低内存效率,拖累矩阵计算单元的数据吞吐率。
为解决该问题,昇腾引入了矩阵分形存储格式(Fractal Format)。其核心思想是:通过软件层对数据进行重排,使每个 16×16 计算块在物理内存中连续存放。这样硬件仅需发起一次连续读取,即可将整个数据块加载至片上寄存器,避免了多地址寻址开销,从而大幅提升矩阵计算单元的数据吞吐率。
从源码结构看,这种"分形"思想已经沉淀为 asc-devkit 中的一等公民——在 kernel_utils_mode.h 中可以看到,TShapeType枚举直接定义了FRACTAL_NZ、FRACTAL_Z、FRACTAL_Z_3D等分形格式类型,并给出了ND -> FRACTAL_NZ、ND -> FRACTAL_Z、NC1HWC0 -> FRACTAL_Z等格式转换映射表,这说明分形格式不仅是文档概念,更是接口层面真实的类型系统与转换约束。
二、Cube 单元的数据通路:L0A / L0B / L0C 与分形的关系
要理解分形格式,必须先了解 Cube 计算单元直接访问的专用缓存。矩阵计算单元 文档给出了完整的存储单元画像:
| Buffer 类型 | 说明 |
|---|---|
| L0A Buffer | AI Core 内部物理存储单元,通常用于存储矩阵计算的左矩阵 |
| L0B Buffer | AI Core 内部物理存储单元,通常用于存储矩阵计算的右矩阵 |
| L0C Buffer | AI Core 内部物理存储单元,通常用于存储矩阵计算的结果,以及矩阵累加初始化值 |
| L1 Buffer | AI Core 内部物理存储单元,空间相对较大,通常用于缓存矩阵计算的输入数据 |
| Fixpipe Buffer | 通常用于存储 Fixpipe 搬运过程中所需的量化参数等数据 |
| BiasTable Buffer | 偏置存储,通常用于存储矩阵计算所需的 Bias(偏置)数据 |
矩阵计算的典型数据流是:GM → L1 Buffer → L0A / L0B Buffer → Cube 计算 → L0C Buffer → 写出。每一级搬入/搬出都对数据排布有特定要求,而分形格式正是描述"数据在这条通路上如何摆放"的规范。
三、核心概念:"大Y小x"命名法
为便于理解与记忆,文档采用"大Y小x"的直观命名法,这是解读一切分形格式的钥匙:
- 大 Y(Z/N):表示分形矩阵之间的排列顺序。Z 为 row major(行主序),N 为 column major(列主序)。
- 小 x(z/n):表示分形矩阵内部元素的排列顺序。z 为 row major(行主序),n 为 column major(列主序)。
由此可以推导出四种基础组合:Zz(大块行序 + 内部行序)、Zn(大块行序 + 内部列序)、Nz(大块列序 + 内部行序)、Nn(大块列序 + 内部列序)。其中 Nn 主要出现在 MX 量化系数矩阵场景(详见第五节),Zz / Zn / Nz 则是矩阵乘法的三大主角。
需要特别强调的是,"大 Y"描述的是分形块与分形块之间的摆放顺序,"小 x"描述的是分形块内部元素的摆放顺序。同一个逻辑矩阵,物理排布不同,直接决定了硬件能否以"一次连续读取"完成取数。
四、矩阵乘法的关键分形格式:Zz × Zn = Nz
矩阵乘法 A × B = C 的场景中,各格式有明确的物理要求。综合 关键分形格式详解:
- 针对 Atlas A2/A3 系列、Atlas 训练系列等大部分产品:Zz × Zn = Nz(左矩阵 A 用 Zz,右矩阵 B 用 Zn,结果矩阵 C 用 Nz);
- 针对 Ascend 950PR&950DT 系列产品:Nz × Zn = Nz(左矩阵 A 使用 Nz 格式,右矩阵 B 使用 Zn,结果矩阵 C 使用 Nz)。
下面逐一拆解这三种关键格式。
4.1 Zz 格式(FRACTAL_Zz):左矩阵的行读取需求
定义:大 Z 外部 row major(行主序)+ 小 z 内部 row major(行主序)。
物理位置:通常位于L0A Buffer,作为左矩阵。
设计原理:在 A×B 中,A 矩阵是被"横向扫描"的,Cube 单元左侧通道需要按行读取。Zz 格式在微观上按行存储,意味着 A 矩阵同一行的相邻元素在 L0A Buffer 的物理地址是连续的,Cube 单元左侧通道可直接通过 DMA 连续读取,无需跨步(Stride)。
格式转换过程:大小为(M, K)的矩阵被分为 M1 × K1 个分形,分形间按 row major(行主序)排布(形状如 Z 字形);每个分形内部有 M0 × K0 个元素,也按 row major(行主序)排布(形状如 Z 字形),故称 Zz 格式。转换公式如下:
(…, B, M, K)->pad->(…, B, M1 * M0, K1 * K0)->reshape->(…, B, M1, M0, K1, K0)->transpose->(…, B, M1, K1, M0, K0)分形大小约束:M0 = 16,K0 取决于数据类型位宽,分形大小为 16 ×(32B / sizeof(Datatype)),共 512 字节:
| 数据类型位宽 | 示例类型 | K0 | 分形 Shape | | ------------ | -------- | -- | ---------- | | 32-bit | float / int32 | 8 | 16×8 | | 16-bit | half | 16 | 16×16 | | 8-bit | int8 | 32 | 16×32 | | 4-bit | 4bit 类型 | 64 | 16×64 |
Zz格式示意图(以half类型为例)
4.2 Zn 格式(FRACTAL_Zn):右矩阵的列读取需求
定义:大 Z 外部 row major(行主序)+ 小 n 内部 column major(列主序)。
物理位置:L0B Buffer(作为右乘矩阵);L1 Buffer / GM(数据流转使用,一般用于保存离线处理好的右乘矩阵数据)。
设计原理:矩阵乘法需要取 B 矩阵的一列。若 B 矩阵为普通 row major(行主序),同一列的数据在内存中会相隔整整一行,导致地址跳跃。Zn 格式在分形内部强制按列存储,使 B 矩阵逻辑上的一列数据在 L0B Buffer 物理内存中被强行排在了一起,Cube 单元只需线性读取即可天然获得 B 矩阵的列向量。
格式转换过程:大小为(K, N)的矩阵被分为 K1 × N1 个分形,按 row major 排布(Z 字形);每个分形内部有 K0 × N0 个元素,按 column major(列主序)排布(N 字形),故称 Zn。转换公式如下:
(…, B, K, N)->pad->(…, B, K1 * K0, N1 * N0)->reshape->(…, B, K1, K0, N1, N0)->transpose->(…, B, K1, N1, N0, K0)分形大小约束:N0 = 16,K0 取决于数据类型(规则同 Zz 格式),例如 half 类型下分形为 16×16,int8 下分形为 16×32。
Zn格式示意图(以half类型为例)
4.3 Nz 格式(FRACTAL_Nz):结果矩阵与中间标准格式
定义:大 N 外部 column major(列主序)+ 小 z 内部 row major(行主序)。
物理位置:
- L0C Buffer:存储矩阵乘法的结果(分形固定为 16×16);
- L1 Buffer / GM:数据流转的通用中间格式(分形为 16×K0,K0 取决于数据类型位宽);
- L0A Buffer:仅 Ascend 950PR&950DT 系列产品下使用,用于存储左矩阵。
设计原理:Nz 是昇腾芯片架构内部的"中间标准格式",是对"线性数据(ND)"和"计算专用数据(Zz/Zn)"的一种折中。大 N 排布(column major Block)通常是为了配合多核并行或后续算子(如 Vector 向量计算)在处理通道(Channel)维度数据时的便利性。
说明:Nz 在 L0C Buffer 中分形固定为 16×16(256 个元素),非常适合 Cube 计算单元进行高效的矩阵乘法运算;而在 L1 Buffer 中,Nz 被采用以便于搬运到 L0A/L0B 时方便地转换为 Zz/Zn 格式,此时分形为 16 ×(32B / sizeof(Datatype))。因此,当数据从 L0C Buffer 搬运到 L1 Buffer 时,分形大小可能会发生变化。
Nz格式示意图
4.4 ND 与 DN:进入分形世界之前的"线性"起点
- ND 格式(N-Dimension):通用的 N 维张量格式,即"线性格式",数据在内存中连续线性排布。通常存在于 Global Memory(GM)中,在进行矩阵运算前需要转换为 Nz 格式以适配 Cube 单元。
- DN 格式(Dimension-N):物理存储顺序对应于 ND 格式在内存维度上的转置排列,即 ND 遵循 row major(行主序),DN 遵循 column major(列主序)。
针对 Ascend 950PR&950DT 系列产品,可以使用 DataCopy(GMToL1随路转换-ND2NZ搬运) 接口在搬运过程中完成 DN→Nz 的转换以适配 Cube 单元。该接口头文件路径为"basic_api/kernel_operator_data_copy_intf.h",函数原型如下:
template <typename T> __aicore__ inline void DataCopy(const LocalTensor<T>& dst, const GlobalTensor<T>& src, const Nd2NzParams& intriParams)针对 Ascend 950PR&950DT 系列产品,还新增了enableSmallC0模板参数(当 dValue 小于等于 4 时,C0_SIZE 会补齐到 4 * sizeof(T) 字节),且该型号下调用该接口必须配置参数 enableSmallC0。
4.5 实战演练:ND 转 Nz 的完整过程
Nz 是最常见的数据格式,下面用(M, N)=(20, 28)的 half 类型 Tensor 演示 ND→Nz 的完整转换(完整过程详见 关键分形格式详解)。
转换公式为:
(…, B, M, N)->pad->(…, B, M1 * M0, N1 * N0)->reshape->(…, B, M1, M0, N1, N0)->transpose->(…, B, N1, M1, M0, N0)原始数据(shape 20×28,行主序线性排布):
data = [x for x in range(20 * 28)] data_a = data * np.ones((20 * 28), dtype="float16") tensor_a = data_a.reshape((20, 28)) print(tensor_a)[[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27.] ... [532. 533. 534. 535. 536. 537. 538. 539. 540. 541. 542. 543. 544. 545. 546. 547. 548. 549. 550. 551. 552. 553. 554. 555. 556. 557. 558. 559.]]转换过程伪代码(pad → reshape → transpose 三步走):
N0 = 16 N1 = (28 + N0 - 1) // N0 pad_n = N1 * N0 - 28 M0 = 16 M1 = (20 + M0 - 1) // M0 pad_m = M1 * M0 - 20 tensor_b = np.pad(tensor_a, [[0, pad_m], [0, pad_n]]) tensor_b = tensor_b.reshape((M1, M0, N1, N0)) tensor_b = tensor_b.transpose((2, 0, 1, 3)) print(tensor_b)转换后,数据被重组为 N1 × M1 个 16×16 分形,分形间按 column major(列主序)排布,分形内部按 row major(行主序)排布,输出 Tensor 形状变为(N1, M1, M0, N0):
[[[[ 0. 1. 2. ... 13. 14. 15.] [ 28. 29. 30. ... 41. 42. 43.] [ 56. 57. 58. ... 69. 70. 71.] ... [ 0. 0. 0. ... 0. 0. 0.]]] ...ND转Nz转换示意图
对比转换前后的打印结果可以直观看到:padding 补齐无效数据、reshape 拆分分形、transpose 重排分形顺序,正是 4.1 节"pad → reshape → transpose"公式的具体落地。类似的,ND(GM)到 Nz(L1 Buffer)的搬运转换示意可见 nd2nz_gm2l1_fractals_transform.png,其中 M=40、K=56 的 half 矩阵经 M 轴补齐至 48(16×3)、K 轴补齐至 64(16×4)后,在 L1 Buffer 中重组为 3×4 个分形块。
4.6 卷积场景的 NC1HWC0 格式
NC1HWC0 是用于卷积加速的数据格式:将 NHWC 数据在 C 维度进行 split,变成 C1 份 NHWC0,然后将 C1 份 NHWC0 在内存中连续排列。C0 固定为 32 Byte,C1 = C/C0,若不整除,最后一份数据需要 padding 到 C0。该格式通常位于 GM 或 L1 Buffer 中,作为卷积的 Feature Map 输入,通常使用 DataCopy(GMToL1随路转换-ND2NZ搬运) 指令完成 NHWC→NC1HWC0 的转换。
五、辅助矩阵分形格式:MX 量化系数与稀疏索引
除了三大主角,还有两类"辅助矩阵"也必须有明确的分形排布,详见 辅助矩阵分形格式详解。
5.1 MX 格式与 scaleA / scaleB 系数矩阵
MX(Microscaling,微缩放)格式是一种块数据格式,若干个数据组成一个块(组),由三部分构成:共享缩放因子 X(位宽 w bits)、私有元素 P_i(位宽 d bits)、块大小 k(多少个低比特数据形成一个块)。每个包含 k 个元素的块可用(w + k×d)位编码,从而在低位宽下保持稳健的 AI 训练和推理模型精度。
MXMmad 即带有量化系数 X 的矩阵乘法:C = (scaleA ⊗ A) × (scaleB ⊗ B) + Bias,K 方向上每 32 个元素共享一个量化因子。其中 A 为左矩阵 shape [M, K],scaleA 为左量化系数矩阵 shape [M, K/32];B 为右矩阵 shape [K, N],scaleB 为右量化系数矩阵 shape [K/32, N]。
- scaleA(Zz 格式):L1 Buffer 上需满足行读取需求,按行存储为小z大Z排布(Zz)。scaleA 矩阵被分为 M1×K1 个分形(row major 排布,Z 字形),每个分形内部 M0×K0 个元素(row major 排布,z 字形);分形 shape 必须与 L0A_MX Buffer 物理内存排布一致,数据类型必须为 fp8_e8m0_t,shape 为 16×2。转换公式:
(…, B, M, K/32)->pad->(…, B, M1 * M0, K1 * K0)->reshape->(…, B, M1, M0, K1, K0)->transpose->(…, B, M1, K1, M0, K0)- scaleB(Nn 格式):L1 Buffer 上需满足列读取需求,按列存储为大N小n排布(Nn)。scaleB 矩阵被分为 K1×N1 个分形(column major 排布,N 字形),每个分形内部 K0×N0 个元素(column major 排布,n 字形);分形 shape 必须与 L0B_MX Buffer 物理内存排布一致,数据类型必须为 fp8_e8m0_t,shape 为 2×16。转换公式:
(…, B, K/32, N)->pad->(…, B, K1 * K0, N1 * N0)->reshape->(…, B, K1, K0, N1, N0)->transpose->(…, B, N1, K1, N0, K0)若 GM 上 scaleA/scaleB 为 ND 排布,可通过 ND2NZ.b16 / DN2NZ.b16 指令搬运到 L1 Buffer,确保 L1 Buffer 上分形为 Zz / Nn 排布,以满足 LoadData(MX 矩阵搬运)接口要求。
源码佐证:仓库示例 load_data_2dmx_l12l0.asc 完整演示了 MX FP4/FP8 矩阵乘法场景下的搬运方法。其中明确标注"L1/L0 上排布格式为 Zz,scaleA 矩阵的对齐"(第 127 行)与"L1/L0 上排布格式为 Nn,scaleB 矩阵的对齐"(第 131 行),且 scaleA 使用fp8_e8m0_t数据类型、scaleB 同样如此(第 141-154 行),与文档描述完全一致。同时该样例展示了用LoadData2DParamsV2控制 A/B 矩阵搬运、用LoadData2DMxParams控制 scaleA/scaleB 矩阵搬运的参数分工(详见文件头部注释第 14-16 行)。具体实现可参考 MXmmad样例。
5.2 4选2稀疏索引矩阵(Zn 格式)
针对 Atlas A2 系列产品和 Atlas A3 系列产品,Sparse 矩阵乘法引入了索引矩阵:
- 定义:原始 B 矩阵稠密化过程中生成的索引矩阵。假设原始 B 矩阵每 4 个元素有至少 2 个零元素,稠密化的 B 矩阵是过滤掉 2 个零的稠密矩阵。
- 生成规则:对于原始矩阵 B 中的每 4 个元素,在 index 矩阵中生成 2 个 2 位索引(索引必须在 {0, 1, 2} 范围内):第一个索引指示前 3 个元素中第 1 个非零元素的相对位置,第二个索引指示第 2 个非零元素在后 3 个元素中的相对位置。索引矩阵中的一个 uint8 元素由 4 个 uint2_t 组合而成。
- 排布要求:sparse 矩阵乘法中 B 矩阵与 Index 矩阵物理地址一一映射,LoadDataWithSparse 指令要求在 L1 Buffer 上 B 矩阵与 Index 矩阵满足列读取需求,因此 Index 矩阵在 GM/L1 Buffer 上为按列存储的大Z小n排布(Zn)。
- 格式转换:原始 B 矩阵(K, N)经 4 选 2 稠密化后,B 与 Index 矩阵大小为(K/2, N)。Index 矩阵被分为 K1×N1 个分形(row major 排布,Z 字形),每个分形内部 N0×K0 个元素(column major 排布,n 字形),故称 Zn。转换公式:
(…, B, K/2, N)->pad->(…, B, K1 * K0, N1 * N0)->reshape->(…, B, K1, K0, N1, N0)->transpose->(…, B, K1, N1, N0, K0)- 分形大小约束:Index 矩阵分形需与 B 矩阵分形保持一致(B 矩阵数据类型为 int8_t,小分形 shape 为 16×32),Index 分形中每个 uint8_t 数据由 4 个 uint2_t 元素拼接而成,元素与 B 矩阵元素地址一一对应,因此分形 shape 也为 16×32,但大小为 128 Byte。
六、分形格式在代码中的落地:从枚举到搬运指令
从仓库源码可以进一步确认分形格式并非纯文档概念,而是贯穿接口定义与实现:
类型系统层面:kernel_utils_mode.h 中的
TShapeType枚举定义了ND、FRACTAL_NZ、FRACTAL_Z、NC1HWC0等完整格式类型;文件头部的注释(第 28-50 行)给出了一整套格式转换映射表,例如ND -> FRACTAL_NZ、ND -> FRACTAL_Z、NC1HWC0 -> NHWC等,说明分形转换是 Ascend C 数据搬运接口内置的语义能力。搬运接口层面:DataCopy(GMToL1随路转换-ND2NZ搬运) 支持在数据搬运过程中完成 ND→NZ 的随路格式转换,产品支持情况覆盖 Ascend 950PR&950DT、Atlas A3、Atlas A2、Atlas 推理系列(AI Core)、Kirin X90/9030 等;而 LoadData 系列接口则通过
LoadData2DMxParams等参数结构体控制 MX 场景下 scaleA/scaleB 的搬运与排布。示例层面:load_data_2dmx_l12l0.asc 中
CopyInScaleA1/CopyInScaleB1函数使用Dn2NzParams/Nd2NzParams完成 scaleA/scaleB 从 GM 到 L1 的搬运,并在注释中明确 L1/L0 上排布为 Zz(scaleA)与 Nn(scaleB),是"文档格式要求 → 接口参数选择"的直接对应示例。
七、总结与实践要点
分形存储格式是昇腾矩阵计算单元高效运转的基石,核心要点可归纳如下:
- 物理动因:16×16 分块计算要求块内数据物理连续,分形重排将"多次离散寻址"变为"一次连续读取",是吞吐率的根本保障。
- 命名法:"大Y小x"中,大写 Z/N 描述分形块间的排布(行/列主序),小写 z/n 描述分形块内的排布(行/列主序),两者独立组合即可推导出 Zz、Zn、Nz、Nn 全部格式。
- 矩阵乘法组合:主流产品为 Zz × Zn = Nz(A×B=C),Ascend 950PR&950DT 为 Nz × Zn = Nz;左矩阵的行读取需求对应 Zz,右矩阵的列读取需求对应 Zn,结果矩阵固定为 Nz。
- 分形尺寸规则:分形大小为 16 ×(32B / sizeof(Datatype)),M0/N0 固定为 16,K0 随位宽变化(32-bit→8、16-bit→16、8-bit→32、4-bit→64)。
- 辅助矩阵同样重要:MX 场景下 scaleA 为 Zz(16×2,fp8_e8m0_t)、scaleB 为 Nn(2×16);Sparse 场景下 Index 矩阵为 Zn,且分形与 B 矩阵一一对应。
- 工程落地:格式转换统一走 "pad → reshape → transpose" 公式;GM→L1 的随路转换可借助
DataCopy+Nd2NzParams/Dn2NzParams完成,MX 场景可参考仓库 load_data_2dmx_l12l0 样例。
在实际使用 Ascend C 的Mmad类接口(参见 mmad_compute)进行算子开发时,建议先确认目标产品型号对应的格式组合,再依据本文的格式约束规划数据搬运与分块逻辑,即可避免因排布错误导致的搬运失败或性能损失。
- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
相关推荐
Mmad(Cube 矩阵乘)性能测试实战:基于 asc-devkit 的仿真测量与 Roofline 算力分析
Mmad(Cube 矩阵乘)性能测试实战:基于 asc devkit 的仿真测量与 Roofline 算力分析 本篇技术指南以 CANN 昇腾算子开发套件 as
人工智能深度学习算子库CANNAscendPyPTO Pro Cube 矩阵计算编程指南:基于 L1/L0A/L0B/L0C 存储分层的分形矩阵乘
PyPTO Pro Cube 矩阵计算编程指南:基于 L1/L0A/L0B/L0C 存储分层的分形矩阵乘 本文以 CANN/PyPTO 项目中的 Cube 计算
人工智能编译器模型编译深度学习高性能计算CANNAscendasc-devkit LoadData(L1→L0)性能测试样例实战指南:场景矩阵、带宽计算与 Roofline 分析
asc devkit LoadData(L1→L0)性能测试样例实战指南:场景矩阵、带宽计算与 Roofline 分析 导读 本文围绕 CANN asc dev
人工智能深度学习算子库CANNAscend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考