已合并
BatchNormReduce算子在ascend 950 NPU上支持ascendc实现 #8543
yulianjie创建于 8月11日
BatchNormReduce算子在ascend 950 NPU上支持ascendc实现 #8543
已合并
yulianjie创建于 8月11日
33 个文件变更+5480-78
@@ -4063,32 +4063,6 @@ REG_OP(AdaptiveMaxPool2d)
4063 /**4063 /**
4064 * @brief Performs reduced batch normalization .4064 * @brief Performs reduced batch normalization .
4065 4065 
4066- * @par Inputs:
4067- * x: A 4D tensor of type float16 or float32 or bfloat16, with format NHWC or NCHW.
4068- * Indicates the input tensor, that is, the original data to be normalized.
4069- 
4070- * @par Outputs:
4071- * @li sum: A 1D tensor of type float32 for SUM reduced "x". It represents the sum of the input tensor "x" on the C
4072- axis.
4073- * The shape of sum is consistent with the C axis of "x". Has the same format as "x".
4074- * @li square_sum: A 1D tensor of type float32 for SUMSQ reduced "x". It represents the sum of squares of the input
4075- tensor "x" on the C axis.
4076- * The shape of sum is consistent with the C axis of "x". Has the same format as "x". \n
4077- 
4078- * @attention Constraints:
4079- * This operator is a BatchNorm fusion operator for updating the moving
4080- * averages for training.
4081- * This operator is used in conjunction with BNTrainingReduce.
4082- */
4083- REG_OP(BNTrainingReduce)
4084- .INPUT(x, TensorType({DT_FLOAT16, DT_FLOAT, DT_BF16}))
4085- .OUTPUT(sum, TensorType({DT_FLOAT}))
4086- .OUTPUT(square_sum, TensorType({DT_FLOAT}))
4087- .OP_END_FACTORY_REG(BNTrainingReduce)
4088- 
4089- /**
4090- * @brief Performs reduced batch normalization .
4091- 
4092 * @par Inputs:4066 * @par Inputs:
4093 * x: A 5D tensor of type float16 or float32 or bfloat16, with format NDHWC or NCDHW.4067 * x: A 5D tensor of type float16 or float32 or bfloat16, with format NDHWC or NCDHW.
4094 * Represents the input tensor in batch normalization training.4068 * Represents the input tensor in batch normalization training.
@@ -84,7 +84,7 @@
84| [aclnnBatchNormElemtBackward](../../norm/sync_batch_norm_backward_elemt/docs/aclnnBatchNormElemtBackward.md) | aclnnBatchNormElemt的反向计算。用于计算输入张量的元素级梯度,以便在反向传播过程中更新模型参数。 | 默认确定性实现 | 默认确定性实现 |84| [aclnnBatchNormElemtBackward](../../norm/sync_batch_norm_backward_elemt/docs/aclnnBatchNormElemtBackward.md) | aclnnBatchNormElemt的反向计算。用于计算输入张量的元素级梯度,以便在反向传播过程中更新模型参数。 | 默认确定性实现 | 默认确定性实现 |
85| [aclnnBatchNormBackward](../../norm/batch_norm_grad_v3/docs/aclnnBatchNormBackward.md) | [aclnnBatchNorm](../../norm/batch_norm_v3/docs/aclnnBatchNorm.md)的反向传播。用于计算输入张量的梯度,以便在反向传播过程中更新模型参数。 | 默认非确定性实现,支持配置开启 | 默认非确定性实现,支持配置开启 |85| [aclnnBatchNormBackward](../../norm/batch_norm_grad_v3/docs/aclnnBatchNormBackward.md) | [aclnnBatchNorm](../../norm/batch_norm_v3/docs/aclnnBatchNorm.md)的反向传播。用于计算输入张量的梯度,以便在反向传播过程中更新模型参数。 | 默认非确定性实现,支持配置开启 | 默认非确定性实现,支持配置开启 |
86| [aclnnBatchNormGatherStatsWithCounts](../../norm/sync_batch_norm_gather_stats_with_counts/docs/aclnnBatchNormGatherStatsWithCounts.md) | 收集所有device的均值和方差,更新全局的均值和标准差的倒数。 | 默认确定性实现 | 默认确定性实现 |86| [aclnnBatchNormGatherStatsWithCounts](../../norm/sync_batch_norm_gather_stats_with_counts/docs/aclnnBatchNormGatherStatsWithCounts.md) | 收集所有device的均值和方差,更新全局的均值和标准差的倒数。 | 默认确定性实现 | 默认确定性实现 |
87-| [aclnnBatchNormReduce](../../norm/bn_training_reduce/docs/aclnnBatchNormReduce.md) | 对数据做正则化处理的第一步,对数据进行求和及平方和 | 默认非确定性实现,支持配置开启 | - |87+| [aclnnBatchNormReduce](../../norm/bn_training_reduce/docs/aclnnBatchNormReduce.md) | 对数据做正则化处理的第一步,对数据进行求和及平方和 | 默认非确定性实现,支持配置开启 | 默认非确定性实现,支持配置开启 |
88| [aclnnBatchNormReduceBackward](../../norm/sync_batch_norm_backward_reduce/docs/aclnnBatchNormReduceBackward.md) | 主要用于反向传播过程中计算BatchNorm操作的梯度,并进行一些中间结果的规约操作以优化计算效率。 | 默认确定性实现 | 默认确定性实现 |88| [aclnnBatchNormReduceBackward](../../norm/sync_batch_norm_backward_reduce/docs/aclnnBatchNormReduceBackward.md) | 主要用于反向传播过程中计算BatchNorm操作的梯度,并进行一些中间结果的规约操作以优化计算效率。 | 默认确定性实现 | 默认确定性实现 |
89| [aclnnBidirectionLSTM](../../rnn/bidirection_lstm/docs/aclnnBidirectionLSTM.md) | 进行LSTM网络计算,接收输入序列和初始状态,返回输出序列和最终状态。 | - | - |89| [aclnnBidirectionLSTM](../../rnn/bidirection_lstm/docs/aclnnBidirectionLSTM.md) | 进行LSTM网络计算,接收输入序列和初始状态,返回输出序列和最终状态。 | - | - |
90| [aclnnBidirectionLSTMV2](../../rnn/bidirection_lstmv2/docs/aclnnBidirectionLSTMV2.md) | 进行LSTM网络计算,接收输入序列和初始状态,返回输出序列和最终状态。 | - | - |90| [aclnnBidirectionLSTMV2](../../rnn/bidirection_lstmv2/docs/aclnnBidirectionLSTMV2.md) | 进行LSTM网络计算,接收输入序列和初始状态,返回输出序列和最终状态。 | - | - |
@@ -3210,12 +3210,12 @@
3210 <tr>3210 <tr>
3211 <td>norm</td>3211 <td>norm</td>
3212 <td><a href="../../norm/bn_training_reduce/README.md">bn_training_reduce</a></td>3212 <td><a href="../../norm/bn_training_reduce/README.md">bn_training_reduce</a></td>
3213- <td>✗</td>
3214- <td>✗</td>
3215 <td>✓</td>3213 <td>✓</td>
3216- <td></td>3214+ <td></td>
3215+ <td>✓</td>
3216+ <td>✓</td>
3217 <td>AI Core</td>3217 <td>AI Core</td>
3218- <td>该算子暂无Ascend C代码实现欢迎开发者补充贡献,贡献式参考<a href="../../CONTRIBUTING.md">贡献指南</a>。</td>3218+ <td>对四维NCHW输入的N、H、W轴执行归约输出每个通道的元素和与平。</td>
3219 </tr>3219 </tr>
3220 <tr>3220 <tr>
3221 <td>norm</td>3221 <td>norm</td>
@@ -1,18 +1,25 @@
1-# Copyright (c) 2025 Huawei Technologies Co., Ltd.1+# ----------------------------------------------------------------------------------------------------------
2-# This program is free software, you can redistribute it and/or modify it under the terms and conditions of 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 of
3# CANN Open Software License Agreement Version 2.0 (the "License").4# CANN Open Software License Agreement Version 2.0 (the "License").
4# 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.
5-# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, 6+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
6# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.7# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
7# 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.
8-#/9+# ----------------------------------------------------------------------------------------------------------
10+# Generated By CANNBot
9 11 
10-file(GLOB CURRENT_DIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)12+set(SUPPORT_COMPUTE_UNIT "ascend950")
11-if(NOT ENABLE_TEST AND NOT BENCHMARK)13+set(SUPPORT_TILING_DIR "arch35")
12- list(REMOVE_ITEM CURRENT_DIRS tests)14+ 
13-endif()15+add_modules_sources(
14-foreach(SUB_DIR ${CURRENT_DIRS})16+ HOSTNAME ${OP_HOST_NAME}
15- if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUB_DIR}/CMakeLists.txt")17+ MODE PRIVATE
16- add_subdirectory(${SUB_DIR})18+ DIR ${CMAKE_CURRENT_SOURCE_DIR}
17- endif()19+ OPTYPE bn_training_reduce
18-endforeach()20+ ACLNNTYPE aclnn_exclude
21+ COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT}
22+ TILING_DIR ${SUPPORT_TILING_DIR}
23+ DISABLE_IN_OPP TRUE
24+ DEPENDENCIES norm_common batch_norm_v3
25+)
@@ -1,3 +1,94 @@
1# BNTrainingReduce1# BNTrainingReduce
2 2 
3-本目录仅包含BNTrainingReduce算子对应的aclnn接口;如您想要贡献该算子的AscendC实现,请参考[贡献流程](../../CONTRIBUTING.md)。3+## 产品支持情况
4+ 
5+| 产品 | 是否支持 |
6+| :----------------------------------------- | :------:|
7+| <term>Ascend 950PR/Ascend 950DT</term> | √ |
8+| <term>Atlas A3 训练系列产品/Atlas A3 推理系列产品</term> | √ |
9+| <term>Atlas A2 训练系列产品/Atlas A2 推理系列产品</term> | √ |
10+| <term>Atlas 200I/500 A2 推理产品</term> | × |
11+| <term>Atlas 推理系列产品</term> | × |
12+| <term>Atlas 训练系列产品</term> | × |
13+ 
14+## 功能说明
15+ 
16+- 算子功能:对四维NCHW或NHWC输入的N、H、W轴执行归约,输出每个通道的元素和与平方和。
17+- 计算公式:
18+ 
19+ $$
20+ sum_c = \sum_{n,h,w}x_{n,c,h,w}
21+ $$
22+ 
23+ $$
24+ square\_sum_c = \sum_{n,h,w}x_{n,c,h,w}^{2}
25+ $$
26+ 
27+## 参数说明
28+ 
29+<table style="table-layout: fixed; width: 1576px"><colgroup>
30+<col style="width: 170px">
31+<col style="width: 170px">
32+<col style="width: 520px">
33+<col style="width: 300px">
34+<col style="width: 170px">
35+</colgroup>
36+<thead>
37+ <tr>
38+ <th>参数名</th>
39+ <th>输入/输出/属性</th>
40+ <th>描述</th>
41+ <th>数据类型</th>
42+ <th>数据格式</th>
43+ </tr></thead>
44+<tbody>
45+ <tr>
46+ <td>x</td>
47+ <td>输入</td>
48+ <td>待统计的四维训练激活。</td>
49+ <td>FLOAT16、BFLOAT16、FLOAT</td>
50+ <td>NCHW、NHWC</td>
51+ </tr>
52+ <tr>
53+ <td>sum</td>
54+ <td>输出</td>
55+ <td>沿N、H、W轴归约得到的每通道元素和。</td>
56+ <td>FLOAT</td>
57+ <td>ND</td>
58+ </tr>
59+ <tr>
60+ <td>square_sum</td>
61+ <td>输出</td>
62+ <td>沿N、H、W轴归约得到的每通道平方和。</td>
63+ <td>FLOAT</td>
64+ <td>ND</td>
65+ </tr>
66+</tbody></table>
67+ 
68+## 约束说明
69+ 
70+- x必须为四维NCHW或NHWC张量,通道轴分别为第1维或第3维。
71+- sum和square_sum必须为一维ND张量,长度等于x的C维,数据类型固定为FLOAT。
72+- FLOAT16和BFLOAT16输入按FLOAT精度执行平方与累加。
73+- 算子无属性,支持空Tensor;归约集合为空时输出为零或空向量。
74+ 
75+## 调用说明
76+ 
77+<table><thead>
78+ <tr>
79+ <th>调用方式</th>
80+ <th>调用样例</th>
81+ <th>说明</th>
82+ </tr></thead>
83+<tbody>
84+ <tr>
85+ <td>aclnn调用</td>
86+ <td><a href="./examples/test_aclnn_batch_norm_reduce.cpp">test_aclnn_batch_norm_reduce</a></td>
87+ <td>通过aclnnBatchNormReduce两段式接口调用BNTrainingReduce算子。</td>
88+ </tr>
89+ <tr>
90+ <td>图模式调用</td>
91+ <td><a href="./examples/test_geir_bn_training_reduce.cpp">test_geir_bn_training_reduce</a></td>
92+ <td>通过算子IR构图并调用BNTrainingReduce算子。</td>
93+ </tr>
94+</tbody></table>
@@ -5,7 +5,7 @@
5## 产品支持情况5## 产品支持情况
6 6 
7<!-- npu="950" id1 -->7<!-- npu="950" id1 -->
8-- <term>Ascend 950PR/Ascend 950DT</term>:支持8+- <term>Ascend 950PR/Ascend 950DT</term>:支持
9<!-- end id1 -->9<!-- end id1 -->
10<!-- npu="A3" id2 -->10<!-- npu="A3" id2 -->
11- <term>Atlas A3 训练系列产品/Atlas A3 推理系列产品</term>:支持11- <term>Atlas A3 训练系列产品/Atlas A3 推理系列产品</term>:支持
@@ -0,0 +1,158 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * 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").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include <iostream>
12+#include <vector>
13+#include "acl/acl.h"
14+#include "aclnnop/aclnn_batch_norm_reduce.h"
15+ 
16+#define CHECK_RET(cond, return_expr) \
17+ do { \
18+ if (!(cond)) { \
19+ return_expr; \
20+ } \
21+ } while (0)
22+ 
23+#define LOG_PRINT(message, ...) \
24+ do { \
25+ printf(message, ##__VA_ARGS__); \
26+ } while (0)
27+ 
28+int64_t GetShapeSize(const std::vector<int64_t>& shape)
29+{
30+ int64_t shape_size = 1;
31+ for (auto i : shape) {
32+ shape_size *= i;
33+ }
34+ return shape_size;
35+}
36+ 
37+int Init(int32_t deviceId, aclrtStream* stream)
38+{
39+ // 固定写法,资源初始化
40+ auto ret = aclInit(nullptr);
41+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
42+ ret = aclrtSetDevice(deviceId);
43+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
44+ ret = aclrtCreateStream(stream);
45+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
46+ 
47+ return 0;
48+}
49+ 
50+template <typename T>
51+int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr,
52+ aclDataType dataType, aclTensor** tensor, aclFormat format)
53+{
54+ auto size = GetShapeSize(shape) * sizeof(T);
55+ // 调用aclrtMalloc申请device侧内存
56+ auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
57+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
58+ 
59+ // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上
60+ ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
61+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);
62+ 
63+ // 计算连续tensor的strides
64+ std::vector<int64_t> strides(shape.size(), 1);
65+ for (int64_t i = static_cast<int64_t>(shape.size()) - 2; i >= 0; --i) {
66+ strides[i] = shape[i + 1] * strides[i + 1];
67+ }
68+ 
69+ // 调用aclCreateTensor接口创建aclTensor
70+ *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, format, shape.data(),
71+ shape.size(), *deviceAddr);
72+ CHECK_RET(*tensor != nullptr, LOG_PRINT("aclCreateTensor failed\n"); return -1);
73+ return 0;
74+}
75+ 
76+int main(int argc, char* argv[])
77+{
78+ // 1. (固定写法)device/stream初始化,参考acl API手册
79+ // 根据自己的实际device填写deviceId
80+ int32_t deviceId = 0;
81+ aclrtStream stream;
82+ auto ret = Init(deviceId, &stream);
83+ // check根据自己的需要处理
84+ CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
85+ 
86+ // 2. 构造输入与输出,需要根据API的接口自定义构造
87+ void* xDeviceAddr = nullptr;
88+ void* sumDeviceAddr = nullptr;
89+ void* squareSumDeviceAddr = nullptr;
90+ aclTensor* x = nullptr;
91+ aclTensor* sum = nullptr;
92+ aclTensor* squareSum = nullptr;
93+ 
94+ std::vector<int64_t> xShape = {1, 2, 3, 4};
95+ std::vector<int64_t> sumShape = {2};
96+ std::vector<int64_t> squareSumShape = {2};
97+ std::vector<float> xHostData(24, 1);
98+ std::vector<float> sumHostData(2, 0);
99+ std::vector<float> squareSumHostData(2, 0);
100+ 
101+ // 创建x aclTensor
102+ ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT, &x, aclFormat::ACL_FORMAT_NCHW);
103+ CHECK_RET(ret == ACL_SUCCESS, return ret);
104+ // 创建sum aclTensor
105+ ret = CreateAclTensor(sumHostData, sumShape, &sumDeviceAddr, aclDataType::ACL_FLOAT, &sum,
106+ aclFormat::ACL_FORMAT_ND);
107+ CHECK_RET(ret == ACL_SUCCESS, return ret);
108+ // 创建squareSum aclTensor
109+ ret = CreateAclTensor(squareSumHostData, squareSumShape, &squareSumDeviceAddr, aclDataType::ACL_FLOAT, &squareSum,
110+ aclFormat::ACL_FORMAT_ND);
111+ CHECK_RET(ret == ACL_SUCCESS, return ret);
112+ // 3. 调用CANN算子库API,需要修改为具体的API
113+ uint64_t workspaceSize = 0;
114+ aclOpExecutor* executor;
115+ // 调用aclnnBatchNormReduce第一段接口
116+ ret = aclnnBatchNormReduceGetWorkspaceSize(x, sum, squareSum, &workspaceSize, &executor);
117+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnBatchNormReduceGetWorkspaceSize failed. ERROR: %d\n", ret);
118+ return ret);
119+ // 根据第一段接口计算出的workspaceSize申请device内存
120+ void* workspaceAddr = nullptr;
121+ if (workspaceSize > 0) {
122+ ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
123+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
124+ }
125+ // 调用aclnnBatchNormReduce第二段接口
126+ ret = aclnnBatchNormReduce(workspaceAddr, workspaceSize, executor, stream);
127+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnBatchNormReduce failed. ERROR: %d\n", ret); return ret);
128+ // 4. (固定写法)同步等待任务执行结束
129+ ret = aclrtSynchronizeStream(stream);
130+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);
131+ // 5. 获取输出的值,将device侧内存上的结果拷贝至host侧,需要根据具体API的接口定义修改
132+ auto size = GetShapeSize(sumShape);
133+ std::vector<float> resultData(size, 0);
134+ ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), sumDeviceAddr,
135+ size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
136+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret);
137+ 
138+ for (int64_t i = 0; i < size; i++) {
139+ LOG_PRINT("result[%ld] is: %f\n", i, resultData[i]);
140+ }
141+ 
142+ // 6. 释放aclTensor,需要根据具体API的接口定义修改
143+ aclDestroyTensor(x);
144+ aclDestroyTensor(sum);
145+ aclDestroyTensor(squareSum);
146+ 
147+ // 7. 释放device资源,需要根据具体API的接口定义修改
148+ aclrtFree(xDeviceAddr);
149+ aclrtFree(sumDeviceAddr);
150+ aclrtFree(squareSumDeviceAddr);
151+ if (workspaceSize > 0) {
152+ aclrtFree(workspaceAddr);
153+ }
154+ aclrtDestroyStream(stream);
155+ aclrtResetDevice(deviceId);
156+ aclFinalize();
157+ return 0;
158+}
@@ -0,0 +1,919 @@
1+/**
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 of
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.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include <acl/acl.h>
12+ 
13+#include <algorithm>
14+#include <cctype>
15+#include <cmath>
16+#include <cstdint>
17+#include <cstdio>
18+#include <cstring>
19+#include <fstream>
20+#include <limits>
21+#include <map>
22+#include <sstream>
23+#include <string>
24+#include <utility>
25+#include <vector>
26+ 
27+#include "ge_api.h"
28+#include "ge_error_codes.h"
29+#include "graph.h"
30+#include "ops_proto_nn.h"
31+#include "tensor.h"
32+#include "types.h"
33+ 
34+// The selected standard domain header owns BNTrainingReduce. Data remains in
35+// ops_proto_legacy.h in this CANN release, so keep its tiny construction class
36+// test-local instead of including the duplicate-heavy compatibility header.
37+namespace ge {
38+REG_OP(Data).INPUT(x, TensorType::ALL()).OUTPUT(y, TensorType::ALL()).ATTR(index, Int, 0).OP_END_FACTORY_REG(Data)
39+} // namespace ge
40+ 
41+namespace {
42+ 
43+constexpr float kRtol = 1.0e-4F;
44+constexpr float kAtol = 1.0e-4F;
45+ 
46+struct CaseDef {
47+ std::string id;
48+ std::string category;
49+ std::string mode;
50+ std::string capabilities;
51+ std::string dtype;
52+ std::string inputFormat;
53+ std::string graphShape;
54+ std::string feedShapes;
55+ std::string pattern;
56+ std::string expectation;
57+ std::string acceptance;
58+};
59+ 
60+struct InputData {
61+ ge::Tensor tensor;
62+ std::vector<float> quantized;
63+};
64+ 
65+struct Expected {
66+ std::vector<float> sum;
67+ std::vector<float> squareSum;
68+};
69+ 
70+struct Observed {
71+ bool readable = false;
72+ bool valuePass = false;
73+ std::vector<float> sum;
74+ std::vector<float> squareSum;
75+};
76+ 
77+struct GraphBundle {
78+ ge::Graph graph;
79+ ge::Operator reduce;
80+ std::string node;
81+};
82+ 
83+std::vector<std::string> Split(const std::string& text, char delimiter)
84+{
85+ std::vector<std::string> fields;
86+ std::stringstream stream(text);
87+ std::string field;
88+ while (std::getline(stream, field, delimiter)) {
89+ fields.push_back(field);
90+ }
91+ if (!text.empty() && text.back() == delimiter) {
92+ fields.emplace_back();
93+ }
94+ return fields;
95+}
96+ 
97+bool LoadCases(const std::string& path, std::vector<CaseDef>& cases)
98+{
99+ std::ifstream input(path);
100+ if (!input.is_open()) {
101+ std::fprintf(stderr, "GEIR-INFRA manifest=%s reason=open_failed value=FAIL\n", path.c_str());
102+ return false;
103+ }
104+ std::string line;
105+ if (!std::getline(input, line)) {
106+ return false;
107+ }
108+ const std::string expectedHeader = "id\tcategory\tmode\tcapabilities\tdtype\tinput_format\tgraph_shape\tfeed_"
109+ "shapes\tpattern\texpectation\tacceptance";
110+ if (line != expectedHeader) {
111+ std::fprintf(stderr, "GEIR-INFRA manifest=%s reason=bad_header value=FAIL\n", path.c_str());
112+ return false;
113+ }
114+ std::map<std::string, bool> ids;
115+ while (std::getline(input, line)) {
116+ if (line.empty()) {
117+ continue;
118+ }
119+ const auto fields = Split(line, '\t');
120+ if (fields.size() != 11U || fields[0].empty() || ids[fields[0]]) {
121+ std::fprintf(stderr, "GEIR-INFRA manifest=%s reason=bad_or_duplicate_row row=%s value=FAIL\n", path.c_str(),
122+ line.c_str());
123+ return false;
124+ }
125+ ids[fields[0]] = true;
126+ cases.push_back({fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], fields[7],
127+ fields[8], fields[9], fields[10]});
128+ }
129+ return !cases.empty();
130+}
131+ 
132+std::vector<int64_t> ParseShape(const std::string& text)
133+{
134+ if (text == "UNKNOWN_RANK") {
135+ return ge::UNKNOWN_RANK;
136+ }
137+ if (text.size() < 2U || text.front() != '[' || text.back() != ']') {
138+ return {};
139+ }
140+ const std::string inner = text.substr(1, text.size() - 2U);
141+ if (inner.empty()) {
142+ return {};
143+ }
144+ std::vector<int64_t> dims;
145+ for (const auto& field : Split(inner, ',')) {
146+ dims.push_back(std::stoll(field));
147+ }
148+ return dims;
149+}
150+ 
151+std::string ShapeString(const std::vector<int64_t>& dims)
152+{
153+ if (dims == ge::UNKNOWN_RANK) {
154+ return "UNKNOWN_RANK";
155+ }
156+ std::ostringstream out;
157+ out << '[';
158+ for (size_t i = 0; i < dims.size(); ++i) {
159+ if (i != 0U) {
160+ out << ',';
161+ }
162+ out << dims[i];
163+ }
164+ out << ']';
165+ return out.str();
166+}
167+ 
168+std::string ShapeString(const ge::Shape& shape) { return ShapeString(shape.GetDims()); }
169+ 
170+size_t Numel(const std::vector<int64_t>& shape)
171+{
172+ if (shape.empty()) {
173+ return 1U;
174+ }
175+ size_t count = 1U;
176+ for (const int64_t dim : shape) {
177+ if (dim <= 0) {
178+ return 0U;
179+ }
180+ count *= static_cast<size_t>(dim);
181+ }
182+ return count;
183+}
184+ 
185+ge::DataType ParseDtype(const std::string& dtype)
186+{
187+ if (dtype == "F16") {
188+ return ge::DT_FLOAT16;
189+ }
190+ if (dtype == "BF16") {
191+ return ge::DT_BF16;
192+ }
193+ if (dtype == "I32") {
194+ return ge::DT_INT32;
195+ }
196+ return ge::DT_FLOAT;
197+}
198+ 
199+const char* DtypeName(ge::DataType dtype)
200+{
201+ switch (dtype) {
202+ case ge::DT_FLOAT16:
203+ return "FLOAT16";
204+ case ge::DT_BF16:
205+ return "BFLOAT16";
206+ case ge::DT_FLOAT:
207+ return "FLOAT32";
208+ case ge::DT_INT32:
209+ return "INT32";
210+ case ge::DT_UNDEFINED:
211+ return "UNDEFINED";
212+ default:
213+ return "OTHER";
214+ }
215+}
216+ 
217+ge::Format ParseFormat(const std::string& format) { return format == "NHWC" ? ge::FORMAT_NHWC : ge::FORMAT_NCHW; }
218+ 
219+const char* FormatName(ge::Format format)
220+{
221+ if (format == ge::FORMAT_ND) {
222+ return "ND";
223+ }
224+ if (format == ge::FORMAT_NCHW) {
225+ return "NCHW";
226+ }
227+ if (format == ge::FORMAT_NHWC) {
228+ return "NHWC";
229+ }
230+ return "OTHER";
231+}
232+ 
233+uint32_t RoundRightShiftToEven(uint32_t value, uint32_t shift)
234+{
235+ const uint32_t truncated = value >> shift;
236+ const uint32_t remainder = value & ((1U << shift) - 1U);
237+ const uint32_t halfway = 1U << (shift - 1U);
238+ return truncated + static_cast<uint32_t>(remainder > halfway || (remainder == halfway && (truncated & 1U) != 0U));
239+}
240+ 
241+aclFloat16 FloatToFloat16(float value)
242+{
243+ uint32_t bits = 0U;
244+ std::memcpy(&bits, &value, sizeof(bits));
245+ const uint32_t sign = (bits >> 16U) & 0x8000U;
246+ const uint32_t exponent = (bits >> 23U) & 0xFFU;
247+ const uint32_t mantissa = bits & 0x7FFFFFU;
248+ if (exponent == 0xFFU) {
249+ return static_cast<aclFloat16>(sign | 0x7C00U | (mantissa == 0U ? 0U : 0x0200U));
250+ }
251+ if (exponent == 0U) {
252+ return static_cast<aclFloat16>(sign);
253+ }
254+ 
255+ int32_t halfExponent = static_cast<int32_t>(exponent) - 127 + 15;
256+ if (halfExponent <= 0) {
257+ if (halfExponent < -10) {
258+ return static_cast<aclFloat16>(sign);
259+ }
260+ const uint32_t rounded = RoundRightShiftToEven(mantissa | 0x800000U, 14U - halfExponent);
261+ return static_cast<aclFloat16>(sign | rounded);
262+ }
263+ if (halfExponent >= 31) {
264+ return static_cast<aclFloat16>(sign | 0x7C00U);
265+ }
266+ 
267+ uint32_t roundedMantissa = RoundRightShiftToEven(mantissa, 13U);
268+ if (roundedMantissa == 0x400U) {
269+ roundedMantissa = 0U;
270+ ++halfExponent;
271+ if (halfExponent >= 31) {
272+ return static_cast<aclFloat16>(sign | 0x7C00U);
273+ }
274+ }
275+ return static_cast<aclFloat16>(sign | (static_cast<uint32_t>(halfExponent) << 10U) | roundedMantissa);
276+}
277+ 
278+float Float16ToFloat(aclFloat16 value)
279+{
280+ const uint32_t sign = (static_cast<uint32_t>(value) & 0x8000U) << 16U;
281+ uint32_t exponent = (static_cast<uint32_t>(value) >> 10U) & 0x1FU;
282+ uint32_t mantissa = static_cast<uint32_t>(value) & 0x03FFU;
283+ uint32_t bits = sign;
284+ if (exponent == 0U && mantissa != 0U) {
285+ int32_t normalizedExponent = -14;
286+ while ((mantissa & 0x0400U) == 0U) {
287+ mantissa <<= 1U;
288+ --normalizedExponent;
289+ }
290+ mantissa &= 0x03FFU;
291+ bits |= static_cast<uint32_t>(normalizedExponent + 127) << 23U;
292+ bits |= mantissa << 13U;
293+ } else if (exponent == 0x1FU) {
294+ bits |= 0x7F800000U | (mantissa << 13U);
295+ } else if (exponent != 0U) {
296+ bits |= (exponent - 15U + 127U) << 23U;
297+ bits |= mantissa << 13U;
298+ }
299+ float result = 0.0F;
300+ std::memcpy(&result, &bits, sizeof(result));
301+ return result;
302+}
303+ 
304+uint16_t FloatToBfloat16(float value)
305+{
306+ uint32_t bits = 0U;
307+ std::memcpy(&bits, &value, sizeof(bits));
308+ const uint32_t roundingBias = 0x7FFFU + ((bits >> 16U) & 1U);
309+ return static_cast<uint16_t>((bits + roundingBias) >> 16U);
310+}
311+ 
312+float Bfloat16ToFloat(uint16_t value)
313+{
314+ uint32_t bits = static_cast<uint32_t>(value) << 16U;
315+ float result = 0.0F;
316+ std::memcpy(&result, &bits, sizeof(result));
317+ return result;
318+}
319+ 
320+std::vector<float> GenerateValues(const std::vector<int64_t>& shape, const std::string& pattern)
321+{
322+ const size_t count = Numel(shape);
323+ std::vector<float> values(count, 0.0F);
324+ for (size_t i = 0; i < count; ++i) {
325+ const int64_t signedIndex = static_cast<int64_t>(i % 17U) - 8;
326+ values[i] = static_cast<float>(signedIndex) * 0.125F;
327+ }
328+ if (pattern == "zero") {
329+ std::fill(values.begin(), values.end(), 0.0F);
330+ } else if (pattern == "alternate") {
331+ for (size_t i = 0; i < count; ++i) {
332+ values[i] = (i % 2U == 0U) ? 16.0F : -16.0F;
333+ }
334+ } else if (pattern == "60000") {
335+ std::fill(values.begin(), values.end(), 60000.0F);
336+ } else if (pattern == "1e20") {
337+ std::fill(values.begin(), values.end(), 1.0e20F);
338+ } else if (pattern == "partition" && shape.size() == 4U) {
339+ const int64_t n = shape[0];
340+ const int64_t c = shape[1];
341+ const int64_t h = shape[2];
342+ const int64_t w = shape[3];
343+ for (int64_t ni = 0; ni < n; ++ni) {
344+ for (int64_t ci = 0; ci < c; ++ci) {
345+ for (int64_t hi = 0; hi < h; ++hi) {
346+ for (int64_t wi = 0; wi < w; ++wi) {
347+ const size_t offset = static_cast<size_t>(((ni * c + ci) * h + hi) * w + wi);
348+ const int64_t code = ((ni % 2) * 13 + ci * 7 + hi * 3 + wi) % 19 - 9;
349+ values[offset] = static_cast<float>(code) * 0.0625F;
350+ }
351+ }
352+ }
353+ }
354+ }
355+ 
356+ if (shape.size() == 4U && count != 0U && shape[1] > 0) {
357+ const int64_t channel = shape[1] > 1 ? 1 : 0;
358+ const size_t first = static_cast<size_t>(channel * shape[2] * shape[3]);
359+ if (pattern == "nan") {
360+ values[first] = std::numeric_limits<float>::quiet_NaN();
361+ } else if (pattern == "posinf") {
362+ values[first] = std::numeric_limits<float>::infinity();
363+ } else if (pattern == "posneginf") {
364+ values[first] = std::numeric_limits<float>::infinity();
365+ if (first + 1U < values.size()) {
366+ values[first + 1U] = -std::numeric_limits<float>::infinity();
367+ }
368+ }
369+ }
370+ return values;
371+}
372+ 
373+InputData MakeInput(const CaseDef& test, const std::vector<int64_t>& shape)
374+{
375+ const ge::DataType dtype = ParseDtype(test.dtype);
376+ std::vector<float> source = GenerateValues(shape, test.pattern);
377+ std::vector<uint8_t> bytes;
378+ std::vector<float> quantized(source.size());
379+ if (dtype == ge::DT_FLOAT16) {
380+ std::vector<aclFloat16> raw(source.size());
381+ for (size_t i = 0; i < source.size(); ++i) {
382+ raw[i] = FloatToFloat16(source[i]);
383+ quantized[i] = Float16ToFloat(raw[i]);
384+ }
385+ bytes.resize(raw.size() * sizeof(aclFloat16));
386+ if (!bytes.empty()) {
387+ std::memcpy(bytes.data(), raw.data(), bytes.size());
388+ }
389+ } else if (dtype == ge::DT_BF16) {
390+ std::vector<uint16_t> raw(source.size());
391+ for (size_t i = 0; i < source.size(); ++i) {
392+ raw[i] = FloatToBfloat16(source[i]);
393+ quantized[i] = Bfloat16ToFloat(raw[i]);
394+ }
395+ bytes.resize(raw.size() * sizeof(uint16_t));
396+ if (!bytes.empty()) {
397+ std::memcpy(bytes.data(), raw.data(), bytes.size());
398+ }
399+ } else if (dtype == ge::DT_INT32) {
400+ std::vector<int32_t> raw(source.size());
401+ for (size_t i = 0; i < source.size(); ++i) {
402+ raw[i] = static_cast<int32_t>(source[i]);
403+ quantized[i] = static_cast<float>(raw[i]);
404+ }
405+ bytes.resize(raw.size() * sizeof(int32_t));
406+ if (!bytes.empty()) {
407+ std::memcpy(bytes.data(), raw.data(), bytes.size());
408+ }
409+ } else {
410+ quantized = source;
411+ bytes.resize(source.size() * sizeof(float));
412+ if (!bytes.empty()) {
413+ std::memcpy(bytes.data(), source.data(), bytes.size());
414+ }
415+ }
416+ 
417+ ge::TensorDesc desc(ge::Shape(shape), ParseFormat(test.inputFormat), dtype);
418+ desc.SetPlacement(ge::kPlacementHost);
419+ desc.SetRealDimCnt(static_cast<int64_t>(shape.size()));
420+ return {ge::Tensor(desc, bytes), std::move(quantized)};
421+}
422+ 
423+Expected ComputeExpected(const std::vector<float>& input, const std::vector<int64_t>& shape)
424+{
425+ Expected expected;
426+ if (shape.size() != 4U || shape[1] < 0) {
427+ return expected;
428+ }
429+ const int64_t n = shape[0];
430+ const int64_t c = shape[1];
431+ const int64_t h = shape[2];
432+ const int64_t w = shape[3];
433+ expected.sum.assign(static_cast<size_t>(c), 0.0F);
434+ expected.squareSum.assign(static_cast<size_t>(c), 0.0F);
435+ for (int64_t ni = 0; ni < n; ++ni) {
436+ for (int64_t ci = 0; ci < c; ++ci) {
437+ for (int64_t hi = 0; hi < h; ++hi) {
438+ for (int64_t wi = 0; wi < w; ++wi) {
439+ const size_t offset = static_cast<size_t>(((ni * c + ci) * h + hi) * w + wi);
440+ const float value = input[offset];
441+ expected.sum[static_cast<size_t>(ci)] += value;
442+ expected.squareSum[static_cast<size_t>(ci)] += value * value;
443+ }
444+ }
445+ }
446+ }
447+ return expected;
448+}
449+ 
450+bool Close(float actual, float expected, float& absoluteError)
451+{
452+ if (std::isnan(expected)) {
453+ return std::isnan(actual);
454+ }
455+ if (std::isinf(expected)) {
456+ return std::isinf(actual) && std::signbit(actual) == std::signbit(expected);
457+ }
458+ if (!std::isfinite(actual)) {
459+ return false;
460+ }
461+ absoluteError = std::fabs(actual - expected);
462+ return absoluteError <= kAtol + kRtol * std::fabs(expected);
463+}
464+ 
465+std::string OutputShapes(const std::vector<ge::Tensor>& outputs)
466+{
467+ if (outputs.size() != 2U) {
468+ return "COUNT_" + std::to_string(outputs.size());
469+ }
470+ return "{" + ShapeString(outputs[0].GetTensorDesc().GetShape()) + "," +
471+ ShapeString(outputs[1].GetTensorDesc().GetShape()) + "}";
472+}
473+ 
474+std::string OutputDtypes(const std::vector<ge::Tensor>& outputs)
475+{
476+ if (outputs.size() != 2U) {
477+ return "COUNT_" + std::to_string(outputs.size());
478+ }
479+ return "{" + std::string(DtypeName(outputs[0].GetTensorDesc().GetDataType())) + "," +
480+ DtypeName(outputs[1].GetTensorDesc().GetDataType()) + "}";
481+}
482+ 
483+std::string OutputFormats(const std::vector<ge::Tensor>& outputs)
484+{
485+ if (outputs.size() != 2U) {
486+ return "COUNT_" + std::to_string(outputs.size());
487+ }
488+ return "{" + std::string(FormatName(outputs[0].GetTensorDesc().GetFormat())) + "," +
489+ FormatName(outputs[1].GetTensorDesc().GetFormat()) + "}";
490+}
491+ 
492+Observed ValidateOutputs(const CaseDef& test, const std::vector<int64_t>& feedShape, const Expected& expected,
493+ const std::vector<ge::Tensor>& outputs, std::string& maxErrors, std::string& actualNumel)
494+{
495+ Observed observed;
496+ if (outputs.size() != 2U || feedShape.size() != 4U) {
497+ maxErrors = "{NA,NA}";
498+ actualNumel = "{NA,NA}";
499+ return observed;
500+ }
501+ const int64_t channel = feedShape[1];
502+ const std::vector<int64_t> expectedShape = {channel};
503+ bool metadataPass = true;
504+ for (const auto& output : outputs) {
505+ const ge::TensorDesc desc = output.GetTensorDesc();
506+ metadataPass = metadataPass && desc.GetShape().GetDims() == expectedShape;
507+ metadataPass = metadataPass && desc.GetDataType() == ge::DT_FLOAT;
508+ metadataPass = metadataPass && desc.GetFormat() == ge::FORMAT_ND;
509+ metadataPass = metadataPass && output.GetSize() == static_cast<size_t>(channel) * sizeof(float);
510+ }
511+ 
512+ observed.sum.resize(static_cast<size_t>(channel));
513+ observed.squareSum.resize(static_cast<size_t>(channel));
514+ if (channel > 0) {
515+ if (outputs[0].GetData() == nullptr || outputs[1].GetData() == nullptr) {
516+ maxErrors = "{NA,NA}";
517+ actualNumel = "{0,0}";
518+ return observed;
519+ }
520+ std::memcpy(observed.sum.data(), outputs[0].GetData(), observed.sum.size() * sizeof(float));
521+ std::memcpy(observed.squareSum.data(), outputs[1].GetData(), observed.squareSum.size() * sizeof(float));
522+ }
523+ observed.readable = true;
524+ 
525+ float maxSumError = 0.0F;
526+ float maxSquareError = 0.0F;
527+ bool valuesPass = expected.sum.size() == observed.sum.size() &&
528+ expected.squareSum.size() == observed.squareSum.size();
529+ for (size_t i = 0; valuesPass && i < observed.sum.size(); ++i) {
530+ float error = 0.0F;
531+ valuesPass = Close(observed.sum[i], expected.sum[i], error);
532+ maxSumError = std::max(maxSumError, error);
533+ error = 0.0F;
534+ valuesPass = valuesPass && Close(observed.squareSum[i], expected.squareSum[i], error);
535+ maxSquareError = std::max(maxSquareError, error);
536+ if (test.pattern == "zero") {
537+ valuesPass = valuesPass && observed.sum[i] == 0.0F && observed.squareSum[i] == 0.0F;
538+ }
539+ if (test.pattern == "alternate" && std::isfinite(observed.squareSum[i])) {
540+ valuesPass = valuesPass && observed.squareSum[i] >= 0.0F;
541+ }
542+ }
543+ observed.valuePass = metadataPass && valuesPass;
544+ std::ostringstream errors;
545+ errors << '{' << maxSumError << ',' << maxSquareError << '}';
546+ maxErrors = errors.str();
547+ actualNumel = "{" + std::to_string(outputs[0].GetSize() / sizeof(float)) + "," +
548+ std::to_string(outputs[1].GetSize() / sizeof(float)) + "}";
549+ return observed;
550+}
551+ 
552+bool IsUnknownRank(const ge::TensorDesc& desc) { return desc.GetShape().GetDims() == ge::UNKNOWN_RANK; }
553+ 
554+bool PrintAndCheckShapeInit(const std::string& caseId, const GraphBundle& bundle)
555+{
556+ const ge::TensorDesc sumDesc = bundle.reduce.GetOutputDescByName("sum");
557+ const ge::TensorDesc squareDesc = bundle.reduce.GetOutputDescByName("square_sum");
558+ const bool valid = IsUnknownRank(sumDesc) && IsUnknownRank(squareDesc) && sumDesc.GetDataType() == ge::DT_FLOAT &&
559+ squareDesc.GetDataType() == ge::DT_FLOAT;
560+ std::printf("SHAPE-INIT case=%s outputs={sum:{shape:%s,dtype:%s},square_sum:{shape:%s,dtype:%s}} value=%s\n",
561+ caseId.c_str(), ShapeString(sumDesc.GetShape()).c_str(), DtypeName(sumDesc.GetDataType()),
562+ ShapeString(squareDesc.GetShape()).c_str(), DtypeName(squareDesc.GetDataType()),
563+ valid ? "PASS" : "FAIL");
564+ return valid;
565+}
566+ 
567+GraphBundle BuildGraph(const CaseDef& test)
568+{
569+ GraphBundle bundle;
570+ bundle.node = test.id == "route-target" ? "bn_training_reduce_route" : "bn_training_reduce_" + test.id;
571+ bundle.graph = ge::Graph((bundle.node + "_graph").c_str());
572+ auto reduce = ge::op::BNTrainingReduce(bundle.node.c_str());
573+ const ge::TensorDesc outDesc(ge::Shape(ge::UNKNOWN_RANK), ge::FORMAT_ND, ge::DT_FLOAT);
574+ reduce.update_output_desc_sum(outDesc);
575+ reduce.update_output_desc_square_sum(outDesc);
576+ 
577+ std::vector<ge::Operator> inputs;
578+ const std::vector<int64_t> graphShape = test.mode == "missing-input" ? std::vector<int64_t>{1, 1, 1, 1} :
579+ ParseShape(test.graphShape);
580+ ge::TensorDesc xDesc(ge::Shape(graphShape), ParseFormat(test.inputFormat), ParseDtype(test.dtype));
581+ xDesc.SetRealDimCnt(graphShape == ge::UNKNOWN_RANK ? 0 : static_cast<int64_t>(graphShape.size()));
582+ auto data = ge::op::Data((bundle.node + "_x").c_str()).set_attr_index(0);
583+ data.update_input_desc_x(xDesc);
584+ data.update_output_desc_y(xDesc);
585+ reduce.set_input_x(data);
586+ reduce.update_input_desc_x(xDesc);
587+ bundle.graph.AddOp(data);
588+ inputs.push_back(data);
589+ bundle.graph.AddOp(reduce);
590+ const std::vector<std::pair<ge::Operator, std::vector<size_t>>> outputs = {{reduce, {0U, 1U}}};
591+ if (!inputs.empty()) {
592+ bundle.graph.SetInputs(inputs);
593+ }
594+ bundle.graph.SetOutputs(outputs);
595+ bundle.reduce = reduce;
596+ return bundle;
597+}
598+ 
599+const char* GraphMode(const CaseDef& test)
600+{
601+ return test.mode == "dynamic" || test.mode == "unknown-rank" ? "dynamic" : "static";
602+}
603+ 
604+std::string Sanitize(std::string text)
605+{
606+ if (text.size() > 160U) {
607+ text.resize(160U);
608+ }
609+ for (char& ch : text) {
610+ const bool safe = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') ||
611+ ch == '_' || ch == '-' || ch == '.';
612+ if (!safe) {
613+ ch = '_';
614+ }
615+ }
616+ return text.empty() ? "NONE" : text;
617+}
618+ 
619+std::string CurrentError()
620+{
621+ const ge::AscendString message = ge::GEGetErrorMsgV2();
622+ return message.GetString() == nullptr ? "NONE" : Sanitize(message.GetString());
623+}
624+ 
625+std::string ClassifyError(const std::string& raw)
626+{
627+ std::string lower = raw;
628+ std::transform(lower.begin(), lower.end(), lower.begin(),
629+ [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
630+ if (lower.find("dtype") != std::string::npos || lower.find("data_type") != std::string::npos ||
631+ lower.find("datatype") != std::string::npos) {
632+ return "dtype_not_supported";
633+ }
634+ if (lower.find("format") != std::string::npos || lower.find("shape") != std::string::npos ||
635+ lower.find("rank") != std::string::npos || lower.find("dim") != std::string::npos) {
636+ return "shape_mismatch";
637+ }
638+ if (lower.find("null") != std::string::npos || lower.find("missing") != std::string::npos ||
639+ lower.find("input") != std::string::npos || lower.find("anchor") != std::string::npos ||
640+ lower.find("edge") != std::string::npos) {
641+ return "null_input";
642+ }
643+ return "unclassified";
644+}
645+ 
646+Observed RunPositiveFeed(ge::Session& session, uint32_t gid, const CaseDef& test, const GraphBundle& bundle,
647+ const std::vector<int64_t>& feedShape, const std::string& feedLabel,
648+ const std::string& runMode, bool& runCompleted)
649+{
650+ const InputData input = MakeInput(test, feedShape);
651+ const Expected expected = ComputeExpected(input.quantized, feedShape);
652+ std::vector<ge::Tensor> outputs;
653+ const ge::Status status = session.RunGraph(gid, {input.tensor}, outputs);
654+ runCompleted = status == ge::SUCCESS;
655+ std::string maxErrors = "{NA,NA}";
656+ std::string actualNumel = "{NA,NA}";
657+ Observed observed;
658+ if (runCompleted) {
659+ observed = ValidateOutputs(test, feedShape, expected, outputs, maxErrors, actualNumel);
660+ }
661+ const std::string runError = runCompleted ? "NONE" : CurrentError();
662+ const int64_t channel = feedShape.size() == 4U ? feedShape[1] : -1;
663+ std::printf("GRAPH-RUN mode=%s case=%s node=%s gid=%u feed=%s expected_shape={[%ld],[%ld]} actual_shape=%s "
664+ "expected_dtype={FLOAT32,FLOAT32} actual_dtype=%s expected_format={ND,ND} actual_format=%s "
665+ "expected_numel={%ld,%ld} actual_numel=%s max_abs=%s rtol=%.1e atol=%.1e status=%u error=%s value=%s\n",
666+ runMode.c_str(), feedLabel.c_str(), bundle.node.c_str(), gid, ShapeString(feedShape).c_str(), channel,
667+ channel, OutputShapes(outputs).c_str(), OutputDtypes(outputs).c_str(), OutputFormats(outputs).c_str(),
668+ channel, channel, actualNumel.c_str(), maxErrors.c_str(), kRtol, kAtol, status, runError.c_str(),
669+ observed.valuePass ? "PASS" : "FAIL");
670+ return observed;
671+}
672+ 
673+bool CheckPartitionInvariant(const Observed& whole, const Observed& half, float& maxError)
674+{
675+ if (!whole.readable || !half.readable || whole.sum.size() != half.sum.size()) {
676+ return false;
677+ }
678+ bool pass = true;
679+ maxError = 0.0F;
680+ for (size_t i = 0; i < whole.sum.size(); ++i) {
681+ float error = 0.0F;
682+ pass = pass && Close(whole.sum[i], half.sum[i] * 2.0F, error);
683+ maxError = std::max(maxError, error);
684+ error = 0.0F;
685+ pass = pass && Close(whole.squareSum[i], half.squareSum[i] * 2.0F, error);
686+ maxError = std::max(maxError, error);
687+ }
688+ return pass;
689+}
690+ 
691+bool RunAcceptCase(ge::Session& session, uint32_t gid, const CaseDef& test, bool& infraFailure)
692+{
693+ GraphBundle bundle = BuildGraph(test);
694+ const char* graphMode = GraphMode(test);
695+ const ge::Status addStatus = session.AddGraph(gid, bundle.graph, std::map<ge::AscendString, ge::AscendString>{});
696+ std::printf("GRAPH-ADD mode=%s case=%s node=%s gid=%u declared_input_shape=%s status=%u\n", graphMode,
697+ test.id.c_str(), bundle.node.c_str(), gid, test.graphShape.c_str(), addStatus);
698+ if (addStatus != ge::SUCCESS) {
699+ std::printf("GRAPH-RUN mode=%s case=%s node=%s gid=%u actual_stage=ADD_GRAPH status=%u error=%s value=FAIL\n",
700+ graphMode, test.id.c_str(), bundle.node.c_str(), gid, addStatus, CurrentError().c_str());
701+ return false;
702+ }
703+ if (!PrintAndCheckShapeInit(test.id, bundle)) {
704+ infraFailure = true;
705+ }
706+ 
707+ const auto feedFields = Split(test.feedShapes, ';');
708+ bool pass = true;
709+ std::vector<Observed> observations;
710+ for (size_t i = 0; i < feedFields.size(); ++i) {
711+ bool completed = false;
712+ const std::string label = feedFields.size() == 1U ? test.id : test.id + "#feed" + std::to_string(i);
713+ observations.push_back(
714+ RunPositiveFeed(session, gid, test, bundle, ParseShape(feedFields[i]), label, graphMode, completed));
715+ pass = pass && completed && observations.back().valuePass;
716+ }
717+ if (test.mode == "partition") {
718+ float maxError = 0.0F;
719+ const bool invariantPass = observations.size() == 2U &&
720+ CheckPartitionInvariant(observations[0], observations[1], maxError);
721+ std::printf("INVARIANT case=%s name=batch_partition_additivity actual=whole_vs_two_halves max_abs=%g "
722+ "rtol=%.1e atol=%.1e value=%s\n",
723+ test.id.c_str(), maxError, kRtol, kAtol, invariantPass ? "PASS" : "FAIL");
724+ pass = pass && invariantPass;
725+ }
726+ session.RemoveGraph(gid);
727+ return pass;
728+}
729+ 
730+bool RunRejectCase(ge::Session& session, uint32_t gid, const CaseDef& test, bool& infraFailure)
731+{
732+ GraphBundle bundle = BuildGraph(test);
733+ const char* graphMode = GraphMode(test);
734+ const ge::Status addStatus = session.AddGraph(gid, bundle.graph, std::map<ge::AscendString, ge::AscendString>{});
735+ std::printf("GRAPH-ADD mode=%s case=%s node=%s gid=%u declared_input_shape=%s status=%u\n", graphMode,
736+ test.id.c_str(), bundle.node.c_str(), gid, test.graphShape.c_str(), addStatus);
737+ std::string stage = "ADD_GRAPH";
738+ ge::Status status = addStatus;
739+ std::string rawError = CurrentError();
740+ if (addStatus == ge::SUCCESS) {
741+ if (!PrintAndCheckShapeInit(test.id, bundle)) {
742+ infraFailure = true;
743+ }
744+ stage = "RUN_GRAPH";
745+ std::vector<ge::Tensor> feeds;
746+ if (test.mode != "missing-input") {
747+ feeds.push_back(MakeInput(test, ParseShape(test.feedShapes)).tensor);
748+ }
749+ std::vector<ge::Tensor> outputs;
750+ status = session.RunGraph(gid, feeds, outputs);
751+ rawError = CurrentError();
752+ session.RemoveGraph(gid);
753+ }
754+ const std::string actualError = status == ge::SUCCESS ? "accepted" : ClassifyError(rawError);
755+ const bool pass = status != ge::SUCCESS && actualError == test.expectation;
756+ std::printf(
757+ "GRAPH-REJECT case=%s node=%s inputs={shape:%s,format:%s,dtype:%s} actual_stage=%s status=%u value=%s\n",
758+ test.id.c_str(), bundle.node.c_str(), test.graphShape.c_str(), test.inputFormat.c_str(), test.dtype.c_str(),
759+ stage.c_str(), status, pass ? "PASS" : "FAIL");
760+ std::printf("REJECT-DETAIL case=%s expected_error=%s actual_error=%s raw_error=%s\n", test.id.c_str(),
761+ test.expectation.c_str(), actualError.c_str(), rawError.c_str());
762+ return pass;
763+}
764+ 
765+bool RunRuntimeInputContractCase(ge::Session& session, uint32_t gid, const CaseDef& test, bool& infraFailure)
766+{
767+ GraphBundle bundle = BuildGraph(test);
768+ const char* graphMode = GraphMode(test);
769+ const ge::Status addStatus = session.AddGraph(gid, bundle.graph, std::map<ge::AscendString, ge::AscendString>{});
770+ std::printf("GRAPH-ADD mode=%s case=%s node=%s gid=%u declared_input_shape=%s status=%u\n", graphMode,
771+ test.id.c_str(), bundle.node.c_str(), gid, test.graphShape.c_str(), addStatus);
772+ std::string stage = "ADD_GRAPH";
773+ ge::Status status = addStatus;
774+ std::string rawError = CurrentError();
775+ if (addStatus == ge::SUCCESS) {
776+ if (!PrintAndCheckShapeInit(test.id, bundle)) {
777+ infraFailure = true;
778+ }
779+ stage = "RUN_GRAPH";
780+ std::vector<ge::Tensor> outputs;
781+ status = session.RunGraph(gid, {}, outputs);
782+ rawError = CurrentError();
783+ session.RemoveGraph(gid);
784+ }
785+ const std::string actualError = status == ge::SUCCESS ? "accepted" : ClassifyError(rawError);
786+ const bool pass = status != ge::SUCCESS && actualError == test.expectation;
787+ std::printf("RUNTIME-INPUT-CONTRACT case=%s node=%s graph=LEGAL_DATA_TO_OP runtime_feeds=EMPTY actual_stage=%s "
788+ "status=%u expected_error=%s actual_error=%s kernel_start_policy=ALLOWED value=%s\n",
789+ test.id.c_str(), bundle.node.c_str(), stage.c_str(), status, test.expectation.c_str(),
790+ actualError.c_str(), pass ? "PASS" : "FAIL");
791+ std::printf("RUNTIME-CONTRACT-DETAIL case=%s raw_error=%s\n", test.id.c_str(), rawError.c_str());
792+ return pass;
793+}
794+ 
795+bool InitializeGe()
796+{
797+ const std::map<ge::AscendString, ge::AscendString> options = {{"ge.exec.deviceId", "0"}, {"ge.graphRunMode", "1"}};
798+ const ge::Status status = ge::GEInitialize(options);
799+ if (status != ge::SUCCESS) {
800+ std::fprintf(stderr, "GEIR-INFRA stage=GEInitialize status=%u error=%s value=FAIL\n", status,
801+ CurrentError().c_str());
802+ return false;
803+ }
804+ return true;
805+}
806+ 
807+int RunRoute()
808+{
809+ if (!InitializeGe()) {
810+ return 2;
811+ }
812+ ge::Session session(std::map<ge::AscendString, ge::AscendString>{});
813+ CaseDef route = {"route-target", "positive", "static", "route", "F32", "NCHW",
814+ "[1,1,1,1]", "[1,1,1,1]", "finite", "oracle", "accept"};
815+ GraphBundle bundle = BuildGraph(route);
816+ constexpr uint32_t gid = 7000U;
817+ const ge::Status addStatus = session.AddGraph(gid, bundle.graph, std::map<ge::AscendString, ge::AscendString>{});
818+ std::printf("GRAPH-ADD mode=route signature=F32_NCHW_1x1x1x1 node=%s gid=%u declared_input_shape=%s status=%u\n",
819+ bundle.node.c_str(), gid, route.graphShape.c_str(), addStatus);
820+ int probeExit = 0;
821+ bool businessPass = false;
822+ if (addStatus != ge::SUCCESS) {
823+ probeExit = 3;
824+ std::printf("GRAPH-RUN mode=route case=route-target node=%s gid=%u actual_stage=ADD_GRAPH status=%u "
825+ "error=%s value=FAIL\n",
826+ bundle.node.c_str(), gid, addStatus, CurrentError().c_str());
827+ } else if (!PrintAndCheckShapeInit("route-target", bundle)) {
828+ probeExit = 4;
829+ } else {
830+ bool completed = false;
831+ const Observed observed = RunPositiveFeed(session, gid, route, bundle, {1, 1, 1, 1}, "route-target", "route",
832+ completed);
833+ if (!completed || !observed.readable) {
834+ probeExit = 5;
835+ }
836+ businessPass = observed.valuePass;
837+ session.RemoveGraph(gid);
838+ }
839+ const ge::Status finalizeStatus = ge::GEFinalize();
840+ if (finalizeStatus != ge::SUCCESS && probeExit == 0) {
841+ probeExit = 6;
842+ }
843+ std::printf("ROUTE-PROBE op=BNTrainingReduce node=%s status=DONE value=%s probe_exit=%d\n", bundle.node.c_str(),
844+ businessPass ? "PASS" : "FAIL", probeExit);
845+ return probeExit;
846+}
847+ 
848+int RunSelected(const std::vector<CaseDef>& cases, const std::string& selectedId, bool printSummary)
849+{
850+ if (!InitializeGe()) {
851+ return 2;
852+ }
853+ ge::Session session(std::map<ge::AscendString, ge::AscendString>{});
854+ int total = 0;
855+ int passed = 0;
856+ bool infraFailure = false;
857+ uint32_t gid = 8000U;
858+ for (const auto& test : cases) {
859+ if (!selectedId.empty() && test.id != selectedId) {
860+ continue;
861+ }
862+ ++total;
863+ bool pass = false;
864+ if (test.acceptance == "reject") {
865+ pass = RunRejectCase(session, gid, test, infraFailure);
866+ } else if (test.acceptance == "runtime-contract") {
867+ pass = RunRuntimeInputContractCase(session, gid, test, infraFailure);
868+ } else {
869+ pass = RunAcceptCase(session, gid, test, infraFailure);
870+ }
871+ std::printf("CASE %s %s\n", test.id.c_str(), pass ? "PASS" : "FAIL");
872+ if (pass) {
873+ ++passed;
874+ }
875+ ++gid;
876+ }
877+ const ge::Status finalizeStatus = ge::GEFinalize();
878+ if (finalizeStatus != ge::SUCCESS) {
879+ infraFailure = true;
880+ }
881+ if (printSummary) {
882+ std::printf("GEIR-SUMMARY total=%d pass=%d fail=%d skip=0\n", total, passed, total - passed);
883+ }
884+ if (total == 0) {
885+ std::fprintf(stderr, "GEIR-INFRA selected=%s reason=no_matching_case value=FAIL\n", selectedId.c_str());
886+ return 2;
887+ }
888+ return infraFailure ? 2 : 0;
889+}
890+ 
891+} // namespace
892+ 
893+int main(int argc, char** argv)
894+{
895+ if (argc < 2) {
896+ return RunRoute();
897+ }
898+ if (std::string(argv[1]) == "--route") {
899+ return RunRoute();
900+ }
901+ std::string manifest;
902+ std::string selected;
903+ bool printSummary = true;
904+ if (std::string(argv[1]) == "--test" && argc == 3) {
905+ manifest = argv[2];
906+ } else if (std::string(argv[1]) == "--case" && argc == 4) {
907+ selected = argv[2];
908+ manifest = argv[3];
909+ printSummary = false;
910+ } else {
911+ std::fprintf(stderr, "usage: %s {--route|--test|--case ID} cases.tsv\n", argv[0]);
912+ return 2;
913+ }
914+ std::vector<CaseDef> cases;
915+ if (!LoadCases(manifest, cases)) {
916+ return 2;
917+ }
918+ return RunSelected(cases, selected, printSummary);
919+}
@@ -0,0 +1,919 @@
1+/**
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 of
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.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include <acl/acl.h>
12+ 
13+#include <algorithm>
14+#include <cctype>
15+#include <cmath>
16+#include <cstdint>
17+#include <cstdio>
18+#include <cstring>
19+#include <fstream>
20+#include <limits>
21+#include <map>
22+#include <sstream>
23+#include <string>
24+#include <utility>
25+#include <vector>
26+ 
27+#include "ge_api.h"
28+#include "ge_error_codes.h"
29+#include "graph.h"
30+#include "ops_proto_nn.h"
31+#include "tensor.h"
32+#include "types.h"
33+ 
34+// The selected standard domain header owns BNTrainingReduce. Data remains in
35+// ops_proto_legacy.h in this CANN release, so keep its tiny construction class
36+// test-local instead of including the duplicate-heavy compatibility header.
37+namespace ge {
38+REG_OP(Data).INPUT(x, TensorType::ALL()).OUTPUT(y, TensorType::ALL()).ATTR(index, Int, 0).OP_END_FACTORY_REG(Data)
39+} // namespace ge
40+ 
41+namespace {
42+ 
43+constexpr float kRtol = 1.0e-4F;
44+constexpr float kAtol = 1.0e-4F;
45+ 
46+struct CaseDef {
47+ std::string id;
48+ std::string category;
49+ std::string mode;
50+ std::string capabilities;
51+ std::string dtype;
52+ std::string inputFormat;
53+ std::string graphShape;
54+ std::string feedShapes;
55+ std::string pattern;
56+ std::string expectation;
57+ std::string acceptance;
58+};
59+ 
60+struct InputData {
61+ ge::Tensor tensor;
62+ std::vector<float> quantized;
63+};
64+ 
65+struct Expected {
66+ std::vector<float> sum;
67+ std::vector<float> squareSum;
68+};
69+ 
70+struct Observed {
71+ bool readable = false;
72+ bool valuePass = false;
73+ std::vector<float> sum;
74+ std::vector<float> squareSum;
75+};
76+ 
77+struct GraphBundle {
78+ ge::Graph graph;
79+ ge::Operator reduce;
80+ std::string node;
81+};
82+ 
83+std::vector<std::string> Split(const std::string& text, char delimiter)
84+{
85+ std::vector<std::string> fields;
86+ std::stringstream stream(text);
87+ std::string field;
88+ while (std::getline(stream, field, delimiter)) {
89+ fields.push_back(field);
90+ }
91+ if (!text.empty() && text.back() == delimiter) {
92+ fields.emplace_back();
93+ }
94+ return fields;
95+}
96+ 
97+bool LoadCases(const std::string& path, std::vector<CaseDef>& cases)
98+{
99+ std::ifstream input(path);
100+ if (!input.is_open()) {
101+ std::fprintf(stderr, "GEIR-INFRA manifest=%s reason=open_failed value=FAIL\n", path.c_str());
102+ return false;
103+ }
104+ std::string line;
105+ if (!std::getline(input, line)) {
106+ return false;
107+ }
108+ const std::string expectedHeader = "id\tcategory\tmode\tcapabilities\tdtype\tinput_format\tgraph_shape\tfeed_"
109+ "shapes\tpattern\texpectation\tacceptance";
110+ if (line != expectedHeader) {
111+ std::fprintf(stderr, "GEIR-INFRA manifest=%s reason=bad_header value=FAIL\n", path.c_str());
112+ return false;
113+ }
114+ std::map<std::string, bool> ids;
115+ while (std::getline(input, line)) {
116+ if (line.empty()) {
117+ continue;
118+ }
119+ const auto fields = Split(line, '\t');
120+ if (fields.size() != 11U || fields[0].empty() || ids[fields[0]]) {
121+ std::fprintf(stderr, "GEIR-INFRA manifest=%s reason=bad_or_duplicate_row row=%s value=FAIL\n", path.c_str(),
122+ line.c_str());
123+ return false;
124+ }
125+ ids[fields[0]] = true;
126+ cases.push_back({fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], fields[7],
127+ fields[8], fields[9], fields[10]});
128+ }
129+ return !cases.empty();
130+}
131+ 
132+std::vector<int64_t> ParseShape(const std::string& text)
133+{
134+ if (text == "UNKNOWN_RANK") {
135+ return ge::UNKNOWN_RANK;
136+ }
137+ if (text.size() < 2U || text.front() != '[' || text.back() != ']') {
138+ return {};
139+ }
140+ const std::string inner = text.substr(1, text.size() - 2U);
141+ if (inner.empty()) {
142+ return {};
143+ }
144+ std::vector<int64_t> dims;
145+ for (const auto& field : Split(inner, ',')) {
146+ dims.push_back(std::stoll(field));
147+ }
148+ return dims;
149+}
150+ 
151+std::string ShapeString(const std::vector<int64_t>& dims)
152+{
153+ if (dims == ge::UNKNOWN_RANK) {
154+ return "UNKNOWN_RANK";
155+ }
156+ std::ostringstream out;
157+ out << '[';
158+ for (size_t i = 0; i < dims.size(); ++i) {
159+ if (i != 0U) {
160+ out << ',';
161+ }
162+ out << dims[i];
163+ }
164+ out << ']';
165+ return out.str();
166+}
167+ 
168+std::string ShapeString(const ge::Shape& shape) { return ShapeString(shape.GetDims()); }
169+ 
170+size_t Numel(const std::vector<int64_t>& shape)
171+{
172+ if (shape.empty()) {
173+ return 1U;
174+ }
175+ size_t count = 1U;
176+ for (const int64_t dim : shape) {
177+ if (dim <= 0) {
178+ return 0U;
179+ }
180+ count *= static_cast<size_t>(dim);
181+ }
182+ return count;
183+}
184+ 
185+ge::DataType ParseDtype(const std::string& dtype)
186+{
187+ if (dtype == "F16") {
188+ return ge::DT_FLOAT16;
189+ }
190+ if (dtype == "BF16") {
191+ return ge::DT_BF16;
192+ }
193+ if (dtype == "I32") {
194+ return ge::DT_INT32;
195+ }
196+ return ge::DT_FLOAT;
197+}
198+ 
199+const char* DtypeName(ge::DataType dtype)
200+{
201+ switch (dtype) {
202+ case ge::DT_FLOAT16:
203+ return "FLOAT16";
204+ case ge::DT_BF16:
205+ return "BFLOAT16";
206+ case ge::DT_FLOAT:
207+ return "FLOAT32";
208+ case ge::DT_INT32:
209+ return "INT32";
210+ case ge::DT_UNDEFINED:
211+ return "UNDEFINED";
212+ default:
213+ return "OTHER";
214+ }
215+}
216+ 
217+ge::Format ParseFormat(const std::string& format) { return format == "NHWC" ? ge::FORMAT_NHWC : ge::FORMAT_NCHW; }
218+ 
219+const char* FormatName(ge::Format format)
220+{
221+ if (format == ge::FORMAT_ND) {
222+ return "ND";
223+ }
224+ if (format == ge::FORMAT_NCHW) {
225+ return "NCHW";
226+ }
227+ if (format == ge::FORMAT_NHWC) {
228+ return "NHWC";
229+ }
230+ return "OTHER";
231+}
232+ 
233+uint32_t RoundRightShiftToEven(uint32_t value, uint32_t shift)
234+{
235+ const uint32_t truncated = value >> shift;
236+ const uint32_t remainder = value & ((1U << shift) - 1U);
237+ const uint32_t halfway = 1U << (shift - 1U);
238+ return truncated + static_cast<uint32_t>(remainder > halfway || (remainder == halfway && (truncated & 1U) != 0U));
239+}
240+ 
241+aclFloat16 FloatToFloat16(float value)
242+{
243+ uint32_t bits = 0U;
244+ std::memcpy(&bits, &value, sizeof(bits));
245+ const uint32_t sign = (bits >> 16U) & 0x8000U;
246+ const uint32_t exponent = (bits >> 23U) & 0xFFU;
247+ const uint32_t mantissa = bits & 0x7FFFFFU;
248+ if (exponent == 0xFFU) {
249+ return static_cast<aclFloat16>(sign | 0x7C00U | (mantissa == 0U ? 0U : 0x0200U));
250+ }
251+ if (exponent == 0U) {
252+ return static_cast<aclFloat16>(sign);
253+ }
254+ 
255+ int32_t halfExponent = static_cast<int32_t>(exponent) - 127 + 15;
256+ if (halfExponent <= 0) {
257+ if (halfExponent < -10) {
258+ return static_cast<aclFloat16>(sign);
259+ }
260+ const uint32_t rounded = RoundRightShiftToEven(mantissa | 0x800000U, 14U - halfExponent);
261+ return static_cast<aclFloat16>(sign | rounded);
262+ }
263+ if (halfExponent >= 31) {
264+ return static_cast<aclFloat16>(sign | 0x7C00U);
265+ }
266+ 
267+ uint32_t roundedMantissa = RoundRightShiftToEven(mantissa, 13U);
268+ if (roundedMantissa == 0x400U) {
269+ roundedMantissa = 0U;
270+ ++halfExponent;
271+ if (halfExponent >= 31) {
272+ return static_cast<aclFloat16>(sign | 0x7C00U);
273+ }
274+ }
275+ return static_cast<aclFloat16>(sign | (static_cast<uint32_t>(halfExponent) << 10U) | roundedMantissa);
276+}
277+ 
278+float Float16ToFloat(aclFloat16 value)
279+{
280+ const uint32_t sign = (static_cast<uint32_t>(value) & 0x8000U) << 16U;
281+ uint32_t exponent = (static_cast<uint32_t>(value) >> 10U) & 0x1FU;
282+ uint32_t mantissa = static_cast<uint32_t>(value) & 0x03FFU;
283+ uint32_t bits = sign;
284+ if (exponent == 0U && mantissa != 0U) {
285+ int32_t normalizedExponent = -14;
286+ while ((mantissa & 0x0400U) == 0U) {
287+ mantissa <<= 1U;
288+ --normalizedExponent;
289+ }
290+ mantissa &= 0x03FFU;
291+ bits |= static_cast<uint32_t>(normalizedExponent + 127) << 23U;
292+ bits |= mantissa << 13U;
293+ } else if (exponent == 0x1FU) {
294+ bits |= 0x7F800000U | (mantissa << 13U);
295+ } else if (exponent != 0U) {
296+ bits |= (exponent - 15U + 127U) << 23U;
297+ bits |= mantissa << 13U;
298+ }
299+ float result = 0.0F;
300+ std::memcpy(&result, &bits, sizeof(result));
301+ return result;
302+}
303+ 
304+uint16_t FloatToBfloat16(float value)
305+{
306+ uint32_t bits = 0U;
307+ std::memcpy(&bits, &value, sizeof(bits));
308+ const uint32_t roundingBias = 0x7FFFU + ((bits >> 16U) & 1U);
309+ return static_cast<uint16_t>((bits + roundingBias) >> 16U);
310+}
311+ 
312+float Bfloat16ToFloat(uint16_t value)
313+{
314+ uint32_t bits = static_cast<uint32_t>(value) << 16U;
315+ float result = 0.0F;
316+ std::memcpy(&result, &bits, sizeof(result));
317+ return result;
318+}
319+ 
320+std::vector<float> GenerateValues(const std::vector<int64_t>& shape, const std::string& pattern)
321+{
322+ const size_t count = Numel(shape);
323+ std::vector<float> values(count, 0.0F);
324+ for (size_t i = 0; i < count; ++i) {
325+ const int64_t signedIndex = static_cast<int64_t>(i % 17U) - 8;
326+ values[i] = static_cast<float>(signedIndex) * 0.125F;
327+ }
328+ if (pattern == "zero") {
329+ std::fill(values.begin(), values.end(), 0.0F);
330+ } else if (pattern == "alternate") {
331+ for (size_t i = 0; i < count; ++i) {
332+ values[i] = (i % 2U == 0U) ? 16.0F : -16.0F;
333+ }
334+ } else if (pattern == "60000") {
335+ std::fill(values.begin(), values.end(), 60000.0F);
336+ } else if (pattern == "1e20") {
337+ std::fill(values.begin(), values.end(), 1.0e20F);
338+ } else if (pattern == "partition" && shape.size() == 4U) {
339+ const int64_t n = shape[0];
340+ const int64_t c = shape[1];
341+ const int64_t h = shape[2];
342+ const int64_t w = shape[3];
343+ for (int64_t ni = 0; ni < n; ++ni) {
344+ for (int64_t ci = 0; ci < c; ++ci) {
345+ for (int64_t hi = 0; hi < h; ++hi) {
346+ for (int64_t wi = 0; wi < w; ++wi) {
347+ const size_t offset = static_cast<size_t>(((ni * c + ci) * h + hi) * w + wi);
348+ const int64_t code = ((ni % 2) * 13 + ci * 7 + hi * 3 + wi) % 19 - 9;
349+ values[offset] = static_cast<float>(code) * 0.0625F;
350+ }
351+ }
352+ }
353+ }
354+ }
355+ 
356+ if (shape.size() == 4U && count != 0U && shape[1] > 0) {
357+ const int64_t channel = shape[1] > 1 ? 1 : 0;
358+ const size_t first = static_cast<size_t>(channel * shape[2] * shape[3]);
359+ if (pattern == "nan") {
360+ values[first] = std::numeric_limits<float>::quiet_NaN();
361+ } else if (pattern == "posinf") {
362+ values[first] = std::numeric_limits<float>::infinity();
363+ } else if (pattern == "posneginf") {
364+ values[first] = std::numeric_limits<float>::infinity();
365+ if (first + 1U < values.size()) {
366+ values[first + 1U] = -std::numeric_limits<float>::infinity();
367+ }
368+ }
369+ }
370+ return values;
371+}
372+ 
373+InputData MakeInput(const CaseDef& test, const std::vector<int64_t>& shape)
374+{
375+ const ge::DataType dtype = ParseDtype(test.dtype);
376+ std::vector<float> source = GenerateValues(shape, test.pattern);
377+ std::vector<uint8_t> bytes;
378+ std::vector<float> quantized(source.size());
379+ if (dtype == ge::DT_FLOAT16) {
380+ std::vector<aclFloat16> raw(source.size());
381+ for (size_t i = 0; i < source.size(); ++i) {
382+ raw[i] = FloatToFloat16(source[i]);
383+ quantized[i] = Float16ToFloat(raw[i]);
384+ }
385+ bytes.resize(raw.size() * sizeof(aclFloat16));
386+ if (!bytes.empty()) {
387+ std::memcpy(bytes.data(), raw.data(), bytes.size());
388+ }
389+ } else if (dtype == ge::DT_BF16) {
390+ std::vector<uint16_t> raw(source.size());
391+ for (size_t i = 0; i < source.size(); ++i) {
392+ raw[i] = FloatToBfloat16(source[i]);
393+ quantized[i] = Bfloat16ToFloat(raw[i]);
394+ }
395+ bytes.resize(raw.size() * sizeof(uint16_t));
396+ if (!bytes.empty()) {
397+ std::memcpy(bytes.data(), raw.data(), bytes.size());
398+ }
399+ } else if (dtype == ge::DT_INT32) {
400+ std::vector<int32_t> raw(source.size());
401+ for (size_t i = 0; i < source.size(); ++i) {
402+ raw[i] = static_cast<int32_t>(source[i]);
403+ quantized[i] = static_cast<float>(raw[i]);
404+ }
405+ bytes.resize(raw.size() * sizeof(int32_t));
406+ if (!bytes.empty()) {
407+ std::memcpy(bytes.data(), raw.data(), bytes.size());
408+ }
409+ } else {
410+ quantized = source;
411+ bytes.resize(source.size() * sizeof(float));
412+ if (!bytes.empty()) {
413+ std::memcpy(bytes.data(), source.data(), bytes.size());
414+ }
415+ }
416+ 
417+ ge::TensorDesc desc(ge::Shape(shape), ParseFormat(test.inputFormat), dtype);
418+ desc.SetPlacement(ge::kPlacementHost);
419+ desc.SetRealDimCnt(static_cast<int64_t>(shape.size()));
420+ return {ge::Tensor(desc, bytes), std::move(quantized)};
421+}
422+ 
423+Expected ComputeExpected(const std::vector<float>& input, const std::vector<int64_t>& shape)
424+{
425+ Expected expected;
426+ if (shape.size() != 4U || shape[1] < 0) {
427+ return expected;
428+ }
429+ const int64_t n = shape[0];
430+ const int64_t c = shape[1];
431+ const int64_t h = shape[2];
432+ const int64_t w = shape[3];
433+ expected.sum.assign(static_cast<size_t>(c), 0.0F);
434+ expected.squareSum.assign(static_cast<size_t>(c), 0.0F);
435+ for (int64_t ni = 0; ni < n; ++ni) {
436+ for (int64_t ci = 0; ci < c; ++ci) {
437+ for (int64_t hi = 0; hi < h; ++hi) {
438+ for (int64_t wi = 0; wi < w; ++wi) {
439+ const size_t offset = static_cast<size_t>(((ni * c + ci) * h + hi) * w + wi);
440+ const float value = input[offset];
441+ expected.sum[static_cast<size_t>(ci)] += value;
442+ expected.squareSum[static_cast<size_t>(ci)] += value * value;
443+ }
444+ }
445+ }
446+ }
447+ return expected;
448+}
449+ 
450+bool Close(float actual, float expected, float& absoluteError)
451+{
452+ if (std::isnan(expected)) {
453+ return std::isnan(actual);
454+ }
455+ if (std::isinf(expected)) {
456+ return std::isinf(actual) && std::signbit(actual) == std::signbit(expected);
457+ }
458+ if (!std::isfinite(actual)) {
459+ return false;
460+ }
461+ absoluteError = std::fabs(actual - expected);
462+ return absoluteError <= kAtol + kRtol * std::fabs(expected);
463+}
464+ 
465+std::string OutputShapes(const std::vector<ge::Tensor>& outputs)
466+{
467+ if (outputs.size() != 2U) {
468+ return "COUNT_" + std::to_string(outputs.size());
469+ }
470+ return "{" + ShapeString(outputs[0].GetTensorDesc().GetShape()) + "," +
471+ ShapeString(outputs[1].GetTensorDesc().GetShape()) + "}";
472+}
473+ 
474+std::string OutputDtypes(const std::vector<ge::Tensor>& outputs)
475+{
476+ if (outputs.size() != 2U) {
477+ return "COUNT_" + std::to_string(outputs.size());
478+ }
479+ return "{" + std::string(DtypeName(outputs[0].GetTensorDesc().GetDataType())) + "," +
480+ DtypeName(outputs[1].GetTensorDesc().GetDataType()) + "}";
481+}
482+ 
483+std::string OutputFormats(const std::vector<ge::Tensor>& outputs)
484+{
485+ if (outputs.size() != 2U) {
486+ return "COUNT_" + std::to_string(outputs.size());
487+ }
488+ return "{" + std::string(FormatName(outputs[0].GetTensorDesc().GetFormat())) + "," +
489+ FormatName(outputs[1].GetTensorDesc().GetFormat()) + "}";
490+}
491+ 
492+Observed ValidateOutputs(const CaseDef& test, const std::vector<int64_t>& feedShape, const Expected& expected,
493+ const std::vector<ge::Tensor>& outputs, std::string& maxErrors, std::string& actualNumel)
494+{
495+ Observed observed;
496+ if (outputs.size() != 2U || feedShape.size() != 4U) {
497+ maxErrors = "{NA,NA}";
498+ actualNumel = "{NA,NA}";
499+ return observed;
500+ }
501+ const int64_t channel = feedShape[1];
502+ const std::vector<int64_t> expectedShape = {channel};
503+ bool metadataPass = true;
504+ for (const auto& output : outputs) {
505+ const ge::TensorDesc desc = output.GetTensorDesc();
506+ metadataPass = metadataPass && desc.GetShape().GetDims() == expectedShape;
507+ metadataPass = metadataPass && desc.GetDataType() == ge::DT_FLOAT;
508+ metadataPass = metadataPass && desc.GetFormat() == ge::FORMAT_ND;
509+ metadataPass = metadataPass && output.GetSize() == static_cast<size_t>(channel) * sizeof(float);
510+ }
511+ 
512+ observed.sum.resize(static_cast<size_t>(channel));
513+ observed.squareSum.resize(static_cast<size_t>(channel));
514+ if (channel > 0) {
515+ if (outputs[0].GetData() == nullptr || outputs[1].GetData() == nullptr) {
516+ maxErrors = "{NA,NA}";
517+ actualNumel = "{0,0}";
518+ return observed;
519+ }
520+ std::memcpy(observed.sum.data(), outputs[0].GetData(), observed.sum.size() * sizeof(float));
521+ std::memcpy(observed.squareSum.data(), outputs[1].GetData(), observed.squareSum.size() * sizeof(float));
522+ }
523+ observed.readable = true;
524+ 
525+ float maxSumError = 0.0F;
526+ float maxSquareError = 0.0F;
527+ bool valuesPass = expected.sum.size() == observed.sum.size() &&
528+ expected.squareSum.size() == observed.squareSum.size();
529+ for (size_t i = 0; valuesPass && i < observed.sum.size(); ++i) {
530+ float error = 0.0F;
531+ valuesPass = Close(observed.sum[i], expected.sum[i], error);
532+ maxSumError = std::max(maxSumError, error);
533+ error = 0.0F;
534+ valuesPass = valuesPass && Close(observed.squareSum[i], expected.squareSum[i], error);
535+ maxSquareError = std::max(maxSquareError, error);
536+ if (test.pattern == "zero") {
537+ valuesPass = valuesPass && observed.sum[i] == 0.0F && observed.squareSum[i] == 0.0F;
538+ }
539+ if (test.pattern == "alternate" && std::isfinite(observed.squareSum[i])) {
540+ valuesPass = valuesPass && observed.squareSum[i] >= 0.0F;
541+ }
542+ }
543+ observed.valuePass = metadataPass && valuesPass;
544+ std::ostringstream errors;
545+ errors << '{' << maxSumError << ',' << maxSquareError << '}';
546+ maxErrors = errors.str();
547+ actualNumel = "{" + std::to_string(outputs[0].GetSize() / sizeof(float)) + "," +
548+ std::to_string(outputs[1].GetSize() / sizeof(float)) + "}";
549+ return observed;
550+}
551+ 
552+bool IsUnknownRank(const ge::TensorDesc& desc) { return desc.GetShape().GetDims() == ge::UNKNOWN_RANK; }
553+ 
554+bool PrintAndCheckShapeInit(const std::string& caseId, const GraphBundle& bundle)
555+{
556+ const ge::TensorDesc sumDesc = bundle.reduce.GetOutputDescByName("sum");
557+ const ge::TensorDesc squareDesc = bundle.reduce.GetOutputDescByName("square_sum");
558+ const bool valid = IsUnknownRank(sumDesc) && IsUnknownRank(squareDesc) && sumDesc.GetDataType() == ge::DT_FLOAT &&
559+ squareDesc.GetDataType() == ge::DT_FLOAT;
560+ std::printf("SHAPE-INIT case=%s outputs={sum:{shape:%s,dtype:%s},square_sum:{shape:%s,dtype:%s}} value=%s\n",
561+ caseId.c_str(), ShapeString(sumDesc.GetShape()).c_str(), DtypeName(sumDesc.GetDataType()),
562+ ShapeString(squareDesc.GetShape()).c_str(), DtypeName(squareDesc.GetDataType()),
563+ valid ? "PASS" : "FAIL");
564+ return valid;
565+}
566+ 
567+GraphBundle BuildGraph(const CaseDef& test)
568+{
569+ GraphBundle bundle;
570+ bundle.node = test.id == "route-target" ? "bn_training_reduce_route" : "bn_training_reduce_" + test.id;
571+ bundle.graph = ge::Graph((bundle.node + "_graph").c_str());
572+ auto reduce = ge::op::BNTrainingReduce(bundle.node.c_str());
573+ const ge::TensorDesc outDesc(ge::Shape(ge::UNKNOWN_RANK), ge::FORMAT_ND, ge::DT_FLOAT);
574+ reduce.update_output_desc_sum(outDesc);
575+ reduce.update_output_desc_square_sum(outDesc);
576+ 
577+ std::vector<ge::Operator> inputs;
578+ const std::vector<int64_t> graphShape = test.mode == "missing-input" ? std::vector<int64_t>{1, 1, 1, 1} :
579+ ParseShape(test.graphShape);
580+ ge::TensorDesc xDesc(ge::Shape(graphShape), ParseFormat(test.inputFormat), ParseDtype(test.dtype));
581+ xDesc.SetRealDimCnt(graphShape == ge::UNKNOWN_RANK ? 0 : static_cast<int64_t>(graphShape.size()));
582+ auto data = ge::op::Data((bundle.node + "_x").c_str()).set_attr_index(0);
583+ data.update_input_desc_x(xDesc);
584+ data.update_output_desc_y(xDesc);
585+ reduce.set_input_x(data);
586+ reduce.update_input_desc_x(xDesc);
587+ bundle.graph.AddOp(data);
588+ inputs.push_back(data);
589+ bundle.graph.AddOp(reduce);
590+ const std::vector<std::pair<ge::Operator, std::vector<size_t>>> outputs = {{reduce, {0U, 1U}}};
591+ if (!inputs.empty()) {
592+ bundle.graph.SetInputs(inputs);
593+ }
594+ bundle.graph.SetOutputs(outputs);
595+ bundle.reduce = reduce;
596+ return bundle;
597+}
598+ 
599+const char* GraphMode(const CaseDef& test)
600+{
601+ return test.mode == "dynamic" || test.mode == "unknown-rank" ? "dynamic" : "static";
602+}
603+ 
604+std::string Sanitize(std::string text)
605+{
606+ if (text.size() > 160U) {
607+ text.resize(160U);
608+ }
609+ for (char& ch : text) {
610+ const bool safe = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') ||
611+ ch == '_' || ch == '-' || ch == '.';
612+ if (!safe) {
613+ ch = '_';
614+ }
615+ }
616+ return text.empty() ? "NONE" : text;
617+}
618+ 
619+std::string CurrentError()
620+{
621+ const ge::AscendString message = ge::GEGetErrorMsgV2();
622+ return message.GetString() == nullptr ? "NONE" : Sanitize(message.GetString());
623+}
624+ 
625+std::string ClassifyError(const std::string& raw)
626+{
627+ std::string lower = raw;
628+ std::transform(lower.begin(), lower.end(), lower.begin(),
629+ [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
630+ if (lower.find("dtype") != std::string::npos || lower.find("data_type") != std::string::npos ||
631+ lower.find("datatype") != std::string::npos) {
632+ return "dtype_not_supported";
633+ }
634+ if (lower.find("format") != std::string::npos || lower.find("shape") != std::string::npos ||
635+ lower.find("rank") != std::string::npos || lower.find("dim") != std::string::npos) {
636+ return "shape_mismatch";
637+ }
638+ if (lower.find("null") != std::string::npos || lower.find("missing") != std::string::npos ||
639+ lower.find("input") != std::string::npos || lower.find("anchor") != std::string::npos ||
640+ lower.find("edge") != std::string::npos) {
641+ return "null_input";
642+ }
643+ return "unclassified";
644+}
645+ 
646+Observed RunPositiveFeed(ge::Session& session, uint32_t gid, const CaseDef& test, const GraphBundle& bundle,
647+ const std::vector<int64_t>& feedShape, const std::string& feedLabel,
648+ const std::string& runMode, bool& runCompleted)
649+{
650+ const InputData input = MakeInput(test, feedShape);
651+ const Expected expected = ComputeExpected(input.quantized, feedShape);
652+ std::vector<ge::Tensor> outputs;
653+ const ge::Status status = session.RunGraph(gid, {input.tensor}, outputs);
654+ runCompleted = status == ge::SUCCESS;
655+ std::string maxErrors = "{NA,NA}";
656+ std::string actualNumel = "{NA,NA}";
657+ Observed observed;
658+ if (runCompleted) {
659+ observed = ValidateOutputs(test, feedShape, expected, outputs, maxErrors, actualNumel);
660+ }
661+ const std::string runError = runCompleted ? "NONE" : CurrentError();
662+ const int64_t channel = feedShape.size() == 4U ? feedShape[1] : -1;
663+ std::printf("GRAPH-RUN mode=%s case=%s node=%s gid=%u feed=%s expected_shape={[%ld],[%ld]} actual_shape=%s "
664+ "expected_dtype={FLOAT32,FLOAT32} actual_dtype=%s expected_format={ND,ND} actual_format=%s "
665+ "expected_numel={%ld,%ld} actual_numel=%s max_abs=%s rtol=%.1e atol=%.1e status=%u error=%s value=%s\n",
666+ runMode.c_str(), feedLabel.c_str(), bundle.node.c_str(), gid, ShapeString(feedShape).c_str(), channel,
667+ channel, OutputShapes(outputs).c_str(), OutputDtypes(outputs).c_str(), OutputFormats(outputs).c_str(),
668+ channel, channel, actualNumel.c_str(), maxErrors.c_str(), kRtol, kAtol, status, runError.c_str(),
669+ observed.valuePass ? "PASS" : "FAIL");
670+ return observed;
671+}
672+ 
673+bool CheckPartitionInvariant(const Observed& whole, const Observed& half, float& maxError)
674+{
675+ if (!whole.readable || !half.readable || whole.sum.size() != half.sum.size()) {
676+ return false;
677+ }
678+ bool pass = true;
679+ maxError = 0.0F;
680+ for (size_t i = 0; i < whole.sum.size(); ++i) {
681+ float error = 0.0F;
682+ pass = pass && Close(whole.sum[i], half.sum[i] * 2.0F, error);
683+ maxError = std::max(maxError, error);
684+ error = 0.0F;
685+ pass = pass && Close(whole.squareSum[i], half.squareSum[i] * 2.0F, error);
686+ maxError = std::max(maxError, error);
687+ }
688+ return pass;
689+}
690+ 
691+bool RunAcceptCase(ge::Session& session, uint32_t gid, const CaseDef& test, bool& infraFailure)
692+{
693+ GraphBundle bundle = BuildGraph(test);
694+ const char* graphMode = GraphMode(test);
695+ const ge::Status addStatus = session.AddGraph(gid, bundle.graph, std::map<ge::AscendString, ge::AscendString>{});
696+ std::printf("GRAPH-ADD mode=%s case=%s node=%s gid=%u declared_input_shape=%s status=%u\n", graphMode,
697+ test.id.c_str(), bundle.node.c_str(), gid, test.graphShape.c_str(), addStatus);
698+ if (addStatus != ge::SUCCESS) {
699+ std::printf("GRAPH-RUN mode=%s case=%s node=%s gid=%u actual_stage=ADD_GRAPH status=%u error=%s value=FAIL\n",
700+ graphMode, test.id.c_str(), bundle.node.c_str(), gid, addStatus, CurrentError().c_str());
701+ return false;
702+ }
703+ if (!PrintAndCheckShapeInit(test.id, bundle)) {
704+ infraFailure = true;
705+ }
706+ 
707+ const auto feedFields = Split(test.feedShapes, ';');
708+ bool pass = true;
709+ std::vector<Observed> observations;
710+ for (size_t i = 0; i < feedFields.size(); ++i) {
711+ bool completed = false;
712+ const std::string label = feedFields.size() == 1U ? test.id : test.id + "#feed" + std::to_string(i);
713+ observations.push_back(
714+ RunPositiveFeed(session, gid, test, bundle, ParseShape(feedFields[i]), label, graphMode, completed));
715+ pass = pass && completed && observations.back().valuePass;
716+ }
717+ if (test.mode == "partition") {
718+ float maxError = 0.0F;
719+ const bool invariantPass = observations.size() == 2U &&
720+ CheckPartitionInvariant(observations[0], observations[1], maxError);
721+ std::printf("INVARIANT case=%s name=batch_partition_additivity actual=whole_vs_two_halves max_abs=%g "
722+ "rtol=%.1e atol=%.1e value=%s\n",
723+ test.id.c_str(), maxError, kRtol, kAtol, invariantPass ? "PASS" : "FAIL");
724+ pass = pass && invariantPass;
725+ }
726+ session.RemoveGraph(gid);
727+ return pass;
728+}
729+ 
730+bool RunRejectCase(ge::Session& session, uint32_t gid, const CaseDef& test, bool& infraFailure)
731+{
732+ GraphBundle bundle = BuildGraph(test);
733+ const char* graphMode = GraphMode(test);
734+ const ge::Status addStatus = session.AddGraph(gid, bundle.graph, std::map<ge::AscendString, ge::AscendString>{});
735+ std::printf("GRAPH-ADD mode=%s case=%s node=%s gid=%u declared_input_shape=%s status=%u\n", graphMode,
736+ test.id.c_str(), bundle.node.c_str(), gid, test.graphShape.c_str(), addStatus);
737+ std::string stage = "ADD_GRAPH";
738+ ge::Status status = addStatus;
739+ std::string rawError = CurrentError();
740+ if (addStatus == ge::SUCCESS) {
741+ if (!PrintAndCheckShapeInit(test.id, bundle)) {
742+ infraFailure = true;
743+ }
744+ stage = "RUN_GRAPH";
745+ std::vector<ge::Tensor> feeds;
746+ if (test.mode != "missing-input") {
747+ feeds.push_back(MakeInput(test, ParseShape(test.feedShapes)).tensor);
748+ }
749+ std::vector<ge::Tensor> outputs;
750+ status = session.RunGraph(gid, feeds, outputs);
751+ rawError = CurrentError();
752+ session.RemoveGraph(gid);
753+ }
754+ const std::string actualError = status == ge::SUCCESS ? "accepted" : ClassifyError(rawError);
755+ const bool pass = status != ge::SUCCESS && actualError == test.expectation;
756+ std::printf(
757+ "GRAPH-REJECT case=%s node=%s inputs={shape:%s,format:%s,dtype:%s} actual_stage=%s status=%u value=%s\n",
758+ test.id.c_str(), bundle.node.c_str(), test.graphShape.c_str(), test.inputFormat.c_str(), test.dtype.c_str(),
759+ stage.c_str(), status, pass ? "PASS" : "FAIL");
760+ std::printf("REJECT-DETAIL case=%s expected_error=%s actual_error=%s raw_error=%s\n", test.id.c_str(),
761+ test.expectation.c_str(), actualError.c_str(), rawError.c_str());
762+ return pass;
763+}
764+ 
765+bool RunRuntimeInputContractCase(ge::Session& session, uint32_t gid, const CaseDef& test, bool& infraFailure)
766+{
767+ GraphBundle bundle = BuildGraph(test);
768+ const char* graphMode = GraphMode(test);
769+ const ge::Status addStatus = session.AddGraph(gid, bundle.graph, std::map<ge::AscendString, ge::AscendString>{});
770+ std::printf("GRAPH-ADD mode=%s case=%s node=%s gid=%u declared_input_shape=%s status=%u\n", graphMode,
771+ test.id.c_str(), bundle.node.c_str(), gid, test.graphShape.c_str(), addStatus);
772+ std::string stage = "ADD_GRAPH";
773+ ge::Status status = addStatus;
774+ std::string rawError = CurrentError();
775+ if (addStatus == ge::SUCCESS) {
776+ if (!PrintAndCheckShapeInit(test.id, bundle)) {
777+ infraFailure = true;
778+ }
779+ stage = "RUN_GRAPH";
780+ std::vector<ge::Tensor> outputs;
781+ status = session.RunGraph(gid, {}, outputs);
782+ rawError = CurrentError();
783+ session.RemoveGraph(gid);
784+ }
785+ const std::string actualError = status == ge::SUCCESS ? "accepted" : ClassifyError(rawError);
786+ const bool pass = status != ge::SUCCESS && actualError == test.expectation;
787+ std::printf("RUNTIME-INPUT-CONTRACT case=%s node=%s graph=LEGAL_DATA_TO_OP runtime_feeds=EMPTY actual_stage=%s "
788+ "status=%u expected_error=%s actual_error=%s kernel_start_policy=ALLOWED value=%s\n",
789+ test.id.c_str(), bundle.node.c_str(), stage.c_str(), status, test.expectation.c_str(),
790+ actualError.c_str(), pass ? "PASS" : "FAIL");
791+ std::printf("RUNTIME-CONTRACT-DETAIL case=%s raw_error=%s\n", test.id.c_str(), rawError.c_str());
792+ return pass;
793+}
794+ 
795+bool InitializeGe()
796+{
797+ const std::map<ge::AscendString, ge::AscendString> options = {{"ge.exec.deviceId", "0"}, {"ge.graphRunMode", "1"}};
798+ const ge::Status status = ge::GEInitialize(options);
799+ if (status != ge::SUCCESS) {
800+ std::fprintf(stderr, "GEIR-INFRA stage=GEInitialize status=%u error=%s value=FAIL\n", status,
801+ CurrentError().c_str());
802+ return false;
803+ }
804+ return true;
805+}
806+ 
807+int RunRoute()
808+{
809+ if (!InitializeGe()) {
810+ return 2;
811+ }
812+ ge::Session session(std::map<ge::AscendString, ge::AscendString>{});
813+ CaseDef route = {"route-target", "positive", "static", "route", "F32", "NCHW",
814+ "[1,1,1,1]", "[1,1,1,1]", "finite", "oracle", "accept"};
815+ GraphBundle bundle = BuildGraph(route);
816+ constexpr uint32_t gid = 7000U;
817+ const ge::Status addStatus = session.AddGraph(gid, bundle.graph, std::map<ge::AscendString, ge::AscendString>{});
818+ std::printf("GRAPH-ADD mode=route signature=F32_NCHW_1x1x1x1 node=%s gid=%u declared_input_shape=%s status=%u\n",
819+ bundle.node.c_str(), gid, route.graphShape.c_str(), addStatus);
820+ int probeExit = 0;
821+ bool businessPass = false;
822+ if (addStatus != ge::SUCCESS) {
823+ probeExit = 3;
824+ std::printf("GRAPH-RUN mode=route case=route-target node=%s gid=%u actual_stage=ADD_GRAPH status=%u "
825+ "error=%s value=FAIL\n",
826+ bundle.node.c_str(), gid, addStatus, CurrentError().c_str());
827+ } else if (!PrintAndCheckShapeInit("route-target", bundle)) {
828+ probeExit = 4;
829+ } else {
830+ bool completed = false;
831+ const Observed observed = RunPositiveFeed(session, gid, route, bundle, {1, 1, 1, 1}, "route-target", "route",
832+ completed);
833+ if (!completed || !observed.readable) {
834+ probeExit = 5;
835+ }
836+ businessPass = observed.valuePass;
837+ session.RemoveGraph(gid);
838+ }
839+ const ge::Status finalizeStatus = ge::GEFinalize();
840+ if (finalizeStatus != ge::SUCCESS && probeExit == 0) {
841+ probeExit = 6;
842+ }
843+ std::printf("ROUTE-PROBE op=BNTrainingReduce node=%s status=DONE value=%s probe_exit=%d\n", bundle.node.c_str(),
844+ businessPass ? "PASS" : "FAIL", probeExit);
845+ return probeExit;
846+}
847+ 
848+int RunSelected(const std::vector<CaseDef>& cases, const std::string& selectedId, bool printSummary)
849+{
850+ if (!InitializeGe()) {
851+ return 2;
852+ }
853+ ge::Session session(std::map<ge::AscendString, ge::AscendString>{});
854+ int total = 0;
855+ int passed = 0;
856+ bool infraFailure = false;
857+ uint32_t gid = 8000U;
858+ for (const auto& test : cases) {
859+ if (!selectedId.empty() && test.id != selectedId) {
860+ continue;
861+ }
862+ ++total;
863+ bool pass = false;
864+ if (test.acceptance == "reject") {
865+ pass = RunRejectCase(session, gid, test, infraFailure);
866+ } else if (test.acceptance == "runtime-contract") {
867+ pass = RunRuntimeInputContractCase(session, gid, test, infraFailure);
868+ } else {
869+ pass = RunAcceptCase(session, gid, test, infraFailure);
870+ }
871+ std::printf("CASE %s %s\n", test.id.c_str(), pass ? "PASS" : "FAIL");
872+ if (pass) {
873+ ++passed;
874+ }
875+ ++gid;
876+ }
877+ const ge::Status finalizeStatus = ge::GEFinalize();
878+ if (finalizeStatus != ge::SUCCESS) {
879+ infraFailure = true;
880+ }
881+ if (printSummary) {
882+ std::printf("GEIR-SUMMARY total=%d pass=%d fail=%d skip=0\n", total, passed, total - passed);
883+ }
884+ if (total == 0) {
885+ std::fprintf(stderr, "GEIR-INFRA selected=%s reason=no_matching_case value=FAIL\n", selectedId.c_str());
886+ return 2;
887+ }
888+ return infraFailure ? 2 : 0;
889+}
890+ 
891+} // namespace
892+ 
893+int main(int argc, char** argv)
894+{
895+ if (argc < 2) {
896+ return RunRoute();
897+ }
898+ if (std::string(argv[1]) == "--route") {
899+ return RunRoute();
900+ }
901+ std::string manifest;
902+ std::string selected;
903+ bool printSummary = true;
904+ if (std::string(argv[1]) == "--test" && argc == 3) {
905+ manifest = argv[2];
906+ } else if (std::string(argv[1]) == "--case" && argc == 4) {
907+ selected = argv[2];
908+ manifest = argv[3];
909+ printSummary = false;
910+ } else {
911+ std::fprintf(stderr, "usage: %s {--route|--test|--case ID} cases.tsv\n", argv[0]);
912+ return 2;
913+ }
914+ std::vector<CaseDef> cases;
915+ if (!LoadCases(manifest, cases)) {
916+ return 2;
917+ }
918+ return RunSelected(cases, selected, printSummary);
919+}
Rnorm/bn_training_reduce/op_host/op_api/aclnn_batch_norm_reduce.cppnorm/bn_training_reduce/op_api/aclnn_batch_norm_reduce.cpp+20-14
@@ -64,8 +64,8 @@ static bool CheckFormat(const aclTensor* x, const aclTensor* sum, const aclTenso
64 auto xFormat = x->GetStorageFormat();64 auto xFormat = x->GetStorageFormat();
65 auto sumFormat = sum->GetStorageFormat();65 auto sumFormat = sum->GetStorageFormat();
66 auto squareSumFormat = squareSum->GetStorageFormat();66 auto squareSumFormat = squareSum->GetStorageFormat();
67- if (xFormat != op::Format::FORMAT_NCHW) {67+ if (xFormat != op::Format::FORMAT_NCHW && xFormat != op::Format::FORMAT_NHWC) {
68- OP_LOGE(ACLNN_ERR_PARAM_INVALID, "Format of x only supports [NCHW], but format is [%s].",68+ OP_LOGE(ACLNN_ERR_PARAM_INVALID, "Format of x only supports [NCHW, NHWC], but format is [%s].",
69 op::ToString(xFormat).GetString());69 op::ToString(xFormat).GetString());
70 return false;70 return false;
71 }71 }
@@ -129,10 +129,13 @@ static aclnnStatus CheckParams(const aclTensor* x, aclTensor* sum, aclTensor* sq
129 return ACLNN_SUCCESS;129 return ACLNN_SUCCESS;
130}130}
131 131 
132-const aclTensor* ResizeTo4D(const aclTensor* input, aclOpExecutor* executor)132+const aclTensor* ResizeTo4D(const aclTensor* input, op::Format format, aclOpExecutor* executor)
133{133{
134- const int64_t appendDim[] = {0, 2, 3};134+ const int64_t appendDimNchw[] = {0, 2, 3};
135- aclIntArray* newShape = executor->AllocIntArray(appendDim, sizeof(appendDim) / sizeof(int64_t));135+ const int64_t appendDimNhwc[] = {0, 1, 2};
136+ const int64_t* appendDim = format == op::Format::FORMAT_NHWC ? appendDimNhwc : appendDimNchw;
137+ constexpr size_t appendDimNum = sizeof(appendDimNchw) / sizeof(appendDimNchw[0]);
138+ aclIntArray* newShape = executor->AllocIntArray(appendDim, appendDimNum);
136 139 
137 auto inputUnsqueeze = l0op::UnsqueezeNd(input, newShape, executor);140 auto inputUnsqueeze = l0op::UnsqueezeNd(input, newShape, executor);
138 if (inputUnsqueeze == nullptr) {141 if (inputUnsqueeze == nullptr) {
@@ -141,16 +144,19 @@ const aclTensor* ResizeTo4D(const aclTensor* input, aclOpExecutor* executor)
141 auto formatTensor = executor == nullptr ? const_cast<aclTensor*>(inputUnsqueeze) :144 auto formatTensor = executor == nullptr ? const_cast<aclTensor*>(inputUnsqueeze) :
142 executor->CreateView(inputUnsqueeze, inputUnsqueeze->GetViewShape(),145 executor->CreateView(inputUnsqueeze, inputUnsqueeze->GetViewShape(),
143 inputUnsqueeze->GetViewOffset());146 inputUnsqueeze->GetViewOffset());
144- formatTensor->SetViewFormat(Format::FORMAT_NCHW);147+ formatTensor->SetViewFormat(format);
145- formatTensor->SetOriginalFormat(Format::FORMAT_NCHW);148+ formatTensor->SetOriginalFormat(format);
146- formatTensor->SetStorageFormat(Format::FORMAT_NCHW);149+ formatTensor->SetStorageFormat(format);
147 return formatTensor;150 return formatTensor;
148}151}
149 152 
150const aclTensor* ResizeTo1D(const aclTensor* input, aclOpExecutor* executor)153const aclTensor* ResizeTo1D(const aclTensor* input, aclOpExecutor* executor)
151{154{
152- const int64_t removeDim[] = {0, 2, 3};155+ const int64_t removeDimNchw[] = {0, 2, 3};
153- aclIntArray* newShape = executor->AllocIntArray(removeDim, sizeof(removeDim) / sizeof(int64_t));156+ const int64_t removeDimNhwc[] = {0, 1, 2};
157+ const int64_t* removeDim = input->GetStorageFormat() == op::Format::FORMAT_NHWC ? removeDimNhwc : removeDimNchw;
158+ constexpr size_t removeDimNum = sizeof(removeDimNchw) / sizeof(removeDimNchw[0]);
159+ aclIntArray* newShape = executor->AllocIntArray(removeDim, removeDimNum);
154 160 
155 auto inputSqueeze = l0op::SqueezeNd(input, newShape, executor);161 auto inputSqueeze = l0op::SqueezeNd(input, newShape, executor);
156 if (inputSqueeze == nullptr) {162 if (inputSqueeze == nullptr) {
@@ -193,10 +199,10 @@ aclnnStatus aclnnBatchNormReduceGetWorkspaceSize(const aclTensor* x, aclTensor*
193 auto xContiguous = l0op::Contiguous(x, uniqueExecutor.get());199 auto xContiguous = l0op::Contiguous(x, uniqueExecutor.get());
194 CHECK_RET(xContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);200 CHECK_RET(xContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
195 201 
196- auto sumNCHW = ResizeTo4D(sum, uniqueExecutor.get());202+ auto sum4D = ResizeTo4D(sum, xContiguous->GetStorageFormat(), uniqueExecutor.get());
197- CHECK_RET(sumNCHW != nullptr, ACLNN_ERR_INNER_NULLPTR);203+ CHECK_RET(sum4D != nullptr, ACLNN_ERR_INNER_NULLPTR);
198 204 
199- std::array<aclTensor*, TENSOR_NUM> sumTensor = l0op::BNTrainingReduce(xContiguous, sumNCHW->GetViewShape(),205+ std::array<aclTensor*, TENSOR_NUM> sumTensor = l0op::BNTrainingReduce(xContiguous, sum4D->GetViewShape(),
200 uniqueExecutor.get());206 uniqueExecutor.get());
201 207 
202 auto sumND = ResizeTo1D(sumTensor[0], uniqueExecutor.get());208 auto sumND = ResizeTo1D(sumTensor[0], uniqueExecutor.get());
@@ -225,4 +231,4 @@ aclnnStatus aclnnBatchNormReduce(void* workspace, uint64_t workspaceSize, aclOpE
225 231 
226#ifdef __cplusplus232#ifdef __cplusplus
227}233}
228-#endif234+#endif
Rnorm/bn_training_reduce/op_host/op_api/aclnn_batch_norm_reduce.hnorm/bn_training_reduce/op_api/aclnn_batch_norm_reduce.h+0-0
文件重命名但无更改。
@@ -0,0 +1,39 @@
1+/**
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 of
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.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "op_common/log/log.h"
12+#include "register/op_impl_registry.h"
13+ 
14+using namespace ge;
15+ 
16+namespace ops {
17+static ge::graphStatus InferDataType4BNTrainingReduce(gert::InferDataTypeContext* context)
18+{
19+ if (context == nullptr) {
20+ return GRAPH_FAILED;
21+ }
22+ const DataType xDataType = context->GetInputDataType(0);
23+ if (xDataType != DT_FLOAT16 && xDataType != DT_BF16 && xDataType != DT_FLOAT) {
24+ OP_LOGE(context, "BNTrainingReduce input x dtype is not supported: %d.", static_cast<int32_t>(xDataType));
25+ return GRAPH_FAILED;
26+ }
27+ if (context->SetOutputDataType(0, DT_FLOAT) != GRAPH_SUCCESS) {
28+ OP_LOGE(context, "Failed to set BNTrainingReduce sum output dtype.");
29+ return GRAPH_FAILED;
30+ }
31+ if (context->SetOutputDataType(1, DT_FLOAT) != GRAPH_SUCCESS) {
32+ OP_LOGE(context, "Failed to set BNTrainingReduce square_sum output dtype.");
33+ return GRAPH_FAILED;
34+ }
35+ return GRAPH_SUCCESS;
36+}
37+ 
38+IMPL_OP(BNTrainingReduce).InferDataType(InferDataType4BNTrainingReduce);
39+} // namespace ops
@@ -0,0 +1,43 @@
1+/**
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 of
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.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef OPS_BN_TRAINING_REDUCE_PROTO_H_
12+#define OPS_BN_TRAINING_REDUCE_PROTO_H_
13+ 
14+#include "graph/operator_reg.h"
15+ 
16+namespace ge {
17+/**
18+* @brief Performs reduced batch normalization .
19+ 
20+* @par Inputs:
21+* x: A 4D tensor of type float16 or float32 or bfloat16, with format NHWC or NCHW.
22+* Indicates the input tensor, that is, the original data to be normalized.
23+ 
24+* @par Outputs:
25+* @li sum: A 1D tensor of type float32 for SUM reduced "x". It represents the sum of the input tensor "x" on the C axis.
26+* The shape of sum is consistent with the C axis of "x". Has the same format as "x".
27+* @li square_sum: A 1D tensor of type float32 for SUMSQ reduced "x". It represents the sum of squares of the input
28+tensor "x" on the C axis.
29+* The shape of sum is consistent with the C axis of "x". Has the same format as "x". \n
30+ 
31+* @attention Constraints:
32+* This operator is a BatchNorm fusion operator for updating the moving
33+* averages for training.
34+* This operator is used in conjunction with BNTrainingReduce.
35+*/
36+REG_OP(BNTrainingReduce)
37+ .INPUT(x, TensorType({DT_FLOAT16, DT_FLOAT, DT_BF16}))
38+ .OUTPUT(sum, TensorType({DT_FLOAT}))
39+ .OUTPUT(square_sum, TensorType({DT_FLOAT}))
40+ .OP_END_FACTORY_REG(BNTrainingReduce)
41+} // namespace ge
42+ 
43+#endif // OPS_BN_TRAINING_REDUCE_PROTO_H_
@@ -1,12 +0,0 @@
1-# Copyright (c) 2025 Huawei Technologies Co., Ltd.
2-# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
3-# CANN Open Software License Agreement Version 2.0 (the "License").
4-# Please refer to the License for details. You may not use this file except in compliance with the License.
5-# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
6-# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
7-# See LICENSE in the root of the software repository for the full text of the License.
8-#/
9- 
10-message(STATUS "=== Debug: start ops.norm.bn_training_reduce.op_host.CMakeLists.txt ")
11- 
12-add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE bn_training_reduce ACLNNTYPE aclnn_exclude DEPENDENCIES norm_common batch_norm_v3)
@@ -0,0 +1,292 @@
1+/**
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 of
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.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "bn_training_reduce_tiling_arch35.h"
12+ 
13+#include "bn_training_reduce_tiling_public.h"
14+#include <algorithm>
15+#include <limits>
16+ 
17+#include "op_common/op_host/util/platform_util.h"
18+#include "tiling/platform/platform_ascendc.h"
19+#include "op_common/log/log.h"
20+#include "register/op_impl_registry.h"
21+ 
22+#include "../../op_kernel/arch35/bn_training_reduce_tiling_data.h"
23+ 
24+namespace optiling {
25+namespace {
26+constexpr int64_t kSmallRTileChannels = 64;
27+constexpr int64_t kBlockBytes = 32;
28+ 
29+struct BNTrainingReduceCompileInfo {};
30+ 
31+ge::graphStatus TilingParseForBNTrainingReduce([[maybe_unused]] gert::TilingParseContext* context)
32+{
33+ return ge::GRAPH_SUCCESS;
34+}
35+ 
36+int64_t DTypeBytes(BNTrainingReducePublicDType dtype)
37+{
38+ if (dtype == BNTrainingReducePublicDType::FLOAT32) {
39+ return 4;
40+ }
41+ if (dtype == BNTrainingReducePublicDType::FLOAT16 || dtype == BNTrainingReducePublicDType::BFLOAT16) {
42+ return 2;
43+ }
44+ return 0;
45+}
46+ 
47+int64_t AlignBytes(int64_t bytes) { return (bytes + kBlockBytes - 1) / kBlockBytes * kBlockBytes; }
48+ 
49+bool IsFirstRoundSmallR(int64_t reduceLen)
50+{
51+ return reduceLen == 1 || reduceLen == 3 || reduceLen == 10 || reduceLen == 17;
52+}
53+ 
54+bool TryBuildSmallRConfig(const BNTrainingReducePublicInputs& inputs, BNTrainingReducePublicResult& result)
55+{
56+ const int64_t n = inputs.shape[0];
57+ const int64_t channels = inputs.shape[1];
58+ const int64_t h = inputs.shape[2];
59+ const int64_t w = inputs.shape[3];
60+ const int64_t dtypeBytes = DTypeBytes(inputs.inputDtype);
61+ if (!inputs.inputPresent || inputs.rank != 4 || inputs.format != BNTrainingReducePublicFormat::NCHW || n != 1 ||
62+ channels <= 0 || h <= 0 || w <= 0 || dtypeBytes == 0 || inputs.coreNum <= 0 || inputs.ubSize <= 0 ||
63+ h > std::numeric_limits<int64_t>::max() / w) {
64+ return false;
65+ }
66+ const int64_t reduceLen = h * w;
67+ if (!IsFirstRoundSmallR(reduceLen)) {
68+ return false;
69+ }
70+ 
71+ const int64_t inputBytes = AlignBytes(kSmallRTileChannels * reduceLen * dtypeBytes);
72+ const int64_t outputBytes = AlignBytes(kSmallRTileChannels * static_cast<int64_t>(sizeof(float)));
73+ if (inputBytes > inputs.ubSize || outputBytes > inputs.ubSize - inputBytes ||
74+ outputBytes > inputs.ubSize - inputBytes - outputBytes) {
75+ return false;
76+ }
77+ 
78+ const int64_t tileCount = (channels + kSmallRTileChannels - 1) / kSmallRTileChannels;
79+ const int32_t usedCores = static_cast<int32_t>(std::min<int64_t>(tileCount, inputs.coreNum));
80+ if (usedCores <= 0) {
81+ return false;
82+ }
83+ const int64_t smallLoops = tileCount / usedCores;
84+ const int32_t bigCores = static_cast<int32_t>(tileCount % usedCores);
85+ 
86+ result.status = BNTrainingReducePublicStatus::SUCCESS;
87+ result.tilingKey = static_cast<int64_t>(BNTrainingReduceTilingKey::SMALL_R);
88+ result.blockDim = static_cast<uint32_t>(usedCores);
89+ result.workspaceSize = inputs.systemWorkspaceSize;
90+ result.scheduleMode = 0;
91+ auto& td = result.tilingData;
92+ td.axisNum = 2;
93+ for (int32_t i = 0; i < MAX_PATTERN_RANK; ++i) {
94+ td.axisShape[i] = 1;
95+ td.axisStride[i] = 0;
96+ }
97+ td.axisShape[0] = channels;
98+ td.axisShape[1] = reduceLen;
99+ td.axisStride[0] = reduceLen;
100+ td.axisStride[1] = 1;
101+ td.aLoopCntTotal = tileCount;
102+ td.aSplitChunkCnt = tileCount;
103+ td.aBigCoreLoopCnt = smallLoops + (bigCores > 0 ? 1 : 0);
104+ td.aSmallCoreLoopCnt = smallLoops;
105+ td.aBigCoreCnt = bigCores;
106+ td.usedCoreNum = usedCores;
107+ td.aSplitAxisIdx = 0;
108+ td.rSplitAxisIdx = 1;
109+ td.aUbFactor = kSmallRTileChannels;
110+ td.aUbFactorAlign = kSmallRTileChannels;
111+ td.rUbFactor = reduceLen;
112+ td.rUbFactorAlign = reduceLen;
113+ td.innerAProd = 1;
114+ td.innerAProdAlign = 1;
115+ td.innerRProd = 1;
116+ td.innerRProdAlign = 1;
117+ td.rLoopCntTotal = 1;
118+ td.preReduceUbSize = inputBytes;
119+ td.postReduceUbSize = outputBytes;
120+ td.tmpBufUbSize = outputBytes;
121+ td.cacheBufUbSize = 0;
122+ td.rGroupCnt = 0;
123+ return true;
124+}
125+ 
126+BNTrainingReducePublicDType ConvertDType(ge::DataType dtype)
127+{
128+ switch (dtype) {
129+ case ge::DT_FLOAT16:
130+ return BNTrainingReducePublicDType::FLOAT16;
131+ case ge::DT_BF16:
132+ return BNTrainingReducePublicDType::BFLOAT16;
133+ case ge::DT_FLOAT:
134+ return BNTrainingReducePublicDType::FLOAT32;
135+ default:
136+ return BNTrainingReducePublicDType::INT32;
137+ }
138+}
139+ 
140+bool NormalizeOutputShape(const gert::Shape& outputShape, BNTrainingReducePublicFormat format, int32_t& rank,
141+ int64_t& channel)
142+{
143+ const size_t outputRank = outputShape.GetDimNum();
144+ if (outputRank > static_cast<size_t>(std::numeric_limits<int32_t>::max())) {
145+ return false;
146+ }
147+ 
148+ rank = static_cast<int32_t>(outputRank);
149+ channel = rank == 1 ? outputShape.GetDim(0) : 0;
150+ // ACLNN expands the public one-dimensional output to the input layout
151+ // before the AICore launch and squeezes it back afterwards.
152+ const bool isNchwOutput = format == BNTrainingReducePublicFormat::NCHW && rank == 4 && outputShape.GetDim(0) == 1 &&
153+ outputShape.GetDim(2) == 1 && outputShape.GetDim(3) == 1;
154+ const bool isNhwcOutput = format == BNTrainingReducePublicFormat::NHWC && rank == 4 && outputShape.GetDim(0) == 1 &&
155+ outputShape.GetDim(1) == 1 && outputShape.GetDim(2) == 1;
156+ if (isNchwOutput || isNhwcOutput) {
157+ rank = 1;
158+ channel = outputShape.GetDim(format == BNTrainingReducePublicFormat::NCHW ? 1 : 3);
159+ }
160+ return true;
161+}
162+ 
163+bool PopulateInterfaceInputs(gert::TilingContext* context, BNTrainingReducePublicInputs& inputs)
164+{
165+ const auto* inputShape = context->GetInputShape(0);
166+ const auto* inputDesc = context->GetInputDesc(0);
167+ inputs.inputPresent = inputShape != nullptr && inputDesc != nullptr;
168+ if (!inputs.inputPresent) {
169+ return true;
170+ }
171+ 
172+ const auto& xShape = inputShape->GetStorageShape();
173+ const size_t inputRank = xShape.GetDimNum();
174+ if (inputRank > static_cast<size_t>(std::numeric_limits<int32_t>::max())) {
175+ return false;
176+ }
177+ inputs.rank = static_cast<int32_t>(inputRank);
178+ if (inputs.rank == 4) {
179+ for (size_t i = 0; i < inputs.shape.size(); ++i) {
180+ inputs.shape[i] = xShape.GetDim(i);
181+ }
182+ }
183+ const ge::Format storageFormat = inputDesc->GetStorageFormat();
184+ if (storageFormat == ge::FORMAT_NCHW) {
185+ inputs.format = BNTrainingReducePublicFormat::NCHW;
186+ } else if (storageFormat == ge::FORMAT_NHWC) {
187+ inputs.format = BNTrainingReducePublicFormat::NHWC;
188+ } else {
189+ OP_LOGE(context, "BNTrainingReduce only supports NCHW and NHWC, but got format %d.",
190+ static_cast<int32_t>(storageFormat));
191+ return false;
192+ }
193+ inputs.inputDtype = ConvertDType(inputDesc->GetDataType());
194+ 
195+ const auto* sumShape = context->GetOutputShape(0);
196+ const auto* squareSumShape = context->GetOutputShape(1);
197+ const auto* sumDesc = context->GetOutputDesc(0);
198+ const auto* squareSumDesc = context->GetOutputDesc(1);
199+ if (sumShape == nullptr || squareSumShape == nullptr || sumDesc == nullptr || squareSumDesc == nullptr) {
200+ return false;
201+ }
202+ 
203+ const auto& sumStorageShape = sumShape->GetStorageShape();
204+ if (!NormalizeOutputShape(sumStorageShape, inputs.format, inputs.sumRank, inputs.sumDim0)) {
205+ return false;
206+ }
207+ inputs.sumDtype = ConvertDType(sumDesc->GetDataType());
208+ const auto& squareSumStorageShape = squareSumShape->GetStorageShape();
209+ if (!NormalizeOutputShape(squareSumStorageShape, inputs.format, inputs.squareSumRank, inputs.squareSumDim0)) {
210+ return false;
211+ }
212+ inputs.squareSumDtype = ConvertDType(squareSumDesc->GetDataType());
213+ inputs.deterministic = context->GetDeterministic() == 1;
214+ return true;
215+}
216+ 
217+bool PopulatePlatformInputs(gert::TilingContext* context, BNTrainingReducePublicInputs& inputs)
218+{
219+ auto* platformInfo = context->GetPlatformInfo();
220+ if (platformInfo == nullptr) {
221+ return false;
222+ }
223+ const auto platform = platform_ascendc::PlatformAscendC(platformInfo);
224+ inputs.coreNum = static_cast<int64_t>(platform.GetCoreNumAiv());
225+ uint64_t ubSize = 0;
226+ platform.GetCoreMemSize(platform_ascendc::CoreMemType::UB, ubSize);
227+ inputs.ubSize = static_cast<int64_t>(ubSize);
228+ inputs.blockSize = static_cast<int64_t>(Ops::Base::GetUbBlockSize(context));
229+ inputs.cacheLineSize = static_cast<int64_t>(Ops::Base::GetCacheLineSize(context));
230+ inputs.vectorSize = static_cast<int64_t>(Ops::Base::GetVRegSize(context));
231+ inputs.systemWorkspaceSize = platform.GetLibApiWorkSpaceSize();
232+ return true;
233+}
234+ 
235+bool IsLegalTilingKey(int64_t tilingKey)
236+{
237+ const auto key = static_cast<BNTrainingReduceTilingKey>(tilingKey);
238+ return key == BNTrainingReduceTilingKey::NORMAL_TAIL_A || key == BNTrainingReduceTilingKey::GROUP_TAIL_A ||
239+ key == BNTrainingReduceTilingKey::EMPTY || key == BNTrainingReduceTilingKey::NORMAL_TAIL_R ||
240+ key == BNTrainingReduceTilingKey::GROUP_TAIL_R || key == BNTrainingReduceTilingKey::SMALL_R ||
241+ key == BNTrainingReduceTilingKey::DETERMINISTIC_GROUP_TAIL_A ||
242+ key == BNTrainingReduceTilingKey::DETERMINISTIC_GROUP_TAIL_R;
243+}
244+ 
245+} // namespace
246+ 
247+ge::graphStatus TilingFunc(gert::TilingContext* context)
248+{
249+ if (context == nullptr) {
250+ return ge::GRAPH_FAILED;
251+ }
252+ 
253+ OP_LOGI(context->GetNodeName(), "Enter TilingFunc");
254+ BNTrainingReducePublicInputs inputs;
255+ if (!PopulateInterfaceInputs(context, inputs) || !PopulatePlatformInputs(context, inputs)) {
256+ OP_LOGE(context, "Failed to populate BNTrainingReduce tiling inputs.");
257+ return ge::GRAPH_FAILED;
258+ }
259+ BNTrainingReducePublicResult result;
260+ if (!TryBuildSmallRConfig(inputs, result)) {
261+ result = ComputeBNTrainingReducePublicTiling(inputs);
262+ }
263+ if (result.status != BNTrainingReducePublicStatus::SUCCESS || !IsLegalTilingKey(result.tilingKey)) {
264+ OP_LOGE(context, "Failed to compute a legal BNTrainingReduce tiling result.");
265+ return ge::GRAPH_FAILED;
266+ }
267+ 
268+ auto* tilingData = context->GetTilingData<BNTrainingReduceTilingData>();
269+ size_t* workspaceSizes = context->GetWorkspaceSizes(1);
270+ if (tilingData == nullptr || workspaceSizes == nullptr) {
271+ OP_LOGE(context, "Failed to get BNTrainingReduce tiling data or workspace.");
272+ return ge::GRAPH_FAILED;
273+ }
274+ if (result.scheduleMode == 1 && context->SetScheduleMode(1) != ge::GRAPH_SUCCESS) {
275+ OP_LOGE(context, "Failed to set BNTrainingReduce schedule mode.");
276+ return ge::GRAPH_FAILED;
277+ }
278+ if (context->SetTilingKey(static_cast<uint64_t>(result.tilingKey)) != ge::GRAPH_SUCCESS ||
279+ context->SetBlockDim(result.blockDim) != ge::GRAPH_SUCCESS) {
280+ OP_LOGE(context, "Failed to set BNTrainingReduce tiling key or block dim.");
281+ return ge::GRAPH_FAILED;
L
Lliuchuangdev8月13日

异常退出分支可以增加日志

likedislike
282+ }
283+ 
284+ *tilingData = result.tilingData;
285+ workspaceSizes[0] = result.workspaceSize;
286+ return ge::GRAPH_SUCCESS;
287+}
288+ 
289+IMPL_OP_OPTILING(BNTrainingReduce)
290+ .Tiling(TilingFunc)
291+ .TilingParse<BNTrainingReduceCompileInfo>(TilingParseForBNTrainingReduce);
292+} // namespace optiling
@@ -0,0 +1,21 @@
1+/**
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 of
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.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef BN_TRAINING_REDUCE_TILING_ARCH35_H
12+#define BN_TRAINING_REDUCE_TILING_ARCH35_H
13+ 
14+#include "exe_graph/runtime/tiling_context.h"
15+#include "graph/types.h"
16+ 
17+namespace optiling {
18+ge::graphStatus TilingFunc(gert::TilingContext* context);
19+} // namespace optiling
20+ 
21+#endif // BN_TRAINING_REDUCE_TILING_ARCH35_H