Merged
fix: migrate L2Normalize and INTrainingReduceV2 inference #12515
fix: migrate L2Normalize and INTrainingReduceV2 inference #12515
Merged
raoliang_sacAdd on 19 days ago
Total: 18 files changed+402-204
@@ -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,33 @@
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+# This standalone example must not add installed-CANN dependencies to the host build.
10+if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
11+ return()
12+endif()
13+ 
14+cmake_minimum_required(VERSION 3.18)
15+project(GE_IR_EXAMPLE LANGUAGES CXX)
16+ 
17+set(ASCEND_PATH "$ENV{ASCEND_HOME_PATH}")
18+if(NOT ASCEND_PATH)
19+ message(FATAL_ERROR "Source the CANN set_env.sh before configuring this example.")
20+endif()
21+find_library(GRAPH_LIBRARY NAMES libgraph.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
22+find_library(GE_RUNNER_LIBRARY NAMES libge_runner.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
23+find_library(GRAPH_BASE_LIBRARY NAMES libgraph_base.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
24+find_library(GE_COMPILER_LIBRARY NAMES libge_compiler.so PATHS "${ASCEND_PATH}/lib64" NO_DEFAULT_PATH REQUIRED)
25+ 
26+add_executable(test_geir_l2_normalize test_geir_l2_normalize.cpp)
27+target_include_directories(test_geir_l2_normalize PRIVATE
28+ "${ASCEND_PATH}/include/graph"
29+ "${ASCEND_PATH}/include/ge"
30+ "${ASCEND_PATH}/opp/built-in/op_proto/inc"
31+ "${ASCEND_PATH}/include")
32+target_link_libraries(test_geir_l2_normalize PRIVATE
33+ ${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 同样恒等复制。