Merged
fix: harden SGD, Relu6Grad and NormalizeBBox #12376
fix: harden SGD, Relu6Grad and NormalizeBBox #12376
Merged
raoliang_sacAdd on 21 days ago
Total: 24 files changed+460-190
@@ -57,14 +57,14 @@
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>
@@ -73,8 +73,13 @@
73## 约束说明73## 约束说明
74 74 
75- gradients、features、backprops的数据类型须保持一致,不支持混合数据类型。75- gradients、features、backprops的数据类型须保持一致,不支持混合数据类型。
76-- 不支持广播:features的shape须与gradients完全一致,或为标量[1]。
77- 支持动态shape与动态rank。76- 支持动态shape与动态rank。
77+- <term>Ascend 950PR&950DT系列产品</term>:
78+ - 输入shape约束:gradients与features支持双向广播,各自rank不超过8;GE图模式支持满足广播关系的空Tensor。
79+ - 输出shape约束:输出为两个输入广播后的shape;空Tensor的输出元素数为0。
80+- <term>Atlas A3系列产品</term>、<term>Atlas A2系列产品</term>:
81+ - 输入shape约束:features的shape须与gradients一致,或为[1]。
82+ - 输出shape约束:与gradients一致。
78 83 
79## 实现方案84## 实现方案
80 85 
@@ -83,6 +88,7 @@
83| 计算图原型 | `op_graph/relu6_grad_proto.h` | `REG_OP(Relu6Grad)`,二输入一输出 |88| 计算图原型 | `op_graph/relu6_grad_proto.h` | `REG_OP(Relu6Grad)`,二输入一输出 |
84| 算子定义 | `op_host/relu6_grad_def.cpp` | `OpDef::AddConfig("ascend950", ...)` |89| 算子定义 | `op_host/relu6_grad_def.cpp` | `OpDef::AddConfig("ascend950", ...)` |
85| InferShape | `op_host/relu6_grad_infershape.cpp` | 复用`Ops::Base::InferShape4Broadcast` |90| InferShape | `op_host/relu6_grad_infershape.cpp` | 复用`Ops::Base::InferShape4Broadcast` |
91+| 图模式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>` |92| 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中间精度 |93| 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` |94| Struct | `op_kernel/arch35/relu6_grad_struct.h` | `BRC_TEMP_SCH_MODE_KEY_DECL/SEL` |
@@ -90,7 +96,7 @@
90 96 
91### fp32通路97### fp32通路
92 98 
93-```Cpp99+```text
94In0(dy) ─CopyInBrc─+100In0(dy) ─CopyInBrc─+
95 +──────────────────────────────+101 +──────────────────────────────+
96 |102 |
@@ -102,13 +108,13 @@ In1(x) ─CopyInBrc─+ |
102 Const(0,fp32) ─Duplicate─ Zero ─────────────── ┴ Select(mask, dy, Zero) ─ CopyOut ─ Out0108 Const(0,fp32) ─Duplicate─ Zero ─────────────── ┴ Select(mask, dy, Zero) ─ CopyOut ─ Out0
103```109```
104 110 
105-```Cpp
106`Vec::Compare<u8, fp32, GT/LT>`输出位掩码,`Vec::And<u8>`合成`(0 < x < 6)`111`Vec::Compare<u8, fp32, GT/LT>`输出位掩码,`Vec::And<u8>`合成`(0 < x < 6)`
107区间mask;`Vec::Select<u8, fp32, TENSOR_TENSOR>`按`mask ? dy : 0`逐元素112区间mask;`Vec::Select<u8, fp32, TENSOR_TENSOR>`按`mask ? dy : 0`逐元素
108选择,避免分支,硬件原生vsel实现。113选择,避免分支,硬件原生vsel实现。
109 114 
110### fp16 / bf16通路115### fp16 / bf16通路
111 116 
117+```text
112In0/In1 ─CopyInBrc─ Cast(->fp32) ─+118In0/In1 ─CopyInBrc─ Cast(->fp32) ─+
113 +─ Compare ── And ─ Select ── Cast(->T,RINT) ─ CopyOut ─ Out0119 +─ Compare ── And ─ Select ── Cast(->T,RINT) ─ CopyOut ─ Out0
114 Const(0,fp32) ── Zero ─+120 Const(0,fp32) ── Zero ─+
@@ -126,4 +132,4 @@ In0/In1 ─CopyInBrc─ Cast(->fp32) ─+
126 132 
127| 调用方式 | 样例代码 | 说明 |133| 调用方式 | 样例代码 | 说明 |
128| ---------------- | --------------------------- |-----------------------------------------------|134| ---------------- | --------------------------- |-----------------------------------------------|
129-| 图模式 | [test_geir_relu6_grad.cpp](examples/arch35/test_geir_relu6_grad.cpp) | 通过[算子IR](op_graph/relu6_grad_proto.h)构图方式调用Relu6Grad算子。 |135+| 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>
@@ -61,23 +61,32 @@
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- 
69-### 产品差异说明
70- 
71-| 产品 | 数据类型 | 静态shape格式 | 动态shape格式 | shape/rank及空Tensor限制 |
72-| --- | --- | --- | --- | --- |
73-| <term>Ascend 950PR&950DT系列产品</term> | 各输入、输出支持FLOAT、FLOAT16、BFLOAT16 | 各输入、输出均为ND | 支持动态shape和动态rank,各输入、输出均为ND | `parameters`的rank为1~8,不支持空Tensor |
74-| <term>Atlas A3系列产品</term><br><term>Atlas A2系列产品</term> | 各输入、输出支持FLOAT、FLOAT16、BFLOAT16 | `parameters`、`gradient`、`accum`、`stat`及输出支持NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D;`learning_rate`和`momentum`为ND | 不支持 | `parameters`的rank为1~8,不支持空Tensor |
75-| <term>Atlas 200I/500 A2推理产品</term><br><term>Atlas推理系列产品</term><br><term>Atlas训练系列产品</term> | 各输入、输出支持FLOAT、FLOAT16 | `parameters`、`gradient`、`accum`、`stat`及输出支持NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D;`learning_rate`和`momentum`为ND | 不支持 | `parameters`的rank为1~8,不支持空Tensor |
76 68 
77## 约束说明69## 约束说明
78 70 
79- 图上仅声明一个`parameters`输出,但`parameters`、`accum`和`stat`均可能被原地更新,71- 图上仅声明一个`parameters`输出,但`parameters`、`accum`和`stat`均可能被原地更新,
80 调用方必须将这三个输入作为可写Tensor。72 调用方必须将这三个输入作为可写Tensor。
73+- `momentum=0`仅阻止`accum`和`stat`回写,不跳过权重更新中的动量乘法;当`accum`含Inf时,`0×Inf`产生的NaN会传播到权重结果。
74+- <term>Ascend 950PR&950DT系列产品</term>:
75+ - 数据类型:各输入、输出支持FLOAT、FLOAT16、BFLOAT16。
76+ - 数据格式:静态shape和动态shape的各输入、输出均为ND。
77+ - 动态shape能力:支持动态shape和动态rank。
78+ - 输入shape约束:`parameters`的rank为1~8,不支持空Tensor。
79+ - 中间计算精度:使用FLOAT进行中间计算,FLOAT16、BFLOAT16结果按就近偶数舍入转换回输入数据类型。
80+- <term>Atlas A3系列产品</term>、<term>Atlas A2系列产品</term>:
81+ - 数据类型:各输入、输出支持FLOAT、FLOAT16、BFLOAT16。
82+ - 静态shape数据格式:`parameters`、`gradient`、`accum`、`stat`及输出支持NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D;`learning_rate`和`momentum`为ND。
83+ - 动态shape能力:不支持动态shape。
84+ - 输入shape约束:`parameters`的rank为1~8,不支持空Tensor。
85+- <term>Atlas 200I/500 A2推理产品</term>、<term>Atlas推理系列产品</term>、<term>Atlas训练系列产品</term>:
86+ - 数据类型:各输入、输出支持FLOAT、FLOAT16。
87+ - 静态shape数据格式:`parameters`、`gradient`、`accum`、`stat`及输出支持NC1HWC0、NDC1HWC0、ND、FRACTAL_Z、FRACTAL_Z_3D;`learning_rate`和`momentum`为ND。
88+ - 动态shape能力:不支持动态shape。
89+ - 输入shape约束:`parameters`的rank为1~8,不支持空Tensor。
81 90 
82## 调用说明91## 调用说明
83 92 
@@ -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,15 +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 |
39- 38+| reversed_box | 可选属性 | 指定`boxes`的坐标轴位置,默认值为false。 | BOOL | - |
40-### 产品差异说明
41- 
42-| 产品 | 静态shape能力 | 动态shape能力 | shape/rank及空Tensor限制 |
43-| --- | --- | --- | --- |
44-| <term>Ascend 950PR&950DT系列产品</term> | 输入、输出均为ND | 支持动态shape和动态rank,输入、输出均为ND | `boxes`的rank为2~8;支持batch或非坐标维为0的空Tensor,坐标维必须为4 |
45-| <term>Atlas A3系列产品</term><br><term>Atlas A2系列产品</term><br><term>Atlas 200I/500 A2推理产品</term><br><term>Atlas推理系列产品</term><br><term>Atlas训练系列产品</term> | 输入、输出均为ND | 不支持 | `boxes`仅支持3维,不支持空Tensor |
46 39 
47## 约束说明40## 约束说明
48 41 
@@ -50,12 +43,19 @@
50- 当`reversed_box=false`时,`boxes`的最后一维必须为4;当`reversed_box=true`时,43- 当`reversed_box=false`时,`boxes`的最后一维必须为4;当`reversed_box=true`时,
51 `boxes`的第1维必须为4。44 `boxes`的第1维必须为4。
52- `shape_hw`中的高度和宽度直接作为除数,算子不校验其取值范围。45- `shape_hw`中的高度和宽度直接作为除数,算子不校验其取值范围。
46+- <term>Ascend 950PR&950DT系列产品</term>:
47+ - 数据格式:静态shape和动态shape的输入、输出均为ND。
48+ - 动态shape能力:支持动态shape和动态rank。
49+ - 输入shape约束:`boxes`的rank为2~8;支持batch或非坐标维为0的空Tensor,坐标维必须为4。
50+- <term>Atlas A3系列产品</term>、<term>Atlas A2系列产品</term>、<term>Atlas 200I/500 A2推理产品</term>、<term>Atlas推理系列产品</term>、<term>Atlas训练系列产品</term>:
51+ - 数据格式:静态shape的输入、输出均为ND。
52+ - 动态shape能力:不支持动态shape。
53+ - 输入shape约束:`boxes`仅支持3维,不支持空Tensor。
53 54 
54## 调用说明55## 调用说明
55 56 
56-本算子不提供aclnn单算子接口,支持图引擎(Graph Engine,GE)图模式及TensorFlow图导入。57+本算子不提供aclnn单算子接口,通过图引擎(Graph Engine,GE)图模式调用。
57 58 
58| 调用方式 | 调用样例 | 说明 |59| 调用方式 | 调用样例 | 说明 |
59| --- | --- | --- |60| --- | --- | --- |
60| GE图模式 | [test_geir_normalize_bbox.cpp](examples/arch35/test_geir_normalize_bbox.cpp) | 通过[NormalizeBBox算子IR](op_graph/normalize_bbox_proto.h)构图并调用算子。 |61| 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算子。 |
@@ -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;