Closed
refactor: finalize native inference migration after July quality fixes #12383
raoliang_sacAdd on 21 days agoClosed at 20 days ago
refactor: finalize native inference migration after July quality fixes #12383
Closed
raoliang_sacAdd on 21 days agoClosed at 20 days ago
Total: 42 files changed+835-378
@@ -57,23 +57,29 @@
57 <tr>57 <tr>
58 <td>features</td>58 <td>features</td>
59 <td>输入</td>59 <td>输入</td>
60- <td>作为输入传递给对应Relu6操作的特征,或其输出,使用任一者产生相同结果,公式中的features。其shape须与gradients一致,或为标量[1]。</td>60+ <td>作为输入传递给对应Relu6操作的特征,或其输出,使用任一者产生相同结果,公式中的features。与gradients的shape关系见产品差异说明。</td>
61 <td>FLOAT16、FLOAT、BFLOAT16</td>61 <td>FLOAT16、FLOAT、BFLOAT16</td>
62 <td>ND</td>62 <td>ND</td>
63 </tr>63 </tr>
64 <tr>64 <tr>
65 <td>backprops</td>65 <td>backprops</td>
66 <td>输出</td>66 <td>输出</td>
67- <td>公式中的输出张量,shape与gradients一致。</td>67+ <td>公式中的输出张量,shape由输入shape按各产品的广播约束确定,见产品差异说明。</td>
68 <td>FLOAT16、FLOAT、BFLOAT16</td>68 <td>FLOAT16、FLOAT、BFLOAT16</td>
69 <td>ND</td>69 <td>ND</td>
70 </tr>70 </tr>
71 </tbody></table>71 </tbody></table>
72 72 
73+### 产品差异说明
74+ 
75+| 产品 | 输入shape约束 | 输出shape |
76+| --- | --- | --- |
77+| <term>Ascend 950PR&950DT系列产品</term> | gradients与features支持双向广播,各自rank不超过8;GE图模式支持满足广播关系的空Tensor | 两个输入广播后的shape;空Tensor的输出元素数为0 |
78+| <term>Atlas A3系列产品</term>、<term>Atlas A2系列产品</term> | features的shape须与gradients一致,或为[1] | 与gradients一致 |
79+ 
73## 约束说明80## 约束说明
74 81 
75- gradients、features、backprops的数据类型须保持一致,不支持混合数据类型。82- gradients、features、backprops的数据类型须保持一致,不支持混合数据类型。
76-- 不支持广播:features的shape须与gradients完全一致,或为标量[1]。
77- 支持动态shape与动态rank。83- 支持动态shape与动态rank。
78 84 
79## 实现方案85## 实现方案
@@ -83,6 +89,7 @@
83| 计算图原型 | `op_graph/relu6_grad_proto.h` | `REG_OP(Relu6Grad)`,二输入一输出 |89| 计算图原型 | `op_graph/relu6_grad_proto.h` | `REG_OP(Relu6Grad)`,二输入一输出 |
84| 算子定义 | `op_host/relu6_grad_def.cpp` | `OpDef::AddConfig("ascend950", ...)` |90| 算子定义 | `op_host/relu6_grad_def.cpp` | `OpDef::AddConfig("ascend950", ...)` |
85| InferShape | `op_host/relu6_grad_infershape.cpp` | 复用`Ops::Base::InferShape4Broadcast` |91| InferShape | `op_host/relu6_grad_infershape.cpp` | 复用`Ops::Base::InferShape4Broadcast` |
92+| 图模式InferDataType | `op_graph/relu6_grad_graph_infer.cpp` | 校验两个输入dtype一致并推导输出dtype |
86| Tiling | `op_host/arch35/relu6_grad_tiling_arch35.{h,cpp}` | 按dtype分支调用`Ops::Base::BroadcastBaseTiling<OpDag>` |93| Tiling | `op_host/arch35/relu6_grad_tiling_arch35.{h,cpp}` | 按dtype分支调用`Ops::Base::BroadcastBaseTiling<OpDag>` |
87| DAG | `op_kernel/arch35/relu6_grad_dag.h` | fp32通路原生计算;fp16/bf16通路提升fp32中间精度 |94| DAG | `op_kernel/arch35/relu6_grad_dag.h` | fp32通路原生计算;fp16/bf16通路提升fp32中间精度 |
88| Struct | `op_kernel/arch35/relu6_grad_struct.h` | `BRC_TEMP_SCH_MODE_KEY_DECL/SEL` |95| Struct | `op_kernel/arch35/relu6_grad_struct.h` | `BRC_TEMP_SCH_MODE_KEY_DECL/SEL` |
@@ -90,7 +97,7 @@
90 97 
91### fp32通路98### fp32通路
92 99 
93-```Cpp100+```text
94In0(dy) ─CopyInBrc─+101In0(dy) ─CopyInBrc─+
95 +──────────────────────────────+102 +──────────────────────────────+
96 |103 |
@@ -102,13 +109,13 @@ In1(x) ─CopyInBrc─+ |
102 Const(0,fp32) ─Duplicate─ Zero ─────────────── ┴ Select(mask, dy, Zero) ─ CopyOut ─ Out0109 Const(0,fp32) ─Duplicate─ Zero ─────────────── ┴ Select(mask, dy, Zero) ─ CopyOut ─ Out0
103```110```
104 111 
105-```Cpp
106`Vec::Compare<u8, fp32, GT/LT>`输出位掩码,`Vec::And<u8>`合成`(0 < x < 6)`112`Vec::Compare<u8, fp32, GT/LT>`输出位掩码,`Vec::And<u8>`合成`(0 < x < 6)`
107区间mask;`Vec::Select<u8, fp32, TENSOR_TENSOR>`按`mask ? dy : 0`逐元素113区间mask;`Vec::Select<u8, fp32, TENSOR_TENSOR>`按`mask ? dy : 0`逐元素
108选择,避免分支,硬件原生vsel实现。114选择,避免分支,硬件原生vsel实现。
109 115 
110### fp16 / bf16通路116### fp16 / bf16通路
111 117 
118+```text
112In0/In1 ─CopyInBrc─ Cast(->fp32) ─+119In0/In1 ─CopyInBrc─ Cast(->fp32) ─+
113 +─ Compare ── And ─ Select ── Cast(->T,RINT) ─ CopyOut ─ Out0120 +─ Compare ── And ─ Select ── Cast(->T,RINT) ─ CopyOut ─ Out0
114 Const(0,fp32) ── Zero ─+121 Const(0,fp32) ── Zero ─+
@@ -126,4 +133,4 @@ In0/In1 ─CopyInBrc─ Cast(->fp32) ─+
126 133 
127| 调用方式 | 样例代码 | 说明 |134| 调用方式 | 样例代码 | 说明 |
128| ---------------- | --------------------------- |-----------------------------------------------|135| ---------------- | --------------------------- |-----------------------------------------------|
129-| 图模式 | [test_geir_relu6_grad.cpp](examples/arch35/test_geir_relu6_grad.cpp) | 通过[算子IR](op_graph/relu6_grad_proto.h)构图方式调用Relu6Grad算子。 |136+| GE图模式 | [test_geir_relu6_grad.cpp](examples/arch35/test_geir_relu6_grad.cpp) | 通过[算子IR](op_graph/relu6_grad_proto.h)构图方式调用Relu6Grad算子。 |
@@ -0,0 +1,28 @@
1+# Copyright (c) 2026 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+cmake_minimum_required(VERSION 3.18)
10+project(GE_IR_EXAMPLE LANGUAGES CXX)
11+ 
12+set(ASCEND_PATH "$ENV{ASCEND_HOME_PATH}")
13+if(NOT ASCEND_PATH)
14+ message(FATAL_ERROR "Source the CANN set_env.sh before configuring this example.")
15+endif()
16+find_library(GRAPH_LIBRARY NAMES libgraph.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
17+find_library(GE_RUNNER_LIBRARY NAMES libge_runner.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
18+find_library(GRAPH_BASE_LIBRARY NAMES libgraph_base.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
19+find_library(GE_COMPILER_LIBRARY NAMES libge_compiler.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
20+ 
21+add_executable(test_geir_relu6_grad test_geir_relu6_grad.cpp)
22+target_include_directories(test_geir_relu6_grad PRIVATE
23+ "${ASCEND_PATH}/include/graph"
24+ "${ASCEND_PATH}/include/ge"
25+ "${ASCEND_PATH}/opp/built-in/op_proto/inc"
26+ "${ASCEND_PATH}/include")
27+target_link_libraries(test_geir_relu6_grad PRIVATE
28+ ${GRAPH_LIBRARY} ${GE_RUNNER_LIBRARY} ${GRAPH_BASE_LIBRARY} ${GE_COMPILER_LIBRARY})
@@ -35,6 +35,8 @@
35#include <map>35#include <map>
36#include <cmath>36#include <cmath>
37#include <limits>37#include <limits>
38+#include <memory>
39+#include <new>
38#include "assert.h"40#include "assert.h"
39 41 
40#include "graph.h"42#include "graph.h"
@@ -159,23 +161,41 @@ static int32_t GenConstTensor(const vector<int64_t>& shape, DataType dtype, doub
159 size_t bytes = numel * GetDataTypeSize(dtype);161 size_t bytes = numel * GetDataTypeSize(dtype);
160 162 
161 if (dtype == ge::DT_FLOAT) {163 if (dtype == ge::DT_FLOAT) {
162- float* p = new (std::nothrow) float[numel];164+ std::unique_ptr<float[]> p(new (std::nothrow) float[numel]);
165+ if (!p) {
166+ return FAILED;
167+ }
163 float v = (float)value;168 float v = (float)value;
164 for (size_t i = 0; i < numel; ++i)169 for (size_t i = 0; i < numel; ++i)
165 p[i] = v;170 p[i] = v;
166- tensor = Tensor(desc, (uint8_t*)p, bytes);171+ tensor = Tensor(desc);
172+ if (tensor.SetData(reinterpret_cast<const uint8_t*>(p.get()), bytes) != ge::GRAPH_SUCCESS) {
173+ return FAILED;
174+ }
167 } else if (dtype == ge::DT_FLOAT16) {175 } else if (dtype == ge::DT_FLOAT16) {
168- uint16_t* p = new (std::nothrow) uint16_t[numel];176+ std::unique_ptr<uint16_t[]> p(new (std::nothrow) uint16_t[numel]);
177+ if (!p) {
178+ return FAILED;
179+ }
169 uint16_t v = FloatToHalfBits((float)value);180 uint16_t v = FloatToHalfBits((float)value);
170 for (size_t i = 0; i < numel; ++i)181 for (size_t i = 0; i < numel; ++i)
171 p[i] = v;182 p[i] = v;
172- tensor = Tensor(desc, (uint8_t*)p, bytes);183+ tensor = Tensor(desc);
184+ if (tensor.SetData(reinterpret_cast<const uint8_t*>(p.get()), bytes) != ge::GRAPH_SUCCESS) {
185+ return FAILED;
186+ }
173 } else if (dtype == ge::DT_BF16) {187 } else if (dtype == ge::DT_BF16) {
174- uint16_t* p = new (std::nothrow) uint16_t[numel];188+ std::unique_ptr<uint16_t[]> p(new (std::nothrow) uint16_t[numel]);
189+ if (!p) {
190+ return FAILED;
191+ }
175 uint16_t v = FloatToBf16Bits((float)value);192 uint16_t v = FloatToBf16Bits((float)value);
176 for (size_t i = 0; i < numel; ++i)193 for (size_t i = 0; i < numel; ++i)
177 p[i] = v;194 p[i] = v;
178- tensor = Tensor(desc, (uint8_t*)p, bytes);195+ tensor = Tensor(desc);
196+ if (tensor.SetData(reinterpret_cast<const uint8_t*>(p.get()), bytes) != ge::GRAPH_SUCCESS) {
197+ return FAILED;
198+ }
179 } else {199 } else {
180 return FAILED;200 return FAILED;
181 }201 }
@@ -200,6 +220,7 @@ static int BuildRelu6GradGraph(Graph& graph, std::vector<ge::Tensor>& inputData,
200 if (GenConstTensor(sDy, dtype, dyVal, t, d) != SUCCESS)220 if (GenConstTensor(sDy, dtype, dyVal, t, d) != SUCCESS)
201 return FAILED;221 return FAILED;
202 ph.update_input_desc_x(d);222 ph.update_input_desc_x(d);
223+ ph.update_output_desc_y(d);
203 inputData.push_back(t);224 inputData.push_back(t);
204 graph.AddOp(ph);225 graph.AddOp(ph);
205 op.set_input_gradients(ph);226 op.set_input_gradients(ph);
@@ -207,7 +228,7 @@ static int BuildRelu6GradGraph(Graph& graph, std::vector<ge::Tensor>& inputData,
207 }228 }
208 // Input features (x)229 // Input features (x)
209 {230 {
210- auto ph = ge::op::Data("phX_" + caseTag).set_attr_index(0);231+ auto ph = ge::op::Data("phX_" + caseTag).set_attr_index(1);
211 TensorDesc d(ge::Shape(sX), FORMAT_ND, dtype);232 TensorDesc d(ge::Shape(sX), FORMAT_ND, dtype);
212 d.SetPlacement(ge::kPlacementHost);233 d.SetPlacement(ge::kPlacementHost);
213 d.SetFormat(FORMAT_ND);234 d.SetFormat(FORMAT_ND);
@@ -215,6 +236,7 @@ static int BuildRelu6GradGraph(Graph& graph, std::vector<ge::Tensor>& inputData,
215 if (GenConstTensor(sX, dtype, xVal, t, d) != SUCCESS)236 if (GenConstTensor(sX, dtype, xVal, t, d) != SUCCESS)
216 return FAILED;237 return FAILED;
217 ph.update_input_desc_x(d);238 ph.update_input_desc_x(d);
239+ ph.update_output_desc_y(d);
218 inputData.push_back(t);240 inputData.push_back(t);
219 graph.AddOp(ph);241 graph.AddOp(ph);
220 op.set_input_features(ph);242 op.set_input_features(ph);
@@ -273,7 +295,7 @@ static int RunOneCase(const CaseSpec& spec)
273 graph.SetInputs(inputs).SetOutputs(outputs);295 graph.SetInputs(inputs).SetOutputs(outputs);
274 296 
275 std::map<AscendString, AscendString> buildOpts;297 std::map<AscendString, AscendString> buildOpts;
276- ge::Session* session = new Session(buildOpts);298+ ge::Session* session = new (std::nothrow) Session(buildOpts);
277 if (!session)299 if (!session)
278 return FAILED;300 return FAILED;
279 301 
@@ -293,10 +315,23 @@ static int RunOneCase(const CaseSpec& spec)
293 return FAILED;315 return FAILED;
294 }316 }
295 317 
318+ if (output.size() != 1) {
319+ printf("Expected exactly one output, got %zu\n", output.size());
320+ delete session;
321+ return FAILED;
322+ }
296 int failCount = 0;323 int failCount = 0;
297 for (size_t i = 0; i < output.size(); ++i) {324 for (size_t i = 0; i < output.size(); ++i) {
298- int64_t numel = output[i].GetTensorDesc().GetShape().GetShapeSize();325+ const auto& desc = output[i].GetTensorDesc();
326+ int64_t numel = desc.GetShape().GetShapeSize();
299 uint8_t* raw = output[i].GetData();327 uint8_t* raw = output[i].GetData();
328+ if (desc.GetShape().GetDims() != spec.outShape || desc.GetDataType() != spec.dtype || numel < 0 ||
329+ output[i].GetSize() != static_cast<size_t>(numel) * GetDataTypeSize(spec.dtype) ||
330+ (numel != 0 && raw == nullptr)) {
331+ printf("Output shape, dtype or buffer mismatch\n");
332+ delete session;
333+ return FAILED;
334+ }
300 for (int64_t j = 0; j < numel; ++j) {335 for (int64_t j = 0; j < numel; ++j) {
301 double got;336 double got;
302 if (spec.dtype == ge::DT_FLOAT) {337 if (spec.dtype == ge::DT_FLOAT) {
@@ -336,12 +371,8 @@ static int RunOneCase(const CaseSpec& spec)
336 371 
337int main(int argc, char* argv[])372int main(int argc, char* argv[])
338{373{
339- (void)argc;374+ if (argc > 2) {
340- (void)argv;375+ printf("Usage: %s [case_tag|--list]\n", argv[0]);
341- 
342- std::map<AscendString, AscendString> globalOpts = {{"ge.exec.deviceId", "0"}, {"ge.graphRunMode", "1"}};
343- if (ge::GEInitialize(globalOpts) != SUCCESS) {
344- printf("GEInitialize failed\n");
345 return FAILED;376 return FAILED;
346 }377 }
347 378 
@@ -377,6 +408,16 @@ int main(int argc, char* argv[])
377 {"bf16_boundary_x_eq_0", ge::DT_BF16, 1.0, 0.0, {4}, {4}, {4}, exact(0.0, 2e-2)},408 {"bf16_boundary_x_eq_0", ge::DT_BF16, 1.0, 0.0, {4}, {4}, {4}, exact(0.0, 2e-2)},
378 {"bf16_boundary_x_eq_6", ge::DT_BF16, 1.0, 6.0, {4}, {4}, {4}, exact(0.0, 2e-2)},409 {"bf16_boundary_x_eq_6", ge::DT_BF16, 1.0, 6.0, {4}, {4}, {4}, exact(0.0, 2e-2)},
379 {"bf16_x_pos_inf", ge::DT_BF16, 1.0, kInf, {4}, {4}, {4}, exact(0.0, 2e-2)},410 {"bf16_x_pos_inf", ge::DT_BF16, 1.0, kInf, {4}, {4}, {4}, exact(0.0, 2e-2)},
411+ // --- output shape must be inferred from both broadcast inputs ---
412+ {"fp16_broadcast", ge::DT_FLOAT16, 1.5, 2.0, {2, 1, 5}, {1, 3, 1}, {2, 3, 5}, exact(1.5, 5e-3)},
413+ {"fp32_broadcast_rank8",
414+ ge::DT_FLOAT,
415+ 2.5,
416+ 3.0,
417+ {2, 1, 2, 1, 2, 1, 2, 1},
418+ {1, 2, 1, 2, 1, 2, 1, 2},
419+ {2, 2, 2, 2, 2, 2, 2, 2},
420+ exact(2.5, 1e-5)},
380 // the case passes iff the graph compiles and runs (expect is unused for numel == 0). ---421 // the case passes iff the graph compiles and runs (expect is unused for numel == 0). ---
381 {"fp32_empty_1d", ge::DT_FLOAT, 2.5, 3.0, {0}, {0}, {0}, exact(0.0, 1e-5)},422 {"fp32_empty_1d", ge::DT_FLOAT, 2.5, 3.0, {0}, {0}, {0}, exact(0.0, 1e-5)},
382 {"fp32_empty_2d", ge::DT_FLOAT, 2.5, 3.0, {2, 0}, {2, 0}, {2, 0}, exact(0.0, 1e-5)},423 {"fp32_empty_2d", ge::DT_FLOAT, 2.5, 3.0, {2, 0}, {2, 0}, {2, 0}, exact(0.0, 1e-5)},
@@ -386,6 +427,32 @@ int main(int argc, char* argv[])
386 {"bf16_empty_2d", ge::DT_BF16, 1.0, 2.0, {0, 3}, {0, 3}, {0, 3}, exact(0.0, 2e-2)},427 {"bf16_empty_2d", ge::DT_BF16, 1.0, 2.0, {0, 3}, {0, 3}, {0, 3}, exact(0.0, 2e-2)},
387 };428 };
388 429 
430+ if (argc == 2 && string(argv[1]) == "--list") {
431+ for (const auto& c : cases) {
432+ std::cout << c.tag << std::endl;
433+ }
434+ return SUCCESS;
435+ }
436+ if (argc == 2) {
437+ vector<CaseSpec> selected;
438+ for (const auto& c : cases) {
439+ if (c.tag == argv[1]) {
440+ selected.push_back(c);
441+ }
442+ }
443+ if (selected.empty()) {
444+ printf("Unknown case: %s\n", argv[1]);
445+ return FAILED;
446+ }
447+ cases = std::move(selected);
448+ }
449+ 
450+ std::map<AscendString, AscendString> globalOpts = {{"ge.exec.deviceId", "0"}, {"ge.graphRunMode", "1"}};
451+ if (ge::GEInitialize(globalOpts) != SUCCESS) {
452+ printf("GEInitialize failed\n");
453+ return FAILED;
454+ }
455+ 
389 int allFail = 0;456 int allFail = 0;
390 for (const auto& c : cases) {457 for (const auto& c : cases) {
391 if (RunOneCase(c) != SUCCESS) {458 if (RunOneCase(c) != SUCCESS) {
@@ -0,0 +1,28 @@
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 "log/log.h"
12+#include "register/op_impl_registry.h"
13+ 
14+namespace ops {
15+ge::graphStatus InferDataType4Relu6Grad(gert::InferDataTypeContext* context)
16+{
17+ if (context == nullptr) {
18+ return ge::GRAPH_FAILED;
19+ }
20+ const auto dtype = context->GetInputDataType(0);
21+ OP_CHECK_IF(dtype != context->GetInputDataType(1),
22+ OP_LOGE(context->GetNodeName(), "gradients and features must have the same dtype."),
23+ return ge::GRAPH_FAILED);
24+ return context->SetOutputDataType(0, dtype);
25+}
26+ 
27+IMPL_OP(Relu6Grad).InferDataType(InferDataType4Relu6Grad);
28+} // namespace ops
@@ -24,25 +24,29 @@ namespace ge {
24* @brief Gradient of ReLU6. Strict open-interval semantics:24* @brief Gradient of ReLU6. Strict open-interval semantics:
25* dx[i] = gradients[i] if 0 < features[i] < 625* dx[i] = gradients[i] if 0 < features[i] < 6
26* dx[i] = 0 otherwise (including features[i] == 0 and 6)26* dx[i] = 0 otherwise (including features[i] == 0 and 6)
27-* Broadcasting is NOT supported: features must have the same shape as27+* On <term>Ascend 950PR&950DT系列产品</term>, gradients and features support
28-* gradients, or be a scalar ([1]). backprops always has the same shape28+* bidirectional broadcasting: dimensions are aligned from the right and
29-* as gradients.29+* each pair must be equal or contain a dimension of size 1.
30+* On <term>Atlas A3系列产品</term> and <term>Atlas A2系列产品</term>, features
31+* must have the same shape as gradients or have shape [1].
30 32 
31* @par Inputs:33* @par Inputs:
32* Two inputs, including:34* Two inputs, including:
33* @li gradients: A ND tensor. Upstream gradient (dy). Must be one of the35* @li gradients: A ND tensor. Upstream gradient (dy). Must be one of the
34* following types: float16, float32, bfloat16.36* following types: float16, float32, bfloat16.
35-* @li features: A ND tensor. Forward input x of ReLU6 (NOT the forward37+* @li features: A ND tensor. Forward input or output of ReLU6; either gives
36-* output). Must have the same dtype as gradients; shape must be the same38+* the same interval mask. Must have the same dtype as gradients and a
37-* as gradients, or be a scalar ([1]). \n39+* shape satisfying the product-specific constraints above. \n
38 40 
39* @par Outputs:41* @par Outputs:
40* backprops: A ND tensor. Has the same dtype as gradients; shape is the42* backprops: A ND tensor. Has the same dtype as gradients; shape is the
41-* same as gradients. \n43+* broadcast shape under the product-specific constraints above. \n
42 44 
43* @par Third-party framework compatibility45* @par Third-party framework compatibility
44-* Compatible with the TensorFlow operator Relu6Grad and PyTorch46+* For finite gradients, matches TensorFlow Relu6Grad and PyTorch
45-* hardtanh_backward(min=0, max=6) at boundary x in {0, 6} returning 0.47+* hardtanh_backward(min=0, max=6) at features in {0, 6}, returning 0. Unlike multiplication by a zero mask,
48+* the selection semantics above also return 0 for NaN/Inf gradients
49+* outside the open interval.
46*/50*/
47#ifndef OPS_PROTO_DEF_RELU6GRAD51#ifndef OPS_PROTO_DEF_RELU6GRAD
48#define OPS_PROTO_DEF_RELU6GRAD52#define OPS_PROTO_DEF_RELU6GRAD
@@ -18,7 +18,7 @@
18#include "tiling/platform/platform_ascendc.h"18#include "tiling/platform/platform_ascendc.h"
19#include "log/log.h"19#include "log/log.h"
20#include "register/tilingdata_base.h"20#include "register/tilingdata_base.h"
21-#include "atvoss/elewise/elewise_tiling.h"21+#include "atvoss/broadcast/broadcast_tiling.h"
22#include "activation/relu6_grad/op_kernel/arch35/relu6_grad_dag.h"22#include "activation/relu6_grad/op_kernel/arch35/relu6_grad_dag.h"
23#include "activation/relu6_grad/op_kernel/arch35/relu6_grad_struct.h"23#include "activation/relu6_grad/op_kernel/arch35/relu6_grad_struct.h"
24#include "op_host/tiling_templates_registry.h"24#include "op_host/tiling_templates_registry.h"
@@ -138,12 +138,22 @@ static ge::graphStatus Tiling4Relu6Grad(gert::TilingContext* tilingContextGen)
138 return tiling.DoTiling();138 return tiling.DoTiling();
139}139}
140 140 
141-ge::graphStatus TilingPrepareForRelu6Grad([[maybe_unused]] gert::TilingParseContext* context)141+ge::graphStatus TilingPrepareForRelu6Grad(gert::TilingParseContext* context)
142{142{
143+ auto compileInfo = context->GetCompiledInfo<Ops::Base::BroadcastCompileInfo>();
144+ OP_CHECK_NULL_WITH_CONTEXT(context, compileInfo);
145+ auto platformInfo = context->GetPlatformInfo();
146+ OP_CHECK_NULL_WITH_CONTEXT(context, platformInfo);
147+ 
148+ auto ascendcPlatform = platform_ascendc::PlatformAscendC(platformInfo);
149+ compileInfo->coreNum = ascendcPlatform.GetCoreNumAiv();
150+ ascendcPlatform.GetCoreMemSize(platform_ascendc::CoreMemType::UB, compileInfo->ubSize);
143 return ge::GRAPH_SUCCESS;151 return ge::GRAPH_SUCCESS;
144}152}
145 153 
146-IMPL_OP_OPTILING(Relu6Grad).Tiling(Tiling4Relu6Grad).TilingParse<ElewiseCompileInfo>(TilingPrepareForRelu6Grad);154+IMPL_OP_OPTILING(Relu6Grad)
155+ .Tiling(Tiling4Relu6Grad)
156+ .TilingParse<Ops::Base::BroadcastCompileInfo>(TilingPrepareForRelu6Grad);
147 157 
148REGISTER_OPS_TILING_TEMPLATE(Relu6Grad, Relu6GradTiling, RELU6_GRAD_COMMON_TILING_PRIORITY);158REGISTER_OPS_TILING_TEMPLATE(Relu6Grad, Relu6GradTiling, RELU6_GRAD_COMMON_TILING_PRIORITY);
149} // namespace optiling159} // namespace optiling
@@ -25,7 +25,7 @@ using namespace Ops::Base;
25 25 
26class Relu6GradTiling : public TilingBaseClass {26class Relu6GradTiling : public TilingBaseClass {
27public:27public:
28- explicit Relu6GradTiling(gert::TilingContext* context) : TilingBaseClass(context){};28+ explicit Relu6GradTiling(gert::TilingContext* context) : TilingBaseClass(context) {}
29 29 
30protected:30protected:
31 bool IsCapable() override;31 bool IsCapable() override;
@@ -25,18 +25,6 @@ static ge::graphStatus InferShape4Relu6Grad(gert::InferShapeContext* context)
25 return Ops::Base::InferShape4Broadcast(context);25 return Ops::Base::InferShape4Broadcast(context);
26}26}
27 27 
28-static ge::graphStatus InferDataType4Relu6Grad(gert::InferDataTypeContext* context)28+IMPL_OP_INFERSHAPE(Relu6Grad).InferShape(InferShape4Relu6Grad);
29-{
30- if (context == nullptr) {
31- return ge::GRAPH_FAILED;
32- }
33- OP_LOGD(context->GetNodeName(), "InferDataType4Relu6Grad enter");
34- // backprops 与 gradients 同类型
35- context->SetOutputDataType(0, context->GetInputDataType(0));
36- OP_LOGD(context->GetNodeName(), "InferDataType4Relu6Grad end");
37- return ge::GRAPH_SUCCESS;
38-}
39- 
40-IMPL_OP_INFERSHAPE(Relu6Grad).InferShape(InferShape4Relu6Grad).InferDataType(InferDataType4Relu6Grad);
41 29 
42} // namespace ops30} // namespace ops
@@ -12,7 +12,12 @@
12 12 
13import numpy as np13import numpy as np
14 14 
15-__golden__ = {"kernel": {"relu6_grad": "relu6_grad_golden"}}15+__golden__ = {
16+ "kernel": {
17+ "relu6_grad": "relu6_grad_golden",
18+ "Relu6Grad": "relu6_grad_golden",
19+ }
20+}
16 21 
17 22 
18def relu6_grad_golden(gradients, features, **kwargs):23def relu6_grad_golden(gradients, features, **kwargs):
@@ -29,22 +34,18 @@ def relu6_grad_golden(gradients, features, **kwargs):
29 (any comparison with NaN is false). dy is passed through verbatim when34 (any comparison with NaN is false). dy is passed through verbatim when
30 the mask is true, so dy = NaN/Inf propagates only inside the (0, 6) band.35 the mask is true, so dy = NaN/Inf propagates only inside the (0, 6) band.
31 """36 """
32- import tensorflow as tf37+ import torch
33 38 
34- with tf.device("/CPU:0"):39+ output_dtype = np.asarray(gradients).dtype
35- dy = tf.convert_to_tensor(np.asarray(gradients))40+ # Keep CPU reference arithmetic independent of the device's float32 path.
36- x = tf.convert_to_tensor(np.asarray(features))41+ # NumPy conversion also accepts bfloat16 arrays unsupported by from_numpy.
37- output_dtype = dy.dtype42+ dy = torch.from_numpy(np.array(gradients, dtype=np.float64, copy=True))
38- promote = kwargs.get("golden_mode") == "Promote"43+ x = torch.from_numpy(np.array(features, dtype=np.float64, copy=True))
39- if not promote and output_dtype in (tf.float16, tf.bfloat16):44+ mask = (x > 0.0) & (x < 6.0)
40- dy, x = tf.cast(dy, tf.float32), tf.cast(x, tf.float32)45+ output = torch.where(mask, dy, torch.zeros_like(dy)).numpy()
41- mask = tf.logical_and(x > 0.0, x < 6.0)46+ if kwargs.get("golden_mode") == "Promote":
42- # Kernel uses Select. Native TF Relu6Grad multiplies by the mask and47+ return output
43- # produces NaN for out-of-band NaN/Inf gradients; keep the paths distinct.48+ return output.astype(output_dtype)
44- output = tf.where(mask, dy, tf.zeros_like(dy))
45- if not promote:
46- output = tf.cast(output, output_dtype)
47- return np.asarray(output.numpy())
48 49 
49 50 
50# ----------------------------------------------------------------------------51# ----------------------------------------------------------------------------
@@ -108,6 +109,7 @@ class Relu6GradTensorFlowSpec:
108 109 
109__spec__ = {110__spec__ = {
110 "relu6_grad": "Relu6GradKernelSpec",111 "relu6_grad": "Relu6GradKernelSpec",
112+ "Relu6Grad": "Relu6GradKernelSpec",
111 "tf.raw_ops.Relu6Grad": "Relu6GradTensorFlowSpec",113 "tf.raw_ops.Relu6Grad": "Relu6GradTensorFlowSpec",
112}114}
113 115 
@@ -0,0 +1,11 @@
1+# Copyright (c) 2026 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+if(UT_TEST_ALL OR OP_GRAPH_UT)
10+ add_modules_ut_sources(HOSTNAME ${OP_GRAPH_MODULE_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR})
11+endif()
@@ -0,0 +1,47 @@
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 <gtest/gtest.h>
12+#include "base/context_builder/op_infer_datatype_context_builder.h"
13+ 
14+namespace ops {
15+ge::graphStatus InferDataType4Relu6Grad(gert::InferDataTypeContext* context);
16+} // namespace ops
17+ 
18+namespace {
19+void CheckDtype(ge::DataType gradients, ge::DataType features, ge::graphStatus expected)
20+{
21+ gert::OpInferDataTypeContextBuilder builder;
22+ builder.OpType("Relu6Grad").OpName("relu6_grad_dtype").IONum(2, 1);
23+ builder.InputTensorDesc(0, gradients, ge::FORMAT_ND, ge::FORMAT_ND);
24+ builder.InputTensorDesc(1, features, ge::FORMAT_ND, ge::FORMAT_ND);
25+ builder.OutputTensorDesc(0, ge::FORMAT_ND, ge::FORMAT_ND);
26+ auto holder = builder.Build();
27+ ASSERT_NE(holder.GetContext(), nullptr);
28+ ASSERT_EQ(holder.GetContext()->SetOutputDataType(0, ge::DT_UNDEFINED), ge::GRAPH_SUCCESS);
29+ ASSERT_EQ(ops::InferDataType4Relu6Grad(holder.GetContext()), expected);
30+ if (expected == ge::GRAPH_SUCCESS) {
31+ EXPECT_EQ(holder.GetContext()->GetOutputDataType(0), gradients);
32+ }
33+}
34+} // namespace
35+ 
36+TEST(Relu6GradGraphInfer, PreservesAllSupportedDataTypes)
37+{
38+ for (auto dtype : {ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_BF16}) {
39+ CheckDtype(dtype, dtype, ge::GRAPH_SUCCESS);
40+ }
41+}
42+ 
43+TEST(Relu6GradGraphInfer, RejectsMismatchedInputsWithoutLegacyVerifier)
44+{
45+ CheckDtype(ge::DT_FLOAT16, ge::DT_FLOAT, ge::GRAPH_FAILED);
46+ CheckDtype(ge::DT_BF16, ge::DT_FLOAT16, ge::GRAPH_FAILED);
47+}
@@ -20,7 +20,7 @@
20#include "exe_graph/runtime/storage_shape.h"20#include "exe_graph/runtime/storage_shape.h"
21#include "platform/platform_infos_def.h"21#include "platform/platform_infos_def.h"
22#include "ut_op_util.h"22#include "ut_op_util.h"
23-#include "atvoss/elewise/elewise_tiling.h"23+#include "atvoss/broadcast/broadcast_tiling.h"
24#include "../../../../op_host/arch35/relu6_grad_tiling_arch35.h"24#include "../../../../op_host/arch35/relu6_grad_tiling_arch35.h"
25 25 
26using namespace ut_util;26using namespace ut_util;
@@ -62,7 +62,7 @@ static ge::graphStatus RunTiling(ge::DataType gradDtype, ge::DataType featDtype,
62 fe::PlatFormInfos platform_info;62 fe::PlatFormInfos platform_info;
63 platform_info.Init();63 platform_info.Init();
64 64 
65- optiling::ElewiseCompileInfo compile_info;65+ Ops::Base::BroadcastCompileInfo compile_info;
66 compile_info.coreNum = 64;66 compile_info.coreNum = 64;
67 compile_info.ubSize = 262144;67 compile_info.ubSize = 262144;
68 68 
@@ -337,28 +337,3 @@ TEST_F(relu6grad, relu6grad_infer_shape_dynamic)
337 337 
338 ASSERT_EQ(inferShapeFunc(holder.GetContext<gert::InferShapeContext>()), ge::GRAPH_SUCCESS);338 ASSERT_EQ(inferShapeFunc(holder.GetContext<gert::InferShapeContext>()), ge::GRAPH_SUCCESS);
339}339}
340- 
341-TEST_F(relu6grad, relu6grad_infer_datatype)
342-{
343- ASSERT_NE(gert::OpImplRegistry::GetInstance().GetOpImpl("Relu6Grad"), nullptr);
344- auto dataTypeFunc = gert::OpImplRegistry::GetInstance().GetOpImpl("Relu6Grad")->infer_datatype;
345- ASSERT_NE(dataTypeFunc, nullptr);
346- // backprops 跟随 gradients,覆盖 def 中声明的 fp16/fp32/bf16
347- for (auto dtype : {ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_BF16}) {
348- ge::DataType in0 = dtype;
349- ge::DataType in1 = dtype;
350- ge::DataType expOut0 = ge::DT_UNDEFINED;
351- auto contextHolder = gert::InferDataTypeContextFaker()
352- .NodeIoNum(2, 1)
353- .NodeInputTd(0, dtype, ge::FORMAT_ND, ge::FORMAT_ND)
354- .NodeInputTd(1, dtype, ge::FORMAT_ND, ge::FORMAT_ND)
355- .NodeOutputTd(0, dtype, ge::FORMAT_ND, ge::FORMAT_ND)
356- .InputDataTypes({&in0, &in1})
357- .OutputDataTypes({&expOut0})
358- .Build();
359- auto context = contextHolder.GetContext<gert::InferDataTypeContext>();
360- ASSERT_NE(context, nullptr);
361- EXPECT_EQ(dataTypeFunc(context), ge::GRAPH_SUCCESS);
362- EXPECT_EQ(context->GetOutputDataType(0), dtype);
363- }
364-}
@@ -5793,10 +5793,10 @@
5793 <td><a href="../../vfusion/normalize_bbox/README.md">normalize_bbox</a></td>5793 <td><a href="../../vfusion/normalize_bbox/README.md">normalize_bbox</a></td>
5794 <td>✓</td>5794 <td>✓</td>
5795 <td>✓</td>5795 <td>✓</td>
5796- <td>✓</td>5796+ <td>✗</td>
5797 <td>✓</td>5797 <td>✓</td>
5798 <td>AI Core</td>5798 <td>AI Core</td>
5799- <td>将目标检测Prediction阶段筛选后的预选框绝对像素坐标,按对应图像的高与宽逐batch归一化到[0, 1]区间,得到相对坐标框。</td>5799+ <td>将目标检测Prediction阶段筛选后的预选框绝对像素坐标,按对应图像的高与宽逐batch换算为相对坐标,结果不截断,输入框越界时输出可超出[0, 1]。</td>
5800 </tr>5800 </tr>
5801 <tr>5801 <tr>
5802 <td>vfusion</td>5802 <td>vfusion</td>
@@ -13,58 +13,10 @@
13 * \brief INTrainingReduceV2 graph infer registration.13 * \brief INTrainingReduceV2 graph infer registration.
14 */14 */
15 15 
16-#include <cstddef>
17-#include <cstdint>
18-#include <vector>
19- 
20#include "../in_training_reduce_v2_infer_common.h"16#include "../in_training_reduce_v2_infer_common.h"
21-#include "graph/operator_reg.h"
22#include "log/log.h"17#include "log/log.h"
23#include "register/op_impl_registry.h"18#include "register/op_impl_registry.h"
24 19 
25-namespace ge {
26-IMPLEMT_COMMON_INFERFUNC(INTrainingReduceV2LegacyV1InferShape)
27-{
28- const TensorDesc xDesc = op.GetInputDescByName("x");
29- const std::vector<int64_t> xDims = xDesc.GetShape().GetDims();
30- const Format originFormat = xDesc.GetOriginFormat();
31- const bool isChannelLast = (originFormat == FORMAT_NHWC || originFormat == FORMAT_NDHWC);
32- if (xDims != UNKNOWN_RANK && !ops::IsINTrainingReduceV2RankValid(originFormat, xDims.size())) {
33- OP_LOGE_FOR_INVALID_SHAPEDIM_WITH_REASON(
34- "INTrainingReduceV2", "x", std::to_string(xDims.size()).c_str(),
35- "NCHW/NHWC require rank 4, NCDHW/NDHWC require rank 5, and ND requires rank in [2, 8]");
36- return GRAPH_FAILED;
37- }
38- 
39- std::vector<int64_t> outputDims(xDims.size(), 1);
40- const size_t channelIndex = (isChannelLast && !xDims.empty()) ? (xDims.size() - 1U) : 1U;
41- for (size_t i = 0; i < xDims.size(); ++i) {
42- if (i == 0U || i == channelIndex) {
43- outputDims[i] = xDims[i];
44- }
45- }
46- 
47- TensorDesc sumDesc = op.GetOutputDescByName("sum");
48- TensorDesc squareSumDesc = op.GetOutputDescByName("square_sum");
49- sumDesc.SetShape(Shape(outputDims));
50- squareSumDesc.SetShape(Shape(outputDims));
51- sumDesc.SetOriginShape(Shape(outputDims));
52- squareSumDesc.SetOriginShape(Shape(outputDims));
53- sumDesc.SetDataType(DT_FLOAT);
54- squareSumDesc.SetDataType(DT_FLOAT);
55- if (op.UpdateOutputDesc("sum", sumDesc) != GRAPH_SUCCESS ||
56- op.UpdateOutputDesc("square_sum", squareSumDesc) != GRAPH_SUCCESS) {
57- return GRAPH_FAILED;
58- }
59- return GRAPH_SUCCESS;
60-}
61- 
62-// Static GEIR graphs still invoke the legacy V1 registry for this built-in op.
63-// The custom package is loaded before libopsproto.so, so register the corrected
64-// ND channel-second contract here as well as in the runtime2.0 registry below.
65-COMMON_INFER_FUNC_REG(INTrainingReduceV2, INTrainingReduceV2LegacyV1InferShape);
66-} // namespace ge
67- 
68namespace ops {20namespace ops {
69ge::graphStatus InferShapeForINTrainingReduceV2(gert::InferShapeContext* context)21ge::graphStatus InferShapeForINTrainingReduceV2(gert::InferShapeContext* context)
70{22{
@@ -113,7 +113,7 @@ protected:
113 ge::graphStatus PostTiling() override;113 ge::graphStatus PostTiling() override;
114 114 
115private:115private:
116- // sub-R 分块 tiling(R 超单次 UB 容量,DESIGN §6.3 路 A)116+ // sub-R 分块 tiling(R 超单次 UB 容量)
117 bool DoSubRTiling(uint64_t rAlign, uint64_t binAddQuotient, int64_t elemSize);117 bool DoSubRTiling(uint64_t rAlign, uint64_t binAddQuotient, int64_t elemSize);
118 // sub-R 路径 Kernel 侧 UB 精确占用(须与 op_kernel/arch35 的 InitSubR() 逐项一致)118 // sub-R 路径 Kernel 侧 UB 精确占用(须与 op_kernel/arch35 的 InitSubR() 逐项一致)
119 uint64_t CalcSubRUbBytes(uint64_t rFactor, uint64_t chunksPerGroup, uint64_t numGroups, int64_t elemSize) const;119 uint64_t CalcSubRUbBytes(uint64_t rFactor, uint64_t chunksPerGroup, uint64_t numGroups, int64_t elemSize) const;
@@ -99,7 +99,7 @@ bool INTrainingReduceV2ARFullReduceTiling::IsCapable()
99 outputBytesPerRow > usable - alignedInputBytes * DOUBLE_BUFFER_COUNT ||99 outputBytesPerRow > usable - alignedInputBytes * DOUBLE_BUFFER_COUNT ||
100 binAddBufferOneline >100 binAddBufferOneline >
101 (usable - alignedInputBytes * DOUBLE_BUFFER_COUNT - outputBytesPerRow) / REDUCE_SCRATCH_COUNT) {101 (usable - alignedInputBytes * DOUBLE_BUFFER_COUNT - outputBytesPerRow) / REDUCE_SCRATCH_COUNT) {
102- // 单行 R 全载超单次 UB 容量 → sub-R 分块路径(DESIGN §6.3 路 A),同 TilingKey + isSubRTiling 标志。102+ // 单行 R 全载超单次 UB 容量 → sub-R 分块路径,同 TilingKey + isSubRTiling 标志。
103 return DoSubRTiling(rAlign, binAddQuotient, elemSize);103 return DoSubRTiling(rAlign, binAddQuotient, elemSize);
104 }104 }
105 const uint64_t bytesPerRow = alignedInputBytes * DOUBLE_BUFFER_COUNT + outputBytesPerRow +105 const uint64_t bytesPerRow = alignedInputBytes * DOUBLE_BUFFER_COUNT + outputBytesPerRow +
@@ -189,7 +189,7 @@ uint64_t INTrainingReduceV2ARFullReduceTiling::CalcSubRUbBytes(uint64_t rFactor,
189 return inBytes + partialBytes + outBytes;189 return inBytes + partialBytes + outBytes;
190}190}
191 191 
192-// sub-R 分块 tiling(DESIGN §6.3 路 A):R 超单次 UB 容量时,按 rFactor 分块搬入,192+// sub-R 分块 tiling:R 超单次 UB 容量时,按 rFactor 分块搬入,
193// 每块归约累进独立 fp32 累加器,跨块固定顺序树归约。按行(N*C)切核。193// 每块归约累进独立 fp32 累加器,跨块固定顺序树归约。按行(N*C)切核。
194//194//
195// 两段式:195// 两段式:
@@ -98,7 +98,7 @@ public:
98 pipe_.InitBuffer(squareBinaryAddBuf_, scratchBufferBytes_);98 pipe_.InitBuffer(squareBinaryAddBuf_, scratchBufferBytes_);
99 }99 }
100 100 
101- // sub-R 分块路径 buffer 规划(DESIGN §6.3 路 A)。101+ // sub-R 分块路径 buffer 规划。
102 __aicore__ inline void InitSubR()102 __aicore__ inline void InitSubR()
103 {103 {
104 // 部分和槽位只与 chunksPerGroup 有关,与 R 无关;分组时多留 1 格给组间 carry。104 // 部分和槽位只与 chunksPerGroup 有关,与 R 无关;分组时多留 1 格给组间 carry。
@@ -10,7 +10,7 @@
10 10 
11/*!11/*!
12 * \file in_training_reduce_v2_sub_r.h12 * \file in_training_reduce_v2_sub_r.h
13- * \brief sub-R 分块路径(DESIGN §6.3 路 A):R 超单次 UB 容量时按 rFactor 分块搬入,13+ * \brief sub-R 分块路径:R 超单次 UB 容量时按 rFactor 分块搬入,
14 * 每块归约得 Σx/Σx² 的高低双分量部分和,再做固定顺序补偿树归约。14 * 每块归约得 Σx/Σx² 的高低双分量部分和,再做固定顺序补偿树归约。
15 * 部分和缓存按 chunksPerGroup 分组:每组折叠一次,高低两分量写入 carry 槽参与下一组折叠,15 * 部分和缓存按 chunksPerGroup 分组:每组折叠一次,高低两分量写入 carry 槽参与下一组折叠,
16 * 避免全量部分和缓存限制 R;numGroups==1 时只需一次组内折叠。16 * 避免全量部分和缓存限制 R;numGroups==1 时只需一次组内折叠。
@@ -28,7 +28,7 @@ struct INTrainingReduceV2ARFullReduceTilingData {
28 int64_t cTail; // C 轴尾块长度28 int64_t cTail; // C 轴尾块长度
29 uint64_t binaryAddQuotient; // 小于 rAlign 的最大 2 的幂(pairwise 折叠点)29 uint64_t binaryAddQuotient; // 小于 rAlign 的最大 2 的幂(pairwise 折叠点)
30 int64_t perCoreCnt; // 平均每个核处理的 tile 数30 int64_t perCoreCnt; // 平均每个核处理的 tile 数
31- // ---- sub-R 分块路径(DESIGN §6.3 路 A;R 超单次 UB 容量时启用)----31+ // ---- sub-R 分块路径(R 超单次 UB 容量时启用)----
32 uint64_t isSubRTiling; // 0=R 全载路径;1=sub-R 分块路径32 uint64_t isSubRTiling; // 0=R 全载路径;1=sub-R 分块路径
33 uint64_t rFactor; // sub-R 分块的块大小(元素数,VL_FP32 整数倍)33 uint64_t rFactor; // sub-R 分块的块大小(元素数,VL_FP32 整数倍)
34 uint64_t numChunks; // ceil(numR / rFactor),每行分块数34 uint64_t numChunks; // ceil(numR / rFactor),每行分块数
@@ -11,8 +11,8 @@
11message(STATUS "=== Debug: start ops.norm.in_training_reduce_v2.tests.CMakeLists.txt ")11message(STATUS "=== Debug: start ops.norm.in_training_reduce_v2.tests.CMakeLists.txt ")
12 12 
13# 仅纳入 ut 子目录参与主构建(UT 集成到 nn_op_host_ut)。13# 仅纳入 ut 子目录参与主构建(UT 集成到 nn_op_host_ut)。
14-# st/ 是独立自包含的 C++ ST 工程(自带 project() + Real 模式 FATAL_ERROR),14+# 系统测试参考实现位于 tests/assets/golden.py,由 ops-test-kit 加载,
15-# 通过 tests/st/run.sh 单独构建,不并入主 UT 构建树。15+# 不作为 C++ ST 工程并入主 UT 构建树。
16if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ut/CMakeLists.txt")16if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ut/CMakeLists.txt")
17 add_subdirectory(ut)17 add_subdirectory(ut)
18endif()18endif()
@@ -12,6 +12,7 @@
12 12 
13#include "base/context_builder/op_infer_datatype_context_builder.h"13#include "base/context_builder/op_infer_datatype_context_builder.h"
14#include "base/context_builder/op_infer_shape_context_builder.h"14#include "base/context_builder/op_infer_shape_context_builder.h"
15+#include "base/registry/op_impl_space_registry_v2.h"
15#include "exe_graph/runtime/runtime_tensor.h"16#include "exe_graph/runtime/runtime_tensor.h"
16 17 
17namespace ops {18namespace ops {
@@ -81,3 +82,92 @@ TEST(INTrainingReduceV2GraphInferTest, graph_infer_rejects_invalid_nd_rank_004)
81 ASSERT_NE(holder.GetContext(), nullptr);82 ASSERT_NE(holder.GetContext(), nullptr);
82 EXPECT_EQ(ops::InferShapeForINTrainingReduceV2(holder.GetContext()), ge::GRAPH_FAILED);83 EXPECT_EQ(ops::InferShapeForINTrainingReduceV2(holder.GetContext()), ge::GRAPH_FAILED);
83}84}
85+ 
86+namespace {
87+void CheckRegisteredInfer(const gert::Shape& inputShape, ge::Format format, const gert::Shape& expected,
88+ ge::graphStatus status = ge::GRAPH_SUCCESS)
89+{
90+ auto registry = gert::DefaultOpImplSpaceRegistryV2::GetInstance().GetSpaceRegistry();
91+ ASSERT_NE(registry, nullptr);
92+ const auto* impl = registry->GetOpImpl("INTrainingReduceV2");
93+ ASSERT_NE(impl, nullptr);
94+ ASSERT_NE(impl->infer_shape, nullptr);
95+ gert::StorageShape storage;
96+ storage.MutableOriginShape() = inputShape;
97+ storage.MutableStorageShape() = inputShape;
98+ gert::Tensor input(storage, gert::StorageFormat(format, format, {}), ge::DT_FLOAT16);
99+ gert::OpInferShapeContextBuilder builder;
100+ builder.OpType("INTrainingReduceV2").OpName("registered_reduce");
101+ builder.IONum(1, 2).InputTensors({&input});
102+ builder.OutputTensorDesc(0, ge::DT_FLOAT, ge::FORMAT_ND, ge::FORMAT_ND);
103+ builder.OutputTensorDesc(1, ge::DT_FLOAT, ge::FORMAT_ND, ge::FORMAT_ND);
104+ auto holder = builder.Build();
105+ ASSERT_NE(holder.GetContext(), nullptr);
106+ auto* context = holder.GetContext();
107+ ASSERT_NE(context->GetOutputShape(0), nullptr);
108+ ASSERT_NE(context->GetOutputShape(1), nullptr);
109+ *context->GetOutputShape(0) = gert::Shape({91, 92});
110+ *context->GetOutputShape(1) = gert::Shape({93});
111+ ASSERT_EQ(impl->infer_shape(context), status);
112+ EXPECT_EQ(*context->GetInputShape(0), inputShape);
113+ if (status == ge::GRAPH_SUCCESS) {
114+ EXPECT_EQ(*context->GetOutputShape(0), expected);
115+ EXPECT_EQ(*context->GetOutputShape(1), expected);
116+ ASSERT_EQ(impl->infer_shape(context), ge::GRAPH_SUCCESS);
117+ EXPECT_EQ(*context->GetOutputShape(0), expected);
118+ EXPECT_EQ(*context->GetOutputShape(1), expected);
119+ }
120+}
121+} // namespace
122+ 
123+TEST(INTrainingReduceV2GraphInferTest, RegisteredChannelFirstFormats)
124+{
125+ CheckRegisteredInfer({2, 3, 5, 7}, ge::FORMAT_NCHW, {2, 3, 1, 1});
126+ CheckRegisteredInfer({2, 3, 5, 7, 11}, ge::FORMAT_NCDHW, {2, 3, 1, 1, 1});
127+}
128+ 
129+TEST(INTrainingReduceV2GraphInferTest, RegisteredChannelLastFormats)
130+{
131+ CheckRegisteredInfer({2, 5, 7, 3}, ge::FORMAT_NHWC, {2, 1, 1, 3});
132+ CheckRegisteredInfer({2, 5, 7, 11, 3}, ge::FORMAT_NDHWC, {2, 1, 1, 1, 3});
133+}
134+ 
135+TEST(INTrainingReduceV2GraphInferTest, RegisteredNDRankBounds)
136+{
137+ CheckRegisteredInfer({2, 3}, ge::FORMAT_ND, {2, 3});
138+ CheckRegisteredInfer({2, 3, 1, 2, 3, 4, 5, 6}, ge::FORMAT_ND, {2, 3, 1, 1, 1, 1, 1, 1});
139+}
140+ 
141+TEST(INTrainingReduceV2GraphInferTest, RegisteredDynamicAndEmptyShapes)
142+{
143+ CheckRegisteredInfer({-2}, ge::FORMAT_ND, {-2});
144+ CheckRegisteredInfer({-1, -1, 5, 7}, ge::FORMAT_NCHW, {-1, -1, 1, 1});
145+ CheckRegisteredInfer({2, 5, -1, -1}, ge::FORMAT_NHWC, {2, 1, 1, -1});
146+ CheckRegisteredInfer({0, 3, 5}, ge::FORMAT_ND, {0, 3, 1});
147+}
148+ 
149+TEST(INTrainingReduceV2GraphInferTest, RegisteredRejectsInvalidFormatAndRank)
150+{
151+ CheckRegisteredInfer({3}, ge::FORMAT_ND, {}, ge::GRAPH_FAILED);
152+ CheckRegisteredInfer({2, 3, 4}, ge::FORMAT_NHWC, {}, ge::GRAPH_FAILED);
153+ CheckRegisteredInfer({2, 3, 4, 5}, ge::FORMAT_NDHWC, {}, ge::GRAPH_FAILED);
154+ CheckRegisteredInfer({2, 3, 4, 5}, ge::FORMAT_HWCN, {}, ge::GRAPH_FAILED);
155+}
156+ 
157+TEST(INTrainingReduceV2GraphInferTest, DtypeOverwritesBothOutputs)
158+{
159+ for (auto dtype : {ge::DT_FLOAT16, ge::DT_FLOAT}) {
160+ gert::OpInferDataTypeContextBuilder builder;
161+ builder.OpType("INTrainingReduceV2").OpName("registered_reduce_dtype").IONum(1, 2);
162+ builder.InputTensorDesc(0, dtype, ge::FORMAT_ND, ge::FORMAT_ND);
163+ builder.OutputTensorDesc(0, ge::FORMAT_ND, ge::FORMAT_ND);
164+ builder.OutputTensorDesc(1, ge::FORMAT_ND, ge::FORMAT_ND);
165+ auto holder = builder.Build();
166+ ASSERT_NE(holder.GetContext(), nullptr);
167+ ASSERT_EQ(holder.GetContext()->SetOutputDataType(0, ge::DT_FLOAT16), ge::GRAPH_SUCCESS);
168+ ASSERT_EQ(holder.GetContext()->SetOutputDataType(1, ge::DT_INT32), ge::GRAPH_SUCCESS);
169+ ASSERT_EQ(ops::InferDataType4INTrainingReduceV2(holder.GetContext()), ge::GRAPH_SUCCESS);
170+ EXPECT_EQ(holder.GetContext()->GetOutputDataType(0), ge::DT_FLOAT);
171+ EXPECT_EQ(holder.GetContext()->GetOutputDataType(1), ge::DT_FLOAT);
172+ }
173+}
@@ -11,7 +11,7 @@
11/*!11/*!
12 * \file test_in_training_reduce_v2_tiling.cpp12 * \file test_in_training_reduce_v2_tiling.cpp
13 * \brief arch35 核心路径 UT —— Tiling(AR_FULL_REDUCE / R 全载)13 * \brief arch35 核心路径 UT —— Tiling(AR_FULL_REDUCE / R 全载)
14- * 契约(spec.yaml / DESIGN §5.1/§6.3):14+ * 切分约束(对应op_host/arch35中的Tiling实现):
15 * - TilingKey == 200000(AR_FULL_REDUCE)15 * - TilingKey == 200000(AR_FULL_REDUCE)
16 * - IsCapable 对 NCHW / ND / NCDHW 返回 true(一期支持)16 * - IsCapable 对 NCHW / ND / NCDHW 返回 true(一期支持)
17 * - 输入 1(x)/ 输出 2(sum, square_sum),无 attr17 * - 输入 1(x)/ 输出 2(sum, square_sum),无 attr
@@ -11,7 +11,7 @@
11/*!11/*!
12 * \file test_in_training_reduce_v2_infershape.cpp12 * \file test_in_training_reduce_v2_infershape.cpp
13 * \brief arch35 核心路径 UT —— InferShape13 * \brief arch35 核心路径 UT —— InferShape
14- * 契约(spec.yaml / DESIGN §6.2):14+ * 推导约束(对应op_host/in_training_reduce_v2_infershape.cpp):
15 * - InferShape:输入 x [N,C,H,W] → sum/square_sum shape 均为 [N,C,1,1]15 * - InferShape:输入 x [N,C,H,W] → sum/square_sum shape 均为 [N,C,1,1]
16 * (N,C 取自 x、空间轴置 1);5D [N,C,D,H,W] → [N,C,1,1,1]。16 * (N,C 取自 x、空间轴置 1);5D [N,C,D,H,W] → [N,C,1,1,1]。
17 * - C 轴位置由 origin format 决定:channel-first(NCHW/NCDHW/ND)在 dim 1,17 * - C 轴位置由 origin format 决定:channel-first(NCHW/NCDHW/ND)在 dim 1,
@@ -19,7 +19,7 @@
19 * storage format,约束不到 origin format —— 后者由用户网络决定(TF 来源19 * storage format,约束不到 origin format —— 后者由用户网络决定(TF 来源
20 * 天然是 NHWC),GE 会插 TransData 转排布,但 InferShape 跑在其之前。20 * 天然是 NHWC),GE 会插 TransData 转排布,但 InferShape 跑在其之前。
21 *21 *
22- * InferDataType(输出恒 DT_FLOAT)和静态图 legacy V1 bridge 位于22+ * InferDataType(输出恒 DT_FLOAT)和图推导的runtime2.0注册位于
23 * op_graph/in_training_reduce_v2_graph_infer.cpp,由 op_graph UT 与安装后的23 * op_graph/in_training_reduce_v2_graph_infer.cpp,由 op_graph UT 与安装后的
24 * GEIR 用例验证;本文件只验证 runtime2.0 InferShape。24 * GEIR 用例验证;本文件只验证 runtime2.0 InferShape。
25 */25 */
@@ -0,0 +1,28 @@
1+# Copyright (c) 2026 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+cmake_minimum_required(VERSION 3.18)
10+project(GE_IR_EXAMPLE LANGUAGES CXX)
11+ 
12+set(ASCEND_PATH "$ENV{ASCEND_HOME_PATH}")
13+if(NOT ASCEND_PATH)
14+ message(FATAL_ERROR "Source the CANN set_env.sh before configuring this example.")
15+endif()
16+find_library(GRAPH_LIBRARY NAMES libgraph.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
17+find_library(GE_RUNNER_LIBRARY NAMES libge_runner.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
18+find_library(GRAPH_BASE_LIBRARY NAMES libgraph_base.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
19+find_library(GE_COMPILER_LIBRARY NAMES libge_compiler.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
20+ 
21+add_executable(test_geir_l2_normalize test_geir_l2_normalize.cpp)
22+target_include_directories(test_geir_l2_normalize PRIVATE
23+ "${ASCEND_PATH}/include/graph"
24+ "${ASCEND_PATH}/include/ge"
25+ "${ASCEND_PATH}/opp/built-in/op_proto/inc"
26+ "${ASCEND_PATH}/include")
27+target_link_libraries(test_geir_l2_normalize PRIVATE
28+ ${GRAPH_LIBRARY} ${GE_RUNNER_LIBRARY} ${GRAPH_BASE_LIBRARY} ${GE_COMPILER_LIBRARY})
@@ -13,7 +13,9 @@
13 * \brief GE graph construction sample for L2Normalize (arch35 ND format).13 * \brief GE graph construction sample for L2Normalize (arch35 ND format).
14 */14 */
15#include <cstdint>15#include <cstdint>
16+#include <cmath>
16#include <cstdio>17#include <cstdio>
18+#include <cstring>
17#include <ctime>19#include <ctime>
18#include <map>20#include <map>
19#include <string>21#include <string>
@@ -121,6 +123,27 @@ int CreateGraph(DataType dt, std::vector<ge::Tensor>& input, std::vector<Operato
121 return SUCCESS;123 return SUCCESS;
122}124}
123 125 
126+bool CheckOutput(const std::vector<ge::Tensor>& output)
127+{
128+ constexpr size_t elementCount = 4 * 16;
129+ if (output.size() != 1 || output[0].GetTensorDesc().GetShape().GetDims() != std::vector<int64_t>{4, 16} ||
130+ output[0].GetTensorDesc().GetDataType() != DT_FLOAT || output[0].GetSize() != elementCount * sizeof(float) ||
131+ output[0].GetData() == nullptr) {
132+ LOG_PRINT("FAIL - unexpected output metadata or buffer\n");
133+ return false;
134+ }
135+ // Each row contains sixteen ones: 1 / sqrt(16) = 0.25.
136+ for (size_t index = 0; index < elementCount; ++index) {
137+ float value = 0.0f;
138+ std::memcpy(&value, output[0].GetData() + index * sizeof(float), sizeof(float));
139+ if (!std::isfinite(value) || std::fabs(value - 0.25f) > 1e-6f) {
140+ LOG_PRINT("FAIL - output[%zu]=%.9g, expected 0.25\n", index, static_cast<double>(value));
141+ return false;
142+ }
143+ }
144+ return true;
145+}
146+ 
124int main(int argc, char* argv[])147int main(int argc, char* argv[])
125{148{
126 Graph graph("tc_ge_irrun_test");149 Graph graph("tc_ge_irrun_test");
@@ -152,6 +175,8 @@ int main(int argc, char* argv[])
152 } else if (session.RunGraph(gid, input, output) != SUCCESS) {175 } else if (session.RunGraph(gid, input, output) != SUCCESS) {
153 LOG_PRINT("%s - FAIL - [XIR]: RunGraph failed\n", GetTime().c_str());176 LOG_PRINT("%s - FAIL - [XIR]: RunGraph failed\n", GetTime().c_str());
154 result = FAILED;177 result = FAILED;
178+ } else if (!CheckOutput(output)) {
179+ result = FAILED;
155 } else {180 } else {
156 LOG_PRINT("%s - PASS - [XIR]: Session RunGraph succeeded, outputs=%zu\n", GetTime().c_str(), output.size());181 LOG_PRINT("%s - PASS - [XIR]: Session RunGraph succeeded, outputs=%zu\n", GetTime().c_str(), output.size());
157 for (size_t i = 0; i < output.size(); i++) {182 for (size_t i = 0; i < output.size(); i++) {
@@ -8,171 +8,76 @@
8 * See LICENSE in the root of the software repository for the full text of the License.8 * See LICENSE in the root of the software repository for the full text of the License.
9 */9 */
10 10 
11-// =============================================================================11+/*!
12-// norm/l2_normalize/op_graph/l2_normalize_graph_infer.cpp12+ * \file l2_normalize_graph_infer.cpp
13-// =============================================================================13+ * \brief Runtime 2.0 data type inference for L2Normalize.
14-//14+ */
15-// ROLE: Graph-level inference registration for the L2Normalize operator.
16-// - V1(COMMON_INFER_FUNC_REG,ge::Operator 旧式推导):GE 图编译期
17-// (AddGraph/RunGraph 的 shape/type 推导 pass)实际调用的推导入口。本算子 IR
18-// 与内置 nn_batch_norm_ops.h 的 L2Normalize 同名,内置
19-// .so 亦按同名注册 V1 推导(OneInOneOutDynamicInfer);本包在 custom 路径
20-// 先于内置包加载(AddSoToRegistry 顺序),按 insert-if-absent 语义以本实现
21-// 占据同名 V1 槽位,保证推导与校验语义来自本包(y.shape = x.shape、
22-// y.dtype = x.dtype)。
23-// - V2(IMPL_OP … InferDataType,gert 新式推导):动态图执行期 executor 直接
24-// 调用的 gert kernel 注册(InferShape 在 op_host/l2_normalize_infershape.cpp
25-// 注册)。两条注册路径规则保持一致(恒等透传 + 同一套参数校验)。
26-// - 参数校验(GEIR 图模式):rank-0 标量拒绝(契约 rank 1–8,无合法
27-// axis);声明输出 shape/dtype 与 x 不一致拒绝(与 tiling 硬门禁保持一致;
28-// 未声明(rank-0 空 shape / DT_UNDEFINED)与未知维
29-// (-1/-2)不校验);声明 format 仅 ND(FORMAT_RESERVED 未声明跳过)。
30-// dtype 合法域仍由 OpDef/REG_OP TensorType 声明承载。
31-// =============================================================================
32 15 
33-#include "register/op_impl_registry.h" // IMPL_OP macro for operator registration16+#include "register/op_impl_registry.h"
34-#include "graph/operator_reg.h" // IMPLEMT_COMMON_INFERFUNC / COMMON_INFER_FUNC_REG17+#include "op_common/log/log.h"
35-#include "graph/utils/type_utils.h" // TypeUtils::DataTypeToSerialString18+#include "l2_normalize_proto.h"
36-#include "op_common/log/log.h" // OP_LOGE / OP_LOGE_FOR_INVALID_* macros
37-#include <string>
38-#include <vector>
39- 
40-using namespace ge;
41 19 
42namespace {20namespace {
43- 21+bool DeclaredShapeCompatible(const ge::Shape& declared, const ge::Shape& input)
44-constexpr size_t INPUT_X_IDX = 0;
45-constexpr size_t OUTPUT_Y_IDX = 0;
46-constexpr size_t MAX_INPUT_RANK = 8;
47- 
48-std::string ShapeToString(const ge::Shape& shape)
49{22{
50- std::string str = "[";23+ if (declared.GetDimNum() == 0U || declared.GetDims() == ge::UNKNOWN_RANK || input.GetDims() == ge::UNKNOWN_RANK) {
51- for (size_t i = 0; i < shape.GetDimNum(); ++i) {24+ return true;
52- if (i > 0) {
53- str += ",";
54- }
55- str += std::to_string(shape.GetDim(i));
56 }25 }
57- str += "]";26+ if (declared.GetDimNum() != input.GetDimNum()) {
58- return str;
59-}
60- 
61-// 声明输出 shape 与 x 一致性:rank 相等且双侧均为已知维(≥0)时逐维相等。
62-// 任一侧未知维(<0,动态 shape -1/-2)不参与比对。
63-bool DeclaredShapeCompatible(const ge::Shape& declared, const ge::Shape& xShape)
64-{
65- if (declared.GetDimNum() != xShape.GetDimNum()) {
66 return false;27 return false;
67 }28 }
68- for (size_t i = 0; i < xShape.GetDimNum(); ++i) {29+ for (size_t index = 0U; index < input.GetDimNum(); ++index) {
69- const int64_t xDim = xShape.GetDim(i);30+ if (input.GetDim(index) >= 0 && declared.GetDim(index) >= 0 && input.GetDim(index) != declared.GetDim(index)) {
70- const int64_t yDim = declared.GetDim(i);
71- if (xDim >= 0 && yDim >= 0 && xDim != yDim) {
72 return false;31 return false;
73 }32 }
74 }33 }
75 return true;34 return true;
76}35}
77- 
78} // namespace36} // namespace
79 37 
80-namespace ops {38+namespace ge {
81- 39+// Validate graph declarations before inference replaces the output descriptors.
82-// ---------------------------------------------------------------------------40+// Runtime 2.0 output buffers contain inferred values, not the original declarations.
83-// V1 图编译期推导(ge::Operator 旧式签名,ge::Operator& 入参):41+IMPLEMT_VERIFIER(L2Normalize, VerifyL2Normalize)
84-// 0. 未知秩(rank=-2):GE 未知秩在 ge::Shape 中存为 dims={-2} 且
85-// GetDimNum()==0(与真标量取值相同,须以 GetDims() 区分,见 graph/tensor.h
86-// ge::Shape 注释);零元素张量在动态图下即以该形态到达(FR-P1-011
87-// L0_empty_027/034:desc/ori_shape=[-2])。未知秩跳过步骤 1/2 的维数校验,
88-// y 恒等复制未知秩;dtype/format 契约校验与维数无关,保留。
89-// 1. rank-0 标量拒绝(契约 rank 1–8;rank-0 无合法 axis);
90-// 2. 声明输出 shape 一致性(y.shape = x.shape;未知维/未声明跳过);
91-// 3. 声明输出 dtype 一致性(y.dtype = x.dtype;DT_UNDEFINED 跳过);
92-// 4. 声明 format 校验(x/y 仅 ND;FORMAT_RESERVED 未声明跳过);
93-// 5. 恒等推导:y ← x(shape + dtype,语义对齐内置 OneInOneOutDynamicInfer)。
94-// ---------------------------------------------------------------------------
95-static ge::graphStatus L2NormalizeGraphInferFunc(ge::Operator& op)
96{42{
97- const ge::TensorDesc xDesc = op.GetInputDescByName("x");43+ const auto x = op.GetInputDesc("x");
98- const ge::Shape xShape = xDesc.GetShape();44+ const auto y = op.GetOutputDesc("y");
99- const ge::DataType xDtype = xDesc.GetDataType();45+ const auto shape = x.GetShape();
100- 46+ if (shape.GetDims() != UNKNOWN_RANK && (shape.GetDimNum() == 0U || shape.GetDimNum() > 8U)) {
101- ge::TensorDesc yDesc = op.GetOutputDescByName("y");47+ OP_LOGE("L2Normalize", "x rank must be in [1, 8].");
102- const ge::Shape yShape = yDesc.GetShape();48+ return GRAPH_FAILED;
103- const ge::DataType yDtype = yDesc.GetDataType();
104- 
105- // 未知秩标记(GE_UNKNOWN_RANK):步骤 1/2 的维数相关校验对 rank=-2 无意义,整体跳过
106- const bool isUnknownRank = (xShape.GetDims() == ge::UNKNOWN_RANK);
107- 
108- // 1. rank-0 标量拒绝(契约 rank 1–8;GE 图管线会把 rank-0 规整为 (1) 后才到
109- // tiling,故该门禁须在推导期拦截)。标量判定按 ge::Shape 官方推荐
110- // GetDims().empty()(GetDimNum()==0 与未知秩取值冲突,见步骤 0)。
111- if (!isUnknownRank && (xShape.GetDims().empty() || xShape.GetDimNum() > MAX_INPUT_RANK)) {
112- OP_LOGE_FOR_INVALID_SHAPEDIM("L2Normalize", "rank", std::to_string(xShape.GetDimNum()).c_str(), "[1, 8]");
113- return ge::GRAPH_FAILED;
114 }49 }
115- 50+ if (!DeclaredShapeCompatible(y.GetShape(), shape)) {
116- // 2. 声明输出 shape 一致性(host 侧硬门禁;缺省会落设备写坏 out 缓冲);51+ OP_LOGE("L2Normalize", "Declared y shape must match x shape.");
117- // 未知秩无维可校,跳过(y 由步骤 5 恒等复制未知秩)52+ return GRAPH_FAILED;
118- if (!isUnknownRank && yShape.GetDimNum() != 0U && !DeclaredShapeCompatible(yShape, xShape)) {
119- OP_LOGE_FOR_INVALID_SHAPE("L2Normalize", "y", ShapeToString(yShape).c_str(), ShapeToString(xShape).c_str());
120- return ge::GRAPH_FAILED;
121 }53 }
122- 54+ if (y.GetDataType() != DT_UNDEFINED && y.GetDataType() != x.GetDataType()) {
123- // 3. 声明输出 dtype 一致性(y 与 x 恒一致,same_as_first_input)55+ OP_LOGE("L2Normalize", "Declared y dtype must match x dtype.");
124- if (yDtype != ge::DT_UNDEFINED && yDtype != xDtype) {56+ return GRAPH_FAILED;
125- OP_LOGE_FOR_INVALID_DTYPE("L2Normalize", "y", ge::TypeUtils::DataTypeToSerialString(yDtype).c_str(),
126- ge::TypeUtils::DataTypeToSerialString(xDtype).c_str());
127- return ge::GRAPH_FAILED;
128 }57 }
129- 58+ if ((x.GetFormat() != FORMAT_ND && x.GetFormat() != FORMAT_RESERVED) ||
130- // 4. 声明 format 校验:x / y 仅支持 ND;未声明(FORMAT_RESERVED)不校验。y 侧 format 缺该59+ (y.GetFormat() != FORMAT_ND && y.GetFormat() != FORMAT_RESERVED)) {
131- // 门禁时 FRACTAL_NZ 声明会落入图执行(tiling 仅校验 x 侧 format)。60+ OP_LOGE("L2Normalize", "x and y must use ND format.");
132- if (xDesc.GetFormat() != ge::FORMAT_ND && xDesc.GetFormat() != ge::FORMAT_RESERVED) {61+ return GRAPH_FAILED;
133- OP_LOGE_FOR_INVALID_FORMAT("L2Normalize", "x", std::to_string(static_cast<int64_t>(xDesc.GetFormat())).c_str(),
134- "ND");
135- return ge::GRAPH_FAILED;
136 }62 }
137- if (yDesc.GetFormat() != ge::FORMAT_ND && yDesc.GetFormat() != ge::FORMAT_RESERVED) {63+ return GRAPH_SUCCESS;
138- OP_LOGE_FOR_INVALID_FORMAT("L2Normalize", "y", std::to_string(static_cast<int64_t>(yDesc.GetFormat())).c_str(),
139- "ND");
140- return ge::GRAPH_FAILED;
141- }
142- 
143- // 5. 恒等推导:y.shape = x.shape、y.dtype = x.dtype(format 不改写)
144- yDesc.SetShape(xShape);
145- yDesc.SetDataType(xDtype);
146- if (op.UpdateOutputDesc("y", yDesc) != ge::GRAPH_SUCCESS) {
147- OP_LOGE("L2Normalize", "Update output desc y failed.");
148- return ge::GRAPH_FAILED;
149- }
150- return ge::GRAPH_SUCCESS;
151}64}
152- 65+VERIFY_FUNC_REG(L2Normalize, VerifyL2Normalize);
153-} // namespace ops66+} // namespace ge
154- 
155-// V1 推导注册(与 canndev 内置 nn_batch_norm_ops.cc 的 COMMON_INFER_FUNC_REG 同机制;
156-// 本包 custom 路径先加载,占据同名 V1 槽位)
157-COMMON_INFER_FUNC_REG(L2Normalize, ops::L2NormalizeGraphInferFunc);
158 67 
159namespace ops {68namespace ops {
69+namespace {
70+constexpr size_t INPUT_X_IDX = 0;
71+constexpr size_t OUTPUT_Y_IDX = 0;
72+} // namespace
160 73 
161-// ---------------------------------------------------------------------------74+ge::graphStatus InferDataTypeForL2Normalize(gert::InferDataTypeContext* context)
162-// V2 InferDataType(gert 新式推导,动态图执行期 executor 调用):
163-// y.dtype = x.dtype(same_as_first_input 单输入恒等,无接口级
164-// promotion;fp16 内部 fp32 提升不改变输出 dtype,推导不感知)。dtype 合法域
165-// ({DT_FLOAT16, DT_FLOAT})由 OpDef/REG_OP TensorType 声明承载,本函数仅做
166-// 恒等透传,不做值域校验。
167-// ---------------------------------------------------------------------------
168-static ge::graphStatus InferDataTypeForL2Normalize(gert::InferDataTypeContext* context)
169{75{
170- context->SetOutputDataType(OUTPUT_Y_IDX, context->GetInputDataType(INPUT_X_IDX));76+ if (context == nullptr) {
171- return ge::GRAPH_SUCCESS;77+ return ge::GRAPH_FAILED;
78+ }
79+ return context->SetOutputDataType(OUTPUT_Y_IDX, context->GetInputDataType(INPUT_X_IDX));
172}80}
173 81 
174-// IMPL_OP(L2Normalize).InferDataType(func):
175-// Registers InferDataTypeForL2Normalize as the type inference function
176-// for the L2Normalize operator type.
177IMPL_OP(L2Normalize).InferDataType(InferDataTypeForL2Normalize);82IMPL_OP(L2Normalize).InferDataType(InferDataTypeForL2Normalize);
178} // namespace ops83} // namespace ops
@@ -13,14 +13,13 @@
13// =============================================================================13// =============================================================================
14//14//
15// ROLE: V2 shape inference (gert) for the L2Normalize operator.15// ROLE: V2 shape inference (gert) for the L2Normalize operator.
16-// 动态图执行期由 gert executor 直接调用(编译期图推导走 op_graph/ 的 V1 注册,16+// 图编译和动态图执行均使用 gert InferShape 注册。
17-// 两条路径规则一致)。
18// - y.shape = x.shape 恒等复制(动态 -1 维、空 0 维一并透传,纯结构级映射,17// - y.shape = x.shape 恒等复制(动态 -1 维、空 0 维一并透传,纯结构级映射,
19// 不读属性);18// 不读属性);
20// - rank-0 标量拒绝(契约 rank 1–8):GE 图管线会把 rank-0 规整为 (1) 后才到19// - rank-0 标量拒绝(契约 rank 1–8):GE 图管线会把 rank-0 规整为 (1) 后才到
21// tiling,动态执行期须在此拦截(GEIR L2 负向 dimval 场景)。20// tiling,动态执行期须在此拦截(GEIR L2 负向 dimval 场景)。
22-// - dtype/format/属性值域校验不在本函数职责内(OpDef TensorType / V1 推导 /21+// - 输入/输出 ND format 在此校验;原始图声明由 op_graph 的 Verify 校验。
23-// tiling 硬门禁承载)。22+// dtype 推导位于 op_graph,属性值域校验由 tiling 承载。
24//23//
25// CONTENTS:24// CONTENTS:
26// - InferShape4L2Normalize() — rank-0 gate + identity shape copy25// - InferShape4L2Normalize() — rank-0 gate + identity shape copy
@@ -42,6 +41,12 @@ static constexpr size_t INPUT_X_IDX = 0;
42static constexpr size_t OUTPUT_Y_IDX = 0;41static constexpr size_t OUTPUT_Y_IDX = 0;
43static constexpr size_t MAX_INPUT_RANK = 8;42static constexpr size_t MAX_INPUT_RANK = 8;
44 43 
44+static bool IsSupportedFormat(const gert::CompileTimeTensorDesc& desc)
45+{
46+ const auto format = desc.GetStorageFormat();
47+ return format == ge::FORMAT_ND || format == ge::FORMAT_RESERVED;
48+}
49+ 
45// ---------------------------------------------------------------------------50// ---------------------------------------------------------------------------
46// InferShape4L2Normalize(context) — shape inference function51// InferShape4L2Normalize(context) — shape inference function
47//52//
@@ -56,6 +61,9 @@ static constexpr size_t MAX_INPUT_RANK = 8;
56// ---------------------------------------------------------------------------61// ---------------------------------------------------------------------------
57static ge::graphStatus InferShape4L2Normalize(gert::InferShapeContext* context)62static ge::graphStatus InferShape4L2Normalize(gert::InferShapeContext* context)
58{63{
64+ if (context == nullptr) {
65+ return ge::GRAPH_FAILED;
66+ }
59 // GetInputShape(0): reads the shape of the first input (index 0 = x)67 // GetInputShape(0): reads the shape of the first input (index 0 = x)
60 const gert::Shape* input_shape = context->GetInputShape(INPUT_X_IDX);68 const gert::Shape* input_shape = context->GetInputShape(INPUT_X_IDX);
61 OP_CHECK_NULL_WITH_CONTEXT(context, input_shape);69 OP_CHECK_NULL_WITH_CONTEXT(context, input_shape);
@@ -64,6 +72,12 @@ static ge::graphStatus InferShape4L2Normalize(gert::InferShapeContext* context)
64 gert::Shape* output_shape = context->GetOutputShape(OUTPUT_Y_IDX);72 gert::Shape* output_shape = context->GetOutputShape(OUTPUT_Y_IDX);
65 OP_CHECK_NULL_WITH_CONTEXT(context, output_shape);73 OP_CHECK_NULL_WITH_CONTEXT(context, output_shape);
66 74 
75+ const auto* inputDesc = context->GetInputDesc(INPUT_X_IDX);
76+ const auto* outputDesc = context->GetOutputDesc(OUTPUT_Y_IDX);
77+ OP_CHECK_NULL_WITH_CONTEXT(context, inputDesc);
78+ OP_CHECK_NULL_WITH_CONTEXT(context, outputDesc);
79+ OP_CHECK_IF(!IsSupportedFormat(*inputDesc) || !IsSupportedFormat(*outputDesc),
80+ OP_LOGE(context, "x and y must use ND format."), return ge::GRAPH_FAILED);
67 // 动态 rank 使用框架标准标记透传,避免把 {-2} 当成普通的一维 shape 参与后续 rank 校验。81 // 动态 rank 使用框架标准标记透传,避免把 {-2} 当成普通的一维 shape 参与后续 rank 校验。
68 if (Ops::Base::IsUnknownRank(*input_shape)) {82 if (Ops::Base::IsUnknownRank(*input_shape)) {
69 Ops::Base::SetUnknownRank(*output_shape);83 Ops::Base::SetUnknownRank(*output_shape);
@@ -0,0 +1,11 @@
1+testcase_name,network_name,op_name,input_shapes,input_dtypes,input_formats,output_shapes,output_dtypes,output_formats,input_ori_shapes,input_ori_formats,output_ori_shapes,output_ori_formats,attributes,input_data_ranges,precision_tolerances,absolute_precision,output_inplace_indexes,output_shape_unknown_indexes,is_enabled,remark,soc_series,priority,dump_file_prefix,manual_input_binaries,manual_golden_binaries
2+l2_registry20_rank1_fp16,UNKNOWN,l2_normalize,"((17,),)","('float16',)","('ND',)","((17,),)","('float16',)","('ND',)","((17,),)","('ND',)","((17,),)","('ND',)","{'axis': [-1], 'eps': 0.0001}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
3+l2_registry20_rank2_fp32,UNKNOWN,l2_normalize,"((3, 17),)","('float32',)","('ND',)","((3, 17),)","('float32',)","('ND',)","((3, 17),)","('ND',)","((3, 17),)","('ND',)","{'axis': [1], 'eps': 0.0001}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
4+l2_registry20_multi_axis_fp32,UNKNOWN,l2_normalize,"((2, 3, 5),)","('float32',)","('ND',)","((2, 3, 5),)","('float32',)","('ND',)","((2, 3, 5),)","('ND',)","((2, 3, 5),)","('ND',)","{'axis': [0, 2], 'eps': 0.0001}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
5+l2_registry20_empty_axis_fp16,UNKNOWN,l2_normalize,"((2, 3, 2, 5),)","('float16',)","('ND',)","((2, 3, 2, 5),)","('float16',)","('ND',)","((2, 3, 2, 5),)","('ND',)","((2, 3, 2, 5),)","('ND',)","{'axis': [], 'eps': 0.0001}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
6+l2_registry20_rank8_fp32,UNKNOWN,l2_normalize,"((2, 1, 2, 1, 2, 1, 2, 3),)","('float32',)","('ND',)","((2, 1, 2, 1, 2, 1, 2, 3),)","('float32',)","('ND',)","((2, 1, 2, 1, 2, 1, 2, 3),)","('ND',)","((2, 1, 2, 1, 2, 1, 2, 3),)","('ND',)","{'axis': [-1], 'eps': 0.0001}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
7+l2_registry20_strided_fp16,UNKNOWN,l2_normalize,"((3, 7, 5),)","('float16',)","('ND',)","((3, 7, 5),)","('float16',)","('ND',)","((3, 7, 5),)","('ND',)","((3, 7, 5),)","('ND',)","{'axis': [1], 'eps': 0.0001}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
8+l2_registry20_eps_clamp_fp32,UNKNOWN,l2_normalize,"((2, 5),)","('float32',)","('ND',)","((2, 5),)","('float32',)","('ND',)","((2, 5),)","('ND',)","((2, 5),)","('ND',)","{'axis': [-1], 'eps': 100.0}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
9+l2_registry20_duplicate_axis_fp16,UNKNOWN,l2_normalize,"((2, 3, 5),)","('float16',)","('ND',)","((2, 3, 5),)","('float16',)","('ND',)","((2, 3, 5),)","('ND',)","((2, 3, 5),)","('ND',)","{'axis': [-1, 2, -1], 'eps': 0.0001}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
10+l2_registry20_empty_rank1_fp32,UNKNOWN,l2_normalize,"((0,),)","('float32',)","('ND',)","((0,),)","('float32',)","('ND',)","((0,),)","('ND',)","((0,),)","('ND',)","{'axis': [0], 'eps': 0.0001}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
11+l2_registry20_empty_rank2_fp16,UNKNOWN,l2_normalize,"((0, 0),)","('float16',)","('ND',)","((0, 0),)","('float16',)","('ND',)","((0, 0),)","('ND',)","((0, 0),)","('ND',)","{'axis': [1], 'eps': 0.0001}","[[-1, 1]]","((0.0009765625, 0.0009765625),)",1.52587890625e-5,(),(),1,Runtime 2.0 inference regression; ND shape and dtype preservation,,,,(),()
@@ -0,0 +1,11 @@
1+# Copyright (c) 2026 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+if(UT_TEST_ALL OR OP_GRAPH_UT)
10+ add_modules_ut_sources(HOSTNAME ${OP_GRAPH_MODULE_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR})
11+endif()
@@ -0,0 +1,114 @@
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 <gtest/gtest.h>
12+ 
13+#include "base/context_builder/op_infer_datatype_context_builder.h"
14+#include "../../../op_graph/l2_normalize_proto.h"
15+ 
16+namespace ops {
17+ge::graphStatus InferDataTypeForL2Normalize(gert::InferDataTypeContext* context);
18+} // namespace ops
19+ 
20+namespace {
21+gert::ContextHolder<gert::InferDataTypeContext> BuildContext(ge::DataType inputType)
22+{
23+ gert::OpInferDataTypeContextBuilder builder;
24+ builder.OpType("L2Normalize").OpName("L2Normalize");
25+ builder.IONum(1, 1);
26+ builder.InputTensorDesc(0, inputType, ge::FORMAT_ND, ge::FORMAT_ND);
27+ builder.OutputTensorDesc(0, ge::FORMAT_ND, ge::FORMAT_ND);
28+ return builder.Build();
29+}
30+ 
31+ge::graphStatus RunVerify(const ge::Shape& inputShape, const ge::Shape& outputShape,
32+ ge::DataType inputType = ge::DT_FLOAT, ge::DataType outputType = ge::DT_FLOAT,
33+ ge::Format inputFormat = ge::FORMAT_ND, ge::Format outputFormat = ge::FORMAT_ND)
34+{
35+ ge::op::L2Normalize op("l2_normalize_verify_ut");
36+ EXPECT_EQ(op.UpdateInputDesc("x", ge::TensorDesc(inputShape, inputFormat, inputType)), ge::GRAPH_SUCCESS);
37+ EXPECT_EQ(op.UpdateOutputDesc("y", ge::TensorDesc(outputShape, outputFormat, outputType)), ge::GRAPH_SUCCESS);
38+ return op.VerifyAllAttr(true);
39+}
40+ 
41+} // namespace
42+ 
43+TEST(L2NormalizeGraphInfer, CopiesInputDataType)
44+{
45+ for (auto dtype : {ge::DT_FLOAT16, ge::DT_FLOAT}) {
46+ auto holder = BuildContext(dtype);
47+ auto* context = holder.GetContext();
48+ ASSERT_NE(context, nullptr);
49+ ASSERT_EQ(context->SetOutputDataType(0, ge::DT_UNDEFINED), ge::GRAPH_SUCCESS);
50+ ASSERT_EQ(ops::InferDataTypeForL2Normalize(context), ge::GRAPH_SUCCESS);
51+ EXPECT_EQ(context->GetOutputDataType(0), dtype);
52+ EXPECT_EQ(ops::InferDataTypeForL2Normalize(context), ge::GRAPH_SUCCESS);
53+ }
54+}
55+ 
56+TEST(L2NormalizeGraphInfer, OverwritesReusedOutputDataType)
57+{
58+ auto holder = BuildContext(ge::DT_FLOAT16);
59+ auto* context = holder.GetContext();
60+ ASSERT_NE(context, nullptr);
61+ ASSERT_EQ(context->SetOutputDataType(0, ge::DT_FLOAT), ge::GRAPH_SUCCESS);
62+ ASSERT_EQ(ops::InferDataTypeForL2Normalize(context), ge::GRAPH_SUCCESS);
63+ EXPECT_EQ(context->GetOutputDataType(0), ge::DT_FLOAT16);
64+}
65+ 
66+TEST(L2NormalizeGraphInfer, RejectsNullContext)
67+{
68+ EXPECT_EQ(ops::InferDataTypeForL2Normalize(nullptr), ge::GRAPH_FAILED);
69+}
70+ 
71+TEST(L2NormalizeGraphVerify, RejectsConflictingDeclaredShape)
72+{
73+ EXPECT_EQ(RunVerify(ge::Shape({2, 3}), ge::Shape({2, 4})), ge::GRAPH_FAILED);
74+ EXPECT_EQ(RunVerify(ge::Shape({2, 3}), ge::Shape({6})), ge::GRAPH_FAILED);
75+}
76+ 
77+TEST(L2NormalizeGraphVerify, RejectsConflictingDeclaredDataType)
78+{
79+ EXPECT_EQ(RunVerify(ge::Shape({17}), ge::Shape({17}), ge::DT_FLOAT16, ge::DT_FLOAT), ge::GRAPH_FAILED);
80+}
81+ 
82+TEST(L2NormalizeGraphVerify, RejectsNonNDFormats)
83+{
84+ EXPECT_EQ(RunVerify(ge::Shape({2, 3}), ge::Shape({2, 3}), ge::DT_FLOAT, ge::DT_FLOAT, ge::FORMAT_NCHW),
85+ ge::GRAPH_FAILED);
86+ EXPECT_EQ(RunVerify(ge::Shape({2, 3}), ge::Shape({2, 3}), ge::DT_FLOAT, ge::DT_FLOAT, ge::FORMAT_ND,
87+ ge::FORMAT_FRACTAL_NZ),
88+ ge::GRAPH_FAILED);
89+}
90+ 
91+TEST(L2NormalizeGraphVerify, RejectsScalarAndRankAboveEight)
92+{
93+ EXPECT_EQ(RunVerify(ge::Shape(std::vector<int64_t>{}), ge::Shape(std::vector<int64_t>{})), ge::GRAPH_FAILED);
94+ EXPECT_EQ(RunVerify(ge::Shape({1, 1, 1, 1, 1, 1, 1, 1, 1}), ge::Shape({1, 1, 1, 1, 1, 1, 1, 1, 1})),
95+ ge::GRAPH_FAILED);
96+}
97+ 
98+TEST(L2NormalizeGraphVerify, AcceptsCompatibleAndUndeclaredOutputs)
99+{
100+ EXPECT_EQ(RunVerify(ge::Shape({2, 3}), ge::Shape({2, -1})), ge::GRAPH_SUCCESS);
101+ EXPECT_EQ(RunVerify(ge::Shape({17}), ge::Shape(std::vector<int64_t>{}), ge::DT_FLOAT16, ge::DT_UNDEFINED),
102+ ge::GRAPH_SUCCESS);
103+ EXPECT_EQ(RunVerify(ge::Shape({17}), ge::Shape({17}), ge::DT_FLOAT16, ge::DT_FLOAT16, ge::FORMAT_RESERVED,
104+ ge::FORMAT_RESERVED),
105+ ge::GRAPH_SUCCESS);
106+}
107+ 
108+TEST(L2NormalizeGraphVerify, AcceptsUnknownRankAndEmptyInputs)
109+{
110+ EXPECT_EQ(RunVerify(ge::Shape(ge::UNKNOWN_RANK), ge::Shape({2, 3})), ge::GRAPH_SUCCESS);
111+ EXPECT_EQ(RunVerify(ge::Shape({2, 3}), ge::Shape(ge::UNKNOWN_RANK)), ge::GRAPH_SUCCESS);
112+ EXPECT_EQ(RunVerify(ge::Shape({0}), ge::Shape({0})), ge::GRAPH_SUCCESS);
113+ EXPECT_EQ(RunVerify(ge::Shape({0, 0}), ge::Shape({0, 0})), ge::GRAPH_SUCCESS);
114+}
@@ -17,7 +17,8 @@ namespace {
17 17 
18constexpr const char* OP_TYPE = "L2Normalize";18constexpr const char* OP_TYPE = "L2Normalize";
19 19 
20-ge::graphStatus RunInferShape(gert::Shape& inputShape, gert::Shape& outputShape)20+ge::graphStatus RunInferShape(gert::Shape& inputShape, gert::Shape& outputShape, ge::Format inputFormat = ge::FORMAT_ND,
21+ ge::Format outputFormat = ge::FORMAT_ND)
21{22{
22 const auto* opImpl = gert::OpImplRegistry::GetInstance().GetOpImpl(OP_TYPE);23 const auto* opImpl = gert::OpImplRegistry::GetInstance().GetOpImpl(OP_TYPE);
23 if (opImpl == nullptr || opImpl->infer_shape == nullptr) {24 if (opImpl == nullptr || opImpl->infer_shape == nullptr) {
@@ -28,10 +29,17 @@ ge::graphStatus RunInferShape(gert::Shape& inputShape, gert::Shape& outputShape)
28 .SetOpType(OP_TYPE)29 .SetOpType(OP_TYPE)
29 .NodeIoNum(1, 1)30 .NodeIoNum(1, 1)
30 .IrInstanceNum({1}, {1})31 .IrInstanceNum({1}, {1})
32+ .NodeInputTd(0, ge::DT_FLOAT, inputFormat, inputFormat)
33+ .NodeOutputTd(0, ge::DT_FLOAT, outputFormat, outputFormat)
31 .InputShapes({&inputShape})34 .InputShapes({&inputShape})
32 .OutputShapes({&outputShape})35 .OutputShapes({&outputShape})
33 .Build();36 .Build();
34 auto* context = holder.GetContext<gert::InferShapeContext>();37 auto* context = holder.GetContext<gert::InferShapeContext>();
38+ if (context == nullptr || context->GetOutputShape(0) == nullptr) {
39+ return ge::GRAPH_FAILED;
40+ }
41+ // The context builder allocates output shapes; OutputShapes() does not initialize them.
42+ *context->GetOutputShape(0) = outputShape;
35 const ge::graphStatus status = opImpl->infer_shape(context);43 const ge::graphStatus status = opImpl->infer_shape(context);
36 if (status == ge::GRAPH_SUCCESS && context != nullptr && context->GetOutputShape(0) != nullptr) {44 if (status == ge::GRAPH_SUCCESS && context != nullptr && context->GetOutputShape(0) != nullptr) {
37 outputShape = *context->GetOutputShape(0);45 outputShape = *context->GetOutputShape(0);
@@ -82,6 +90,41 @@ TEST(L2NormalizeInferShape, RejectsScalarAndRankAboveEight)
82 EXPECT_EQ(RunInferShape(rankNineShape, rankNineOutput), ge::GRAPH_FAILED);90 EXPECT_EQ(RunInferShape(rankNineShape, rankNineOutput), ge::GRAPH_FAILED);
83}91}
84 92 
93+TEST(L2NormalizeInferShape, OverwritesReusedOutputShape)
94+{
95+ gert::Shape inputShape = {2, 3};
96+ gert::Shape previousOutput = {2, 4};
97+ ASSERT_EQ(RunInferShape(inputShape, previousOutput), ge::GRAPH_SUCCESS);
98+ EXPECT_EQ(ShapeToVector(previousOutput), (std::vector<int64_t>{2, 3}));
99+ gert::Shape newInput = {6};
100+ ASSERT_EQ(RunInferShape(newInput, previousOutput), ge::GRAPH_SUCCESS);
101+ EXPECT_EQ(ShapeToVector(previousOutput), (std::vector<int64_t>{6}));
102+}
103+ 
104+TEST(L2NormalizeInferShape, OverwritesPartialAndUnknownOutputShapes)
105+{
106+ gert::Shape inputShape = {2, 3};
107+ gert::Shape declaredShape = {2, -1};
108+ ASSERT_EQ(RunInferShape(inputShape, declaredShape), ge::GRAPH_SUCCESS);
109+ EXPECT_EQ(ShapeToVector(declaredShape), (std::vector<int64_t>{2, 3}));
110+ gert::Shape unknownOutput = {-2};
111+ ASSERT_EQ(RunInferShape(inputShape, unknownOutput), ge::GRAPH_SUCCESS);
112+ EXPECT_EQ(ShapeToVector(unknownOutput), (std::vector<int64_t>{2, 3}));
113+ gert::Shape unknownInput = {-2};
114+ gert::Shape declaredOutput = {2, 3};
115+ ASSERT_EQ(RunInferShape(unknownInput, declaredOutput), ge::GRAPH_SUCCESS);
116+ EXPECT_EQ(ShapeToVector(declaredOutput), (std::vector<int64_t>{-2}));
117+}
118+ 
119+TEST(L2NormalizeInferShape, RejectsNonNDInputAndOutputFormats)
120+{
121+ gert::Shape inputShape = {2, 3};
122+ gert::Shape outputShape = {};
123+ EXPECT_EQ(RunInferShape(inputShape, outputShape, ge::FORMAT_NCHW), ge::GRAPH_FAILED);
124+ EXPECT_EQ(RunInferShape(inputShape, outputShape, ge::FORMAT_ND, ge::FORMAT_FRACTAL_NZ), ge::GRAPH_FAILED);
125+ EXPECT_EQ(RunInferShape(inputShape, outputShape, ge::FORMAT_RESERVED, ge::FORMAT_RESERVED), ge::GRAPH_SUCCESS);
126+}
127+ 
85// FR-P1-011:零元素张量在 GE 动态图下以未知秩 desc(GE_UNKNOWN_RANK,dims={-2})到达128// FR-P1-011:零元素张量在 GE 动态图下以未知秩 desc(GE_UNKNOWN_RANK,dims={-2})到达
86// 推导(L0_empty_027/034:data_shape=(0,)/(0,0)、desc/ori_shape=[-2]);未知秩须无条件129// 推导(L0_empty_027/034:data_shape=(0,)/(0,0)、desc/ori_shape=[-2]);未知秩须无条件
87// 透传(不逐维校验),已知秩零元素 shape 同样恒等复制。130// 透传(不逐维校验),已知秩零元素 shape 同样恒等复制。
@@ -61,10 +61,10 @@
61| accum | 输入 | 动量累积量,必须为可写Tensor;shape、数据类型和数据格式与`parameters`一致。 | FLOAT、FLOAT16、BFLOAT16 | NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D |61| accum | 输入 | 动量累积量,必须为可写Tensor;shape、数据类型和数据格式与`parameters`一致。 | FLOAT、FLOAT16、BFLOAT16 | NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D |
62| momentum | 输入 | 动量因子,元素个数必须为1,数据类型与`parameters`一致。 | FLOAT、FLOAT16、BFLOAT16 | ND |62| momentum | 输入 | 动量因子,元素个数必须为1,数据类型与`parameters`一致。 | FLOAT、FLOAT16、BFLOAT16 | ND |
63| stat | 输入 | 逐元素首步标记,取值1表示不施加阻尼;必须为可写Tensor,shape、数据类型和数据格式与`parameters`一致。 | FLOAT、FLOAT16、BFLOAT16 | NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D |63| stat | 输入 | 逐元素首步标记,取值1表示不施加阻尼;必须为可写Tensor,shape、数据类型和数据格式与`parameters`一致。 | FLOAT、FLOAT16、BFLOAT16 | NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D |
64+| parameters | 输出 | 更新后的权重,与输入`parameters`共享内存,shape、数据类型和数据格式保持一致。 | FLOAT、FLOAT16、BFLOAT16 | NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D |
64| dampening | 可选属性 | 动量阻尼系数,默认值为0.0;当`nesterov=true`时必须为0。 | FLOAT | - |65| dampening | 可选属性 | 动量阻尼系数,默认值为0.0;当`nesterov=true`时必须为0。 | FLOAT | - |
65| weight_decay | 可选属性 | 权重衰减系数,默认值为0.0,取值必须大于或等于0。 | FLOAT | - |66| weight_decay | 可选属性 | 权重衰减系数,默认值为0.0,取值必须大于或等于0。 | FLOAT | - |
66| nesterov | 可选属性 | 是否启用Nesterov动量,默认值为false。 | BOOL | - |67| nesterov | 可选属性 | 是否启用Nesterov动量,默认值为false。 | BOOL | - |
67-| parameters | 输出 | 更新后的权重,与输入`parameters`共享内存,shape、数据类型和数据格式保持一致。 | FLOAT、FLOAT16、BFLOAT16 | NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D |
68 68 
69### 产品差异说明69### 产品差异说明
70 70 
@@ -78,6 +78,8 @@
78 78 
79- 图上仅声明一个`parameters`输出,但`parameters`、`accum`和`stat`均可能被原地更新,79- 图上仅声明一个`parameters`输出,但`parameters`、`accum`和`stat`均可能被原地更新,
80 调用方必须将这三个输入作为可写Tensor。80 调用方必须将这三个输入作为可写Tensor。
81+- <term>Ascend 950PR&950DT系列产品</term>使用FLOAT进行中间计算,FLOAT16、BFLOAT16结果按就近偶数舍入转换回输入数据类型。
82+- `momentum=0`仅阻止`accum`和`stat`回写,不跳过权重更新中的动量乘法;当`accum`含Inf时,`0×Inf`产生的NaN会传播到权重结果。
81 83 
82## 调用说明84## 调用说明
83 85 
@@ -33,6 +33,7 @@
33#include <vector>33#include <vector>
34#include <string>34#include <string>
35#include <map>35#include <map>
36+#include <cmath>
36#include "assert.h"37#include "assert.h"
37#include "graph.h"38#include "graph.h"
38#include "types.h"39#include "types.h"
@@ -53,7 +54,7 @@ using std::string;
53using std::vector;54using std::vector;
54#define ADD_INPUT(intputIndex, intputName, intputDtype, inputShape, inputValue) \55#define ADD_INPUT(intputIndex, intputName, intputDtype, inputShape, inputValue) \
55 vector<int64_t> placeholder##intputIndex##_shape = inputShape; \56 vector<int64_t> placeholder##intputIndex##_shape = inputShape; \
56- auto placeholder##intputIndex = op::Data("placeholder" + intputIndex).set_attr_index(0); \57+ auto placeholder##intputIndex = op::Data("placeholder" + intputIndex).set_attr_index(intputIndex - 1); \
57 TensorDesc placeholder##intputIndex##_desc = TensorDesc(ge::Shape(placeholder##intputIndex##_shape), FORMAT_ND, \58 TensorDesc placeholder##intputIndex##_desc = TensorDesc(ge::Shape(placeholder##intputIndex##_shape), FORMAT_ND, \
58 intputDtype); \59 intputDtype); \
59 placeholder##intputIndex##_desc.SetPlacement(ge::kPlacementHost); \60 placeholder##intputIndex##_desc.SetPlacement(ge::kPlacementHost); \
@@ -155,11 +156,15 @@ int32_t GenOnesDataFloat32(vector<int64_t> shapes, Tensor& input_tensor, TensorD
155 uint32_t byteSizeFloat32 = 4;156 uint32_t byteSizeFloat32 = 4;
156 uint32_t data_len = size * byteSizeFloat32;157 uint32_t data_len = size * byteSizeFloat32;
157 float* pData = new (std::nothrow) float[size];158 float* pData = new (std::nothrow) float[size];
159+ if (pData == nullptr) {
160+ return FAILED;
161+ }
158 162 
159 for (size_t i = 0; i < size; ++i) {163 for (size_t i = 0; i < size; ++i) {
160 *(pData + i) = value;164 *(pData + i) = value;
161 }165 }
162 input_tensor = Tensor(input_tensor_desc, (uint8_t*)pData, data_len);166 input_tensor = Tensor(input_tensor_desc, (uint8_t*)pData, data_len);
167+ delete[] pData;
163 return SUCCESS;168 return SUCCESS;
164}169}
165 170 
@@ -173,19 +178,26 @@ int32_t GenOnesData(vector<int64_t> shapes, Tensor& input_tensor, TensorDesc& in
173 }178 }
174 uint32_t data_len = size * GetDataTypeSize(data_type);179 uint32_t data_len = size * GetDataTypeSize(data_type);
175 int32_t* pData = new (std::nothrow) int32_t[data_len];180 int32_t* pData = new (std::nothrow) int32_t[data_len];
181+ if (pData == nullptr) {
182+ return FAILED;
183+ }
176 for (size_t i = 0; i < size; ++i) {184 for (size_t i = 0; i < size; ++i) {
177 *(pData + i) = value;185 *(pData + i) = value;
178 }186 }
179 input_tensor = Tensor(input_tensor_desc, reinterpret_cast<uint8_t*>(pData), data_len);187 input_tensor = Tensor(input_tensor_desc, reinterpret_cast<uint8_t*>(pData), data_len);
188+ delete[] pData;
180 return SUCCESS;189 return SUCCESS;
181}190}
182 191 
183int32_t WriteDataToFile(string bin_file, uint64_t data_size, uint8_t* inputData)192int32_t WriteDataToFile(string bin_file, uint64_t data_size, uint8_t* inputData)
184{193{
185- FILE* fp = fopen(bin_file.c_str(), "w");194+ FILE* fp = fopen(bin_file.c_str(), "wb");
186- fwrite(inputData, sizeof(uint8_t), data_size, fp);195+ if (fp == nullptr) {
187- fclose(fp);196+ return FAILED;
188- return SUCCESS;197+ }
198+ const size_t written = fwrite(inputData, sizeof(uint8_t), data_size, fp);
199+ const int closeStatus = fclose(fp);
200+ return written == data_size && closeStatus == 0 ? SUCCESS : FAILED;
189}201}
190 202 
191int CreateOppInGraph(DataType inDtype, float momentumValue, std::vector<ge::Tensor>& input,203int CreateOppInGraph(DataType inDtype, float momentumValue, std::vector<ge::Tensor>& input,
@@ -210,9 +222,41 @@ int CreateOppInGraph(DataType inDtype, float momentumValue, std::vector<ge::Tens
210 ADD_INPUT_ATTR(nesterov, false);222 ADD_INPUT_ATTR(nesterov, false);
211 223 
212 outputs.push_back(sgd1);224 outputs.push_back(sgd1);
225+ // Expose the two input slots after SGD has completed its in-place writes.
226+ auto accumOut = op::Identity("accum_out").set_input_x(placeholder4);
227+ auto statOut = op::Identity("stat_out").set_input_x(placeholder6);
228+ accumOut.AddControlInput(sgd1);
229+ statOut.AddControlInput(sgd1);
230+ outputs.push_back(accumOut);
231+ outputs.push_back(statOut);
213 return SUCCESS;232 return SUCCESS;
214}233}
215 234 
235+bool CheckOutputs(const std::vector<ge::Tensor>& output, float momentum)
236+{
237+ if (output.size() != 3) {
238+ return false;
239+ }
240+ const double accumT = 3.5 * static_cast<double>(momentum) + 0.5;
241+ const double expected[] = {1.0 - accumT * 0.1, momentum != 0.0f ? accumT : 3.5, momentum != 0.0f ? 0.0 : 1.0};
242+ for (size_t index = 0; index < output.size(); ++index) {
243+ const auto& tensor = output[index];
244+ if (tensor.GetTensorDesc().GetDataType() != DT_FLOAT ||
245+ tensor.GetTensorDesc().GetShape().GetDims() != std::vector<int64_t>({2, 30}) ||
246+ tensor.GetSize() != 60 * sizeof(float) || tensor.GetData() == nullptr) {
247+ return false;
248+ }
249+ const float* values = reinterpret_cast<const float*>(tensor.GetData());
250+ for (size_t i = 0; i < 60; ++i) {
251+ if (!std::isfinite(values[i]) || std::abs(values[i] - expected[index]) > 1e-6) {
252+ printf("output[%zu][%zu] = %f, expected %f\n", index, i, values[i], expected[index]);
253+ return false;
254+ }
255+ }
256+ }
257+ return true;
258+}
259+ 
216int main(int argc, char* argv[])260int main(int argc, char* argv[])
217{261{
218 const char* graph_name = "tc_ge_irrun_test";262 const char* graph_name = "tc_ge_irrun_test";
@@ -246,6 +290,7 @@ int main(int argc, char* argv[])
246 ret = CreateOppInGraph(inDtype, momentumValue, input, inputs, outputs, graph);290 ret = CreateOppInGraph(inDtype, momentumValue, input, inputs, outputs, graph);
247 if (ret != SUCCESS) {291 if (ret != SUCCESS) {
248 printf("%s - ERROR - [XIR]: Create ir session using build options failed\n", GetTime().c_str());292 printf("%s - ERROR - [XIR]: Create ir session using build options failed\n", GetTime().c_str());
293+ GEFinalize();
249 return FAILED;294 return FAILED;
250 }295 }
251 296 
@@ -257,10 +302,11 @@ int main(int argc, char* argv[])
257 302 
258 };303 };
259 printf("%s - INFO - [XIR]: Start to create ir session using build options\n", GetTime().c_str());304 printf("%s - INFO - [XIR]: Start to create ir session using build options\n", GetTime().c_str());
260- ge::Session* session = new Session(build_options);305+ ge::Session* session = new (std::nothrow) Session(build_options);
261 306 
262 if (session == nullptr) {307 if (session == nullptr) {
263 printf("%s - ERROR - [XIR]: Create ir session using build options failed\n", GetTime().c_str());308 printf("%s - ERROR - [XIR]: Create ir session using build options failed\n", GetTime().c_str());
309+ GEFinalize();
264 return FAILED;310 return FAILED;
265 }311 }
266 printf("%s - INFO - [XIR]: Create ir session using build options success\n", GetTime().c_str());312 printf("%s - INFO - [XIR]: Create ir session using build options success\n", GetTime().c_str());
@@ -271,6 +317,12 @@ int main(int argc, char* argv[])
271 };317 };
272 uint32_t graph_id = 0;318 uint32_t graph_id = 0;
273 ret = session->AddGraph(graph_id, graph, graph_options);319 ret = session->AddGraph(graph_id, graph, graph_options);
320+ if (ret != SUCCESS) {
321+ printf("%s - ERROR - [XIR]: AddGraph failed\n", GetTime().c_str());
322+ delete session;
323+ GEFinalize();
324+ return FAILED;
325+ }
274 326 
275 printf("%s - INFO - [XIR]: Session add ir compute graph to ir session success\n", GetTime().c_str());327 printf("%s - INFO - [XIR]: Session add ir compute graph to ir session success\n", GetTime().c_str());
276 printf("%s - INFO - [XIR]: dump graph to txt\n", GetTime().c_str());328 printf("%s - INFO - [XIR]: dump graph to txt\n", GetTime().c_str());
@@ -286,6 +338,12 @@ int main(int argc, char* argv[])
286 return FAILED;338 return FAILED;
287 }339 }
288 printf("%s - INFO - [XIR]: Session run ir compute graph success\n", GetTime().c_str());340 printf("%s - INFO - [XIR]: Session run ir compute graph success\n", GetTime().c_str());
341+ if (!CheckOutputs(output, momentumValue)) {
342+ printf("SGD result verification failed\n");
343+ delete session;
344+ GEFinalize();
345+ return FAILED;
346+ }
289 347 
290 int input_num = input.size();348 int input_num = input.size();
291 for (int i = 0; i < input_num; i++) {349 for (int i = 0; i < input_num; i++) {
@@ -295,7 +353,11 @@ int main(int argc, char* argv[])
295 int64_t input_shape = input[i].GetTensorDesc().GetShape().GetShapeSize();353 int64_t input_shape = input[i].GetTensorDesc().GetShape().GetShapeSize();
296 std::cout << "this is " << i << "th input, input shape size =" << input_shape << std::endl;354 std::cout << "this is " << i << "th input, input shape size =" << input_shape << std::endl;
297 uint32_t data_size = input_shape * GetDataTypeSize(input[i].GetTensorDesc().GetDataType());355 uint32_t data_size = input_shape * GetDataTypeSize(input[i].GetTensorDesc().GetDataType());
298- WriteDataToFile((const char*)input_file.c_str(), data_size, input_data_i);356+ if (WriteDataToFile(input_file, data_size, input_data_i) != SUCCESS) {
357+ delete session;
358+ GEFinalize();
359+ return FAILED;
360+ }
299 }361 }
300 362 
301 int output_num = output.size();363 int output_num = output.size();
@@ -306,7 +368,11 @@ int main(int argc, char* argv[])
306 int64_t output_shape = output[i].GetTensorDesc().GetShape().GetShapeSize();368 int64_t output_shape = output[i].GetTensorDesc().GetShape().GetShapeSize();
307 std::cout << "this is " << i << "th output, output shape size =" << output_shape << std::endl;369 std::cout << "this is " << i << "th output, output shape size =" << output_shape << std::endl;
308 uint32_t data_size = output_shape * GetDataTypeSize(output[i].GetTensorDesc().GetDataType());370 uint32_t data_size = output_shape * GetDataTypeSize(output[i].GetTensorDesc().GetDataType());
309- WriteDataToFile((const char*)output_file.c_str(), data_size, output_data_i);371+ if (WriteDataToFile(output_file, data_size, output_data_i) != SUCCESS) {
372+ delete session;
373+ GEFinalize();
374+ return FAILED;
375+ }
310 float* resultData = (float*)output_data_i;376 float* resultData = (float*)output_data_i;
311 for (int64_t j = 0; j < output_shape; j++) {377 for (int64_t j = 0; j < output_shape; j++) {
312 LOG_PRINT("result[%ld] is: %f\n", j, resultData[j]);378 LOG_PRINT("result[%ld] is: %f\n", j, resultData[j]);
@@ -319,16 +385,6 @@ int main(int argc, char* argv[])
319 ge::AscendString warning_msg = ge::GEGetWarningMsgV2();385 ge::AscendString warning_msg = ge::GEGetWarningMsgV2();
320 std::string warning_str(warning_msg.GetString());386 std::string warning_str(warning_msg.GetString());
321 std::cout << "Warning message: " << warning_str << std::endl;387 std::cout << "Warning message: " << warning_str << std::endl;
322- // 在 GEFinalize() 之前 delete session,与上游样例
323- // /workspace/ops-nn/activation/celu/examples/test_geir_celu.cpp:241-243 对齐。
324- // 本文件原先只在 RunGraph 失败分支删 session、成功路径漏删(session 泄漏)。
325- //
326- // ⚠ 澄清:补这两行【不能】消除进程收尾时的 `corrupted size vs. prev_size` 核心转储。
327- // 实测该崩溃在 GEFinalize() 内部发生,且用**未经改动的上游样例 test_geir_celu
328- // 打内置算子 Celu** 可等价复现(同样的报错、同样的位置、同样 exit 134),
329- // 与本算子无关,属环境/GE 侧收尾问题。
330- // 崩溃点在全部结果打印完毕之后,不影响计算结果的正确性;但也【不得】因此把它
331- // 当成"跑通了"——排障时先跑一遍 celu 对照,再判断是不是自己的问题。
332 delete session;388 delete session;
333 session = nullptr;389 session = nullptr;
334 printf("%s - INFO - [XIR]: Start to finalize ir graph session\n", GetTime().c_str());390 printf("%s - INFO - [XIR]: Start to finalize ir graph session\n", GetTime().c_str());
@@ -76,6 +76,10 @@ namespace ge {
76 * (the TBE implementation declares reuse=('accum', 'parameters', 'stat')).76 * (the TBE implementation declares reuse=('accum', 'parameters', 'stat')).
77 *@li When "momentum" is 0 (including -0.0), "accum" and "stat" are NOT written at all77 *@li When "momentum" is 0 (including -0.0), "accum" and "stat" are NOT written at all
78 * and keep their input values bit-for-bit; "parameters" is still updated as usual.78 * and keep their input values bit-for-bit; "parameters" is still updated as usual.
79+ * The momentum multiplication is still evaluated: 0 * inf can propagate NaN
80+ * into "parameters" even when the writeback mask is zero.
81+ *@li On <term>Ascend 950PR&950DT系列产品</term>, intermediate arithmetic uses float32.
82+ * Float16 and bfloat16 results use round-to-nearest, ties-to-even conversion.
79 *@li The rank of "parameters" must be in the range [1, 8]; rank-0 (scalar) is rejected.83 *@li The rank of "parameters" must be in the range [1, 8]; rank-0 (scalar) is rejected.
80 * Empty tensors (any axis being 0) are rejected as well.84 * Empty tensors (any axis being 0) are rejected as well.
81 *85 *
@@ -147,8 +147,7 @@ ge::graphStatus SgdRegbaseTiling::CheckShapeAndType()
147 OP_CHECK_NULL_WITH_CONTEXT(tilingContext_, inputParamDesc);147 OP_CHECK_NULL_WITH_CONTEXT(tilingContext_, inputParamDesc);
148 auto inputDtype = inputParamDesc->GetDataType();148 auto inputDtype = inputParamDesc->GetDataType();
149 149 
150- // ascend910b 只校验了 parameters 的 rank,其余 5 个输入的同形 / 同 dtype 完全未校验150+ // 检查全部输入的shape与dtype;不能仅依赖parameters的rank校验。
151- // (canndev CheckSgdDimension 仅看 parameters)。ascend950 侧补齐,见 01 §6.5。
152 // 内层 CheckRank 已用 OP_LOGE_FOR_INVALID_SHAPEDIM 结构化上报,此处只作调用链留痕,151 // 内层 CheckRank 已用 OP_LOGE_FOR_INVALID_SHAPEDIM 结构化上报,此处只作调用链留痕,
153 // 不重复打 ERROR(R6 要求的是"校验必须走结构化宏",不是"每层都再报一次")。152 // 不重复打 ERROR(R6 要求的是"校验必须走结构化宏",不是"每层都再报一次")。
154 OP_CHECK_IF(CheckRank(inputStorageShape) != ge::GRAPH_SUCCESS, OP_LOGD(tilingContext_, "rank check failed"),153 OP_CHECK_IF(CheckRank(inputStorageShape) != ge::GRAPH_SUCCESS, OP_LOGD(tilingContext_, "rank check failed"),
@@ -17,11 +17,11 @@
17 * FRACTAL_Z_3D 五种,本算子只做 ND。依据是 ops-nn 仓内 arch35 全族 optim 算子17 * FRACTAL_Z_3D 五种,本算子只做 ND。依据是 ops-nn 仓内 arch35 全族 optim 算子
18 * (apply_momentum / apply_ftrl / apply_adam_w_v2 / apply_adamax /18 * (apply_momentum / apply_ftrl / apply_adam_w_v2 / apply_adamax /
19 * apply_centered_rms_prop)的 def.cpp 一律只声明 ge::FORMAT_ND,无一例声明私有19 * apply_centered_rms_prop)的 def.cpp 一律只声明 ge::FORMAT_ND,无一例声明私有
20- * format —— 跟随本仓 arch35 既定约定,非自行设计。已于 CP1 批准。20+ * format。当前支持范围及产品差异见算子 README。
21 *21 *
22 * R2(ascend950 不碰 ascend910b)天然满足:ops-nn 仓内不存在 SGD 算子(optim/fused_sgd 是语义22 * R2(ascend950 不碰 ascend910b)天然满足:ops-nn 仓内不存在 SGD 算子(optim/fused_sgd 是语义
23 * 不同的另一个算子,且无 arch35 目录),本算子为净新增,只 AddConfig("ascend950"),23 * 不同的另一个算子,且无 arch35 目录),本算子为净新增,只 AddConfig("ascend950"),
24- * 不触碰任何 ascend910b 配置(先例:apply_momentum、apply_adamax 亦只有 ascend950 config)。24+ * 不触碰任何 ascend910b 配置;apply_momentum 另有 ascend350 等配置,不代表本算子的注册范围。
25 *25 *
26 * accum / stat 【不声明为 Output】—— 与 ascend910b 形态一致(canndev proto26 * accum / stat 【不声明为 Output】—— 与 ascend910b 形态一致(canndev proto
27 * nn_training_ops.h:1431 亦只有一个 OUTPUT),两者靠覆写输入 GM 原地回写。27 * nn_training_ops.h:1431 亦只有一个 OUTPUT),两者靠覆写输入 GM 原地回写。
@@ -18,9 +18,8 @@
18 * - UNKNOWN_RANK(-2) 透传 —— GE 下 dims = {-2}(DimNum == 1),不触发 rank 拒绝;18 * - UNKNOWN_RANK(-2) 透传 —— GE 下 dims = {-2}(DimNum == 1),不触发 rank 拒绝;
19 * - nesterov == true 时 dampening 必须为 0;weight_decay >= 0。19 * - nesterov == true 时 dampening 必须为 0;weight_decay >= 0。
20 *20 *
21- * 注:ascend910b 把这两条属性校验放在 InferShape 里而非 Verify(其 SGDVerify 只调21+ * InferShape与op_host/arch35/sgd_tiling.cpp分别校验属性,保证图推导和
22- * CheckSgdDimension),属职责错位(见 01 §6.5)。ascend950 侧在 InferShape 与 Tiling22+ * 独立Tiling入口均拒绝非法属性。
23- * 两个独立入口都做,保证任一入口进来都拦得住。
24 */23 */
25#include "log/log.h"24#include "log/log.h"
26#include "register/op_impl_registry.h"25#include "register/op_impl_registry.h"
@@ -60,7 +59,7 @@ static ge::graphStatus InferShapeForSgd(gert::InferShapeContext* context)
60 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "dampening", std::to_string(dampeningVal).c_str(),59 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "dampening", std::to_string(dampeningVal).c_str(),
61 "attr dampening must be 0 when attr nesterov is true"),60 "attr dampening must be 0 when attr nesterov is true"),
62 return ge::GRAPH_FAILED);61 return ge::GRAPH_FAILED);
63- OP_CHECK_IF(weightDecayVal < 0.0f,62+ OP_CHECK_IF(!(weightDecayVal >= 0.0f),
64 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "weight_decay",63 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "weight_decay",
65 std::to_string(weightDecayVal).c_str(),64 std::to_string(weightDecayVal).c_str(),
66 "attr weight_decay must be more than or equal to 0"),65 "attr weight_decay must be more than or equal to 0"),
@@ -31,9 +31,10 @@ parenthesization: arch35 ``sgd_dag.h`` computes
31slot constraint. Cross-check must reproduce that actual device operation31slot constraint. Cross-check must reproduce that actual device operation
32order, then cast all three writeback values to the NPU input dtype.32order, then cast all three writeback values to the NPU input dtype.
33 33 
34-Promote contract: half/bfloat16 inputs may be lifted to float32 for CPU support;34+The CPU oracle evaluates intermediate arithmetic in float64 to avoid sharing
35-float32 and float64 inputs are never narrowed. TTK promotes both inputs and35+float32 overflow with the device. The independent third-party composition keeps
36-``output_dtypes`` before invoking this Spec when ``cross_check`` is active.36+the device's float32 operation order. Output conversion still follows TTK's
37+requested dtypes, and masked accum/stat writeback preserves the original bits.
37"""38"""
38 39 
39import numpy as np40import numpy as np
@@ -95,13 +96,6 @@ def _numpy_to_torch(array):
95 return torch.from_numpy(value)96 return torch.from_numpy(value)
96 97 
97 98 
98-def _f32_floor(tensor):
99- """Lift only fp16/bf16; preserve fp32/fp64 supplied by Promote."""
100- if tensor.dtype in (torch.float16, torch.bfloat16):
101- return tensor.to(torch.float32)
102- return tensor
103- 
104- 
105def _scalar_value(tensor):99def _scalar_value(tensor):
106 """Read an already-quantized scalar without rebuilding it from a literal."""100 """Read an already-quantized scalar without rebuilding it from a literal."""
107 return float(tensor.reshape(-1)[0].item())101 return float(tensor.reshape(-1)[0].item())
@@ -114,21 +108,22 @@ def _compute(
114 accum,108 accum,
115 momentum,109 momentum,
116 stat,110 stat,
117- dampening=0.0,111+ dampening=None,
118- weight_decay=0.0,112+ weight_decay=None,
119- nesterov=False,113+ nesterov=None,
120 **kwargs,114 **kwargs,
121):115):
122- """Torch competitor composition shared by TestSpec and legacy entry.116+ """CPU reference shared by TestSpec and the legacy entry.
123 117 
124 Inputs and outputs are Torch tensors. This is the high-precision CPU118 Inputs and outputs are Torch tensors. This is the high-precision CPU
125 true-value path, so it keeps the legacy expanded Nesterov formula; the119 true-value path, so it keeps the legacy expanded Nesterov formula; the
126 independent ``_SgdCompose`` below follows the grouped arch35 DAG order.120 independent ``_SgdCompose`` below follows the grouped arch35 DAG order.
127 """121 """
128- if kwargs.get("golden_mode") == "Promote":122+ # Keep the CPU oracle independent from the device's float32 intermediates.
129- p, g, a, s = parameters, gradient, accum, stat123+ # Products may exceed float32 even when the final mathematical result fits.
130- else:124+ p, g, a, s = (
131- p, g, a, s = map(_f32_floor, (parameters, gradient, accum, stat))125+ value.to(torch.float64) for value in (parameters, gradient, accum, stat)
126+ )
132 learning_rate_value = _scalar_value(learning_rate)127 learning_rate_value = _scalar_value(learning_rate)
133 momentum_value = _scalar_value(momentum)128 momentum_value = _scalar_value(momentum)
134 129 
@@ -238,9 +233,9 @@ def _kernel_golden(
238 accum,233 accum,
239 momentum,234 momentum,
240 stat,235 stat,
241- dampening=0.0,236+ dampening=None,
242- weight_decay=0.0,237+ weight_decay=None,
243- nesterov=False,238+ nesterov=None,
244 **kwargs,239 **kwargs,
245):240):
246 """Kernel/GEIR container shell: NumPy in, NumPy out."""241 """Kernel/GEIR container shell: NumPy in, NumPy out."""
@@ -276,9 +271,9 @@ def sgd_golden(
276 accum,271 accum,
277 momentum,272 momentum,
278 stat,273 stat,
279- dampening=0.0,274+ dampening=None,
280- weight_decay=0.0,275+ weight_decay=None,
281- nesterov=False,276+ nesterov=None,
282 **kwargs,277 **kwargs,
283):278):
284 """Compatibility ``__golden__`` entry; delegates to the shared core."""279 """Compatibility ``__golden__`` entry; delegates to the shared core."""
@@ -301,9 +296,9 @@ class _SgdCompose:
301 296 
302 def __init__(297 def __init__(
303 self,298 self,
304- dampening=0.0,299+ dampening=None,
305- weight_decay=0.0,300+ weight_decay=None,
306- nesterov=False,301+ nesterov=None,
307 **kwargs,302 **kwargs,
308 ):303 ):
309 attrs = {304 attrs = {
@@ -397,9 +392,9 @@ class SgdKernelSpec:
397 accum,392 accum,
398 momentum,393 momentum,
399 stat,394 stat,
400- dampening=0.0,395+ dampening=None,
401- weight_decay=0.0,396+ weight_decay=None,
402- nesterov=False,397+ nesterov=None,
403 **kwargs,398 **kwargs,
404 ):399 ):
405 return _kernel_golden(400 return _kernel_golden(
@@ -421,5 +416,4 @@ class SgdKernelSpec:
421 416 
422# 【不存在】ACLNN 通路:optim/sgd/CMakeLists.txt 使用 ACLNNTYPE417# 【不存在】ACLNN 通路:optim/sgd/CMakeLists.txt 使用 ACLNNTYPE
423# aclnn_exclude,且算子目录没有 op_api/aclnn_sgd 实现;因此 __spec__ 不注册 aclnnSgd。418# aclnn_exclude,且算子目录没有 op_api/aclnn_sgd 实现;因此 __spec__ 不注册 aclnnSgd。
424-# 【不存在】e2e 通路:需求明确不提供本算子的 torch_npu 绑定,也没有可调用的419+# 本目录未交付 torch_npu 绑定或可调用的 aclnnSgd 实现,因此不注册 TorchSpec。
425-# aclnnSgd 底层接口;因此不保留 TorchSpec 空壳。
@@ -25,6 +25,7 @@
25 25 
26#include <gtest/gtest.h> // NOLINT26#include <gtest/gtest.h> // NOLINT
27#include <iostream>27#include <iostream>
28+#include <limits>
28#include <vector>29#include <vector>
29#include "infershape_test_util.h" // NOLINT30#include "infershape_test_util.h" // NOLINT
30#include "ut_op_common.h"31#include "ut_op_common.h"
@@ -139,3 +140,28 @@ TEST_F(SGD, sgd_infershape_negative_weight_decay_rejected)
139 gert::StorageShape scalarShape = {{1}, {1}};140 gert::StorageShape scalarShape = {{1}, {1}};
140 ASSERT_EQ(RunSgdInferShape(bigShape, scalarShape, 0.0f, -0.01f, false), ge::GRAPH_FAILED);141 ASSERT_EQ(RunSgdInferShape(bigShape, scalarShape, 0.0f, -0.01f, false), ge::GRAPH_FAILED);
141}142}
143+ 
144+TEST_F(SGD, sgd_infershape_nan_weight_decay_rejected)
145+{
146+ gert::StorageShape bigShape = {{96, 256}, {96, 256}};
147+ gert::StorageShape scalarShape = {{1}, {1}};
148+ ASSERT_EQ(RunSgdInferShape(bigShape, scalarShape, 0.0f, std::numeric_limits<float>::quiet_NaN(), false),
149+ ge::GRAPH_FAILED);
150+}
151+ 
152+TEST_F(SGD, sgd_infershape_negative_infinite_weight_decay_rejected)
153+{
154+ gert::StorageShape bigShape = {{96, 256}, {96, 256}};
155+ gert::StorageShape scalarShape = {{1}, {1}};
156+ ASSERT_EQ(RunSgdInferShape(bigShape, scalarShape, 0.0f, -std::numeric_limits<float>::infinity(), false),
157+ ge::GRAPH_FAILED);
158+}
159+ 
160+TEST_F(SGD, sgd_infershape_nonnegative_weight_decay_boundaries)
161+{
162+ gert::StorageShape bigShape = {{96, 256}, {96, 256}};
163+ gert::StorageShape scalarShape = {{1}, {1}};
164+ for (float weightDecay : {-0.0f, 0.5f, std::numeric_limits<float>::infinity()}) {
165+ EXPECT_EQ(RunSgdInferShape(bigShape, scalarShape, 0.0f, weightDecay, false), ge::GRAPH_SUCCESS);
166+ }
167+}
@@ -34,8 +34,8 @@
34| --- | --- | --- | --- | --- |34| --- | --- | --- | --- | --- |
35| boxes | 输入 | 目标检测框的绝对坐标,对应公式中的`boxes`。 | FLOAT、FLOAT16 | ND |35| boxes | 输入 | 目标检测框的绝对坐标,对应公式中的`boxes`。 | FLOAT、FLOAT16 | ND |
36| shape_hw | 输入 | 每个batch的图像尺寸,shape为`[batch, 3]`;前两个元素依次为高度和宽度,第3个元素为保留位。 | INT32 | ND |36| shape_hw | 输入 | 每个batch的图像尺寸,shape为`[batch, 3]`;前两个元素依次为高度和宽度,第3个元素为保留位。 | INT32 | ND |
37-| reversed_box | 可选属性 | 指定`boxes`的坐标轴位置,默认值为false。 | BOOL | - |
38| y | 输出 | 换算后的相对坐标,shape和数据类型与`boxes`一致。 | FLOAT、FLOAT16 | ND |37| y | 输出 | 换算后的相对坐标,shape和数据类型与`boxes`一致。 | FLOAT、FLOAT16 | ND |
38+| reversed_box | 可选属性 | 指定`boxes`的坐标轴位置,默认值为false。 | BOOL | - |
39 39 
40### 产品差异说明40### 产品差异说明
41 41 
@@ -58,4 +58,4 @@
58| 调用方式 | 调用样例 | 说明 |58| 调用方式 | 调用样例 | 说明 |
59| --- | --- | --- |59| --- | --- | --- |
60| GE图模式 | [test_geir_normalize_bbox.cpp](examples/arch35/test_geir_normalize_bbox.cpp) | 通过[NormalizeBBox算子IR](op_graph/normalize_bbox_proto.h)构图并调用算子。 |60| GE图模式 | [test_geir_normalize_bbox.cpp](examples/arch35/test_geir_normalize_bbox.cpp) | 通过[NormalizeBBox算子IR](op_graph/normalize_bbox_proto.h)构图并调用算子。 |
61-| TensorFlow图导入 | - | 通过[TensorFlow适配定义](framework/normalize_bbox_tf_plugin.cpp)将TensorFlow的`NormalizeBBox`节点解析为GE算子。 |61+| TensorFlow图导入 | - | 通过[TensorFlow适配定义](framework/normalize_bbox_tf_plugin.cpp)接收作用域融合生成的NormalizeBBox算子,`reversed_box`保持默认值false。 |
@@ -15,6 +15,7 @@
15#include <vector>15#include <vector>
16#include <string>16#include <string>
17#include <map>17#include <map>
18+#include <cmath>
18#include "assert.h"19#include "assert.h"
19 20 
20#include "graph.h"21#include "graph.h"
@@ -25,7 +26,7 @@
25#include "ge_api.h"26#include "ge_api.h"
26#include "array_ops.h"27#include "array_ops.h"
27#include "ge_ir_build.h"28#include "ge_ir_build.h"
28-#include "nn_detect_ops.h"29+#include "../../op_graph/normalize_bbox_proto.h"
29 30 
30#define FAILED -131#define FAILED -1
31#define SUCCESS 032#define SUCCESS 0
@@ -146,6 +147,7 @@ int main(int argc, char* argv[])
146 ret = GenFloatData(boxesShape, tensor_boxes, boxesDesc, boxesDtype);147 ret = GenFloatData(boxesShape, tensor_boxes, boxesDesc, boxesDtype);
147 if (ret != SUCCESS) {148 if (ret != SUCCESS) {
148 printf("GenFloatData boxes failed\n");149 printf("GenFloatData boxes failed\n");
150+ GEFinalize();
149 return FAILED;151 return FAILED;
150 }152 }
151 boxesData.update_input_desc_x(boxesDesc);153 boxesData.update_input_desc_x(boxesDesc);
@@ -163,6 +165,7 @@ int main(int argc, char* argv[])
163 ret = GenInt32Data(shapeHwShape, tensor_shape_hw, shapeHwDesc);165 ret = GenInt32Data(shapeHwShape, tensor_shape_hw, shapeHwDesc);
164 if (ret != SUCCESS) {166 if (ret != SUCCESS) {
165 printf("GenInt32Data shape_hw failed\n");167 printf("GenInt32Data shape_hw failed\n");
168+ GEFinalize();
166 return FAILED;169 return FAILED;
167 }170 }
168 shapeHwData.update_input_desc_x(shapeHwDesc);171 shapeHwData.update_input_desc_x(shapeHwDesc);
@@ -182,9 +185,10 @@ int main(int argc, char* argv[])
182 185 
183 std::map<AscendString, AscendString> build_options = {};186 std::map<AscendString, AscendString> build_options = {};
184 printf("%s - INFO - [XIR]: Create session\n", GetTime().c_str());187 printf("%s - INFO - [XIR]: Create session\n", GetTime().c_str());
185- ge::Session* session = new Session(build_options);188+ ge::Session* session = new (std::nothrow) Session(build_options);
186 if (session == nullptr) {189 if (session == nullptr) {
187 printf("Create session failed\n");190 printf("Create session failed\n");
191+ GEFinalize();
188 return FAILED;192 return FAILED;
189 }193 }
190 194 
@@ -212,28 +216,41 @@ int main(int argc, char* argv[])
212 }216 }
213 printf("%s - INFO - [XIR]: RunGraph success\n", GetTime().c_str());217 printf("%s - INFO - [XIR]: RunGraph success\n", GetTime().c_str());
214 218 
215- // verify output219+ bool valid = output.size() == 1;
216- int output_num = output.size();220+ if (valid) {
217- printf("output_num=%d\n", output_num);221+ const auto& tensor = output[0];
218- for (int i = 0; i < output_num; i++) {222+ valid = tensor.GetTensorDesc().GetDataType() == boxesDtype &&
219- DataType outDtype = output[i].GetTensorDesc().GetDataType();223+ tensor.GetTensorDesc().GetShape().GetDims() == boxesShape &&
220- int64_t outSize = output[i].GetTensorDesc().GetShape().GetShapeSize();224+ tensor.GetSize() == 8 * GetDataTypeSize(boxesDtype) && tensor.GetData() != nullptr;
221- printf("output[%d] dtype=%d shapeSize=%ld\n", i, outDtype, outSize);225+ for (size_t j = 0; valid && j < 8; ++j) {
222- uint8_t* outData = output[i].GetData();226+ float actual;
223- if (outData == nullptr) {227+ if (boxesDtype == DT_FLOAT) {
224- printf("output[%d] GetData() returned nullptr\n", i);228+ memcpy(&actual, tensor.GetData() + j * sizeof(float), sizeof(float));
225- continue;229+ } else {
226- }230+ uint16_t bits;
227- if (outDtype == ge::DT_FLOAT) {231+ memcpy(&bits, tensor.GetData() + j * sizeof(bits), sizeof(bits));
228- float* p = (float*)outData;232+ const int exponent = (bits >> 10) & 0x1f;
229- // boxes[i] = (i*10+5)%1000, divisor = [100, 200, 100, 200] (h=100, w=200)233+ const int mantissa = bits & 0x3ff;
230- float divisors[4] = {100.0f, 200.0f, 100.0f, 200.0f};234+ actual = exponent == 0 ? std::ldexp(static_cast<float>(mantissa), -24) :
231- for (int64_t j = 0; j < outSize; j++) {235+ std::ldexp(static_cast<float>(1024 + mantissa), exponent - 25);
232- float expect = static_cast<float>((j * 10 + 5) % 1000) / divisors[j % 4];236+ actual = (bits & 0x8000) ? -actual : actual;
233- printf(" result[%ld] = %f (expect %f)\n", j, p[j], expect);237+ if (exponent == 31) {
238+ valid = false;
239+ break;
240+ }
234 }241 }
242+ const float expected = static_cast<float>((j * 10 + 5) % 1000) / (j % 2 == 0 ? 100.0f : 200.0f);
243+ const float tolerance = boxesDtype == DT_FLOAT16 ? 1e-4f + 1e-3f * std::abs(expected) : 1e-6f;
244+ printf("result[%zu] = %f (expect %f)\n", j, actual, expected);
245+ valid = std::isfinite(actual) && std::abs(actual - expected) <= tolerance;
235 }246 }
236 }247 }
248+ if (!valid) {
249+ printf("NormalizeBBox result verification failed\n");
250+ delete session;
251+ GEFinalize();
252+ return FAILED;
253+ }
237 254 
238 delete session;255 delete session;
239 session = nullptr;256 session = nullptr;