Closed
fix(mul_no_nan): 补齐推导校验并修正测试参考 #6971
raoliang_sacAdd on 9 days agoClosed at 7 days ago
fix(mul_no_nan): 补齐推导校验并修正测试参考 #6971
Closed
Total: 8 files changed+140-25
| @@ -65,16 +65,13 @@ | |||
| 65 | </tr> | 65 | </tr> |
| 66 | </tbody></table> | 66 | </tbody></table> |
| 67 | 67 | ||
| 68 | -### 产品差异说明 | ||
| 69 | - | ||
| 70 | -- <term>Ascend 950PR&950DT系列产品</term>:x1、x2的维度数均不超过8。 | ||
| 71 | -- <term>Ascend 950PR&950DT系列产品</term>:GE图模式支持满足广播关系的空Tensor,输出保持广播后的shape,元素数为0。 | ||
| 72 | - | ||
| 73 | ## 约束说明 | 68 | ## 约束说明 |
| 74 | 69 | ||
| 75 | - x1、x2、y必须为同一种数据类型,不支持混合数据类型。 | 70 | - x1、x2、y必须为同一种数据类型,不支持混合数据类型。 |
| 76 | - 仅判x2一侧,`x2 = -0`同样进入零臂(IEEE-754 `-0 == 0`);`x2 != 0`时所有IEEE行为按普通乘法保留(NaN / Inf正常传播)。 | 71 | - 仅判x2一侧,`x2 = -0`同样进入零臂(IEEE-754 `-0 == 0`);`x2 != 0`时所有IEEE行为按普通乘法保留(NaN / Inf正常传播)。 |
| 77 | -- 支持任意NumPy广播形态(含标量、单维broadcast、跨rank broadcast),支持动态shape与动态rank。 | 72 | +- x1、x2的维度数均不超过8。在该范围内支持NumPy广播(含标量、单维broadcast、跨rank broadcast),支持动态shape与动态rank;运行时确定的维度数也须满足上述上限。 |
| 73 | +- <term>Ascend 950PR&950DT系列产品</term>: | ||
| 74 | + - GE图模式支持满足广播关系的空Tensor,输出保持广播后的shape,元素数为0。 | ||
| 78 | 75 | ||
| 79 | ## 实现方案 | 76 | ## 实现方案 |
| 80 | 77 | ||
| @@ -125,4 +122,4 @@ In0/In1 -- CopyInBrc -- Cast(->fp32) --+ | |||
| 125 | 122 | ||
| 126 | | 调用方式 | 样例代码 | 说明 | | 123 | | 调用方式 | 样例代码 | 说明 | |
| 127 | | ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | 124 | | ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | |
| 128 | -| GE图模式 | [test_geir_mul_no_nan](examples/arch35/test_geir_mul_no_nan.cpp) | 通过[算子IR](op_graph/mul_no_nan_proto.h)构图方式调用MulNoNan算子。 | | 125 | +| GE图模式 | [test_geir_mul_no_nan](examples/arch35/test_geir_mul_no_nan.cpp) | 通过[算子IR](op_graph/mul_no_nan_proto.h)构图方式调用MulNoNan算子,样例可通过`--device-id=N`选择设备。 | |
| @@ -13,6 +13,7 @@ | |||
| 13 | // Each test case builds a one-op graph, runs it on device, and checks the | 13 | // Each test case builds a one-op graph, runs it on device, and checks the |
| 14 | // output against a pre-computed expected value. The example exits with | 14 | // output against a pre-computed expected value. The example exits with |
| 15 | // status 0 only if every case passes. | 15 | // status 0 only if every case passes. |
| 16 | +// Usage: test_geir_mul_no_nan [case_name|--list] [--device-id=N] | ||
| 16 | // | 17 | // |
| 17 | // Semantics: y = (x2 == 0) ? 0 : x1 * x2 (element-wise, broadcast). | 18 | // Semantics: y = (x2 == 0) ? 0 : x1 * x2 (element-wise, broadcast). |
| 18 | // Cases below specifically exercise the special-value contract that | 19 | // Cases below specifically exercise the special-value contract that |
| @@ -381,11 +382,6 @@ static int RunOneCase(const CaseSpec& spec) | |||
| 381 | 382 | ||
| 382 | int main(int argc, char* argv[]) | 383 | int main(int argc, char* argv[]) |
| 383 | { | 384 | { |
| 384 | - if (argc > 2) { | ||
| 385 | - printf("Usage: %s [case_tag|--list]\n", argv[0]); | ||
| 386 | - return FAILED; | ||
| 387 | - } | ||
| 388 | - | ||
| 389 | const double kInf = std::numeric_limits<double>::infinity(); | 385 | const double kInf = std::numeric_limits<double>::infinity(); |
| 390 | const double kNeg0 = -0.0; | 386 | const double kNeg0 = -0.0; |
| 391 | const double kNaN = std::numeric_limits<double>::quiet_NaN(); | 387 | const double kNaN = std::numeric_limits<double>::quiet_NaN(); |
| @@ -443,27 +439,48 @@ int main(int argc, char* argv[]) | |||
| 443 | {"fp32_empty_bc", ge::DT_FLOAT, 2.0, 3.0, {0, 4}, {1, 4}, {0, 4}, exact(6.0, 1e-5)}, | 439 | {"fp32_empty_bc", ge::DT_FLOAT, 2.0, 3.0, {0, 4}, {1, 4}, {0, 4}, exact(6.0, 1e-5)}, |
| 444 | }; | 440 | }; |
| 445 | 441 | ||
| 446 | - if (argc == 2 && string(argv[1]) == "--list") { | 442 | + string selectedCase; |
| 443 | + string deviceId = "0"; | ||
| 444 | + bool listOnly = false; | ||
| 445 | + for (int i = 1; i < argc; ++i) { | ||
| 446 | + const string arg = argv[i]; | ||
| 447 | + if (arg == "--list") { | ||
| 448 | + listOnly = true; | ||
| 449 | + } else if (arg.rfind("--device-id=", 0) == 0) { | ||
| 450 | + deviceId = arg.substr(string("--device-id=").size()); | ||
| 451 | + if (deviceId.empty() || deviceId.find_first_not_of("0123456789") != string::npos) { | ||
| 452 | + std::cerr << "Invalid device ID: " << deviceId << std::endl; | ||
| 453 | + return FAILED; | ||
| 454 | + } | ||
| 455 | + } else if (selectedCase.empty() && arg.rfind("--", 0) != 0) { | ||
| 456 | + selectedCase = arg; | ||
| 457 | + } else { | ||
| 458 | + std::cerr << "Usage: " << argv[0] << " [case_name|--list] [--device-id=N]" << std::endl; | ||
| 459 | + return FAILED; | ||
| 460 | + } | ||
| 461 | + } | ||
| 462 | + if (listOnly) { | ||
| 447 | for (const auto& c : cases) { | 463 | for (const auto& c : cases) { |
| 448 | std::cout << c.tag << std::endl; | 464 | std::cout << c.tag << std::endl; |
| 449 | } | 465 | } |
| 450 | return SUCCESS; | 466 | return SUCCESS; |
| 451 | } | 467 | } |
| 452 | - if (argc == 2) { | 468 | + if (!selectedCase.empty()) { |
| 453 | vector<CaseSpec> selected; | 469 | vector<CaseSpec> selected; |
| 454 | for (const auto& c : cases) { | 470 | for (const auto& c : cases) { |
| 455 | - if (c.tag == argv[1]) { | 471 | + if (c.tag == selectedCase) { |
| 456 | selected.push_back(c); | 472 | selected.push_back(c); |
| 457 | } | 473 | } |
| 458 | } | 474 | } |
| 459 | if (selected.empty()) { | 475 | if (selected.empty()) { |
| 460 | - printf("Unknown case: %s\n", argv[1]); | 476 | + printf("Unknown case: %s\n", selectedCase.c_str()); |
| 461 | return FAILED; | 477 | return FAILED; |
| 462 | } | 478 | } |
| 463 | cases = std::move(selected); | 479 | cases = std::move(selected); |
| 464 | } | 480 | } |
| 465 | 481 | ||
| 466 | - std::map<AscendString, AscendString> globalOpts = {{"ge.exec.deviceId", "0"}, {"ge.graphRunMode", "1"}}; | 482 | + std::map<AscendString, AscendString> globalOpts = {{"ge.exec.deviceId", deviceId.c_str()}, |
| 483 | + {"ge.graphRunMode", "1"}}; | ||
| 467 | if (ge::GEInitialize(globalOpts) != SUCCESS) { | 484 | if (ge::GEInitialize(globalOpts) != SUCCESS) { |
| 468 | printf("GEInitialize failed\n"); | 485 | printf("GEInitialize failed\n"); |
| 469 | return FAILED; | 486 | return FAILED; |
| @@ -18,8 +18,16 @@ ge::graphStatus InferDataType4MulNoNan(gert::InferDataTypeContext* context) | |||
| 18 | return ge::GRAPH_FAILED; | 18 | return ge::GRAPH_FAILED; |
| 19 | } | 19 | } |
| 20 | const auto dtype = context->GetInputDataType(0); | 20 | const auto dtype = context->GetInputDataType(0); |
| 21 | + OP_CHECK_IF(dtype != ge::DT_FLOAT16 && dtype != ge::DT_FLOAT && dtype != ge::DT_INT32 && dtype != ge::DT_BF16, | ||
F | |||
| 22 | + OP_LOGE_FOR_INVALID_DTYPE_WITH_REASON(context->GetNodeName(), "x1", Ops::Base::ToString(dtype), | ||
| 23 | + "only float16, float32, int32 and bfloat16 are supported"), | ||
| 24 | + return ge::GRAPH_FAILED); | ||
| 21 | OP_CHECK_IF(dtype != context->GetInputDataType(1), | 25 | OP_CHECK_IF(dtype != context->GetInputDataType(1), |
| 22 | - OP_LOGE(context->GetNodeName(), "x1 and x2 must have the same dtype."), return ge::GRAPH_FAILED); | 26 | + OP_LOGE_FOR_INVALID_DTYPES_WITH_REASON( |
| 27 | + context->GetNodeName(), "x1, x2", | ||
| 28 | + Ops::Base::ToString(dtype) + ", " + Ops::Base::ToString(context->GetInputDataType(1)), | ||
| 29 | + "inputs must have the same dtype"), | ||
| 30 | + return ge::GRAPH_FAILED); | ||
| 23 | return context->SetOutputDataType(0, dtype); | 31 | return context->SetOutputDataType(0, dtype); |
| 24 | } | 32 | } |
| 25 | 33 | ||
| @@ -20,7 +20,20 @@ using namespace ge; | |||
| 20 | namespace ops { | 20 | namespace ops { |
| 21 | static ge::graphStatus InferShape4MulNoNan(gert::InferShapeContext* context) | 21 | static ge::graphStatus InferShape4MulNoNan(gert::InferShapeContext* context) |
| 22 | { | 22 | { |
| 23 | + if (context == nullptr) { | ||
| 24 | + return ge::GRAPH_FAILED; | ||
| 25 | + } | ||
| 23 | OP_LOGD(context->GetNodeName(), "Begin to do InferShape4MulNoNan in ops-math"); | 26 | OP_LOGD(context->GetNodeName(), "Begin to do InferShape4MulNoNan in ops-math"); |
| 27 | + constexpr size_t MAX_INPUT_RANK = 8; | ||
| 28 | + for (size_t i = 0; i < 2; ++i) { | ||
| 29 | + const auto* shape = context->GetInputShape(i); | ||
| 30 | + OP_CHECK_NULL_WITH_CONTEXT(context, shape); | ||
| 31 | + OP_CHECK_IF( | ||
| 32 | + shape->GetDimNum() > MAX_INPUT_RANK, | ||
F [Major] rank≤8 校验对所有产品无条件生效,与 README 的 950 专属描述及“动态 rank”约束矛盾 新增的 InferShape 对 x1、x2 统一用 ![]() ![]() | |||
| 33 | + OP_LOGE_FOR_INVALID_SHAPEDIM_WITH_REASON(context->GetNodeName(), i == 0 ? "x1" : "x2", | ||
| 34 | + std::to_string(shape->GetDimNum()), "rank must not exceed 8"), | ||
| 35 | + return ge::GRAPH_FAILED); | ||
| 36 | + } | ||
| 24 | return Ops::Base::InferShape4Broadcast(context); | 37 | return Ops::Base::InferShape4Broadcast(context); |
| 25 | } | 38 | } |
| 26 | 39 | ||
| @@ -37,7 +37,7 @@ def mul_no_nan_golden(x1, x2, **kwargs): | |||
| 37 | """ | 37 | """ |
| 38 | Kernel golden for mul_no_nan. | 38 | Kernel golden for mul_no_nan. |
| 39 | All the parameters follow @mul_no_nan_def.cpp without outputs. | 39 | All the parameters follow @mul_no_nan_def.cpp without outputs. |
| 40 | - All the input Tensors are numpy.ndarray. | 40 | + Inputs are array-like tensors. |
| 41 | kwargs may contain: short_soc_version, input_ori_shapes, output_ori_shapes, | 41 | kwargs may contain: short_soc_version, input_ori_shapes, output_ori_shapes, |
| 42 | input_formats, output_formats, input_ori_formats, output_ori_formats, | 42 | input_formats, output_formats, input_ori_formats, output_ori_formats, |
| 43 | input_dtypes, output_dtypes. | 43 | input_dtypes, output_dtypes. |
| @@ -48,6 +48,10 @@ def mul_no_nan_golden(x1, x2, **kwargs): | |||
| 48 | by x1 == inf (inf * 0) or by x1 == NaN. Conversely, where x2 != 0 the | 48 | by x1 == inf (inf * 0) or by x1 == NaN. Conversely, where x2 != 0 the |
| 49 | product is returned as is, so x1 == 0 with x2 == inf still yields NaN and | 49 | product is returned as is, so x1 == 0 with x2 == inf still yields NaN and |
| 50 | NOT 0. This is the core differentiator vs plain Mul. | 50 | NOT 0. This is the core differentiator vs plain Mul. |
| 51 | + | ||
| 52 | + TensorFlow's CPU MulNoNan lacks INT32 and some valid six-dimensional | ||
| 53 | + broadcasts. Use a Torch composition to cover the full CANN input domain | ||
| 54 | + and retain fp64 intermediate precision for the mathematical reference. | ||
| 51 | """ | 55 | """ |
| 52 | import torch | 56 | import torch |
| 53 | 57 | ||
| @@ -61,11 +65,26 @@ def mul_no_nan_golden(x1, x2, **kwargs): | |||
| 61 | output = torch.where(y == 0, torch.zeros_like(product), product).numpy() | 65 | output = torch.where(y == 0, torch.zeros_like(product), product).numpy() |
| 62 | if kwargs.get("golden_mode") == "Promote": | 66 | if kwargs.get("golden_mode") == "Promote": |
| 63 | return output | 67 | return output |
| 64 | - return output.astype(output_dtype) | 68 | + # Use the framework cast for rounding; NumPy only carries the final bytes. |
| 69 | + if output_dtype.name == "bfloat16": | ||
| 70 | + return ( | ||
| 71 | + torch.from_numpy(output) | ||
| 72 | + .to(torch.bfloat16) | ||
| 73 | + .view(torch.int16) | ||
| 74 | + .numpy() | ||
| 75 | + .view(output_dtype) | ||
| 76 | + ) | ||
| 77 | + target_dtype = { | ||
| 78 | + "float16": torch.float16, | ||
| 79 | + "float32": torch.float32, | ||
| 80 | + "float64": torch.float64, | ||
| 81 | + "int32": torch.int32, | ||
| 82 | + }[output_dtype.name] | ||
| 83 | + return torch.from_numpy(output).to(target_dtype).numpy() | ||
| 65 | 84 | ||
| 66 | 85 | ||
| 67 | class _MulNoNanCompose: | 86 | class _MulNoNanCompose: |
| 68 | - """Third-party reference executed on the remote GPU server.""" | 87 | + """Torch GPU composition, including INT32 which the TensorFlow API lacks.""" |
| 69 | 88 | ||
| 70 | def __call__(self, x1, x2, **kwargs): | 89 | def __call__(self, x1, x2, **kwargs): |
| 71 | del kwargs | 90 | del kwargs |
| @@ -100,9 +119,13 @@ class _MulNoNanE2ECompose: | |||
| 100 | class MulNoNanTensorFlowSpec: | 119 | class MulNoNanTensorFlowSpec: |
| 101 | """TensorFlow E2E spec registered by the CSV ``api_name``.""" | 120 | """TensorFlow E2E spec registered by the CSV ``api_name``.""" |
| 102 | 121 | ||
| 103 | - # Omit a custom golden: TTK's native CPU TF fallback applies Promote to | 122 | + @staticmethod |
| 104 | - # raw inputs. Its custom E2E plugin path bypasses that promotion. | 123 | + def golden(x, y, **kwargs): |
| 124 | + # Native CPU TF rejects some valid six-dimensional broadcasts. TTK | ||
| 125 | + # does not promote custom E2E inputs, so keep the reference in fp64. | ||
| 126 | + return [mul_no_nan_golden(x, y, golden_mode="Promote")] | ||
| 127 | + | ||
| 105 | third_party = {"torch": _MulNoNanE2ECompose} | 128 | third_party = {"torch": _MulNoNanE2ECompose} |
| 106 | - # tf.raw_ops.MulNoNan itself supports floating/complex tensors, not int32. | 129 | + # This TensorFlow TestSpec covers only float16/float32/bfloat16. |
| 107 | - # This CANN operator exposes only the three floating types from that overlap. | 130 | + # CANN's kernel/GEIR paths also support int32, which the TF API rejects. |
| 108 | tolerance = _TENSORFLOW_TOLERANCE | 131 | tolerance = _TENSORFLOW_TOLERANCE |
| @@ -0,0 +1,9 @@ | |||
| 1 | +testcase_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,is_enabled,remark | ||
| 2 | +mnn_float16_broadcast,mul_no_nan,"((2, 1, 5), (3, 1))","('float16', 'float16')","('ND', 'ND')","((2, 3, 5),)","('float16',)","('ND',)","((2, 1, 5), (3, 1))","('ND', 'ND')","((2, 3, 5),)","('ND',)",{},"((-3, 3), (-3, 3))",TRUE, | ||
| 3 | +mnn_float32_broadcast,mul_no_nan,"((2, 1, 5), (3, 1))","('float32', 'float32')","('ND', 'ND')","((2, 3, 5),)","('float32',)","('ND',)","((2, 1, 5), (3, 1))","('ND', 'ND')","((2, 3, 5),)","('ND',)",{},"((-3, 3), (-3, 3))",TRUE, | ||
| 4 | +mnn_bfloat16_broadcast,mul_no_nan,"((2, 1, 5), (3, 1))","('bfloat16', 'bfloat16')","('ND', 'ND')","((2, 3, 5),)","('bfloat16',)","('ND',)","((2, 1, 5), (3, 1))","('ND', 'ND')","((2, 3, 5),)","('ND',)",{},"((-3, 3), (-3, 3))",TRUE, | ||
| 5 | +mnn_int32_broadcast,mul_no_nan,"((2, 1, 5), (3, 1))","('int32', 'int32')","('ND', 'ND')","((2, 3, 5),)","('int32',)","('ND',)","((2, 1, 5), (3, 1))","('ND', 'ND')","((2, 3, 5),)","('ND',)",{},"((-3, 3), (-3, 3))",TRUE, | ||
| 6 | +mnn_float16_nan_zero,mul_no_nan,"((17,), (1,))","('float16', 'float16')","('ND', 'ND')","((17,),)","('float16',)","('ND',)","((17,), (1,))","('ND', 'ND')","((17,),)","('ND',)",{},"((float(""nan""), float(""nan"")), (0, 0))",TRUE, | ||
| 7 | +mnn_float32_zero_inf,mul_no_nan,"((17,), (1,))","('float32', 'float32')","('ND', 'ND')","((17,),)","('float32',)","('ND',)","((17,), (1,))","('ND', 'ND')","((17,),)","('ND',)",{},"((0, 0), (float(""inf""), float(""inf"")))",TRUE, | ||
| 8 | +mnn_bfloat16_inf_zero,mul_no_nan,"((17,), (1,))","('bfloat16', 'bfloat16')","('ND', 'ND')","((17,),)","('bfloat16',)","('ND',)","((17,), (1,))","('ND', 'ND')","((17,),)","('ND',)",{},"((float(""inf""), float(""inf"")), (0, 0))",TRUE, | ||
| 9 | +mnn_int32_wrap,mul_no_nan,"((17,), (1,))","('int32', 'int32')","('ND', 'ND')","((17,),)","('int32',)","('ND',)","((17,), (1,))","('ND', 'ND')","((17,),)","('ND',)",{},"((-2147483648, -2147483648), (-1, -1))",TRUE, | ||
| @@ -98,3 +98,18 @@ TEST(MulNoNanGraphInfer, RejectsMismatchedInputsWithoutLegacyVerifier) | |||
| 98 | ASSERT_NE(holder.GetContext(), nullptr); | 98 | ASSERT_NE(holder.GetContext(), nullptr); |
| 99 | EXPECT_EQ(ops::InferDataType4MulNoNan(holder.GetContext()), ge::GRAPH_FAILED); | 99 | EXPECT_EQ(ops::InferDataType4MulNoNan(holder.GetContext()), ge::GRAPH_FAILED); |
| 100 | } | 100 | } |
| 101 | + | ||
| 102 | +TEST(MulNoNanGraphInfer, RejectsUnsupportedMatchingInputDtypes) | ||
| 103 | +{ | ||
| 104 | + for (auto dtype : {ge::DT_BOOL, ge::DT_DOUBLE, ge::DT_COMPLEX128}) { | ||
| 105 | + SCOPED_TRACE(static_cast<int>(dtype)); | ||
| 106 | + gert::OpInferDataTypeContextBuilder builder; | ||
| 107 | + builder.OpType("MulNoNan").OpName("mul_no_nan_invalid_dtype").IONum(2, 1); | ||
| 108 | + builder.InputTensorDesc(0, dtype, ge::FORMAT_ND, ge::FORMAT_ND); | ||
| 109 | + builder.InputTensorDesc(1, dtype, ge::FORMAT_ND, ge::FORMAT_ND); | ||
| 110 | + builder.OutputTensorDesc(0, ge::FORMAT_ND, ge::FORMAT_ND); | ||
| 111 | + auto holder = builder.Build(); | ||
| 112 | + ASSERT_NE(holder.GetContext(), nullptr); | ||
| 113 | + EXPECT_EQ(ops::InferDataType4MulNoNan(holder.GetContext()), ge::GRAPH_FAILED); | ||
| 114 | + } | ||
| 115 | +} | ||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "infershape_context_faker.h" | 13 | #include "infershape_context_faker.h" |
| 14 | #include "infershape_case_executor.h" | 14 | #include "infershape_case_executor.h" |
| 15 | #include "base/registry/op_impl_space_registry_v2.h" | 15 | #include "base/registry/op_impl_space_registry_v2.h" |
| 16 | +#include "base/context_builder/op_infer_shape_context_builder.h" | ||
| 16 | 17 | ||
| 17 | using namespace ge; | 18 | using namespace ge; |
| 18 | 19 | ||
| @@ -23,6 +24,38 @@ protected: | |||
| 23 | static void TearDownTestCase() { std::cout << "MulNoNanInfershape TearDown" << std::endl; } | 24 | static void TearDownTestCase() { std::cout << "MulNoNanInfershape TearDown" << std::endl; } |
| 24 | }; | 25 | }; |
| 25 | 26 | ||
| 27 | +TEST_F(MulNoNanInfershape, rejects_either_rank9_input_even_with_unknown_other_rank) | ||
| 28 | +{ | ||
| 29 | + const auto registry = gert::DefaultOpImplSpaceRegistryV2::GetInstance().GetSpaceRegistry(); | ||
| 30 | + ASSERT_NE(registry, nullptr); | ||
| 31 | + const auto* impl = registry->GetOpImpl("MulNoNan"); | ||
| 32 | + ASSERT_NE(impl, nullptr); | ||
| 33 | + ASSERT_NE(impl->infer_shape, nullptr); | ||
| 34 | + for (size_t rank9Input = 0; rank9Input < 2; ++rank9Input) { | ||
| 35 | + SCOPED_TRACE(rank9Input); | ||
| 36 | + for (bool unknownOther : {false, true}) { | ||
| 37 | + gert::StorageShape shapes[2]; | ||
| 38 | + for (size_t i = 0; i < 2; ++i) { | ||
| 39 | + const size_t rank = i == rank9Input ? 9 : 1; | ||
| 40 | + for (size_t dim = 0; dim < rank; ++dim) { | ||
| 41 | + const int64_t size = i != rank9Input && unknownOther ? -2 : 1; | ||
| 42 | + shapes[i].MutableOriginShape().AppendDim(size); | ||
| 43 | + shapes[i].MutableStorageShape().AppendDim(size); | ||
| 44 | + } | ||
| 45 | + } | ||
| 46 | + gert::Tensor x1(shapes[0], {ge::FORMAT_ND, ge::FORMAT_ND, {}}, ge::DT_FLOAT); | ||
| 47 | + gert::Tensor x2(shapes[1], {ge::FORMAT_ND, ge::FORMAT_ND, {}}, ge::DT_FLOAT); | ||
| 48 | + gert::OpInferShapeContextBuilder builder; | ||
| 49 | + builder.OpType("MulNoNan").OpName("mul_no_nan_rank9").IONum(2, 1); | ||
| 50 | + builder.InputTensors({&x1, &x2}); | ||
| 51 | + builder.OutputTensorDesc(0, ge::DT_FLOAT, ge::FORMAT_ND, ge::FORMAT_ND); | ||
| 52 | + auto holder = builder.Build(); | ||
| 53 | + ASSERT_NE(holder.GetContext(), nullptr); | ||
| 54 | + EXPECT_EQ(impl->infer_shape(holder.GetContext()), ge::GRAPH_FAILED); | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | +} | ||
| 58 | + | ||
| 26 | // Case 1: two inputs share the same shape, fp16. | 59 | // Case 1: two inputs share the same shape, fp16. |
| 27 | TEST_F(MulNoNanInfershape, same_shape_fp16) | 60 | TEST_F(MulNoNanInfershape, same_shape_fp16) |
| 28 | { | 61 | { |


[Major] InferDataType 白名单新增 int32,但与“仅支持三种浮点类型”的说明及缺失的 kernel/tiling 适配不一致
新的 dtype 白名单显式放行
DT_INT32,同时新增的 ST CSV 也加入了 int32 用例(mnn_int32_broadcast / mnn_int32_wrap)。但 golden.py 的注释(L129-130)仍称“本算子只暴露该交集中的三种浮点类型”,README 约束亦未体现 int32;并且本 patch 未见 op def/proto、tiling、kernel 的 int32 适配。 若 kernel/tiling 实际未支持 int32,则 InferDataType 放行与 ST 用例会驱动不支持的输入,导致上板运行失败或精度用例误判。建议:确认 op def 与 kernel/tiling 已具备 int32 能力并补齐相应改动;否则移除此白名单中的DT_INT32及对应 ST 用例,并统一 README/golden 注释。