已合并
mmad样例优化重构 #1454
wx369创建于 4月7日
mmad样例优化重构 #1454
已合并
wx369创建于 4月7日
11 个文件变更+686-205
Mexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/README.md+1-1
@@ -22,7 +22,7 @@
22| [load_data_with_transpose_b16](./load_data_with_transpose_b16) | 本样例介绍基础API LoadDataWithTranspose b16数据类型下的使用 |22| [load_data_with_transpose_b16](./load_data_with_transpose_b16) | 本样例介绍基础API LoadDataWithTranspose b16数据类型下的使用 |
23| [load_data_with_transpose_b32](./load_data_with_transpose_b32) | 本样例介绍基础API LoadDataWithTranspose b32数据类型下的使用 |23| [load_data_with_transpose_b32](./load_data_with_transpose_b32) | 本样例介绍基础API LoadDataWithTranspose b32数据类型下的使用 |
24| [batch_mmad](./batch_mmad) | 本样例介绍在输入为float数据类型并且左、右矩阵均不转置的场景下,带batch的矩阵乘法,其中从GM-->L1、L0C-->GM、L0C-->L1这三条通路分别采用了DataCopy ND2NZ和Fixpipe批量搬运数据,从L1-->L0A/L0B以及Mmad执行矩阵乘这两个步骤则是循环batch次,每次循环内只处理一对左、右矩阵 |24| [batch_mmad](./batch_mmad) | 本样例介绍在输入为float数据类型并且左、右矩阵均不转置的场景下,带batch的矩阵乘法,其中从GM-->L1、L0C-->GM、L0C-->L1这三条通路分别采用了DataCopy ND2NZ和Fixpipe批量搬运数据,从L1-->L0A/L0B以及Mmad执行矩阵乘这两个步骤则是循环batch次,每次循环内只处理一对左、右矩阵 |
25-| [mmad](./mmad) | 本样例介绍基于基础API Mmad实现矩阵乘 |25+| [mmad](./mmad) | 本样例介绍了输入为ND格式,B4 / B8 / B16 / B32输入数据类型(具体以int4_t / int8_t / half / float为例),四种输入数据类型下的矩阵乘法,说明如何通过Mmad指令实现矩阵乘法计算(C = A x B + Bias) |
26| [mmad_load3dv2](./mmad_load3dv2) | 本样例介绍LoadData3DV2指令将A、B矩阵从L1搬运到L0A/L0B的过程,其中 A 和 B 分别表示矩阵乘法的左右输入矩阵。LoadData3DV2指令参数配置及执行指令前后各个矩阵数据排布变化,均配合示意图进行了说明 |26| [mmad_load3dv2](./mmad_load3dv2) | 本样例介绍LoadData3DV2指令将A、B矩阵从L1搬运到L0A/L0B的过程,其中 A 和 B 分别表示矩阵乘法的左右输入矩阵。LoadData3DV2指令参数配置及执行指令前后各个矩阵数据排布变化,均配合示意图进行了说明 |
27| [mmad_s8_f16_f32_with_A_B_transpose_option](./mmad_s8_f16_f32_with_A_B_transpose_option) | 本样例介绍了在 int8_t / half / float 三种数据类型下,以及左、右矩阵均不转置 / 左矩阵不转置、右矩阵转置 / 左矩阵转置、右矩阵不转置 / 左、右矩阵均转置 共 12 种矩阵乘法场景中,相关指令的使用方法,其中 A 和 B 分别表示矩阵乘法的左右输入矩阵。|27| [mmad_s8_f16_f32_with_A_B_transpose_option](./mmad_s8_f16_f32_with_A_B_transpose_option) | 本样例介绍了在 int8_t / half / float 三种数据类型下,以及左、右矩阵均不转置 / 左矩阵不转置、右矩阵转置 / 左矩阵转置、右矩阵不转置 / 左、右矩阵均转置 共 12 种矩阵乘法场景中,相关指令的使用方法,其中 A 和 B 分别表示矩阵乘法的左右输入矩阵。|
28| [mmad_unitflag](./mmad_unitflag) | 本样例介绍是否使能unitFlag对于Mmad指令执行矩阵乘法性能的影响。 |28| [mmad_unitflag](./mmad_unitflag) | 本样例介绍是否使能unitFlag对于Mmad指令执行矩阵乘法性能的影响。 |
Mexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/CMakeLists.txt+13-15
@@ -1,5 +1,5 @@
1# ----------------------------------------------------------------------------------------------------------1# ----------------------------------------------------------------------------------------------------------
2-# Copyright (c) 2025 Huawei Technologies Co., Ltd.2+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
3# This program is free software, you can redistribute it and/or modify it under the terms and conditions of3# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4# CANN Open Software License Agreement Version 2.0 (the "License").4# CANN Open Software License Agreement Version 2.0 (the "License").
5# Please refer to the License for details. You may not use this file except in compliance with the License.5# Please refer to the License for details. You may not use this file except in compliance with the License.
@@ -8,9 +8,11 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------------------------------------9# ----------------------------------------------------------------------------------------------------------
10 10 
11- 
12cmake_minimum_required(VERSION 3.16)11cmake_minimum_required(VERSION 3.16)
13 12 
13+set(CMAKE_ASC_RUN_MODE "npu" CACHE STRING "Run mode: npu, cpu, sim")
14+set(CMAKE_ASC_ARCHITECTURES "dav-2201" CACHE STRING "NPU architecture: dav-2201, dav-3510")
15+ 
14find_package(ASC REQUIRED)16find_package(ASC REQUIRED)
15 17 
16project(kernel_samples LANGUAGES ASC CXX)18project(kernel_samples LANGUAGES ASC CXX)
@@ -19,20 +21,16 @@ add_executable(demo
19 mmad.asc21 mmad.asc
20)22)
21 23 
22-target_link_libraries(demo PRIVATE24+set(SCENARIO_NUM "1" CACHE STRING "SCENARIO_NUM, e.g. 1 2 3 4")
23- tiling_api25+if(SCENARIO_NUM EQUAL 1 AND CMAKE_ASC_ARCHITECTURES STREQUAL "dav-3510")
24- register26+ message(FATAL_ERROR "When SCENARIO_NUM=1, only CMAKE_ASC_ARCHITECTURES=dav-2201 is supported.")
25- platform27+endif()
26- m28+ 
29+message(STATUS "[INFO] SCENARIO_NUM is ${SCENARIO_NUM}")
30+target_compile_definitions(demo PRIVATE
31+ SCENARIO_NUM=${SCENARIO_NUM}
27)32)
28 33 
29-# ======================================================================================
30-# NPU 编译选项配置
31-#
32-# 说明:
33-# - 需根据实际部署的 NPU 硬件架构选择对应的 `npu-arch` 参数。
34-# ======================================================================================
35target_compile_options(demo PRIVATE34target_compile_options(demo PRIVATE
36- $<$<COMPILE_LANGUAGE:ASC>:--npu-arch=dav-2201>35+ $<$<COMPILE_LANGUAGE:ASC>:--npu-arch=${CMAKE_ASC_ARCHITECTURES}>
37- # $<$<COMPILE_LANGUAGE:ASC>:--npu-arch=dav-3510>
38)36)
Mexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/README.md+193-32
@@ -1,6 +1,6 @@
1# Mmad样例1# Mmad样例
2## 概述2## 概述
3-本样例介绍基于基础API实现矩阵乘Mmad样例3+本样例介绍了输入为ND格式,B4 / B8 / B16 / B32输入数据类型(具体以int4_t / int8_t / half / float为例),四种输入数据类型下的矩阵乘法,说明如何通过Mmad指令实现矩阵乘法计算(C = A x B + Bias)
4 4 
5## 支持的产品5## 支持的产品
6- Ascend 950PR/Ascend 950DT6- Ascend 950PR/Ascend 950DT
@@ -10,6 +10,7 @@
10## 目录结构介绍10## 目录结构介绍
11```11```
12├── mmad12├── mmad
13+│ ├── img // 本文中的插图文件
13│ ├── scripts14│ ├── scripts
14│ │ ├── gen_data.py // 输入数据和真值数据生成脚本15│ │ ├── gen_data.py // 输入数据和真值数据生成脚本
15│ │ └── verify_result.py // 验证输出数据和真值数据是否一致的验证脚本16│ │ └── verify_result.py // 验证输出数据和真值数据是否一致的验证脚本
@@ -18,35 +19,174 @@
18│ └── mmad.asc // Ascend C算子实现 & 调用样例19│ └── mmad.asc // Ascend C算子实现 & 调用样例
19```20```
20 21 
21-## 算子描述22+## 样例描述
22-- 算子功能23+一次完整的矩阵乘法涉及的数据搬运过程包括:GM-->L1、L1-->L0A/L0B、L1-->BT(BiasTable Buffer)、L0C-->GM,其中不同存储单元的数据排布格式,如下表1所示
23- 本样例中实现的是[m, n, k]固定为[32, 32, 32]的Matmul算子,并使用Ascend C基础Api实现。对应的数学表达式为:24+ 
24- ```25+<table border="2" align="center">
25- C = A * B26+<caption>表1:不同存储单元的数据排布格式</caption>
26- ```27+ <tr>
27-- 算子规格:28+ <td >存储单元</td>
28- <table>29+ <td>数据排布格式</td>
29- <tr><td rowspan="1" align="center">算子类型(OpType)</td><td colspan="4" align="center">Add</td></tr>30+ </tr>
30- </tr>31+ <tr>
31- <tr><td rowspan="3" align="center">算子输入</td><td align="center">name</td><td align="center">shape</td><td align="center">data type</td><td align="center">format</td></tr>32+ <td>GM</td>
32- <tr><td align="center">x</td><td align="center">32 * 32</td><td align="center">half</td><td align="center">ND</td></tr>33+ <td>输入A、B矩阵和输出C矩阵为ND排列。</td>
33- <tr><td align="center">y</td><td align="center">32 * 32</td><td align="center">half</td><td align="center">ND</td></tr>34+ </tr>
34- </tr>35+ <tr>
35- </tr>36+ <td>L1</td>
36- <tr><td rowspan="1" align="center">算子输出</td><td align="center">z</td><td align="center">32 * 32</td><td align="center">float</td><td align="center">ND</td></tr>37+ <td>A、B矩阵为Nz排列。</td>
37- </tr>38+ </tr>
38- <tr><td rowspan="1" align="center">核函数名</td><td colspan="4" align="center">mmad_custom</td></tr>39+ <tr>
39- </table>40+ <td>L0A</td>
40-- 算子实现: 41+ <td>Ascend 950PR/Ascend 950DT产品,A矩阵为Nz排列;<br>Atlas A3 训练系列产品/Atlas A3 推理系列产品和Atlas A2 训练系列产品/Atlas A2 推理系列产品,A矩阵为Zz排列。</td>
41- Mmad算子的数学表达式为:42+ </tr>
42- ```43+ <tr>
43- C = A * B44+ <td>L0B</td>
44- ```45+ <td>B矩阵为Zn排列。</td>
45- 计算逻辑是:Ascend C提供的矩阵乘计算接口的操作元素都为LocalTensor,输入数据需要先搬运进片上存储并进行分形转换,然后使用计算接口完成两个输入参数矩阵乘运算,得到最终结果,再搬出到外部存储上。 46+ </tr>
47+ <tr>
48+ <td>BT(BiasTable Buffer)</td>
49+ <td>Bias是shape为[N]的一维Tensor。</td>
50+ </tr>
51+ <tr>
52+ <td>L0C</td>
53+ <td>C矩阵为Nz排列。</td>
54+ </tr>
55+</table>
56+ 
57+通常的矩阵乘法计算公式:C = A × B + Bias,其中A、B、Bias、C矩阵的需要满足的shape分别为[M,K]、[K,N]、[N]和[M,N]。Bias的数据类型与C矩阵数据类型的对应关系,如表2所示:
58+<table border="2" align="center">
59+<caption>表 2:L0C 与输入 Bias 的数据类型对应关系</caption>
60+ <tr>
61+ <td>Bias在GM/L1上的数据类型</td>
62+ <td>Bias在BT(BiasTable Buffer)上的数据类型</td>
63+ <td>矩阵计算输出到L0C上的数据类型</td>
64+ </tr>
65+ <tr>
66+ <td>int32_t</td>
67+ <td>int32_t</td>
68+ <td>int32_t</td>
69+ </tr>
70+ <tr>
71+ <td>bfloat16</td>
72+ <td rowspan="3">float</td>
73+ <td rowspan="3">float</td>
74+ </tr>
75+ <tr>
76+ <td>half</td>
77+ </tr>
78+ <tr>
79+ <td>float</td>
80+ </tr>
81+</table>
82+ 
83+程序中scenarioNum参数不同取值对应的场景,如下表3所示:
84+ 
85+<table border="2" align="center">
86+<caption>表3:scenarioNum不同取值的含义</caption>
87+ <tr>
88+ <td >scenarioNum</td>
89+ <td>输入数据类型</td>
90+ <td>输出数据类型</td>
91+ <td>A矩阵</td>
92+ <td>B矩阵</td>
93+ <td>Bias</td>
94+ </tr>
95+ <tr>
96+ <td>1</td>
97+ <td>int4b_t</td>
98+ <td>int32_t</td>
99+ <td>不转置</td>
100+ <td>转置</td>
101+ <td>不启用Bias,C矩阵的初始值为0</td>
102+ </tr>
103+ <tr>
104+ <td>2</td>
105+ <td>int8_t</td>
106+ <td>int32_t</td>
107+ <td>不转置</td>
108+ <td>不转置</td>
109+ <td>带Bias且不传入biasTensor,C矩阵的初始值来源于C2</td>
110+ </tr>
111+ <tr>
112+ <td>3</td>
113+ <td>bfloat16</td>
114+ <td>bfloat16</td>
115+ <td>不转置</td>
116+ <td>转置</td>
117+ <td>不带Bias,C矩阵累加来源于CO1初始值</td>
118+ </tr>
119+ <tr>
120+ <td>4</td>
121+ <td>float</td>
122+ <td>float</td>
123+ <td>转置</td>
124+ <td>转置</td>
125+ <td>带Bias且传入biasTensor的场景。</td>
126+ </tr>
127+</table>
128+ 
129+### 场景详细说明
130+ 本样例通过编译参数 `SCENARIO_NUM` 选择不同的输出场景,所有场景基于相同的矩阵乘规格:[M, N, K] = [30, 40, 70],核函数名为 `mmad_custom`
131+ 
132+**场景1 int4b_t输入,int32_t输出,C矩阵初始值为0**
133+ 
134+- 输入:A不转置 [30, 70] int4b_t类型,ND格式;B转置 [40, 70] int4b_t类型,ND格式;不带Bias
135+- 输出:C [30, 40] int32_t类型,ND格式
136+- 实现:使用`Mmad`实现矩阵乘法运算,通过参数:`mmadParams.cmatrixInitVal = true`,设置C矩阵初始值为0
137+- 说明:该场景仅支持Atlas A3 训练系列产品/Atlas A3 推理系列产品/Atlas A2 训练系列产品/Atlas A2 推理系列产品,且不支持使用传入biasTensor的方式加Bias(场景4)。
138+ 
139+**场景2 int8_t输入,int32_t输出,C矩阵初始值来源于C2**
140+- 输入:A不转置 [30, 70] int8_t类型,ND格式;B不转置 [70, 40] int8_t类型,ND格式;Bias [40] int32_t类型
141+- 输出:C [30, 40] int32_t类型,ND格式
142+- 实现:使用`Mmad`实现矩阵乘法运算,不传入biasTensor通过参数:`mmadParams.cmatrixInitVal = false、mmadParams.cmatrixSource = true`,设置C矩阵初始值来源于C2
143+- 说明:int8_t类型输入,B矩阵不转置场景下,N轴向2 * 16对齐,填充了全部是无效数据的32 * 16的分形。如下图1所示,如果设置`mmadParams.n = N`,就会导致读入编号为3、7的分形,同时又没能将包含有效数据的编号为9、10的分形读入。因此需要设置:`mmadParams.n = CeilAlign(N, BLOCK_CUBE * fractalNum)`,此时会读入全部分型,虽然矩阵计算结果中包含了无效数据参与计算的结果,但是在Fixpipe指令搬出数据时通过设置`fixpipeParams.nSize = N`来保证无效数据参与计算的结果不会被搬出。
144+<p align="center">
145+ <img src="img/mmad_s8_L0B_转置.png" width="700">
W

图加白色背景

likedislike
146+</p>
147+<p align="center">
148+图1:int8_t类型,B不转置,N轴实际对齐要求与Mmad指令默认不一致
149+</p>
150+ 
151+**场景3 bfloat16输入,float输出,A不转置,B转置,C矩阵初始值来源于CO1**
152+- 输入:A不转置 [30, 70] bfloat16类型,ND格式;B转置 [40, 70] bfloat16类型,ND格式;不带Bias,C矩阵初始值来源于CO1
153+- 输出:C [30, 40] float类型,ND格式
154+- 实现:使用`Mmad`实现矩阵乘法运算,通过参数:`mmadParams.cmatrixInitVal = false、mmadParams.cmatrixSource = false`,设置C矩阵初始值来源于CO1
155+- 说明:该场景进行两次Mmad计算,第一次计算结果存储在CO1,作为下一次计算的C矩阵初始值,最终累加两次Mmad计算结果。
156+ 
157+**场景4 float输入,float输出,A转置,B转置,传入biasTensor,kDirectionAlign值设为true**
158+- 输入:A转置 [70, 30] float类型,ND格式;B转置 [40, 70] float类型,ND格式;Bias [40] float类型
159+- 输出:C [30, 40] float类型,ND格式
160+- 实现:使用`Mmad`实现矩阵乘法运算,传入biasTensor,该场景下`mmadParams.cmatrixSource`参数无效
161+- 说明:float类型输入,A矩阵转置场景下,需要使用`mmadParams.kDirectionAlign`来解决K轴实际向`CeilAlign(K, 8*2)`对齐,与Mmad指令默认要求对齐到`CeilAlign(K, 8)`不一的问题。该场景下该参数设置为真,K轴对齐到`CeilAlign(K, 16)`,矩阵计算单元从L0A读取数据会跳过填充的无效数据,其余场景下该参数默认为flase,K轴仍向`CeilAlign(K, 8)`对齐,如下图2所示:
162+<p align="center">
163+ <img src="img/mmad_f32_L0A_转置.png" width="1100">
164+</p>
W

这个图最下面的字“图:float类型,A转置....”不要写在图里,单独用文字写。不然字体会大小跟其他不一致。

likedislike
165+<p align="center">
166+图2:float类型,A转置,K轴实际对齐与Mmad指令默认要求不一致
167+</p>
168+ 
169+### 矩阵乘法(Mmad)
170+ 
171+下面将介绍如何配置
172+[Mmad](https://www.hiascend.com/document/detail/zh/canncommercial/850/API/ascendcopapi/atlasascendc_api_07_0249.html)指令的MmadParams结构体的成员,各个成员变量的具体含义这里不再赘述。
173+ 
174+需要注意的是当Mmad指令执行时,矩阵计算单元会从L0A/L0B连续读入多个分形参与矩阵乘计算,读入分形的数量根据MmadParams结构体的成员变量m、n、k的取值以及Mmad指令对L0A/L0B上A矩阵和B矩阵各个轴的对齐要求来计算的。以输入为b16类型为例:Mmad指令是按照A矩阵分形为[16,16]、B矩阵分形为[16,16]来连续读入分形的,也就是说矩阵计算单元从L0A/L0B连续读入的分形总数目分别为:2x5=10、5x3=15,写入到L0C的分形总数为2x3=6。如下图所示,图3表示Atlas A3 训练系列产品/Atlas A3 推理系列产品和Atlas A2 训练系列产品/Atlas A2 推理系列产品,图4表示Ascend 950PR/Ascend 950DT,两者在L0A上的数据排布不一致,前者为Zz,后者则是Nz。
175+<p align="center">
176+ <img src="img/mmad_f16_A3.png" width="900">
177+</p>
178+<p align="center">
179+图3:bfloat16类型,L0A上Zz排布,Mmad数据排布示意图
180+</p>
181+<p align="center">
182+ <img src="img/mmad_f16_A5.png" width="900">
183+</p>
184+<p align="center">
185+图4:bfloat16类型,L0A上Nz排布,Mmad数据排布示意图
186+</p>
187+ 
188+Mmad计算中包含了补齐的无效数据,需要配合Fixpipe指令在L0C搬出到GM过程中,排除参数Mmad计算过程中填充的无效数据。
46 189 
47- Mmad算子的实现流程分为基本任务:CopyIn,SplitA,SplitB,Compute,CopyOut。CopyIn任务负责将Global Memory上的输入inputGM搬运到Local Memory A1/B1中,搬运过程中进行ND至NZ分形转换。SplitA/SplitB分别将数据进一步搬运至接口所要求Local Memory A2/B2,Compute任务负责对数据进行矩阵乘运算,计算结果存储在Local Memory CO1中。CopyOut任务负责将输出数据从CO1搬运至Global Memory上的输出outputGm中,同时完成NZ到ND的分形转换。
48- - 调用实现
49- 使用内核调用符<<<>>>调用核函数。
50 190 
51## 编译运行191## 编译运行
52在本样例根目录下执行如下步骤,编译并执行算子。192在本样例根目录下执行如下步骤,编译并执行算子。
@@ -69,12 +209,33 @@
69 209
70- 样例执行210- 样例执行
71 ```bash211 ```bash
212+ SCENARIO=1
72 mkdir -p build && cd build; # 创建并进入build目录213 mkdir -p build && cd build; # 创建并进入build目录
73- cmake ..;make -j; # 编译工程214+ cmake .. -DCMAKE_ASC_ARCHITECTURES=dav-2201 -DSCENARIO_NUM=$SCENARIO;make -j; # 编译工程,默认npu模式
74- python3 ../scripts/gen_data.py # 生成测试输入数据215+ python3 ../scripts/gen_data.py -scenarioNum=$SCENARIO # 生成测试输入数据
75 ./demo # 执行编译生成的可执行程序,执行样例216 ./demo # 执行编译生成的可执行程序,执行样例
76- python3 ../scripts/verify_result.py output/output.bin output/golden.bin # 验证输出结果是否正确,确认算法逻辑正确217+ python3 ../scripts/verify_result.py -scenarioNum=$SCENARIO output/output.bin output/golden.bin # 验证输出结果是否正确,确认算法逻辑正确
77 ```218 ```
219+ 
220+ 使用NPU仿真 模式时,添加`-DCMAKE_ASC_RUN_MODE=sim` 参数即可。
221+
222+ 示例如:
223+ ```bash
224+ cmake .. -DCMAKE_ASC_RUN_MODE=sim -DCMAKE_ASC_ARCHITECTURES=dav-2201 -DSCENARIO_NUM=$SCENARIO;make -j; # NPU仿真模式
225+ ```
226+ 
227+ > **注意:** 切换编译模式前需清理 cmake 缓存,可在 build 目录下执行 `rm CMakeCache.txt` 后重新 cmake。
228+ 
229+- 编译选项说明
230+ 
231+ | 参数 | 说明 | 可选值 | 默认值 |
232+ |------|------|--------|--------|
233+ | CMAKE_ASC_RUN_MODE | 运行模式 | npu、sim | npu |
234+ | CMAKE_ASC_ARCHITECTURES | NPU硬件架构 | dav-2201、dav-3510 | dav-2201 |
235+ | SCENARIO_NUM | 场景编号 | 1-4 | 1 |
236+ 
237+- 执行结果
238+ 
78 执行结果如下,说明精度对比成功。239 执行结果如下,说明精度对比成功。
79 ```bash240 ```bash
80 test pass!241 test pass!
Mexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/data_utils.h+1-1
@@ -1,5 +1,5 @@
1/**1/**
2-* Copyright (c) 2025 Huawei Technologies Co., Ltd.2+* Copyright (c) 2026 Huawei Technologies Co., Ltd.
3* This program is free software, you can redistribute it and/or modify it under the terms and conditions of3* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4* CANN Open Software License Agreement Version 2.0 (the "License").4* CANN Open Software License Agreement Version 2.0 (the "License").
5* Please refer to the License for details. You may not use this file except in compliance with the License.5* Please refer to the License for details. You may not use this file except in compliance with the License.
Aexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/img/mmad_f16_A3.png+3-0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:864ff9243c6c95ce0e61d15eb73eb8e30c9512f9def78ff98546d168d196c4bc
3+size 59044
Aexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/img/mmad_f16_A5.png+3-0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:72d96363ac7a36005aee2de877caa3568c4a0495b06dbf09f96974c86a6f500c
3+size 57652
Aexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/img/mmad_f32_L0A_转置.png+3-0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:333826ed2bbc6a9f6d98df9dc03f2ae6e819e09c873e13123d7587336b1c2ff7
3+size 55443
Aexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/img/mmad_s8_L0B_转置.png+3-0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:d11706405c15e9a1943e925df11d2b3d242f12315211fd532321af3cd4ed897b
3+size 79422
Mexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/mmad.asc+395-132
@@ -1,5 +1,5 @@
1/**1/**
2-* Copyright (c) 2025 Huawei Technologies Co., Ltd.2+* Copyright (c) 2026 Huawei Technologies Co., Ltd.
3* This program is free software, you can redistribute it and/or modify it under the terms and conditions of3* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4* CANN Open Software License Agreement Version 2.0 (the "License").4* CANN Open Software License Agreement Version 2.0 (the "License").
5* Please refer to the License for details. You may not use this file except in compliance with the License.5* Please refer to the License for details. You may not use this file except in compliance with the License.
@@ -11,193 +11,443 @@
11 11 
12/* !12/* !
13 * \file mmad.asc13 * \file mmad.asc
14- * \brief14+ * \brief 本文介绍了int4b_t / int8_t / half / float,四种数据类型下的矩阵乘法,包含3种启用bias的方式和kDirectionAlign参数的使用。
15 */15 */
16- 16+#ifdef ASCENDC_CPU_DEBUG
17+#include "cpu_debug_launch.h"
18+#endif
17#include "acl/acl.h"19#include "acl/acl.h"
18#include "kernel_operator.h"20#include "kernel_operator.h"
19#include "data_utils.h"21#include "data_utils.h"
20 22 
21-// half type, cube block: [16, 16]23+constexpr uint32_t scenarioNum = SCENARIO_NUM;
22-constexpr uint32_t CUBE_BLOCK = 16;24+constexpr bool HAS_BIAS_TENSOR = (SCENARIO_NUM == 2 || SCENARIO_NUM == 4);
23-constexpr uint32_t CUBE_BLOCK_SIZE = 16 * 16;25+constexpr uint32_t BLOCK_CUBE = 16;
26+constexpr uint64_t ADDR_0 = 0;
24 27 
28+template <class T, class U, uint32_t M, uint32_t N, uint32_t K, bool isATranspose, bool isBTranspose>
25class KernelMmad {29class KernelMmad {
26public:30public:
27 __aicore__ inline KernelMmad()31 __aicore__ inline KernelMmad()
28 {32 {
29- aSize = m * k;33+ // 根据不同输入类型设置c0Size
30- bSize = k * n;34+ if constexpr (SCENARIO_NUM == 1) {
31- cSize = m * n;35+ c0Size = 64;
36+ fractalNum = 4;
37+ // A不转,B转
38+ aSizeAlignL1 = CeilAlign(M, BLOCK_CUBE) * CeilAlign(K, c0Size);
39+ aSizeAlignL0 = CeilAlign(M, BLOCK_CUBE) * CeilAlign(K, c0Size);
40+ bSizeAlignL1 = CeilAlign(N, BLOCK_CUBE) * CeilAlign(K, c0Size);
41+ bSizeAlignL0 = CeilAlign(K, c0Size) * CeilAlign(N, BLOCK_CUBE);
42+ } else if constexpr (SCENARIO_NUM == 2) {
43+ c0Size = 32;
44+ fractalNum = 2;
45+ // A、B 都不转
46+ aSizeAlignL1 = CeilAlign(M, BLOCK_CUBE) * CeilAlign(K, c0Size);
47+ aSizeAlignL0 = CeilAlign(M, BLOCK_CUBE) * CeilAlign(K, c0Size);
48+ bSizeAlignL1 = CeilAlign(K, BLOCK_CUBE * fractalNum) * CeilAlign(N, c0Size);
49+ bSizeAlignL0 = CeilAlign(K, c0Size) * CeilAlign(N, BLOCK_CUBE * fractalNum);
50+ } else if constexpr (SCENARIO_NUM == 3) {
51+ c0Size = 16;
52+ fractalNum = 1;
53+ // A不转,B转
54+ aSizeAlignL1 = CeilAlign(M, BLOCK_CUBE) * CeilAlign(K, c0Size);
55+ aSizeAlignL0 = CeilAlign(M, BLOCK_CUBE) * CeilAlign(K, c0Size);
56+ bSizeAlignL1 = CeilAlign(N, BLOCK_CUBE) * CeilAlign(K, c0Size);
57+ bSizeAlignL0 = CeilAlign(K, c0Size) * CeilAlign(N, BLOCK_CUBE);
58+ } else {
59+ c0Size = 8;
60+ fractalNum = 2;
61+ // A、B都转
62+ aSizeAlignL1 = CeilAlign(K, BLOCK_CUBE) * CeilAlign(M, c0Size * fractalNum);
63+ aSizeAlignL0 = CeilAlign(M, BLOCK_CUBE) * CeilAlign(K, c0Size * fractalNum);
64+ bSizeAlignL1 = CeilAlign(N, BLOCK_CUBE) * CeilAlign(K, c0Size);
65+ bSizeAlignL0 = CeilAlign(K, c0Size) * CeilAlign(N, BLOCK_CUBE);
66+ }
67+ fractalSize = BLOCK_CUBE * c0Size;
68+ cSizeAlignL0 = CeilAlign(M, BLOCK_CUBE) * CeilAlign(N, BLOCK_CUBE);
69+ 
70+#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
71+ // 对于Ascend 950PR/Ascend 950DT,C1->C2,要求待搬运数据的连续传输数据块长度(blockLen)的单位是32B,且数据类型为b32时值需为偶数
72+ biasSizeAlign = CeilAlign(CeilDiv(N * sizeof(T), 32), 2) * 32;
73+#else
74+ // Atlas A3 训练系列产品/Atlas A3 推理系列产品、Atlas A2 训练系列产品/Atlas A2 推理系列产品,C1->C2,要求blockLen的单位是64B
75+ biasSizeAlign = CeilAlign(N * sizeof(T), 64);
76+#endif
77+ a1Addr = ADDR_0;
78+ b1Addr = a1Addr + aSizeAlignL1 * sizeof(U);
79+ c1Addr = a1Addr + b1Addr + bSizeAlignL1 * sizeof(U);
32 }80 }
33- __aicore__ inline void Init(GM_ADDR a, GM_ADDR b, GM_ADDR c)81+ __aicore__ inline void Init(GM_ADDR a, GM_ADDR b, GM_ADDR bias, GM_ADDR c)
34 {82 {
35- aGM.SetGlobalBuffer((__gm__ half *)a);83+ aGM.SetGlobalBuffer((__gm__ U *)a);
36- bGM.SetGlobalBuffer((__gm__ half *)b);84+ bGM.SetGlobalBuffer((__gm__ U *)b);
37- cGM.SetGlobalBuffer((__gm__ float *)c);85+ biasGM.SetGlobalBuffer((__gm__ T *)bias);
38- pipe.InitBuffer(inQueueA1, 1, aSize * sizeof(half));86+ cGM.SetGlobalBuffer((__gm__ T *)c);
39- pipe.InitBuffer(inQueueA2, 1, aSize * sizeof(half));
40- pipe.InitBuffer(inQueueB1, 1, bSize * sizeof(half));
41- pipe.InitBuffer(inQueueB2, 1, bSize * sizeof(half));
42- pipe.InitBuffer(outQueueCO1, 1, cSize * sizeof(float));
43 }87 }
44 __aicore__ inline void Process()88 __aicore__ inline void Process()
45 {89 {
46- CopyIn();90+ AscendC::LocalTensor<U> a1Local(AscendC::TPosition::A1, a1Addr, aSizeAlignL1);
47- SplitA();91+ AscendC::LocalTensor<U> b1Local(AscendC::TPosition::B1, b1Addr, bSizeAlignL1);
48- SplitB();92+ AscendC::LocalTensor<T> bias1Local(AscendC::TPosition::C1, c1Addr, biasSizeAlign);
49- Compute();93+ 
50- CopyOut();94+ // GM -> L1
95+ CopyIn(a1Local, b1Local, bias1Local);
96+ // L1 -> L0A
97+ AscendC::LocalTensor<U> a2Local(AscendC::TPosition::A2, ADDR_0, aSizeAlignL0);
98+ if constexpr (isATranspose) {
99+ SplitATranspose(a1Local, a2Local);
100+ } else {
101+ SplitA(a1Local, a2Local);
102+ }
103+ // L1 -> L0B
104+ AscendC::LocalTensor<U> b2Local(AscendC::TPosition::B2, ADDR_0, bSizeAlignL0);
105+ if constexpr (!isBTranspose) {
106+ SplitBTranspose(b1Local, b2Local);
107+ } else {
108+ SplitB(b1Local, b2Local);
109+ }
110+ // L1 -> BT
111+ AscendC::LocalTensor<T> bias2Local(AscendC::TPosition::C2, ADDR_0, biasSizeAlign);
112+ if constexpr (HAS_BIAS_TENSOR) {
113+ SplitBias(bias1Local, bias2Local);
114+ }
115+ // Mmad
116+ AscendC::LocalTensor<T> co1Local(AscendC::TPosition::CO1, ADDR_0, cSizeAlignL0);
117+ Compute(a2Local, b2Local, bias2Local, co1Local);
118+ // L0C -> GM
119+ CopyOut(co1Local);
51 }120 }
52 121 
53private:122private:
54- __aicore__ inline uint32_t CeilCubeBlock(uint32_t len) {123+ // 向上整除
55- return (len + CUBE_BLOCK - 1) / CUBE_BLOCK;124+ __aicore__ inline uint32_t CeilDiv(uint32_t numerator, uint32_t denominator)
125+ {
126+ return (numerator + denominator - 1) / denominator;
127+ }
128+ // 向上对齐
129+ __aicore__ inline uint32_t CeilAlign(uint32_t numerator, uint32_t denominator)
130+ {
131+ return (numerator + denominator - 1) / denominator * denominator;
56 }132 }
57 133 
58- __aicore__ inline void CopyIn()134+ __aicore__ inline void CopyIn(AscendC::LocalTensor<U>& a1Local, AscendC::LocalTensor<U>& b1Local,
135+ AscendC::LocalTensor<T>& bias1Local)
59 {136 {
60- AscendC::LocalTensor<half> a1Local = inQueueA1.AllocTensor<half>();
61- AscendC::LocalTensor<half> b1Local = inQueueB1.AllocTensor<half>();
62- 
63 AscendC::Nd2NzParams nd2nzA1Params;137 AscendC::Nd2NzParams nd2nzA1Params;
64- nd2nzA1Params.ndNum = 1;138+ if constexpr (isATranspose) {
65- nd2nzA1Params.nValue = m;139+#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
66- nd2nzA1Params.dValue = k;140+ // ND -> Nz
67- nd2nzA1Params.srcNdMatrixStride = 0;141+ nd2nzA1Params.ndNum = 1;
68- nd2nzA1Params.srcDValue = k;142+ nd2nzA1Params.nValue = K;
69- nd2nzA1Params.dstNzC0Stride = CeilCubeBlock(m) * CUBE_BLOCK;143+ nd2nzA1Params.dValue = M;
70- nd2nzA1Params.dstNzNStride = 1;144+ nd2nzA1Params.srcNdMatrixStride = 0;
71- nd2nzA1Params.dstNzMatrixStride = 0;145+ nd2nzA1Params.srcDValue = M;
146+ nd2nzA1Params.dstNzC0Stride = CeilAlign(K, BLOCK_CUBE);
147+ nd2nzA1Params.dstNzNStride = 1;
148+ nd2nzA1Params.dstNzMatrixStride = 0;
149+#else
150+ // ND -> Zz
151+ nd2nzA1Params.ndNum = CeilDiv(K, BLOCK_CUBE);
152+ nd2nzA1Params.nValue = BLOCK_CUBE;
153+ nd2nzA1Params.dValue = M;
154+ nd2nzA1Params.srcNdMatrixStride = BLOCK_CUBE * M;
155+ nd2nzA1Params.srcDValue = M;
156+ nd2nzA1Params.dstNzC0Stride = BLOCK_CUBE;
157+ nd2nzA1Params.dstNzNStride = 1;
158+ nd2nzA1Params.dstNzMatrixStride = BLOCK_CUBE * CeilAlign(M, BLOCK_CUBE * fractalNum);
159+#endif
160+ } else {
161+ // ND -> Nz
162+ nd2nzA1Params.ndNum = 1;
163+ nd2nzA1Params.nValue = M;
164+ nd2nzA1Params.dValue = K;
165+ nd2nzA1Params.srcNdMatrixStride = 0;
166+ nd2nzA1Params.srcDValue = K;
167+ nd2nzA1Params.dstNzC0Stride = CeilAlign(M, BLOCK_CUBE);
168+ nd2nzA1Params.dstNzNStride = 1;
169+ nd2nzA1Params.dstNzMatrixStride = 0;
170+ if constexpr (AscendC::IsSameType<U, AscendC::int4b_t>::value) {
171+ // 两个int4b_t合并成int8_t, 进行GM-->L1, nd2nz的搬运
172+ nd2nzA1Params.dValue = K >> 1;
173+ nd2nzA1Params.srcDValue = K >> 1;
174+ }
175+ }
72 AscendC::DataCopy(a1Local, aGM, nd2nzA1Params);176 AscendC::DataCopy(a1Local, aGM, nd2nzA1Params);
177+ AscendC::SetFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID0);
73 178 
74 AscendC::Nd2NzParams nd2nzB1Params;179 AscendC::Nd2NzParams nd2nzB1Params;
75- nd2nzB1Params.ndNum = 1;180+ if constexpr (isBTranspose) {
76- nd2nzB1Params.nValue = k;181+ nd2nzB1Params.ndNum = 1;
77- nd2nzB1Params.dValue = n;182+ nd2nzB1Params.nValue = N;
78- nd2nzB1Params.srcNdMatrixStride = 0;183+ nd2nzB1Params.dValue = K;
79- nd2nzB1Params.srcDValue = n;184+ nd2nzB1Params.srcNdMatrixStride = 0;
80- nd2nzB1Params.dstNzC0Stride = CeilCubeBlock(k) * CUBE_BLOCK;185+ nd2nzB1Params.srcDValue = K;
81- nd2nzB1Params.dstNzNStride = 1;186+ nd2nzB1Params.dstNzC0Stride = CeilAlign(N, BLOCK_CUBE);
82- nd2nzB1Params.dstNzMatrixStride = 0;187+ nd2nzB1Params.dstNzNStride = 1;
188+ nd2nzB1Params.dstNzMatrixStride = 0;
189+ if constexpr (AscendC::IsSameType<U, AscendC::int4b_t>::value) {
190+ // 两个int4b_t合并成int8_t, 进行GM-->L1, nd2nz的搬运
191+ nd2nzB1Params.dValue = K >> 1;
192+ nd2nzB1Params.srcDValue = K >> 1;
193+ }
194+ } else {
195+ nd2nzB1Params.ndNum = 1;
196+ nd2nzB1Params.nValue = K;
197+ nd2nzB1Params.dValue = N;
198+ nd2nzB1Params.srcNdMatrixStride = 0;
199+ nd2nzB1Params.srcDValue = N;
200+ nd2nzB1Params.dstNzC0Stride = CeilAlign(K, BLOCK_CUBE * fractalNum);
201+ nd2nzB1Params.dstNzNStride = 1;
202+ nd2nzB1Params.dstNzMatrixStride = 0;
203+ }
83 AscendC::DataCopy(b1Local, bGM, nd2nzB1Params);204 AscendC::DataCopy(b1Local, bGM, nd2nzB1Params);
205+ AscendC::SetFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID1);
84 206 
85- inQueueA1.EnQue(a1Local);207+ if constexpr (HAS_BIAS_TENSOR) {
86- inQueueB1.EnQue(b1Local);208+ // 由于N可能非32B对齐,“基础数据搬运”无法处理非对齐数据,且“DataCopyPad”不支持GM到L1的搬运,
209+ // 因此,使用“随路转换ND2Nz搬运”实现Bias的GM到L1搬运,确保搬运到L1数据32B对齐。
210+ AscendC::Nd2NzParams nd2nzC1Params = {1, 1, N, 0, N, 1, 1, 0};
211+ AscendC::DataCopy(bias1Local, biasGM, nd2nzC1Params);
212+ AscendC::SetFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID2);
213+ }
87 }214 }
88 215 
89- __aicore__ inline void SplitA()216+ __aicore__ inline void SplitA(AscendC::LocalTensor<U>& a1Local, AscendC::LocalTensor<U>& a2Local)
90 {217 {
91- AscendC::LocalTensor<half> a1Local = inQueueA1.DeQue<half>();218+ AscendC::WaitFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID0);
92- AscendC::LocalTensor<half> a2Local = inQueueA2.AllocTensor<half>();219+#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
93- 220+ // Nz -> Nz, NPU_ARCH为3510时,要求A2上的数据排布为: Nz
94-#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 2201 || __NPU_ARCH__ == 2202)
95- uint32_t dstOffset = CeilCubeBlock(k) * CUBE_BLOCK_SIZE;
96- uint32_t srcOffset = CUBE_BLOCK_SIZE;
97- // Nz -> Zz
98- AscendC::LoadData2DParams loadDataParams;
99- loadDataParams.repeatTimes = CeilCubeBlock(k);
100- loadDataParams.srcStride = CeilCubeBlock(m);
101- loadDataParams.dstGap = 0;
102- loadDataParams.ifTranspose = false;
103- for (int i = 0; i < CeilCubeBlock(m); ++i) {
104- AscendC::LoadData(a2Local[i * dstOffset], a1Local[i * srcOffset], loadDataParams);
105- }
106-#elif defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
107 AscendC::LoadData2DParamsV2 loadDataParams;221 AscendC::LoadData2DParamsV2 loadDataParams;
108 loadDataParams.mStartPosition = 0;222 loadDataParams.mStartPosition = 0;
109 loadDataParams.kStartPosition = 0;223 loadDataParams.kStartPosition = 0;
110- loadDataParams.mStep = AscendC::DivCeil(m, 16);224+ loadDataParams.mStep = CeilDiv(M, BLOCK_CUBE);
111- loadDataParams.kStep = AscendC::DivCeil(k * sizeof(half), 32);225+ loadDataParams.kStep = CeilDiv(K, c0Size);
112- loadDataParams.srcStride = AscendC::DivCeil(m, 16);226+ loadDataParams.srcStride = CeilDiv(M, BLOCK_CUBE);
113- loadDataParams.dstStride = AscendC::DivCeil(m, 16);227+ loadDataParams.dstStride = CeilDiv(M, BLOCK_CUBE);
114- loadDataParams.sid = 0;
115 loadDataParams.ifTranspose = false;228 loadDataParams.ifTranspose = false;
116 AscendC::LoadData(a2Local, a1Local, loadDataParams);229 AscendC::LoadData(a2Local, a1Local, loadDataParams);
117-#endif230+#else
118- inQueueA2.EnQue<half>(a2Local);231+ // Nz -> Zz, NPU_ARCH为2201时,要求A2上的数据排布为: Zz
119- inQueueA1.FreeTensor(a1Local);232+ uint32_t dstOffset = CeilDiv(K, c0Size) * fractalSize;
120- }233+ uint32_t srcOffset = fractalSize;
121- __aicore__ inline void SplitB()
122- {
123- AscendC::LocalTensor<half> b1Local = inQueueB1.DeQue<half>();
124- AscendC::LocalTensor<half> b2Local = inQueueB2.AllocTensor<half>();
125- 
126- uint32_t dstOffset = CeilCubeBlock(n) * CUBE_BLOCK_SIZE;
127- uint32_t srcOffset = CUBE_BLOCK_SIZE;
128- // Nz -> Zn
129 AscendC::LoadData2DParams loadDataParams;234 AscendC::LoadData2DParams loadDataParams;
130- loadDataParams.repeatTimes = CeilCubeBlock(n);235+ loadDataParams.startIndex = 0;
131- loadDataParams.srcStride = CeilCubeBlock(k);236+ loadDataParams.repeatTimes = CeilDiv(K, c0Size);
237+ loadDataParams.srcStride = CeilDiv(M, BLOCK_CUBE);
132 loadDataParams.dstGap = 0;238 loadDataParams.dstGap = 0;
239+ loadDataParams.ifTranspose = false;
240+ for (int i = 0; i < CeilDiv(M, BLOCK_CUBE); ++i) {
241+ AscendC::LoadData(a2Local[i * dstOffset], a1Local[i * srcOffset], loadDataParams);
242+ }
243+#endif
244+ AscendC::SetFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID0);
245+ }
246+ 
247+ __aicore__ inline void SplitATranspose(AscendC::LocalTensor<U>& a1Local, AscendC::LocalTensor<U>& a2Local)
248+ {
249+ AscendC::WaitFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID0);
250+#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
251+ // Nz -> Nz, NPU_ARCH为3510时,要求A2上的数据排布为: Nz
252+ AscendC::LoadData2DParamsV2 loadDataParams;
253+ loadDataParams.mStartPosition = 0;
254+ loadDataParams.kStartPosition = 0;
255+ // 开启转置时,b4 mStep必须是4的倍数,b8 mStep必须是2的倍数,b32 kStep必须是2的倍数
256+ loadDataParams.mStep = CeilDiv(K, BLOCK_CUBE);
257+ loadDataParams.kStep = CeilAlign(CeilDiv(M, c0Size), 2);
258+ loadDataParams.srcStride = CeilDiv(K, BLOCK_CUBE);
259+ loadDataParams.dstStride = CeilDiv(M, c0Size * fractalNum);
133 loadDataParams.ifTranspose = true;260 loadDataParams.ifTranspose = true;
134- for (int i = 0; i < CeilCubeBlock(k); ++i) {261+ AscendC::LoadData(a2Local, a1Local, loadDataParams);
135- AscendC::LoadData(b2Local[i * dstOffset], b1Local[i * srcOffset], loadDataParams);262+#else
263+ // Nz -> Zz, NPU_ARCH为2201时,要求A2上的数据排布为: Zz
264+ uint32_t dstOffset = CeilDiv(K, c0Size * fractalNum) * fractalSize * fractalNum;
265+ uint32_t srcOffset = fractalSize * fractalNum;
266+ 
267+ AscendC::LoadData2dTransposeParams loadDataParams;
268+ loadDataParams.startIndex = 0;
269+ loadDataParams.repeatTimes = CeilDiv(K, c0Size * fractalNum);
270+ loadDataParams.srcStride = CeilDiv(M, c0Size * fractalNum);
271+ loadDataParams.dstGap = 1;
272+ loadDataParams.dstFracGap = 0;
273+ for (int i = 0; i < CeilDiv(M, c0Size * fractalNum); ++i) {
274+ AscendC::LoadDataWithTranspose(a2Local[i * dstOffset], a1Local[i * srcOffset], loadDataParams);
275+ }
276+#endif
277+ AscendC::SetFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID0);
278+ }
279+ 
280+ __aicore__ inline void SplitB(AscendC::LocalTensor<U>& b1Local, AscendC::LocalTensor<U>& b2Local)
281+ {
282+ AscendC::WaitFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID1);
283+ // B2上的数据排布为: Zn
284+ AscendC::LoadData2DParams loadDataParams;
285+ loadDataParams.repeatTimes = CeilDiv(N, BLOCK_CUBE) * CeilDiv(K, c0Size);
286+ loadDataParams.srcStride = 1;
287+ loadDataParams.dstGap = 0;
288+ loadDataParams.ifTranspose = false;
289+ AscendC::LoadData(b2Local, b1Local, loadDataParams);
290+ AscendC::SetFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID1);
291+ }
292+ 
293+ __aicore__ inline void SplitBTranspose(AscendC::LocalTensor<U>& b1Local, AscendC::LocalTensor<U>& b2Local)
294+ {
295+ AscendC::WaitFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID1);
296+ uint32_t dstOffset = CeilDiv(N, BLOCK_CUBE * fractalNum) * fractalSize * fractalNum;
297+ uint32_t srcOffset = fractalSize * fractalNum;
298+ AscendC::LoadData2dTransposeParams loadDataParams;
299+ loadDataParams.startIndex = 0;
300+ loadDataParams.repeatTimes = CeilDiv(N, c0Size);
301+ loadDataParams.srcStride = CeilDiv(K, BLOCK_CUBE * fractalNum);
302+ loadDataParams.dstGap = 1;
303+ loadDataParams.dstFracGap = 0;
304+ for (int i = 0; i < CeilDiv(K, BLOCK_CUBE * fractalNum); ++i) {
305+ AscendC::LoadDataWithTranspose(b2Local[i * dstOffset], b1Local[i * srcOffset], loadDataParams);
306+ }
307+ AscendC::SetFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID1);
308+ }
309+ 
310+ __aicore__ inline void SplitBias(AscendC::LocalTensor<T>& bias1Local, AscendC::LocalTensor<T>& bias2Local)
311+ {
312+ AscendC::WaitFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID2);
313+#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
314+ AscendC::DataCopyParams c12c2Params = {1, static_cast<uint16_t>(biasSizeAlign / 32), 0, 0};
315+#else
316+ AscendC::DataCopyParams c12c2Params = {1, static_cast<uint16_t>(biasSizeAlign / 64), 0, 0};
317+#endif
318+ AscendC::DataCopy(bias2Local, bias1Local, c12c2Params);
319+ AscendC::SetFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID2);
320+ }
321+ 
322+ __aicore__ inline void Compute(AscendC::LocalTensor<U>& a2Local, AscendC::LocalTensor<U>& b2Local,
323+ AscendC::LocalTensor<T>& bias2Local, AscendC::LocalTensor<T>& co1Local)
324+ {
325+ AscendC::WaitFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID0);
326+ AscendC::WaitFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID1);
327+ 
328+ AscendC::MmadParams mmadParams;
329+ mmadParams.m = M;
330+ mmadParams.n = N;
331+ mmadParams.k = K;
332+ 
333+ if constexpr (AscendC::IsSameType<U, int8_t>::value && !isBTranspose) {
334+ mmadParams.n = CeilAlign(N, BLOCK_CUBE * fractalNum);
335+ }
336+ if constexpr (AscendC::IsSameType<U, float>::value && isATranspose) {
337+ mmadParams.kDirectionAlign = true;
136 }338 }
137 339 
138- inQueueB1.FreeTensor(b1Local);340+ if constexpr (SCENARIO_NUM == 1) {
139- inQueueB2.EnQue<half>(b2Local);341+ // 不带Bias场景
342+ mmadParams.cmatrixInitVal = true; // C矩阵初始值为0
343+ AscendC::Mmad(co1Local, a2Local, b2Local, mmadParams);
344+ } else if constexpr (SCENARIO_NUM == 2) {
345+ // 带Bias且不传入biasTensor,C矩阵的初始值来源于C2
346+ mmadParams.cmatrixInitVal = false;
347+ mmadParams.cmatrixSource = true;
348+ AscendC::WaitFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID2);
349+ AscendC::Mmad(co1Local, a2Local, b2Local, mmadParams);
350+ } else if constexpr (SCENARIO_NUM == 3) {
351+ // 不带Bias,C矩阵累加来源于CO1初始值
352+ // 第一次mmad计算,CO1的计算结果作为下一次mmad计算C矩阵的初始值
353+ AscendC::Mmad(co1Local, a2Local, b2Local, mmadParams);
354+ mmadParams.cmatrixInitVal = false;
355+ mmadParams.cmatrixSource = false; // C矩阵的初始值来源于CO1
356+ AscendC::Mmad(co1Local, a2Local, b2Local, mmadParams);
357+ } else {
358+ // 带Bias且传入biasTensor的场景,该场景下cmatrixSource参数无效
359+ mmadParams.cmatrixInitVal = false;
360+ AscendC::WaitFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID2);
361+ AscendC::Mmad(co1Local, a2Local, b2Local, bias2Local, mmadParams);
362+ }
363+ AscendC::SetFlag<AscendC::HardEvent::M_FIX>(EVENT_ID0);
140 }364 }
141- __aicore__ inline void Compute()365+ __aicore__ inline void CopyOut(AscendC::LocalTensor<T>& co1Local)
142 {366 {
143- AscendC::LocalTensor<half> a2Local = inQueueA2.DeQue<half>();367+ AscendC::WaitFlag<AscendC::HardEvent::M_FIX>(EVENT_ID0);
144- AscendC::LocalTensor<half> b2Local = inQueueB2.DeQue<half>();368+#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
145- AscendC::LocalTensor<float> c1Local = outQueueCO1.AllocTensor<float>();369+ AscendC::FixpipeParamsArch3510<AscendC::CO2Layout::ROW_MAJOR> fixpipeParams;
146- AscendC::MmadParams mmadParams;370+#else
147- mmadParams.m = m;
148- mmadParams.n = n;
149- mmadParams.k = k;
150- AscendC::Mmad(c1Local, a2Local, b2Local, mmadParams);
151- outQueueCO1.EnQue<float>(c1Local);
152- inQueueA2.FreeTensor(a2Local);
153- inQueueB2.FreeTensor(b2Local);
154- }
155- __aicore__ inline void CopyOut()
156- {
157- AscendC::LocalTensor<float> c1Local = outQueueCO1.DeQue<float>();
158 AscendC::FixpipeParamsV220 fixpipeParams;371 AscendC::FixpipeParamsV220 fixpipeParams;
159- fixpipeParams.nSize = n;
160- fixpipeParams.mSize = m;
161- fixpipeParams.srcStride = m;
162- fixpipeParams.dstStride = n;
163- 
164 fixpipeParams.ndNum = 1;372 fixpipeParams.ndNum = 1;
165 fixpipeParams.srcNdStride = 0;373 fixpipeParams.srcNdStride = 0;
166 fixpipeParams.dstNdStride = 0;374 fixpipeParams.dstNdStride = 0;
167- AscendC::Fixpipe(cGM, c1Local, fixpipeParams);375+#endif
168- outQueueCO1.FreeTensor(c1Local);376+ fixpipeParams.mSize = M;
377+ fixpipeParams.nSize = N;
378+ fixpipeParams.srcStride = CeilAlign(M, BLOCK_CUBE);
379+ fixpipeParams.dstStride = N;
380+ 
381+ AscendC::Fixpipe(cGM, co1Local, fixpipeParams);
169 }382 }
170 383 
171private:384private:
172- AscendC::TPipe pipe;385+ AscendC::GlobalTensor<U> aGM;
173- AscendC::TQue<AscendC::TPosition::A1, 1> inQueueA1;386+ AscendC::GlobalTensor<U> bGM;
174- AscendC::TQue<AscendC::TPosition::A2, 1> inQueueA2;387+ AscendC::GlobalTensor<T> biasGM;
175- AscendC::TQue<AscendC::TPosition::B1, 1> inQueueB1;388+ AscendC::GlobalTensor<T> cGM;
176- AscendC::TQue<AscendC::TPosition::B2, 1> inQueueB2;
177- AscendC::TQue<AscendC::TPosition::CO1, 1> outQueueCO1;
178 389 
179- AscendC::GlobalTensor<half> aGM;390+ uint32_t c0Size, fractalSize, fractalNum;
180- AscendC::GlobalTensor<half> bGM;391+ uint32_t aSizeAlignL1, bSizeAlignL1, biasSizeAlign, aSizeAlignL0, bSizeAlignL0, cSizeAlignL0;
181- AscendC::GlobalTensor<float> cGM;392+ 
182- uint16_t m = 32, k = 32, n = 32;393+ uint64_t a1Addr, b1Addr, c1Addr;
183- uint16_t aSize, bSize, cSize;
184};394};
185 395 
186-extern "C" __global__ __cube__ void mmad_custom(GM_ADDR a, GM_ADDR b, GM_ADDR c)396+template <uint32_t M, uint32_t N, uint32_t K>
397+__global__ __cube__ void mmad_custom(GM_ADDR a, GM_ADDR b, GM_ADDR bias, GM_ADDR c)
187{398{
188- KernelMmad op;399+ AscendC::InitSocState();
189- op.Init(a, b, c);400+ if constexpr (scenarioNum == 1) {
190- op.Process();401+ KernelMmad<int32_t, AscendC::int4b_t, M, N, K, false, true> op;
402+ op.Init(a, b, bias, c);
403+ op.Process();
404+ } else if constexpr (scenarioNum == 2) {
405+ KernelMmad<int32_t, int8_t, M, N, K, false, false> op;
406+ op.Init(a, b, bias, c);
407+ op.Process();
408+ } else if constexpr (scenarioNum == 3) {
409+ KernelMmad<float, bfloat16_t, M, N, K, false, true> op;
410+ op.Init(a, b, bias, c);
411+ op.Process();
412+ } else {
413+ KernelMmad<float, float, M, N, K, true, true> op;
414+ op.Init(a, b, bias, c);
415+ op.Process();
416+ }
417+ AscendC::PipeBarrier<PIPE_ALL>();
191}418}
192 419 
193int32_t main(int32_t argc, char *argv[])420int32_t main(int32_t argc, char *argv[])
194{421{
195- uint32_t M = 32;422+ constexpr uint32_t M = 30;
196- uint32_t N = 32;423+ constexpr uint32_t N = 40;
197- uint32_t K = 32;424+ constexpr uint32_t K = 70;
198- size_t aFileSize = M * K * sizeof(int16_t); // uint16_t represent half425+ size_t aFileSize = 0;
199- size_t bFileSize = K * N * sizeof(int16_t); // uint16_t represent half426+ size_t bFileSize = 0;
200- size_t cFileSize = M * N * sizeof(float);427+ size_t biasFileSize = 0;
428+ size_t cFileSize = 0;
429+ 
430+ if constexpr (scenarioNum == 1) {
431+ aFileSize = M * K * sizeof(int8_t) / 2;
432+ bFileSize = K * N * sizeof(int8_t) / 2;
433+ biasFileSize = N * sizeof(int32_t);
434+ cFileSize = M * N * sizeof(int32_t);
435+ } else if constexpr (scenarioNum == 2) {
436+ aFileSize = M * K * sizeof(int8_t);
437+ bFileSize = K * N * sizeof(int8_t);
438+ biasFileSize = N * sizeof(int32_t);
439+ cFileSize = M * N * sizeof(int32_t);
440+ } else if constexpr (scenarioNum == 3) {
441+ aFileSize = M * K * sizeof(bfloat16_t);
442+ bFileSize = K * N * sizeof(bfloat16_t);
443+ biasFileSize = N * sizeof(float);
444+ cFileSize = M * N * sizeof(float);
445+ } else {
446+ aFileSize = M * K * sizeof(float);
447+ bFileSize = K * N * sizeof(float);
448+ biasFileSize = N * sizeof(float);
449+ cFileSize = M * N * sizeof(float);
450+ }
201 uint32_t numBlocks = 1;451 uint32_t numBlocks = 1;
202 452 
203 aclInit(nullptr);453 aclInit(nullptr);
@@ -220,12 +470,21 @@ int32_t main(int32_t argc, char *argv[])
220 ReadFile("./input/x2_gm.bin", bFileSize, bHost, bFileSize);470 ReadFile("./input/x2_gm.bin", bFileSize, bHost, bFileSize);
221 aclrtMemcpy(bDevice, bFileSize, bHost, bFileSize, ACL_MEMCPY_HOST_TO_DEVICE);471 aclrtMemcpy(bDevice, bFileSize, bHost, bFileSize, ACL_MEMCPY_HOST_TO_DEVICE);
222 472 
473+ uint8_t *biasHost;
474+ uint8_t *biasDevice;
475+ if constexpr (HAS_BIAS_TENSOR) {
476+ aclrtMallocHost((void **)(&biasHost), biasFileSize);
477+ aclrtMalloc((void **)&biasDevice, biasFileSize, ACL_MEM_MALLOC_HUGE_FIRST);
478+ ReadFile("./input/bias_gm.bin", biasFileSize, biasHost, biasFileSize);
479+ aclrtMemcpy(biasDevice, biasFileSize, biasHost, biasFileSize, ACL_MEMCPY_HOST_TO_DEVICE);
480+ }
481+ 
223 uint8_t *cHost;482 uint8_t *cHost;
224 uint8_t *cDevice;483 uint8_t *cDevice;
225 aclrtMallocHost((void **)(&cHost), cFileSize);484 aclrtMallocHost((void **)(&cHost), cFileSize);
226 aclrtMalloc((void **)&cDevice, cFileSize, ACL_MEM_MALLOC_HUGE_FIRST);485 aclrtMalloc((void **)&cDevice, cFileSize, ACL_MEM_MALLOC_HUGE_FIRST);
227 486 
228- mmad_custom<<<numBlocks, nullptr, stream>>>(aDevice, bDevice, cDevice);487+ mmad_custom<M, N, K><<<numBlocks, nullptr, stream>>>(aDevice, bDevice, biasDevice, cDevice);
229 aclrtSynchronizeStream(stream);488 aclrtSynchronizeStream(stream);
230 489 
231 aclrtMemcpy(cHost, cFileSize, cDevice, cFileSize, ACL_MEMCPY_DEVICE_TO_HOST);490 aclrtMemcpy(cHost, cFileSize, cDevice, cFileSize, ACL_MEMCPY_DEVICE_TO_HOST);
@@ -235,6 +494,10 @@ int32_t main(int32_t argc, char *argv[])
235 aclrtFreeHost(aHost);494 aclrtFreeHost(aHost);
236 aclrtFree(bDevice);495 aclrtFree(bDevice);
237 aclrtFreeHost(bHost);496 aclrtFreeHost(bHost);
497+ if constexpr (HAS_BIAS_TENSOR) {
498+ aclrtFree(biasDevice);
499+ aclrtFreeHost(biasHost);
500+ }
238 aclrtFree(cDevice);501 aclrtFree(cDevice);
239 aclrtFreeHost(cHost);502 aclrtFreeHost(cHost);
240 503 
Mexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/scripts/gen_data.py+50-13
@@ -2,7 +2,7 @@
2# coding=utf-82# coding=utf-8
3 3 
4# ----------------------------------------------------------------------------------------------------------4# ----------------------------------------------------------------------------------------------------------
5-# Copyright (c) 2025 Huawei Technologies Co., Ltd.5+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
6# This program is free software, you can redistribute it and/or modify it under the terms and conditions of6# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
7# CANN Open Software License Agreement Version 2.0 (the "License").7# CANN Open Software License Agreement Version 2.0 (the "License").
8# Please refer to the License for details. You may not use this file except in compliance with the License.8# Please refer to the License for details. You may not use this file except in compliance with the License.
@@ -11,26 +11,63 @@
11# See LICENSE in the root of the software repository for the full text of the License.11# See LICENSE in the root of the software repository for the full text of the License.
12# ----------------------------------------------------------------------------------------------------------12# ----------------------------------------------------------------------------------------------------------
13 13 
14- 14+import argparse
15import os15import os
16import numpy as np16import numpy as np
17+from ml_dtypes import bfloat16
17 18 
18 19 
19-def gen_golden_data():20+def gen_golden_data(scenario_num=1):
20- M = 3221+ m=30
21- N = 3222+ k=70
22- K = 3223+ n=40
24+ if scenario_num == 1:
25+ x1_gm_int8 = np.random.randint(1, 7, [m, k]).astype(np.int8)
26+ x2_gm_int8 = np.random.randint(1, 7, [k, n]).astype(np.int8)
27+ 
28+ golden = np.matmul(x1_gm_int8.astype(np.int32), x2_gm_int8.astype(np.int32)).astype(np.int32)
29+ x2_gm_int8 = x2_gm_int8.transpose()
30+ 
31+ x1_gm = np.zeros(shape=[m, k // 2]).astype(np.int8)
32+ for i in range(m):
33+ for j in range(k):
34+ if j % 2 == 0:
35+ x1_gm[i][j // 2] = (x1_gm_int8[i][j + 1] << 4) + (x1_gm_int8[i][j] & 0x0f)
36+ x2_gm = np.zeros(shape=[n, k // 2]).astype(np.int8)
37+ for i in range(n):
38+ for j in range(k):
39+ if j % 2 == 0:
40+ x2_gm[i][j // 2] = (x2_gm_int8[i][j + 1] << 4) + (x2_gm_int8[i][j] & 0x0f)
41+ elif scenario_num == 2:
42+ x1_gm = np.random.randint(1, 10, [m, k]).astype(np.int8)
43+ x2_gm = np.random.randint(1, 10, [k, n]).astype(np.int8)
44+ bias_gm = np.random.randint(1, 10, [n]).astype(np.int32)
45+ golden = np.matmul(x1_gm.astype(np.int32), x2_gm.astype(np.int32)).astype(np.int32) + bias_gm
46+ elif scenario_num == 3:
47+ x1_gm = np.random.uniform(1, 10, [m, k]).astype(bfloat16)
48+ x2_gm = np.random.uniform(1, 10, [k, n]).astype(bfloat16)
49+ golden = np.matmul(x1_gm.astype(np.float32), x2_gm.astype(np.float32)).astype(np.float32) * 2
50+ x2_gm = x2_gm.transpose()
51+ else:
52+ x1_gm = np.random.uniform(1, 10, [m, k]).astype(np.float32)
53+ x2_gm = np.random.uniform(1, 10, [k, n]).astype(np.float32)
54+ bias_gm = np.random.uniform(1, 10, [n]).astype(np.float32)
55+ golden = np.matmul(x1_gm.astype(np.float32), x2_gm.astype(np.float32)).astype(np.float32) + bias_gm
56+ x1_gm = x1_gm.transpose()
57+ x2_gm = x2_gm.transpose()
58+ 
59+ os.makedirs("input", exist_ok=True)
60+ os.makedirs("output", exist_ok=True)
23 61 
24- x1_gm = np.random.uniform(1, 10, [M, K]).astype(np.float16)
25- x2_gm = np.random.uniform(1, 10, [K, N]).astype(np.float16)
26- golden = np.matmul(x1_gm.astype(np.float32), x2_gm.astype(np.float32))
27- golden = golden.astype(np.float32)
28- os.system("mkdir -p input")
29- os.system("mkdir -p output")
30 x1_gm.tofile("./input/x1_gm.bin")62 x1_gm.tofile("./input/x1_gm.bin")
31 x2_gm.tofile("./input/x2_gm.bin")63 x2_gm.tofile("./input/x2_gm.bin")
64+ if scenario_num in [2, 4]:
65+ bias_gm.tofile("./input/bias_gm.bin")
32 golden.tofile("./output/golden.bin")66 golden.tofile("./output/golden.bin")
33 67 
34 68 
35if __name__ == "__main__":69if __name__ == "__main__":
36- gen_golden_data()70+ parser = argparse.ArgumentParser()
71+ parser.add_argument('-scenarioNum', type=int, default=1, choices=range(1, 5))
72+ args = parser.parse_args()
73+ gen_golden_data(args.scenarioNum)
Mexamples/01_simd_cpp_api/02_features/03_basic_api/01_matrix_compute/mmad/scripts/verify_result.py+21-11
@@ -2,7 +2,7 @@
2# coding=utf-82# coding=utf-8
3 3 
4# ----------------------------------------------------------------------------------------------------------4# ----------------------------------------------------------------------------------------------------------
5-# Copyright (c) 2025 Huawei Technologies Co., Ltd.5+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
6# This program is free software, you can redistribute it and/or modify it under the terms and conditions of6# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
7# CANN Open Software License Agreement Version 2.0 (the "License").7# CANN Open Software License Agreement Version 2.0 (the "License").
8# Please refer to the License for details. You may not use this file except in compliance with the License.8# Please refer to the License for details. You may not use this file except in compliance with the License.
@@ -11,10 +11,9 @@
11# See LICENSE in the root of the software repository for the full text of the License.11# See LICENSE in the root of the software repository for the full text of the License.
12# ----------------------------------------------------------------------------------------------------------12# ----------------------------------------------------------------------------------------------------------
13 13 
14- 14+import argparse
15import sys15import sys
16import numpy as np16import numpy as np
17-import tensorflow as tf
18 17 
19 18 
20# for float3219# for float32
@@ -23,9 +22,13 @@ absolute_tol = 1e-9
23error_tol = 1e-422error_tol = 1e-4
24 23 
25 24 
26-def verify_result(output, golden):25+def verify_result(scenario_num, output, golden):
27- output = np.fromfile(output, dtype=np.float32).reshape(-1)26+ if scenario_num > 2:
28- golden = np.fromfile(golden, dtype=np.float32).reshape(-1)27+ output_type = np.float32
28+ else:
29+ output_type = np.int32
30+ output = np.fromfile(output, dtype=output_type).reshape(-1)
31+ golden = np.fromfile(golden, dtype=output_type).reshape(-1)
29 different_element_results = np.isclose(output,32 different_element_results = np.isclose(output,
30 golden,33 golden,
31 rtol=relative_tol,34 rtol=relative_tol,
@@ -36,10 +39,12 @@ def verify_result(output, golden):
36 real_index = different_element_indexes[index]39 real_index = different_element_indexes[index]
37 golden_data = golden[real_index]40 golden_data = golden[real_index]
38 output_data = output[real_index]41 output_data = output[real_index]
39- print(42+ if scenario_num > 2:
40- "data index: %06d, expected: %-.9f, actual: %-.9f, rdiff: %-.6f" %43+ print("data index: %06d, expected: %-.9f, actual: %-.9f, rdiff: %-.6f" %
41- (real_index, golden_data, output_data,44+ (real_index, golden_data, output_data, abs(output_data - golden_data) / golden_data))
42- abs(output_data - golden_data) / golden_data))45+ else:
46+ print("data index: %06d, expected: %d, actual: %d, rdiff: %-.6f" %
47+ (real_index, golden_data, output_data, abs(output_data - golden_data) / golden_data))
43 if index == 100:48 if index == 100:
44 break49 break
45 error_ratio = float(different_element_indexes.size) / golden.size50 error_ratio = float(different_element_indexes.size) / golden.size
@@ -48,8 +53,13 @@ def verify_result(output, golden):
48 53 
49 54 
50if __name__ == '__main__':55if __name__ == '__main__':
56+ parser = argparse.ArgumentParser()
57+ parser.add_argument('-scenarioNum', type=int, default=1, choices=range(1, 5))
58+ parser.add_argument('output', type=str)
59+ parser.add_argument('golden', type=str)
60+ args = parser.parse_args()
51 try:61 try:
52- res = verify_result(sys.argv[1], sys.argv[2])62+ res = verify_result(args.scenarioNum, args.output, args.golden)
53 if not res:63 if not res:
54 raise ValueError("[ERROR] result error")64 raise ValueError("[ERROR] result error")
55 else:65 else: