已合并
perf: 完善 Autofuse V2 Broadcast 性能建模(#235) #2074
高煜博创建于 9月11日
perf: 完善 Autofuse V2 Broadcast 性能建模(#235) #2074
已合并
共 23 个文件变更+4642-24
| @@ -41,6 +41,7 @@ struct NodeDetail { | |||
| 41 | Expr gm_stride{CreateExpr(0)}; | 41 | Expr gm_stride{CreateExpr(0)}; |
| 42 | Expr ub_stride{CreateExpr(0)}; | 42 | Expr ub_stride{CreateExpr(0)}; |
| 43 | ascir_param::CastNodeParams cast_node_params; | 43 | ascir_param::CastNodeParams cast_node_params; |
| 44 | + ascir_param::BroadcastNodeParams broadcast_node_params; | ||
| 44 | ascir_param::CompareNodeParams compare_node_params; | 45 | ascir_param::CompareNodeParams compare_node_params; |
| 45 | ascir_param::WhereNodeParams where_node_params; | 46 | ascir_param::WhereNodeParams where_node_params; |
| 46 | ascir_param::UnaryBitWidthChangeNodeParams unary_bitwidth_change_node_params; | 47 | ascir_param::UnaryBitWidthChangeNodeParams unary_bitwidth_change_node_params; |
| @@ -38,6 +38,13 @@ af::Status FillCastParams(const ascir_param::AscirNodeParams ¶ms, NodeInfo & | |||
| 38 | return af::SUCCESS; | 38 | return af::SUCCESS; |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | +af::Status FillBroadcastParams(const ascir_param::AscirNodeParams ¶ms, NodeInfo &node_info) { | ||
| 42 | + const auto *broadcast_params = ascir_param::GetSpecificParams<ascir_param::BroadcastNodeParams>(params); | ||
| 43 | + GE_ASSERT_NOTNULL(broadcast_params, "Broadcast specific params is null, node[%s].", node_info.name.c_str()); | ||
| 44 | + node_info.broadcast_node_params = *broadcast_params; | ||
| 45 | + return af::SUCCESS; | ||
| 46 | +} | ||
| 47 | + | ||
| 41 | af::Status FillCompareParams(const ascir_param::AscirNodeParams ¶ms, NodeInfo &node_info) { | 48 | af::Status FillCompareParams(const ascir_param::AscirNodeParams ¶ms, NodeInfo &node_info) { |
| 42 | const auto *compare_params = ascir_param::GetSpecificParams<ascir_param::CompareNodeParams>(params); | 49 | const auto *compare_params = ascir_param::GetSpecificParams<ascir_param::CompareNodeParams>(params); |
| 43 | GE_ASSERT_NOTNULL(compare_params, "Compare specific params is null, node[%s].", node_info.name.c_str()); | 50 | GE_ASSERT_NOTNULL(compare_params, "Compare specific params is null, node[%s].", node_info.name.c_str()); |
| @@ -87,6 +94,9 @@ af::Status FillSpecificParams(const af::AscNodePtr &ge_node, NodeInfo &node_info | |||
| 87 | if (node_info.node_type == kCast) { | 94 | if (node_info.node_type == kCast) { |
| 88 | return FillCastParams(*params, node_info); | 95 | return FillCastParams(*params, node_info); |
| 89 | } | 96 | } |
| 97 | + if (node_info.node_type == kBroadcast) { | ||
| 98 | + return FillBroadcastParams(*params, node_info); | ||
| 99 | + } | ||
| 90 | if (node_info.node_type == kGe || node_info.node_type == kEq || node_info.node_type == kNe || | 100 | if (node_info.node_type == kGe || node_info.node_type == kEq || node_info.node_type == kNe || |
| 91 | node_info.node_type == kGt || node_info.node_type == kLe || node_info.node_type == kLt) { | 101 | node_info.node_type == kGt || node_info.node_type == kLe || node_info.node_type == kLt) { |
| 92 | return FillCompareParams(*params, node_info); | 102 | return FillCompareParams(*params, node_info); |
| @@ -184,6 +184,7 @@ struct NodeInfo { | |||
| 184 | ascir_param::VectorFuncNodeParams vector_func_params; | 184 | ascir_param::VectorFuncNodeParams vector_func_params; |
| 185 | ascir_param::ReduceNodeParams reduce_specific_params; | 185 | ascir_param::ReduceNodeParams reduce_specific_params; |
| 186 | ascir_param::CastNodeParams cast_node_params; | 186 | ascir_param::CastNodeParams cast_node_params; |
| 187 | + ascir_param::BroadcastNodeParams broadcast_node_params; | ||
| 187 | ascir_param::CompareNodeParams compare_node_params; | 188 | ascir_param::CompareNodeParams compare_node_params; |
| 188 | ascir_param::WhereNodeParams where_node_params; | 189 | ascir_param::WhereNodeParams where_node_params; |
| 189 | ascir_param::UnaryBitWidthChangeNodeParams unary_bitwidth_change_node_params; | 190 | ascir_param::UnaryBitWidthChangeNodeParams unary_bitwidth_change_node_params; |
| @@ -81,6 +81,16 @@ struct CastNodeParams { | |||
| 81 | std::vector<ge::Expression> input_strides; | 81 | std::vector<ge::Expression> input_strides; |
| 82 | }; | 82 | }; |
| 83 | 83 | ||
| 84 | +struct BroadcastNodeParams { | ||
| 85 | + bool valid{false}; | ||
| 86 | + bool is_scalar{false}; | ||
| 87 | + int32_t const_rank{-1}; | ||
| 88 | + ge::Expression duplicate_count{ge::Symbol(1U)}; | ||
| 89 | + ParamExprRole duplicate_count_role{ParamExprRole::kSemantic}; | ||
| 90 | + std::vector<ParamExprLeaf> dst_shape; | ||
| 91 | + std::vector<ParamExprLeaf> src_shape; | ||
| 92 | +}; | ||
| 93 | + | ||
| 84 | struct CompareNodeParams { | 94 | struct CompareNodeParams { |
| 85 | bool valid{false}; | 95 | bool valid{false}; |
| 86 | bool is_scalar{false}; | 96 | bool is_scalar{false}; |
| @@ -120,8 +130,8 @@ struct TransposeNodeParams { | |||
| 120 | }; | 130 | }; |
| 121 | 131 | ||
| 122 | using AnySpecificParams = | 132 | using AnySpecificParams = |
| 123 | - std::variant<std::monostate, ReduceNodeParams, VectorFuncNodeParams, CastNodeParams, CompareNodeParams, | 133 | + std::variant<std::monostate, ReduceNodeParams, VectorFuncNodeParams, CastNodeParams, BroadcastNodeParams, |
| 124 | - WhereNodeParams, UnaryBitWidthChangeNodeParams, TransposeNodeParams>; | 134 | + CompareNodeParams, WhereNodeParams, UnaryBitWidthChangeNodeParams, TransposeNodeParams>; |
| 125 | 135 | ||
| 126 | struct AscirNodeParams { | 136 | struct AscirNodeParams { |
| 127 | // 扩展属性载荷版本,用于后续兼容。 | 137 | // 扩展属性载荷版本,用于后续兼容。 |
| @@ -25,6 +25,7 @@ namespace { | |||
| 25 | constexpr const char *kAscirNodeParams = "AscirNodeParams"; | 25 | constexpr const char *kAscirNodeParams = "AscirNodeParams"; |
| 26 | constexpr const char *kVectorFunc = "VectorFunc"; | 26 | constexpr const char *kVectorFunc = "VectorFunc"; |
| 27 | constexpr const char *kCast = "Cast"; | 27 | constexpr const char *kCast = "Cast"; |
| 28 | +constexpr const char *kBroadcast = "Broadcast"; | ||
| 28 | 29 | ||
| 29 | bool IsCompareParamSupported(const std::string &api_name) { | 30 | bool IsCompareParamSupported(const std::string &api_name) { |
| 30 | static const std::set<std::string> kCompareTypes = {"Ge", "Eq", "Ne", "Gt", "Le", "Lt"}; | 31 | static const std::set<std::string> kCompareTypes = {"Ge", "Eq", "Ne", "Gt", "Le", "Lt"}; |
| @@ -123,6 +124,22 @@ af::Status RegisterCastAscirNodeParams(const af::AscNodePtr &node) { | |||
| 123 | return RegisterAscirNodeParams(node, params); | 124 | return RegisterAscirNodeParams(node, params); |
| 124 | } | 125 | } |
| 125 | 126 | ||
| 127 | +af::Status RegisterBroadcastAscirNodeParams(const af::AscNodePtr &node) { | ||
| 128 | + GE_ASSERT_NOTNULL(node); | ||
| 129 | + const auto existing_params = GetAscirNodeParams(node); | ||
| 130 | + if (existing_params != nullptr) { | ||
| 131 | + const auto *broadcast_params = GetSpecificParams<BroadcastNodeParams>(*existing_params); | ||
| 132 | + if (broadcast_params != nullptr && broadcast_params->valid) { | ||
| 133 | + return af::SUCCESS; | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | + auto params = std::make_shared<AscirNodeParams>(); | ||
| 137 | + params->api_name = node->GetType(); | ||
| 138 | + params->status = ParamBuildStatus::kBuilt; | ||
| 139 | + params->specific_params = BroadcastNodeParams{}; | ||
| 140 | + return RegisterAscirNodeParams(node, params); | ||
| 141 | +} | ||
| 142 | + | ||
| 126 | af::Status RegisterCompareAscirNodeParams(const af::AscNodePtr &node) { | 143 | af::Status RegisterCompareAscirNodeParams(const af::AscNodePtr &node) { |
| 127 | GE_ASSERT_NOTNULL(node); | 144 | GE_ASSERT_NOTNULL(node); |
| 128 | auto params = std::make_shared<AscirNodeParams>(); | 145 | auto params = std::make_shared<AscirNodeParams>(); |
| @@ -549,6 +566,9 @@ af::Status EnrichAscirNodeParams(const AscirParamSourceContext &source) { | |||
| 549 | if (source.node->GetType() == kCast) { | 566 | if (source.node->GetType() == kCast) { |
| 550 | return RegisterCastAscirNodeParams(source.node); | 567 | return RegisterCastAscirNodeParams(source.node); |
| 551 | } | 568 | } |
| 569 | + if (source.node->GetType() == kBroadcast) { | ||
| 570 | + return RegisterBroadcastAscirNodeParams(source.node); | ||
| 571 | + } | ||
| 552 | if (IsCompareParamSupported(source.node->GetType())) { | 572 | if (IsCompareParamSupported(source.node->GetType())) { |
| 553 | return RegisterCompareAscirNodeParams(source.node); | 573 | return RegisterCompareAscirNodeParams(source.node); |
| 554 | } | 574 | } |
| @@ -17,6 +17,7 @@ | |||
| 17 | #define private public | 17 | #define private public |
| 18 | #include "expr_gen/generate_tiling_expr.h" | 18 | #include "expr_gen/generate_tiling_expr.h" |
| 19 | #include "parser/ascend_graph_parser.h" | 19 | #include "parser/ascend_graph_parser.h" |
| 20 | +#include "parser/specific_params_builder.h" | ||
| 20 | #undef private | 21 | #undef private |
| 21 | #include "tests/ut/att/utils/graph_construct_utils.h" | 22 | #include "tests/ut/att/utils/graph_construct_utils.h" |
| 22 | 23 | ||
| @@ -93,6 +94,38 @@ Status BuildReduceAscendGraphND(AscGraph &graph) { | |||
| 93 | } // namespace af | 94 | } // namespace af |
| 94 | namespace att { | 95 | namespace att { |
| 95 | namespace { | 96 | namespace { |
| 97 | + | ||
| 98 | +TEST(SpecificParamsBuilderTest, FillBroadcastParamsCopiesPayloadToNodeInfo) { | ||
| 99 | + af::AscGraph graph("test_graph"); | ||
| 100 | + af::ascir_op::Broadcast broadcast_op("broadcast"); | ||
| 101 | + graph.AddNode(broadcast_op); | ||
| 102 | + auto node = graph.FindNode("broadcast"); | ||
| 103 | + ASSERT_NE(node, nullptr); | ||
| 104 | + | ||
| 105 | + auto params = std::make_shared<ascir_param::AscirNodeParams>(); | ||
| 106 | + params->api_name = "Broadcast"; | ||
| 107 | + params->status = ascir_param::ParamBuildStatus::kBuilt; | ||
| 108 | + ascir_param::BroadcastNodeParams broadcast; | ||
| 109 | + broadcast.valid = true; | ||
| 110 | + broadcast.is_scalar = true; | ||
| 111 | + broadcast.duplicate_count = ge::Symbol(8); | ||
| 112 | + broadcast.src_shape = {{ge::Symbol(1), ascir_param::ParamExprRole::kSemantic}, | ||
| 113 | + {ge::Symbol(8), ascir_param::ParamExprRole::kActualSize}}; | ||
| 114 | + broadcast.dst_shape = {{ge::Symbol(4), ascir_param::ParamExprRole::kActualSize}, | ||
| 115 | + {ge::Symbol(8), ascir_param::ParamExprRole::kActualSize}}; | ||
| 116 | + params->specific_params = broadcast; | ||
| 117 | + ASSERT_TRUE(node->GetOpDesc()->SetExtAttr("AscirNodeParams", params)); | ||
| 118 | + | ||
| 119 | + NodeInfo node_info; | ||
| 120 | + node_info.name = "broadcast"; | ||
| 121 | + node_info.node_type = "Broadcast"; | ||
| 122 | + ASSERT_EQ(FillSpecificParams(node, node_info), af::SUCCESS); | ||
| 123 | + EXPECT_TRUE(node_info.broadcast_node_params.valid); | ||
| 124 | + EXPECT_TRUE(node_info.broadcast_node_params.is_scalar); | ||
| 125 | + EXPECT_EQ(node_info.broadcast_node_params.duplicate_count, ge::Symbol(8)); | ||
| 126 | + EXPECT_EQ(node_info.broadcast_node_params.src_shape[0].expr, ge::Symbol(1)); | ||
| 127 | + EXPECT_EQ(node_info.broadcast_node_params.dst_shape[1].role, ascir_param::ParamExprRole::kActualSize); | ||
| 128 | +} | ||
| 96 | ascir::FusedScheduledResult BuildGatherReduceScheduleResult(const af::AscGraph &gather_graph, | 129 | ascir::FusedScheduledResult BuildGatherReduceScheduleResult(const af::AscGraph &gather_graph, |
| 97 | const af::AscGraph &reduce_graph, | 130 | const af::AscGraph &reduce_graph, |
| 98 | const bool enable_group_parallel) { | 131 | const bool enable_group_parallel) { |
| @@ -101,6 +101,96 @@ TEST(AscirNodeParamsTest, EnrichGraphRegistersVectorFuncParams) { | |||
| 101 | EXPECT_TRUE(stored->output_dims.empty()); | 101 | EXPECT_TRUE(stored->output_dims.empty()); |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | +TEST(AscirNodeParamsTest, BroadcastParamsCanBeStoredInAscirNodeParams) { | ||
| 105 | + ascir_param::AscirNodeParams params; | ||
| 106 | + params.specific_params = ascir_param::BroadcastNodeParams{}; | ||
| 107 | + | ||
| 108 | + const auto *stored = ascir_param::GetSpecificParams<ascir_param::BroadcastNodeParams>(params); | ||
| 109 | + ASSERT_NE(stored, nullptr); | ||
| 110 | + EXPECT_FALSE(stored->valid); | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +TEST(AscirNodeParamsTest, EnrichGraphRegistersBroadcastParams) { | ||
| 114 | + af::AscGraph graph("test_graph"); | ||
| 115 | + af::ascir_op::Broadcast broadcast_op("broadcast"); | ||
| 116 | + graph.AddNode(broadcast_op); | ||
| 117 | + auto node = graph.FindNode("broadcast"); | ||
| 118 | + ASSERT_NE(node, nullptr); | ||
| 119 | + | ||
| 120 | + ExpectEnrichSuccess(graph); | ||
| 121 | + auto params = ascir_param::GetAscirNodeParams(node); | ||
| 122 | + ASSERT_NE(params, nullptr); | ||
| 123 | + EXPECT_EQ(params->api_name, "Broadcast"); | ||
| 124 | + EXPECT_EQ(params->status, ascir_param::ParamBuildStatus::kBuilt); | ||
| 125 | + const auto *stored = ascir_param::GetSpecificParams<ascir_param::BroadcastNodeParams>(*params); | ||
| 126 | + ASSERT_NE(stored, nullptr); | ||
| 127 | + EXPECT_FALSE(stored->valid); | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +TEST(AscirNodeParamsTest, EnrichGraphPreservesBroadcastParamsAcrossRepeatedCalls) { | ||
| 131 | + af::AscGraph graph("test_graph"); | ||
| 132 | + af::ascir_op::Broadcast broadcast_op("broadcast"); | ||
| 133 | + graph.AddNode(broadcast_op); | ||
| 134 | + auto node = graph.FindNode("broadcast"); | ||
| 135 | + ASSERT_NE(node, nullptr); | ||
| 136 | + | ||
| 137 | + auto params = std::make_shared<ascir_param::AscirNodeParams>(); | ||
| 138 | + params->api_name = "Broadcast"; | ||
| 139 | + params->status = ascir_param::ParamBuildStatus::kBuilt; | ||
| 140 | + ascir_param::BroadcastNodeParams broadcast; | ||
| 141 | + broadcast.valid = true; | ||
| 142 | + broadcast.is_scalar = true; | ||
| 143 | + broadcast.duplicate_count = Expr(8); | ||
| 144 | + broadcast.src_shape = {{Expr(1), ascir_param::ParamExprRole::kSemantic}, | ||
| 145 | + {Expr(8), ascir_param::ParamExprRole::kActualSize}}; | ||
| 146 | + broadcast.dst_shape = {{Expr(4), ascir_param::ParamExprRole::kActualSize}, | ||
| 147 | + {Expr(8), ascir_param::ParamExprRole::kActualSize}}; | ||
| 148 | + params->specific_params = broadcast; | ||
| 149 | + ASSERT_TRUE(node->GetOpDesc()->SetExtAttr("AscirNodeParams", params)); | ||
| 150 | + | ||
| 151 | + ExpectEnrichSuccess(graph); | ||
| 152 | + ExpectEnrichSuccess(graph); | ||
| 153 | + params = ascir_param::GetAscirNodeParams(node); | ||
| 154 | + ASSERT_NE(params, nullptr); | ||
| 155 | + const auto *stored = ascir_param::GetSpecificParams<ascir_param::BroadcastNodeParams>(*params); | ||
| 156 | + ASSERT_NE(stored, nullptr); | ||
| 157 | + EXPECT_TRUE(stored->valid); | ||
| 158 | + EXPECT_TRUE(stored->is_scalar); | ||
| 159 | + EXPECT_EQ(stored->duplicate_count, Expr(8)); | ||
| 160 | + EXPECT_EQ(stored->src_shape[0].expr, Expr(1)); | ||
| 161 | + EXPECT_EQ(stored->dst_shape[1].role, ascir_param::ParamExprRole::kActualSize); | ||
| 162 | +} | ||
| 163 | + | ||
| 164 | +TEST(AscirNodeParamsTest, EnrichGraphPreservesPrebuiltBroadcastParams) { | ||
| 165 | + af::AscGraph graph("test_graph"); | ||
| 166 | + af::ascir_op::Broadcast broadcast_op("broadcast"); | ||
| 167 | + graph.AddNode(broadcast_op); | ||
| 168 | + auto node = graph.FindNode("broadcast"); | ||
| 169 | + ASSERT_NE(node, nullptr); | ||
| 170 | + | ||
| 171 | + auto params = std::make_shared<ascir_param::AscirNodeParams>(); | ||
| 172 | + params->api_name = "Broadcast"; | ||
| 173 | + params->status = ascir_param::ParamBuildStatus::kBuilt; | ||
| 174 | + ascir_param::BroadcastNodeParams broadcast; | ||
| 175 | + broadcast.valid = true; | ||
| 176 | + broadcast.duplicate_count = Expr(16); | ||
| 177 | + broadcast.src_shape = {{Expr(1), ascir_param::ParamExprRole::kSemantic}, | ||
| 178 | + {Expr(16), ascir_param::ParamExprRole::kSize}}; | ||
| 179 | + broadcast.dst_shape = {{Expr(2), ascir_param::ParamExprRole::kSize}, {Expr(16), ascir_param::ParamExprRole::kSize}}; | ||
| 180 | + params->specific_params = broadcast; | ||
| 181 | + ASSERT_TRUE(node->GetOpDesc()->SetExtAttr("AscirNodeParams", params)); | ||
| 182 | + | ||
| 183 | + ExpectEnrichSuccess(graph); | ||
| 184 | + params = ascir_param::GetAscirNodeParams(node); | ||
| 185 | + ASSERT_NE(params, nullptr); | ||
| 186 | + const auto *stored = ascir_param::GetSpecificParams<ascir_param::BroadcastNodeParams>(*params); | ||
| 187 | + ASSERT_NE(stored, nullptr); | ||
| 188 | + EXPECT_TRUE(stored->valid); | ||
| 189 | + EXPECT_EQ(stored->duplicate_count, Expr(16)); | ||
| 190 | + EXPECT_EQ(stored->src_shape[1].expr, Expr(16)); | ||
| 191 | + EXPECT_EQ(stored->dst_shape[0].role, ascir_param::ParamExprRole::kSize); | ||
| 192 | +} | ||
| 193 | + | ||
| 104 | TEST(AscirNodeParamsTest, EnrichReduceParamsForArSingleReduce) { | 194 | TEST(AscirNodeParamsTest, EnrichReduceParamsForArSingleReduce) { |
| 105 | auto env = MakeArReduceEnv("max"); | 195 | auto env = MakeArReduceEnv("max"); |
| 106 | 196 | ||
| @@ -63,6 +63,8 @@ TEST_F(TestBackendScalarBrcE2e, ScalarBrcE2eCodegen) { | |||
| 63 | EXPECT_EQ(optimizer.Optimize(graph, fused_schedule_result), 0); | 63 | EXPECT_EQ(optimizer.Optimize(graph, fused_schedule_result), 0); |
| 64 | codegen::CodegenResult result; | 64 | codegen::CodegenResult result; |
| 65 | EXPECT_EQ(codegen.Generate(shape_info, fused_schedule_result, result), 0); | 65 | EXPECT_EQ(codegen.Generate(shape_info, fused_schedule_result, result), 0); |
| 66 | + EXPECT_EQ(result.tiling.find("local_3_actual_size"), std::string::npos); | ||
| 67 | + EXPECT_NE(result.tiling.find("z0z1t_size"), std::string::npos); | ||
| 66 | kernel_file << tilig_stub << RemoveSubDirInclude(result.kernel); | 68 | kernel_file << tilig_stub << RemoveSubDirInclude(result.kernel); |
| 67 | tiling_file << result.tiling; | 69 | tiling_file << result.tiling; |
| 68 | tiling_data_file << result.tiling_data; | 70 | tiling_data_file << result.tiling_data; |
Aautofuse/tests/v35/ut/att/gen_model_info/api_perf_register/test_broadcast_last_axis_perf_v2.cpp+464-0
| @@ -0,0 +1,464 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under terms of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 of the License. | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +#include <string> | ||
| 8 | +#include <vector> | ||
| 9 | + | ||
| 10 | +#include "gtest/gtest.h" | ||
| 11 | +#include "ascir_node_param/ascir_node_param.h" | ||
| 12 | +#include "att/api_perf_register/perf_param_v2.h" | ||
| 13 | +#include "base/att_const_values.h" | ||
| 14 | +#include "v35/att/api_perf_register/ascendc_api_perf/broadcast_api_perf_v2.h" | ||
| 15 | +#include "v35/att/api_perf_register/ascendc_api_perf/broadcast_last_axis_perf_v2.h" | ||
| 16 | + | ||
| 17 | +namespace att { | ||
| 18 | +namespace { | ||
| 19 | +using ascendcapi_v2::LastAxisBranch; | ||
| 20 | +using ascendcapi_v2::VfCostAccumulator; | ||
| 21 | + | ||
| 22 | +NodeDetail MakeLastNode(const std::string &dtype, const std::vector<int64_t> &src, const std::vector<int64_t> &dst) { | ||
| 23 | + NodeDetail node; | ||
| 24 | + node.broadcast_node_params.valid = true; | ||
| 25 | + node.broadcast_node_params.const_rank = static_cast<int32_t>(src.size()); | ||
| 26 | + for (size_t i = 0U; i < src.size(); ++i) { | ||
| 27 | + node.broadcast_node_params.src_shape.push_back({CreateExpr(src[i]), ascir_param::ParamExprRole::kSemantic}); | ||
| 28 | + node.broadcast_node_params.dst_shape.push_back({CreateExpr(dst[i]), ascir_param::ParamExprRole::kSemantic}); | ||
| 29 | + node.input_dims.push_back(CreateExpr(src[i])); | ||
| 30 | + node.output_dims.push_back(CreateExpr(dst[i])); | ||
| 31 | + node.repeats.push_back(CreateExpr(src[i])); | ||
| 32 | + } | ||
| 33 | + node.input_dtype = {dtype}; | ||
| 34 | + node.output_dtype = {dtype}; | ||
| 35 | + return node; | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +void AddExpected(const std::string &op, const std::string &dtype, int64_t count, VfCostAccumulator &acc) { | ||
| 39 | + ASSERT_EQ(ascendcapi_v2::AddVfInstructPerf(op, dtype, CreateExpr(count), 1U, acc), af::SUCCESS); | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +Expr ExpectedE2B(const std::string &dtype, int64_t count) { | ||
| 43 | + VfCostAccumulator acc; | ||
| 44 | + AddExpected(kUpdateMask, dtype, count, acc); | ||
| 45 | + AddExpected(kLoad, dtype, count, acc); | ||
| 46 | + AddExpected(kStore, dtype, count, acc); | ||
| 47 | + return ascendcapi_v2::GetVfCost(acc); | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +Expr ExpectedAligned(const std::string &dtype, int64_t loads, int64_t updates, int64_t stores) { | ||
| 51 | + VfCostAccumulator acc; | ||
| 52 | + AddExpected(kUpdateMask, dtype, updates, acc); | ||
| 53 | + AddExpected(kLoad, dtype, loads, acc); | ||
| 54 | + AddExpected(kStore, dtype, stores, acc); | ||
| 55 | + return ascendcapi_v2::GetVfCost(acc); | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +Expr ExpectedRankTwoLargerAligned() { | ||
| 59 | + return ExpectedAligned(kFloat16, 2, 4, 4); | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +Expr ExpectedRankThreeLargerAligned() { | ||
| 63 | + return ExpectedAligned(kFloat16, 6, 12, 12); | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +Expr ExpectedRankFourLargerAligned() { | ||
| 67 | + return ExpectedAligned(kFloat16, 24, 8, 24); | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +Expr ExpectedUnaligned(const std::string &dtype, int64_t loads, int64_t stores) { | ||
| 71 | + VfCostAccumulator acc; | ||
| 72 | + AddExpected(kLoad, dtype, loads, acc); | ||
| 73 | + AddExpected(kStore, dtype, stores, acc); | ||
| 74 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, dtype, acc.max_latency, acc.throughput, CreateExpr(1)), | ||
| 75 | + af::SUCCESS); | ||
| 76 | + return ascendcapi_v2::GetVfCost(acc); | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +Expr ExpectedDynamicLast(const std::string &dtype, int64_t outer, int64_t stores, int64_t helper_calls) { | ||
| 80 | + VfCostAccumulator acc; | ||
| 81 | + AddExpected(kLoad, dtype, outer, acc); | ||
| 82 | + AddExpected(kStore, dtype, stores, acc); | ||
| 83 | + std::string helper_dtype; | ||
| 84 | + EXPECT_EQ(ascendcapi_v2::GetEffectiveHelperDtype(dtype, helper_dtype), af::SUCCESS); | ||
| 85 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, helper_dtype, acc.max_latency, acc.throughput, | ||
| 86 | + CreateExpr(helper_calls)), | ||
| 87 | + af::SUCCESS); | ||
| 88 | + return ascendcapi_v2::GetVfCost(acc); | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +Expr ExpectedRankTwoGather(bool two) { | ||
| 92 | + VfCostAccumulator acc; | ||
| 93 | + AddExpected(kDuplicate, kInt16, two ? 2 : 1, acc); | ||
| 94 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kInt16, acc.max_latency, acc.throughput, CreateExpr(1)), | ||
| 95 | + af::SUCCESS); | ||
| 96 | + AddExpected(kDiv, kInt16, 1, acc); | ||
| 97 | + AddExpected(kStore, kInt16, 1, acc); | ||
| 98 | + if (two) { | ||
| 99 | + AddExpected(kMuls, kUInt16, 1, acc); | ||
| 100 | + AddExpected(kAdd, kUInt16, 1, acc); | ||
| 101 | + AddExpected(kAdds, kUInt16, 1, acc); | ||
| 102 | + } else { | ||
| 103 | + AddExpected(kUpdateMask, kFloat16, 1, acc); | ||
| 104 | + } | ||
| 105 | + AddExpected(kLoad, kUInt16, 1, acc); | ||
| 106 | + EXPECT_EQ( | ||
| 107 | + VfPerfUtils::AddVfInstructPerf(kPlaceholder, kUInt16, acc.max_latency, acc.throughput, CreateExpr(two ? 2 : 1)), | ||
| 108 | + af::SUCCESS); | ||
| 109 | + AddExpected(kStore, kFloat16, two ? 2 : 1, acc); | ||
| 110 | + if (two) { | ||
| 111 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kFloat16, acc.max_latency, acc.throughput, CreateExpr(1)), | ||
| 112 | + af::SUCCESS); | ||
| 113 | + } | ||
| 114 | + return ascendcapi_v2::GetVfCost(acc); | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +Expr ExpectedGatherWrapper(size_t rank, int64_t arithmetic, int64_t gathers) { | ||
| 118 | + VfCostAccumulator acc; | ||
| 119 | + AddExpected(kDuplicate, kInt16, static_cast<int64_t>(rank * 2U), acc); | ||
| 120 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kInt16, acc.max_latency, acc.throughput, CreateExpr(1)), | ||
| 121 | + af::SUCCESS); | ||
| 122 | + AddExpected(kDiv, kInt16, static_cast<int64_t>(rank), acc); | ||
| 123 | + AddExpected(kMul, kInt16, static_cast<int64_t>(rank + 1U), acc); | ||
| 124 | + AddExpected(kSub, kInt16, static_cast<int64_t>(rank), acc); | ||
| 125 | + AddExpected(kMulAddDst, kInt16, static_cast<int64_t>(rank - 1U), acc); | ||
| 126 | + AddExpected(kStore, kInt16, 1, acc); | ||
| 127 | + AddExpected(kDuplicate, kInt16, 2, acc); | ||
| 128 | + AddExpected(kLoad, kInt16, 1, acc); | ||
| 129 | + AddExpected(kMuls, kUInt16, arithmetic, acc); | ||
| 130 | + AddExpected(kAdd, kUInt16, arithmetic, acc); | ||
| 131 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kUInt16, acc.max_latency, acc.throughput, CreateExpr(gathers)), | ||
| 132 | + af::SUCCESS); | ||
| 133 | + AddExpected(kStore, kFloat16, gathers, acc); | ||
| 134 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kFloat16, acc.max_latency, acc.throughput, CreateExpr(1)), | ||
| 135 | + af::SUCCESS); | ||
| 136 | + return ascendcapi_v2::GetVfCost(acc); | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +Expr ExpectedRankTwoB8GatherOne() { | ||
| 140 | + VfCostAccumulator acc; | ||
| 141 | + AddExpected(kDuplicate, kInt16, 2, acc); | ||
| 142 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kInt16, acc.max_latency, acc.throughput, CreateExpr(2)), | ||
| 143 | + af::SUCCESS); | ||
| 144 | + AddExpected(kDiv, kInt16, 2, acc); | ||
| 145 | + AddExpected(kStore, kInt16, 2, acc); | ||
| 146 | + AddExpected(kUpdateMask, kUInt8, 1, acc); | ||
| 147 | + AddExpected(kLoad, kUInt16, 2, acc); | ||
| 148 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kUInt16, acc.max_latency, acc.throughput, CreateExpr(2)), | ||
| 149 | + af::SUCCESS); | ||
| 150 | + AddExpected(kDeInterleave, kUInt8, 1, acc); | ||
| 151 | + AddExpected(kStore, kUInt8, 1, acc); | ||
| 152 | + return ascendcapi_v2::GetVfCost(acc); | ||
| 153 | +} | ||
| 154 | + | ||
| 155 | +Expr ExpectedRankThreeB8GatherWrapper() { | ||
| 156 | + VfCostAccumulator acc; | ||
| 157 | + AddExpected(kDuplicate, kInt16, 6, acc); | ||
| 158 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kInt16, acc.max_latency, acc.throughput, CreateExpr(2)), | ||
| 159 | + af::SUCCESS); | ||
| 160 | + AddExpected(kDiv, kInt16, 6, acc); | ||
| 161 | + AddExpected(kMul, kInt16, 8, acc); | ||
| 162 | + AddExpected(kSub, kInt16, 6, acc); | ||
| 163 | + AddExpected(kMulAddDst, kInt16, 4, acc); | ||
| 164 | + AddExpected(kStore, kInt16, 2, acc); | ||
| 165 | + AddExpected(kDuplicate, kInt16, 2, acc); | ||
| 166 | + AddExpected(kLoad, kInt16, 2, acc); | ||
| 167 | + AddExpected(kMuls, kUInt16, 2, acc); | ||
| 168 | + AddExpected(kAdd, kUInt16, 4, acc); | ||
| 169 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kUInt16, acc.max_latency, acc.throughput, CreateExpr(2)), | ||
| 170 | + af::SUCCESS); | ||
| 171 | + AddExpected(kDeInterleave, kUInt8, 1, acc); | ||
| 172 | + AddExpected(kStore, kUInt8, 1, acc); | ||
| 173 | + EXPECT_EQ(VfPerfUtils::AddVfInstructPerf(kPlaceholder, kUInt8, acc.max_latency, acc.throughput, CreateExpr(1)), | ||
| 174 | + af::SUCCESS); | ||
| 175 | + return ascendcapi_v2::GetVfCost(acc); | ||
| 176 | +} | ||
| 177 | + | ||
| 178 | +ascendcapi_v2::BroadcastTilingInfo BuildTiling(const NodeDetail &node) { | ||
| 179 | + ascendcapi_v2::ParamExprInputs src_inputs{node.input_dims, node.input_dims, node.repeats}; | ||
| 180 | + ascendcapi_v2::ParamExprInputs dst_inputs{node.output_dims, node.output_dims, node.output_dims}; | ||
| 181 | + ascendcapi_v2::BroadcastTilingInfo tiling; | ||
| 182 | + EXPECT_EQ( | ||
| 183 | + ascendcapi_v2::BuildBroadcastTiling(node.broadcast_node_params.src_shape, node.broadcast_node_params.dst_shape, | ||
| 184 | + node.input_dtype[0], src_inputs, dst_inputs, tiling), | ||
| 185 | + af::SUCCESS); | ||
| 186 | + return tiling; | ||
| 187 | +} | ||
| 188 | + | ||
| 189 | +Expr ActualCost(const NodeDetail &node) { | ||
| 190 | + PerfOutputInfo perf; | ||
| 191 | + EXPECT_EQ(ascendcperf_v2::BroadcastPerf(node, perf), af::SUCCESS); | ||
| 192 | + return perf.pipe_res[PipeType::AIV_VEC]; | ||
| 193 | +} | ||
| 194 | + | ||
| 195 | +Expr ActualLastCost(const NodeDetail &node, const ascendcapi_v2::BroadcastTilingInfo &tiling) { | ||
| 196 | + PerfOutputInfo perf; | ||
| 197 | + EXPECT_EQ(ascendcperf_v2::BuildLastAxisPerf(node, tiling, perf), af::SUCCESS); | ||
| 198 | + return perf.pipe_res[PipeType::AIV_VEC]; | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | +struct LastBranchCase { | ||
| 202 | + std::string dtype; | ||
| 203 | + std::vector<int64_t> src; | ||
| 204 | + std::vector<int64_t> dst; | ||
| 205 | + LastAxisBranch branch; | ||
| 206 | +}; | ||
| 207 | + | ||
| 208 | +struct FixedVectorSpec { | ||
| 209 | + int64_t vl; | ||
| 210 | + int64_t half_vl; | ||
| 211 | + int64_t block; | ||
| 212 | +}; | ||
| 213 | + | ||
| 214 | +FixedVectorSpec GetFixedVectorSpec(const std::string &dtype) { | ||
| 215 | + if (dtype == kUInt8 || dtype == kInt8) { | ||
| 216 | + return {256, 128, 32}; | ||
| 217 | + } | ||
| 218 | + if (dtype == kFloat32 || dtype == kUInt32 || dtype == kInt32) { | ||
| 219 | + return {64, 32, 8}; | ||
| 220 | + } | ||
| 221 | + return {128, 64, 16}; | ||
| 222 | +} | ||
| 223 | + | ||
| 224 | +int64_t Product(const std::vector<int64_t> &shape) { | ||
| 225 | + int64_t result = 1; | ||
| 226 | + for (const auto dim : shape) { | ||
| 227 | + result *= dim; | ||
| 228 | + } | ||
| 229 | + return result; | ||
| 230 | +} | ||
| 231 | + | ||
| 232 | +// Independent loop specification copied from the source helper conditions; it is not a production classifier oracle. | ||
| 233 | +LastAxisBranch FixedLastAxisBranch(const std::string &dtype, const std::vector<int64_t> &src, | ||
| 234 | + const std::vector<int64_t> &dst, int32_t const_rank) { | ||
| 235 | + const auto spec = GetFixedVectorSpec(dtype); | ||
| 236 | + if (const_rank > 4) { | ||
| 237 | + return dst.back() <= spec.vl ? LastAxisBranch::kDynamicLessThanVlUnaligned | ||
| 238 | + : LastAxisBranch::kDynamicLargerThanVlUnaligned; | ||
| 239 | + } | ||
| 240 | + size_t rank = dst.size(); | ||
| 241 | + size_t first_dim = 0U; | ||
| 242 | + while (rank > 1U && dst[first_dim] == 1) { | ||
| 243 | + ++first_dim; | ||
| 244 | + --rank; | ||
| 245 | + } | ||
| 246 | + const int64_t last = dst.back(); | ||
| 247 | + const bool b8 = dtype == kUInt8 || dtype == kInt8; | ||
| 248 | + const bool aligned = last % spec.block == 0; | ||
| 249 | + const bool second_axis_contiguous = src.size() > 1U && src[src.size() - 2U] != 1; | ||
| 250 | + const int64_t dst_size = Product(dst); | ||
| 251 | + if (rank == 2U && last == spec.block && !b8) { | ||
| 252 | + return LastAxisBranch::kE2B; | ||
| 253 | + } | ||
| 254 | + if (rank == 3U && !b8 && second_axis_contiguous && last == spec.block && dst[dst.size() - 2U] * last > spec.half_vl && | ||
| 255 | + dst[dst.size() - 2U] % (spec.vl / spec.block) == 0) { | ||
| 256 | + return dst[dst.size() - 2U] * last > spec.vl ? LastAxisBranch::kE2BLargerThanVl : LastAxisBranch::kE2BLessThanVl; | ||
| 257 | + } | ||
| 258 | + if (rank == 4U && !b8 && src.size() > 2U && src[src.size() - 2U] != 1 && last == spec.block && | ||
| 259 | + dst[dst.size() - 2U] % (spec.vl / spec.block) == 0) { | ||
| 260 | + return LastAxisBranch::kE2B; | ||
| 261 | + } | ||
| 262 | + if (last < spec.half_vl && (rank == 2U || !b8)) { | ||
| 263 | + if (rank == 2U) { | ||
| 264 | + return dst_size < spec.vl ? LastAxisBranch::kGatherOne : LastAxisBranch::kGatherTwo; | ||
| 265 | + } | ||
| 266 | + return rank == 3U ? LastAxisBranch::kGatherWrapper : LastAxisBranch::kGatherWrapperForFourDim; | ||
| 267 | + } | ||
| 268 | + if (last <= spec.vl) { | ||
| 269 | + if (rank == 2U) { | ||
| 270 | + return LastAxisBranch::kLessThanVlUnaligned; | ||
| 271 | + } | ||
| 272 | + if (rank == 3U) { | ||
| 273 | + return aligned ? LastAxisBranch::kLessThanVlAligned : LastAxisBranch::kLessThanVlUnaligned; | ||
| 274 | + } | ||
| 275 | + if (aligned) { | ||
| 276 | + return LastAxisBranch::kLessThanVlAligned; | ||
| 277 | + } | ||
| 278 | + return const_rank == -1 ? LastAxisBranch::kFallback : LastAxisBranch::kLessThanVlUnaligned; | ||
| 279 | + } | ||
| 280 | + if (aligned) { | ||
| 281 | + return LastAxisBranch::kLargerThanVlAligned; | ||
| 282 | + } | ||
| 283 | + return const_rank == -1 ? LastAxisBranch::kFallback : LastAxisBranch::kLargerThanVlUnaligned; | ||
| 284 | +} | ||
| 285 | + | ||
| 286 | +TEST(BroadcastLastAxisPerfV2, RankTwoCoversImplementationLeavesAndBoundaries) { | ||
| 287 | + const std::vector<LastBranchCase> cases = { | ||
| 288 | + {kFloat16, {2, 1}, {2, 16}, LastAxisBranch::kE2B}, | ||
| 289 | + {kFloat16, {2, 1}, {2, 63}, LastAxisBranch::kGatherOne}, | ||
| 290 | + {kFloat16, {15, 1}, {15, 8}, LastAxisBranch::kGatherOne}, | ||
| 291 | + {kFloat16, {16, 1}, {16, 8}, LastAxisBranch::kGatherTwo}, | ||
| 292 | + {kFloat16, {17, 1}, {17, 8}, LastAxisBranch::kGatherTwo}, | ||
| 293 | + {kFloat16, {8, 1}, {8, 16}, LastAxisBranch::kE2B}, | ||
| 294 | + {kFloat16, {9, 1}, {9, 15}, LastAxisBranch::kGatherTwo}, | ||
| 295 | + {kFloat16, {2, 1}, {2, 17}, LastAxisBranch::kGatherOne}, | ||
| 296 | + {kFloat16, {2, 1}, {2, 64}, LastAxisBranch::kLessThanVlUnaligned}, | ||
| 297 | + {kFloat16, {2, 1}, {2, 65}, LastAxisBranch::kLessThanVlUnaligned}, | ||
| 298 | + {kFloat16, {2, 1}, {2, 127}, LastAxisBranch::kLessThanVlUnaligned}, | ||
| 299 | + {kFloat16, {2, 1}, {2, 128}, LastAxisBranch::kLessThanVlUnaligned}, | ||
| 300 | + {kFloat16, {2, 1}, {2, 144}, LastAxisBranch::kLargerThanVlAligned}, | ||
| 301 | + {kFloat16, {2, 1}, {2, 129}, LastAxisBranch::kLargerThanVlUnaligned}, | ||
| 302 | + }; | ||
| 303 | + for (const auto &item : cases) { | ||
| 304 | + const auto node = MakeLastNode(item.dtype, item.src, item.dst); | ||
| 305 | + EXPECT_EQ(FixedLastAxisBranch(item.dtype, item.src, item.dst, static_cast<int32_t>(item.dst.size())), item.branch); | ||
| 306 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(node), item.branch); | ||
| 307 | + } | ||
| 308 | +} | ||
| 309 | + | ||
| 310 | +TEST(BroadcastLastAxisPerfV2, RankThreeCoversBothE2BPathsAndGatherWrapper) { | ||
| 311 | + const std::vector<LastBranchCase> cases = { | ||
| 312 | + {kFloat16, {2, 8, 1}, {2, 8, 16}, LastAxisBranch::kE2BLessThanVl}, | ||
| 313 | + {kFloat16, {2, 16, 1}, {2, 16, 16}, LastAxisBranch::kE2BLargerThanVl}, | ||
| 314 | + {kFloat16, {2, 3, 1}, {2, 3, 15}, LastAxisBranch::kGatherWrapper}, | ||
| 315 | + {kFloat16, {2, 3, 1}, {2, 3, 64}, LastAxisBranch::kLessThanVlAligned}, | ||
| 316 | + {kFloat16, {2, 3, 1}, {2, 3, 65}, LastAxisBranch::kLessThanVlUnaligned}, | ||
| 317 | + {kFloat16, {2, 3, 1}, {2, 3, 144}, LastAxisBranch::kLargerThanVlAligned}, | ||
| 318 | + {kFloat16, {2, 3, 1}, {2, 3, 129}, LastAxisBranch::kLargerThanVlUnaligned}, | ||
| 319 | + }; | ||
| 320 | + for (const auto &item : cases) { | ||
| 321 | + const auto node = MakeLastNode(item.dtype, item.src, item.dst); | ||
| 322 | + EXPECT_EQ(FixedLastAxisBranch(item.dtype, item.src, item.dst, static_cast<int32_t>(item.dst.size())), item.branch); | ||
| 323 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(node), item.branch); | ||
| 324 | + } | ||
| 325 | +} | ||
| 326 | + | ||
| 327 | +TEST(BroadcastLastAxisPerfV2, RankThreeDynamicRankKeepsAlignedStaticAndFallsBackOnUnaligned) { | ||
| 328 | + auto unaligned_node = MakeLastNode(kFloat16, {2, 3, 1}, {2, 3, 65}); | ||
| 329 | + unaligned_node.broadcast_node_params.const_rank = -1; | ||
| 330 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(unaligned_node), LastAxisBranch::kDynamicLessThanVlUnaligned); | ||
| 331 | + | ||
| 332 | + auto aligned_node = MakeLastNode(kFloat16, {2, 3, 1}, {2, 3, 64}); | ||
| 333 | + aligned_node.broadcast_node_params.const_rank = -1; | ||
| 334 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(aligned_node), LastAxisBranch::kLessThanVlAligned); | ||
| 335 | +} | ||
| 336 | + | ||
| 337 | +TEST(BroadcastLastAxisPerfV2, RankFourFoldedToThreeDimAlignedUsesAlignedBranch) { | ||
| 338 | + const auto node = MakeLastNode(kFloat16, {1, 1, 2, 1}, {1, 1, 2, 64}); | ||
| 339 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(node), LastAxisBranch::kLessThanVlAligned); | ||
| 340 | +} | ||
| 341 | + | ||
| 342 | +TEST(BroadcastLastAxisPerfV2, RankFourCoversWrapperLeavesAndReductionToThreeDim) { | ||
| 343 | + const std::vector<LastBranchCase> cases = { | ||
| 344 | + {kFloat16, {2, 3, 8, 1}, {2, 3, 8, 16}, LastAxisBranch::kE2B}, | ||
| 345 | + {kFloat16, {2, 3, 2, 1}, {2, 3, 2, 15}, LastAxisBranch::kGatherWrapperForFourDim}, | ||
| 346 | + {kFloat16, {2, 3, 2, 1}, {2, 3, 2, 64}, LastAxisBranch::kLessThanVlAligned}, | ||
| 347 | + {kFloat16, {2, 3, 2, 1}, {2, 3, 2, 65}, LastAxisBranch::kLessThanVlUnaligned}, | ||
| 348 | + {kFloat16, {2, 3, 2, 1}, {2, 3, 2, 144}, LastAxisBranch::kLargerThanVlAligned}, | ||
| 349 | + {kFloat16, {2, 3, 2, 1}, {2, 3, 2, 129}, LastAxisBranch::kLargerThanVlUnaligned}, | ||
| 350 | + {kFloat16, {1, 2, 3, 1}, {1, 2, 3, 65}, LastAxisBranch::kLessThanVlUnaligned}, | ||
| 351 | + }; | ||
| 352 | + for (const auto &item : cases) { | ||
| 353 | + const auto node = MakeLastNode(item.dtype, item.src, item.dst); | ||
| 354 | + EXPECT_EQ(FixedLastAxisBranch(item.dtype, item.src, item.dst, static_cast<int32_t>(item.dst.size())), item.branch); | ||
| 355 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(node), item.branch); | ||
| 356 | + } | ||
| 357 | +} | ||
| 358 | + | ||
| 359 | +TEST(BroadcastLastAxisPerfV2, LeafHelpersUseExactE2BAndAlignedCounts) { | ||
| 360 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 8, 1}, {2, 8, 16})), ExpectedE2B(kFloat16, 2)); | ||
| 361 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 16, 1}, {2, 16, 16})), ExpectedE2B(kFloat16, 4)); | ||
| 362 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 8, 1}, {2, 3, 8, 16})), ExpectedE2B(kFloat16, 6)); | ||
| 363 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 2, 1}, {2, 3, 2, 144})), ExpectedRankFourLargerAligned()); | ||
| 364 | +} | ||
| 365 | + | ||
| 366 | +TEST(BroadcastLastAxisPerfV2, LeafHelpersUseExactUnalignedCounts) { | ||
| 367 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 1}, {2, 65})), ExpectedUnaligned(kFloat16, 2, 2)); | ||
| 368 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 1}, {2, 3, 65})), ExpectedUnaligned(kFloat16, 6, 6)); | ||
| 369 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 2, 1}, {2, 3, 2, 65})), ExpectedUnaligned(kFloat16, 12, 12)); | ||
| 370 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {1, 2, 3, 1}, {1, 2, 3, 65})), ExpectedUnaligned(kFloat16, 6, 6)); | ||
| 371 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 1}, {2, 3, 129})), ExpectedUnaligned(kFloat16, 6, 12)); | ||
| 372 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 2, 1}, {2, 3, 2, 129})), ExpectedUnaligned(kFloat16, 24, 24)); | ||
| 373 | +} | ||
| 374 | + | ||
| 375 | +TEST(BroadcastLastAxisPerfV2, RankTwoGatherAndAlignedHelpersUseExactCounts) { | ||
| 376 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 1}, {2, 15})), ExpectedRankTwoGather(false)); | ||
| 377 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {9, 1}, {9, 15})), ExpectedRankTwoGather(true)); | ||
| 378 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 1}, {2, 144})), ExpectedRankTwoLargerAligned()); | ||
| 379 | + EXPECT_EQ(ActualCost(MakeLastNode(kUInt8, {2, 1}, {2, 63})), ExpectedRankTwoB8GatherOne()); | ||
| 380 | +} | ||
| 381 | + | ||
| 382 | +TEST(BroadcastLastAxisPerfV2, RankThreeAndFourHelpersUseExactCounts) { | ||
| 383 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 1}, {2, 3, 15})), ExpectedGatherWrapper(3U, 3, 2)); | ||
| 384 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 2, 1}, {2, 3, 2, 15})), ExpectedGatherWrapper(4U, 5, 3)); | ||
| 385 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 1}, {2, 3, 64})), ExpectedAligned(kFloat16, 6, 1, 6)); | ||
| 386 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 1}, {2, 3, 144})), ExpectedRankThreeLargerAligned()); | ||
| 387 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 2, 1}, {2, 3, 2, 64})), ExpectedAligned(kFloat16, 12, 1, 12)); | ||
| 388 | + EXPECT_EQ(ActualCost(MakeLastNode(kUInt8, {2, 3, 1}, {2, 3, 15})), ExpectedRankThreeB8GatherWrapper()); | ||
| 389 | +} | ||
| 390 | + | ||
| 391 | +TEST(BroadcastLastAxisPerfV2, RankTwoThreeFourLargerAlignedUseDifferentLoadCounts) { | ||
| 392 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 1}, {2, 144})), ExpectedRankTwoLargerAligned()); | ||
| 393 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 1}, {2, 3, 144})), ExpectedRankThreeLargerAligned()); | ||
| 394 | + EXPECT_EQ(ActualCost(MakeLastNode(kFloat16, {2, 3, 2, 1}, {2, 3, 2, 144})), ExpectedRankFourLargerAligned()); | ||
| 395 | +} | ||
| 396 | + | ||
| 397 | +TEST(BroadcastLastAxisPerfV2, DynamicRanksFiveThroughNineUseOriginalPrefixForHelperCalls) { | ||
| 398 | + const std::vector<std::vector<int64_t>> dst_shapes = { | ||
| 399 | + {2, 3, 2, 3, 65}, {2, 3, 2, 3, 2, 65}, {2, 3, 2, 3, 2, 3, 65}, | ||
| 400 | + {2, 3, 2, 3, 2, 3, 2, 65}, {2, 3, 2, 3, 2, 3, 2, 3, 65}, | ||
| 401 | + }; | ||
| 402 | + for (const auto &dst : dst_shapes) { | ||
| 403 | + auto src = dst; | ||
| 404 | + for (size_t i = 0U; i < src.size(); ++i) { | ||
| 405 | + if (i % 2U == (src.size() - 1U) % 2U) { | ||
| 406 | + src[i] = 1; | ||
| 407 | + } | ||
| 408 | + } | ||
| 409 | + const auto node = MakeLastNode(kFloat16, src, dst); | ||
| 410 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(node), LastAxisBranch::kDynamicLessThanVlUnaligned); | ||
| 411 | + int64_t outer = 1; | ||
| 412 | + for (size_t i = 0U; i + 1U < dst.size(); ++i) { | ||
| 413 | + outer *= dst[i]; | ||
| 414 | + } | ||
| 415 | + const int64_t helper_calls = [&dst]() { | ||
| 416 | + int64_t result = 1; | ||
| 417 | + for (size_t i = 0U; i + 4U < dst.size(); ++i) { | ||
| 418 | + result *= dst[i]; | ||
| 419 | + } | ||
| 420 | + return result; | ||
| 421 | + }(); | ||
| 422 | + EXPECT_EQ(ActualCost(node), ExpectedDynamicLast(kFloat16, outer, outer, helper_calls)); | ||
| 423 | + } | ||
| 424 | +} | ||
| 425 | + | ||
| 426 | +TEST(BroadcastLastAxisPerfV2, DynamicOriginalRankFourUsesOneHelperCall) { | ||
| 427 | + const auto node = MakeLastNode(kUInt64, {2, 3, 1, 1}, {2, 3, 2, 4}); | ||
| 428 | + const auto tiling = BuildTiling(node); | ||
| 429 | + EXPECT_EQ(ascendcapi_v2::GetLastAxisBranch(tiling, kUInt64, 4), LastAxisBranch::kDynamicLessThanVlUnaligned); | ||
| 430 | + EXPECT_EQ(ActualLastCost(node, tiling), ExpectedDynamicLast(kUInt64, 48, 48, 1)); | ||
| 431 | +} | ||
| 432 | + | ||
| 433 | +TEST(BroadcastLastAxisPerfV2, DynamicB8CoversBothVectorSizePaths) { | ||
| 434 | + auto less = MakeLastNode(kUInt8, {1, 3, 1, 3, 1}, {2, 3, 2, 3, 256}); | ||
| 435 | + auto larger = MakeLastNode(kUInt8, {1, 3, 1, 3, 1}, {2, 3, 2, 3, 257}); | ||
| 436 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(less), LastAxisBranch::kDynamicLessThanVlUnaligned); | ||
| 437 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(larger), LastAxisBranch::kDynamicLargerThanVlUnaligned); | ||
| 438 | +} | ||
| 439 | + | ||
| 440 | +TEST(BroadcastLastAxisPerfV2, DynamicLargerHelperUsesExactLoopCounts) { | ||
| 441 | + const auto node = MakeLastNode(kFloat16, {1, 3, 1, 3, 1}, {2, 3, 2, 3, 129}); | ||
| 442 | + EXPECT_EQ(ActualCost(node), ExpectedDynamicLast(kFloat16, 36, 72, 2)); | ||
| 443 | +} | ||
| 444 | + | ||
| 445 | +TEST(BroadcastLastAxisPerfV2, B64LastAxisBecomesEffectiveB32AndAppendsDimension) { | ||
| 446 | + const auto tiling = BuildTiling(MakeLastNode(kUInt64, {2, 1}, {2, 4})); | ||
| 447 | + std::string helper_dtype; | ||
| 448 | + ASSERT_EQ(ascendcapi_v2::GetEffectiveHelperDtype(kUInt64, helper_dtype), af::SUCCESS); | ||
| 449 | + EXPECT_EQ(helper_dtype, kUInt32); | ||
| 450 | + ASSERT_EQ(tiling.rank, 3U); | ||
| 451 | + EXPECT_EQ(tiling.dst_shape[2], CreateExpr(2)); | ||
| 452 | + EXPECT_FALSE(ascendcapi_v2::IsLastAxisBroadcast(tiling)); | ||
| 453 | +} | ||
| 454 | + | ||
| 455 | +TEST(BroadcastLastAxisPerfV2, B64RankNineUsesLoopNumAndStrideNine) { | ||
| 456 | + const auto zero_stride = BuildTiling(MakeLastNode(kUInt64, {1, 3, 1, 3, 1, 3, 1, 3, 1}, {2, 3, 2, 3, 2, 3, 2, 3, 4})); | ||
| 457 | + // ApplyB64Tiling uses the original dstShape[0] as loop_num and appends its stride at index 9. | ||
| 458 | + EXPECT_EQ(zero_stride.loop_num, CreateExpr(2)); | ||
| 459 | + ASSERT_EQ(zero_stride.src_stride.size(), 10U); | ||
| 460 | + EXPECT_EQ(zero_stride.src_stride[9], CreateExpr(0)); | ||
| 461 | +} | ||
| 462 | + | ||
| 463 | +} // namespace | ||
| 464 | +} // namespace att | ||
| @@ -0,0 +1,138 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under terms of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 of the License. | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +#include <string> | ||
| 8 | +#include <vector> | ||
| 9 | + | ||
| 10 | +#include "gtest/gtest.h" | ||
| 11 | +#include "att/api_perf_register/perf_param_v2.h" | ||
| 12 | +#include "ascir_node_param/ascir_node_param.h" | ||
| 13 | +#include "base/att_const_values.h" | ||
| 14 | +#include "v35/att/api_perf_register/ascendc_api_perf/broadcast_api_perf_v2.h" | ||
| 15 | +#include "v35/att/api_perf_register/ascendc_api_perf/broadcast_last_axis_perf_v2.h" | ||
| 16 | +#include "v35/att/api_perf_register/ascendc_api_perf/broadcast_nlast_axis_perf_v2.h" | ||
| 17 | + | ||
| 18 | +namespace att { | ||
| 19 | +namespace { | ||
| 20 | +using ascendcapi_v2::NlastAxisBranch; | ||
| 21 | + | ||
| 22 | +NodeDetail MakeSymbolicNode(const std::vector<Expr> &src, const std::vector<Expr> &dst) { | ||
| 23 | + NodeDetail node; | ||
| 24 | + node.broadcast_node_params.valid = true; | ||
| 25 | + node.broadcast_node_params.const_rank = static_cast<int32_t>(src.size()); | ||
| 26 | + for (size_t i = 0U; i < src.size(); ++i) { | ||
| 27 | + node.broadcast_node_params.src_shape.push_back({src[i], ascir_param::ParamExprRole::kSemantic}); | ||
| 28 | + node.broadcast_node_params.dst_shape.push_back({dst[i], ascir_param::ParamExprRole::kSemantic}); | ||
| 29 | + node.input_dims.push_back(src[i]); | ||
| 30 | + node.output_dims.push_back(dst[i]); | ||
| 31 | + node.repeats.push_back(src[i]); | ||
| 32 | + } | ||
| 33 | + node.input_dtype = {kFloat16}; | ||
| 34 | + node.output_dtype = {kFloat16}; | ||
| 35 | + return node; | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +TEST(BroadcastLastAxisPerfV2Symbolic, RankTwoRegistersFinalDynamicTreeVariable) { | ||
| 39 | + const auto node = MakeSymbolicNode({CreateExpr(2), CreateExpr(1)}, {CreateExpr(2), CreateExpr("last")}); | ||
| 40 | + PerfOutputInfo perf; | ||
| 41 | + ASSERT_EQ(ascendcperf_v2::BroadcastPerf(node, perf), af::SUCCESS); | ||
| 42 | + ASSERT_FALSE(perf.ternary_ops.empty()); | ||
| 43 | + const auto replacements = ConcursiveReplaceVars(perf.ternary_ops); | ||
| 44 | + EXPECT_NE(Str(perf.pipe_res.at(PipeType::AIV_VEC).Replace(replacements)).find("TernaryOp"), std::string::npos); | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +TEST(BroadcastLastAxisPerfV2Symbolic, RankThreeE2BConditionsCascadeIntoFinalVariable) { | ||
| 48 | + const auto node = MakeSymbolicNode({CreateExpr(2), CreateExpr("middle"), CreateExpr(1)}, | ||
| 49 | + {CreateExpr(2), CreateExpr("middle"), CreateExpr("last")}); | ||
| 50 | + PerfOutputInfo perf; | ||
| 51 | + ASSERT_EQ(ascendcperf_v2::BroadcastPerf(node, perf), af::SUCCESS); | ||
| 52 | + ASSERT_FALSE(perf.ternary_ops.empty()); | ||
| 53 | + const auto replacements = ConcursiveReplaceVars(perf.ternary_ops); | ||
| 54 | + const auto expression = Str(perf.pipe_res.at(PipeType::AIV_VEC).Replace(replacements)); | ||
| 55 | + EXPECT_NE(expression.find("last"), std::string::npos); | ||
| 56 | + EXPECT_NE(expression.find("TernaryOp"), std::string::npos); | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +TEST(BroadcastLastAxisPerfV2Symbolic, RankGreaterThanFourUnknownLastUsesDynamicTernaryRoute) { | ||
| 60 | + auto node = MakeSymbolicNode({CreateExpr(2), CreateExpr(1), CreateExpr(2), CreateExpr(1), CreateExpr("last")}, | ||
| 61 | + {CreateExpr(2), CreateExpr(3), CreateExpr(2), CreateExpr(3), CreateExpr("last")}); | ||
| 62 | + node.broadcast_node_params.const_rank = -1; | ||
| 63 | + const auto src_inputs = ascendcapi_v2::ParamExprInputs{node.input_dims, node.input_dims, node.repeats}; | ||
| 64 | + const auto dst_inputs = ascendcapi_v2::ParamExprInputs{node.output_dims, node.output_dims, node.output_dims}; | ||
| 65 | + ascendcapi_v2::BroadcastTilingInfo tiling; | ||
| 66 | + ASSERT_EQ( | ||
| 67 | + ascendcapi_v2::BuildBroadcastTiling(node.broadcast_node_params.src_shape, node.broadcast_node_params.dst_shape, | ||
| 68 | + kFloat16, src_inputs, dst_inputs, tiling), | ||
| 69 | + af::SUCCESS); | ||
| 70 | + EXPECT_EQ(ascendcapi_v2::GetLastAxisBranch(tiling, kFloat16, -1), ascendcapi_v2::LastAxisBranch::kFallback); | ||
| 71 | + EXPECT_EQ(ascendcperf_v2::GetLastAxisPerfBranch(node), ascendcapi_v2::LastAxisBranch::kFallback); | ||
| 72 | + PerfOutputInfo perf; | ||
| 73 | + ASSERT_EQ(ascendcperf_v2::BroadcastPerf(node, perf), af::SUCCESS); | ||
| 74 | + ASSERT_FALSE(perf.ternary_ops.empty()); | ||
| 75 | + const auto replacements = ConcursiveReplaceVars(perf.ternary_ops); | ||
| 76 | + const auto expression = Str(perf.pipe_res.at(PipeType::AIV_VEC).Replace(replacements)); | ||
| 77 | + EXPECT_NE(expression.find("last"), std::string::npos); | ||
| 78 | + EXPECT_NE(expression.find("TernaryOp"), std::string::npos); | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +TEST(BroadcastNlastAxisPerfV2Symbolic, RankTwoUnknownLastBuildsTernaryTree) { | ||
| 82 | + const auto node = MakeSymbolicNode({CreateExpr(1), CreateExpr("last")}, {CreateExpr(2), CreateExpr("last")}); | ||
| 83 | + PerfOutputInfo perf; | ||
| 84 | + ASSERT_EQ(ascendcperf_v2::BroadcastPerf(node, perf), af::SUCCESS); | ||
| 85 | + ASSERT_FALSE(perf.ternary_ops.empty()); | ||
| 86 | + const auto replacements = ConcursiveReplaceVars(perf.ternary_ops); | ||
| 87 | + const auto expression = Str(perf.pipe_res.at(PipeType::AIV_VEC).Replace(replacements)); | ||
| 88 | + EXPECT_NE(expression.find("last"), std::string::npos); | ||
| 89 | + EXPECT_NE(expression.find("TernaryOp"), std::string::npos); | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +TEST(BroadcastNlastAxisPerfV2Symbolic, RankThreeAndFourUnknownLastBuildTernaryTrees) { | ||
| 93 | + for (const size_t rank : {3U, 4U}) { | ||
| 94 | + std::vector<Expr> src(rank, CreateExpr(1)); | ||
| 95 | + std::vector<Expr> dst(rank, CreateExpr(2)); | ||
| 96 | + src.back() = CreateExpr(32); | ||
| 97 | + dst.back() = CreateExpr("last"); | ||
| 98 | + const auto node = MakeSymbolicNode(src, dst); | ||
| 99 | + PerfOutputInfo perf; | ||
| 100 | + ASSERT_EQ(ascendcperf_v2::BroadcastPerf(node, perf), af::SUCCESS); | ||
| 101 | + EXPECT_FALSE(perf.ternary_ops.empty()); | ||
| 102 | + const auto replacements = ConcursiveReplaceVars(perf.ternary_ops); | ||
| 103 | + EXPECT_NE(Str(perf.pipe_res.at(PipeType::AIV_VEC).Replace(replacements)).find("TernaryOp"), std::string::npos); | ||
| 104 | + } | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +TEST(BroadcastNlastAxisPerfV2Symbolic, RankGreaterThanFourUnknownLastBuildsDynamicTree) { | ||
| 108 | + const auto node = MakeSymbolicNode({CreateExpr(2), CreateExpr(1), CreateExpr(2), CreateExpr(1), CreateExpr("last")}, | ||
| 109 | + {CreateExpr(2), CreateExpr(3), CreateExpr(2), CreateExpr(3), CreateExpr("last")}); | ||
| 110 | + EXPECT_NE(ascendcperf_v2::GetNlastAxisPerfBranch(node), NlastAxisBranch::kFallback); | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +TEST(BroadcastNlastAxisPerfV2Symbolic, RankGreaterThanFourUnknownB64LastUsesDynamicTree) { | ||
| 114 | + auto node = MakeSymbolicNode({CreateExpr(2), CreateExpr(1), CreateExpr(2), CreateExpr(1), CreateExpr("last")}, | ||
| 115 | + {CreateExpr(2), CreateExpr(3), CreateExpr(2), CreateExpr(3), CreateExpr("last")}); | ||
| 116 | + node.input_dtype = {kUInt64}; | ||
| 117 | + node.output_dtype = {kUInt64}; | ||
| 118 | + EXPECT_NE(ascendcperf_v2::GetNlastAxisPerfBranch(node), NlastAxisBranch::kFallback); | ||
| 119 | + EXPECT_FALSE(ascendcperf_v2::IsBroadcastFallback(node)); | ||
| 120 | + PerfOutputInfo perf; | ||
| 121 | + ASSERT_EQ(ascendcperf_v2::BroadcastPerf(node, perf), af::SUCCESS); | ||
| 122 | + ASSERT_GE(perf.ternary_ops.size(), 4U); | ||
| 123 | + for (const auto &entry : perf.ternary_ops) { | ||
| 124 | + EXPECT_FALSE(entry.second.GetTernaryOpStr().empty()); | ||
| 125 | + } | ||
| 126 | +} | ||
| 127 | + | ||
| 128 | +TEST(BroadcastNlastAxisPerfV2Symbolic, RankGreaterThanFourB8SkipsGatherLeaf) { | ||
| 129 | + auto node = MakeSymbolicNode({CreateExpr(2), CreateExpr(1), CreateExpr(2), CreateExpr(1), CreateExpr("last")}, | ||
| 130 | + {CreateExpr(2), CreateExpr(3), CreateExpr(2), CreateExpr(3), CreateExpr("last")}); | ||
| 131 | + node.input_dtype = {kUInt8}; | ||
| 132 | + node.output_dtype = {kUInt8}; | ||
| 133 | + EXPECT_EQ(ascendcperf_v2::GetNlastAxisPerfBranch(node), NlastAxisBranch::kDynamicLessThanVlUnaligned); | ||
| 134 | + EXPECT_FALSE(ascendcperf_v2::IsBroadcastFallback(node)); | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +} // namespace | ||
| 138 | +} // namespace att | ||
| @@ -0,0 +1,88 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under terms and | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 of the License. | ||
| 5 | + * Please refer to the License for details. You should 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 <cstdint> | ||
| 12 | +#include <string> | ||
| 13 | +#include <tuple> | ||
| 14 | +#include <utility> | ||
| 15 | +#include <vector> | ||
| 16 | + | ||
| 17 | +#include "gtest/gtest.h" | ||
| 18 | +#include "ascir_node_param/ascir_node_param.h" | ||
| 19 | +#include "common/checker.h" | ||
| 20 | +#include "base/att_const_values.h" | ||
| 21 | +#include "api_perf_register/api_perf_factory.h" | ||
| 22 | +#include "v35/att/api_perf_register/ascendc_api_perf/broadcast_api_perf_v2.h" | ||
| 23 | +#include "v35/att/api_perf_register/ascendc_api_perf/broadcast_perf_utils_v2.h" | ||
| 24 | +#include "../../../../../ut/att/testcase/gen_model_info/api_perf_register/runtime_stub.h" | ||
| 25 | +#include "tests/depends/slog/src/slog_stub.h" | ||
| 26 | + | ||
| 27 | +namespace att { | ||
| 28 | +namespace { | ||
| 29 | +using ascendcapi_v2::VfCostAccumulator; | ||
| 30 | + | ||
| 31 | +TEST(BroadcastScalarDuplicatePerfV2, ScalarDuplicateCountsVectorRegisters) { | ||
| 32 | + const std::vector<std::tuple<std::string, int64_t, double>> cases = { | ||
| 33 | + {kFloat16, 8, 29}, {kFloat16, 129, 30}, {kFloat32, 65, 30}, {kFloat32, 45056, 732}, | ||
| 34 | + {kUInt8, 256, 29}, {kUInt8, 257, 30}, {kInt64, 33, 30}, {kInt64, 64, 30}, | ||
| 35 | + }; | ||
| 36 | + for (const auto &[dtype, duplicate_count, expected_value] : cases) { | ||
| 37 | + TensorShapeInfo input; | ||
| 38 | + input.data_type = dtype; | ||
| 39 | + TensorShapeInfo output; | ||
| 40 | + output.data_type = dtype; | ||
| 41 | + NodeInfo node; | ||
| 42 | + node.broadcast_node_params.valid = true; | ||
| 43 | + node.broadcast_node_params.is_scalar = true; | ||
| 44 | + node.broadcast_node_params.duplicate_count = CreateExpr(duplicate_count); | ||
| 45 | + | ||
| 46 | + const auto api = ApiPerfFactory::Instance().Create(kBroadcast + "V2"); | ||
| 47 | + ASSERT_NE(api, nullptr); | ||
| 48 | + PerfOutputInfo perf; | ||
| 49 | + ASSERT_EQ(api->GetPerfFunc()({input}, {output}, node, perf), af::SUCCESS); | ||
| 50 | + const auto iter = perf.pipe_res.find(PipeType::AIV_VEC); | ||
| 51 | + ASSERT_NE(iter, perf.pipe_res.end()); | ||
| 52 | + double value = 0.0; | ||
| 53 | + const std::vector<std::pair<af::Expression, af::Expression>> no_vars; | ||
| 54 | + ASSERT_EQ(iter->second.GetResult(no_vars, value), af::GRAPH_SUCCESS); | ||
| 55 | + EXPECT_EQ(value, expected_value) << "dtype=" << dtype << " duplicate_count=" << duplicate_count; | ||
| 56 | + } | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +TEST(BroadcastScalarDuplicatePerfV2, ScalarDuplicateSymbolicCountDividesByVectorLength) { | ||
| 60 | + TensorShapeInfo input; | ||
| 61 | + input.data_type = kFloat32; | ||
| 62 | + TensorShapeInfo output; | ||
| 63 | + output.data_type = kFloat32; | ||
| 64 | + NodeInfo node; | ||
| 65 | + node.broadcast_node_params.valid = true; | ||
| 66 | + node.broadcast_node_params.is_scalar = true; | ||
| 67 | + node.broadcast_node_params.duplicate_count = CreateExpr("z1z2t_size"); | ||
| 68 | + | ||
| 69 | + const auto api = ApiPerfFactory::Instance().Create(kBroadcast + "V2"); | ||
| 70 | + ASSERT_NE(api, nullptr); | ||
| 71 | + PerfOutputInfo perf; | ||
| 72 | + ASSERT_EQ(api->GetPerfFunc()({input}, {output}, node, perf), af::SUCCESS); | ||
| 73 | + const auto iter = perf.pipe_res.find(PipeType::AIV_VEC); | ||
| 74 | + ASSERT_NE(iter, perf.pipe_res.end()); | ||
| 75 | + | ||
| 76 | + Expr vl; | ||
| 77 | + Expr half_vl; | ||
| 78 | + Expr block_elements; | ||
| 79 | + ASSERT_EQ(ascendcapi_v2::GetBroadcastVectorElements(kFloat32, vl, half_vl, block_elements), af::SUCCESS); | ||
| 80 | + Expr repeat_time; | ||
| 81 | + ASSERT_EQ(ascendcapi_v2::CeilDiv(CreateExpr("z1z2t_size"), vl, repeat_time), af::SUCCESS); | ||
| 82 | + VfCostAccumulator acc; | ||
| 83 | + ASSERT_EQ(ascendcapi_v2::AddVfInstructPerf(kDuplicate, kFloat32, repeat_time, 1U, acc), af::SUCCESS); | ||
| 84 | + Expr expected = ascendcapi_v2::GetVfCost(acc); | ||
| 85 | + EXPECT_EQ(iter->second, expected); | ||
| 86 | +} | ||
| 87 | +} // namespace | ||
| 88 | +} // namespace att | ||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "node_utils_ex.h" | 12 | #include "node_utils_ex.h" |
| 13 | #include "graph_utils.h" | 13 | #include "graph_utils.h" |
| 14 | #include "ascendc_ir.h" | 14 | #include "ascendc_ir.h" |
| 15 | +#include "ascir_node_param/ascir_node_param.h" | ||
| 15 | #include "ascir_ops.h" | 16 | #include "ascir_ops.h" |
| 16 | #include "ascir_ops_utils.h" | 17 | #include "ascir_ops_utils.h" |
| 17 | #include "utils/api_call_factory.h" | 18 | #include "utils/api_call_factory.h" |
| @@ -556,26 +557,28 @@ TEST(CodegenKernel, ConstantBrcStore) { | |||
| 556 | EXPECT_EQ(result, std::string{"Duplicate(local_1[0], scalar_0, local_1_actual_size);\n"}); | 557 | EXPECT_EQ(result, std::string{"Duplicate(local_1[0], scalar_0, local_1_actual_size);\n"}); |
| 557 | } | 558 | } |
| 558 | 559 | ||
| 559 | -TEST(CodegenKernel, BroadcastRegApiCall_BrcAlign_ABA) { | 560 | +TEST(CodegenKernel, BroadcastRegApiCall_BrcAlign_ABAWithTranspose) { |
| 560 | af::AscGraph graph("test_graph"); | 561 | af::AscGraph graph("test_graph"); |
| 561 | 562 | ||
| 562 | auto s0 = graph.CreateSizeVar(2); | 563 | auto s0 = graph.CreateSizeVar(2); |
| 563 | auto s1 = graph.CreateSizeVar(2); | 564 | auto s1 = graph.CreateSizeVar(2); |
| 564 | - auto s2 = graph.CreateSizeVar(7); | 565 | + auto s2 = graph.CreateSizeVar(8); |
| 565 | - auto z0 = graph.CreateAxis("z0", s0); | 566 | + auto z0 = graph.CreateAxis("z0", af::Axis::kAxisTypeTileInner, s0, {}, -1); |
| 566 | - auto z1 = graph.CreateAxis("z1", s1); | 567 | + auto z1 = graph.CreateAxis("z1", af::Axis::kAxisTypeTileInner, s1, {}, -1); |
| 567 | - auto z2 = graph.CreateAxis("z2", s2); | 568 | + auto z2 = graph.CreateAxis("z2", af::Axis::kAxisTypeTileInner, s2, {}, -1); |
| 568 | 569 | ||
| 569 | Data x_op("x", graph); | 570 | Data x_op("x", graph); |
| 570 | Load load_op("load"); | 571 | Load load_op("load"); |
| 571 | af::ascir_op::Broadcast broadcast_op("broadcast"); | 572 | af::ascir_op::Broadcast broadcast_op("broadcast"); |
| 573 | + af::ascir_op::Transpose transpose_op("transpose"); | ||
| 572 | graph.AddNode(load_op); | 574 | graph.AddNode(load_op); |
| 573 | graph.AddNode(broadcast_op); | 575 | graph.AddNode(broadcast_op); |
| 576 | + graph.AddNode(transpose_op); | ||
| 574 | 577 | ||
| 575 | load_op.x = x_op.y; | 578 | load_op.x = x_op.y; |
| 576 | load_op.attr.sched.axis = {z0.id, z1.id, z2.id}; | 579 | load_op.attr.sched.axis = {z0.id, z1.id, z2.id}; |
| 577 | *load_op.y.axis = {z0.id, z1.id, z2.id}; | 580 | *load_op.y.axis = {z0.id, z1.id, z2.id}; |
| 578 | - *load_op.y.repeats = {s0, One, s2}; // (2, 1, 7) | 581 | + *load_op.y.repeats = {s0, One, s2}; // (2, 1, 8) |
| 579 | *load_op.y.strides = {af::Symbol(8), Zero, One}; // (8, 0, 1) | 582 | *load_op.y.strides = {af::Symbol(8), Zero, One}; // (8, 0, 1) |
| 580 | broadcast_op.x = load_op.y; | 583 | broadcast_op.x = load_op.y; |
| 581 | *broadcast_op.y.axis = {z0.id, z1.id, z2.id}; | 584 | *broadcast_op.y.axis = {z0.id, z1.id, z2.id}; |
| @@ -633,6 +636,14 @@ TEST(CodegenKernel, BroadcastRegApiCall_BrcAlign_ABA) { | |||
| 633 | 636 | ||
| 634 | std::string result; | 637 | std::string result; |
| 635 | call.Generate(tpipe, vector<af::AxisId>{}, result); | 638 | call.Generate(tpipe, vector<af::AxisId>{}, result); |
| 639 | + auto params = ascir_param::GetAscirNodeParams(broadcast); | ||
| 640 | + ASSERT_NE(params, nullptr); | ||
| 641 | + const auto *broadcast_params = ascir_param::GetSpecificParams<ascir_param::BroadcastNodeParams>(*params); | ||
| 642 | + ASSERT_NE(broadcast_params, nullptr); | ||
| 643 | + ASSERT_EQ(broadcast_params->dst_shape.size(), 3UL); | ||
| 644 | + ASSERT_EQ(broadcast_params->src_shape.size(), 3UL); | ||
| 645 | + EXPECT_EQ(broadcast_params->dst_shape.back().role, ascir_param::ParamExprRole::kActualSize); | ||
| 646 | + EXPECT_EQ(broadcast_params->src_shape.back().role, ascir_param::ParamExprRole::kActualSize); | ||
| 636 | EXPECT_EQ(result, | 647 | EXPECT_EQ(result, |
| 637 | "const uint32_t dst_shape_0_brc_to_1[3] = {static_cast<uint32_t>(2), static_cast<uint32_t>(2), " | 648 | "const uint32_t dst_shape_0_brc_to_1[3] = {static_cast<uint32_t>(2), static_cast<uint32_t>(2), " |
| 638 | "static_cast<uint32_t>(8)};\n" | 649 | "static_cast<uint32_t>(8)};\n" |
| @@ -0,0 +1,178 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under terms of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +#include "broadcast_api_perf_v2.h" | ||
| 8 | + | ||
| 9 | +#include "base/att_const_values.h" | ||
| 10 | +#include "common/checker.h" | ||
| 11 | +#include "broadcast_perf_utils_v2.h" | ||
| 12 | +#include "broadcast_last_axis_perf_v2.h" | ||
| 13 | +#include "broadcast_nlast_axis_perf_v2.h" | ||
| 14 | +#include "api_perf_register/ascendc_api_perf.h" | ||
| 15 | + | ||
| 16 | +namespace att { | ||
| 17 | +namespace ascendcperf_v2 { | ||
| 18 | +namespace { | ||
| 19 | +using ascendcapi_v2::BroadcastTilingInfo; | ||
| 20 | +using ascendcapi_v2::ParamExprInputs; | ||
| 21 | +using ascendcapi_v2::VfCostAccumulator; | ||
| 22 | + | ||
| 23 | +ParamExprInputs MakeParamInputs(const std::vector<Expr> &dims, const std::vector<Expr> &actual_dims) { | ||
| 24 | + ParamExprInputs inputs; | ||
| 25 | + inputs.semantic = dims; | ||
| 26 | + inputs.size = dims; | ||
| 27 | + inputs.actual_size = actual_dims.size() == dims.size() ? actual_dims : dims; | ||
| 28 | + return inputs; | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +af::Status SetBroadcastCost(const VfCostAccumulator &acc, PerfOutputInfo &perf) { | ||
| 32 | + perf.pipe_res[PipeType::AIV_VEC] = ascendcapi_v2::GetVfCost(acc); | ||
| 33 | + return af::SUCCESS; | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +af::Status BuildScalarPerf(const NodeDetail &node_info, PerfOutputInfo &perf) { | ||
| 37 | + GE_ASSERT_TRUE(!node_info.input_dtype.empty(), "Broadcast scalar input dtype is missing."); | ||
| 38 | + std::string helper_dtype; | ||
| 39 | + GE_ASSERT_SUCCESS(ascendcapi_v2::GetEffectiveHelperDtype(node_info.input_dtype[0], helper_dtype)); | ||
| 40 | + Expr vl; | ||
| 41 | + Expr half_vl; | ||
| 42 | + Expr block_elements; | ||
| 43 | + GE_ASSERT_SUCCESS(ascendcapi_v2::GetBroadcastVectorElements(node_info.input_dtype[0], vl, half_vl, block_elements)); | ||
| 44 | + (void)half_vl; | ||
| 45 | + (void)block_elements; | ||
| 46 | + Expr repeat_time; | ||
| 47 | + GE_ASSERT_SUCCESS(ascendcapi_v2::CeilDiv(node_info.broadcast_node_params.duplicate_count, vl, repeat_time)); | ||
| 48 | + VfCostAccumulator acc; | ||
| 49 | + GE_ASSERT_SUCCESS(ascendcapi_v2::AddVfInstructPerf(kDuplicate, helper_dtype, repeat_time, 1U, acc)); | ||
| 50 | + return SetBroadcastCost(acc, perf); | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +af::Status BuildEqualSizePerf(const NodeDetail &node_info, PerfOutputInfo &perf) { | ||
| 54 | + GE_ASSERT_TRUE(!node_info.input_dtype.empty(), "Broadcast equal-size input dtype is missing."); | ||
| 55 | + VfCostAccumulator acc; | ||
| 56 | + GE_ASSERT_SUCCESS(ascendcapi_v2::AddBroadcastDataCopyPerf(node_info.input_dtype[0], CreateExpr(1), acc)); | ||
| 57 | + return SetBroadcastCost(acc, perf); | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +af::Status BuildSingleElementPerf(const NodeDetail &node_info, const BroadcastTilingInfo &tiling, | ||
| 61 | + PerfOutputInfo &perf) { | ||
| 62 | + GE_ASSERT_TRUE(!node_info.input_dtype.empty(), "Broadcast duplicate input dtype is missing."); | ||
| 63 | + std::string helper_dtype; | ||
| 64 | + GE_ASSERT_SUCCESS(ascendcapi_v2::GetEffectiveHelperDtype(node_info.input_dtype[0], helper_dtype)); | ||
| 65 | + Expr vl; | ||
| 66 | + Expr half_vl; | ||
| 67 | + Expr block_elements; | ||
| 68 | + GE_ASSERT_SUCCESS(ascendcapi_v2::GetBroadcastVectorElements(node_info.input_dtype[0], vl, half_vl, block_elements)); | ||
| 69 | + (void)half_vl; | ||
| 70 | + (void)block_elements; | ||
| 71 | + Expr repeat_time; | ||
| 72 | + GE_ASSERT_SUCCESS(ascendcapi_v2::CeilDiv(tiling.dst_size, vl, repeat_time)); | ||
| 73 | + VfCostAccumulator acc; | ||
| 74 | + GE_ASSERT_SUCCESS(ascendcapi_v2::AddVfInstructPerf(kLoad, node_info.input_dtype[0], CreateExpr(1), 1U, acc)); | ||
| 75 | + GE_ASSERT_SUCCESS(ascendcapi_v2::AddVfInstructPerf(kUpdateMask, helper_dtype, repeat_time, 1U, acc)); | ||
| 76 | + GE_ASSERT_SUCCESS(ascendcapi_v2::AddVfInstructPerf(kStore, node_info.output_dtype[0], repeat_time, 1U, acc)); | ||
| 77 | + return SetBroadcastCost(acc, perf); | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +af::Status ValidateBroadcastNodeDetail(const NodeDetail &node_info) { | ||
| 81 | + const auto ¶ms = node_info.broadcast_node_params; | ||
| 82 | + GE_ASSERT_TRUE(!node_info.input_dtype.empty() && !node_info.output_dtype.empty(), | ||
| 83 | + "Broadcast dtype information is missing."); | ||
| 84 | + if (params.is_scalar) { | ||
| 85 | + return af::SUCCESS; | ||
| 86 | + } | ||
| 87 | + GE_ASSERT_TRUE(!node_info.input_dims.empty() && node_info.input_dims.size() == node_info.output_dims.size(), | ||
| 88 | + "Broadcast shape ranks are invalid."); | ||
| 89 | + GE_ASSERT_TRUE( | ||
| 90 | + params.src_shape.size() == params.dst_shape.size() && params.src_shape.size() == node_info.input_dims.size(), | ||
| 91 | + "Broadcast parameter shape rank is invalid."); | ||
| 92 | + GE_ASSERT_TRUE(node_info.repeats.size() == node_info.input_dims.size(), "Broadcast input repeats length is invalid."); | ||
| 93 | + return af::SUCCESS; | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +Expr ResolveBroadcastSize(const std::vector<ascir_param::ParamExprLeaf> &shape, const ParamExprInputs &inputs) { | ||
| 97 | + std::vector<Expr> resolved; | ||
| 98 | + for (size_t i = 0U; i < shape.size(); ++i) { | ||
| 99 | + resolved.push_back( | ||
| 100 | + ascendcapi_v2::ResolveParamExprLeaf(shape[i], inputs.semantic[i], inputs.size[i], inputs.actual_size[i])); | ||
| 101 | + } | ||
| 102 | + return ascendcapi_v2::ShapeProduct(resolved); | ||
| 103 | +} | ||
| 104 | +} // namespace | ||
| 105 | + | ||
| 106 | +bool IsBroadcastFallback(const NodeDetail &node_info) { | ||
| 107 | + if (node_info.input_dtype.empty() || node_info.input_dims.empty() || node_info.output_dims.empty()) { | ||
| 108 | + return false; | ||
| 109 | + } | ||
| 110 | + const auto ¶ms = node_info.broadcast_node_params; | ||
| 111 | + const auto src_inputs = MakeParamInputs(node_info.input_dims, node_info.repeats); | ||
| 112 | + const auto dst_inputs = MakeParamInputs(node_info.output_dims, node_info.output_dims); | ||
| 113 | + BroadcastTilingInfo tiling; | ||
| 114 | + if (BuildBroadcastTiling(params.src_shape, params.dst_shape, node_info.input_dtype[0], src_inputs, dst_inputs, | ||
| 115 | + tiling) != af::SUCCESS) { | ||
| 116 | + return false; | ||
| 117 | + } | ||
| 118 | + const Expr src_size = ResolveBroadcastSize(params.src_shape, src_inputs); | ||
| 119 | + const Expr dst_size = ResolveBroadcastSize(params.dst_shape, dst_inputs); | ||
| 120 | + if (src_size == dst_size || src_size == CreateExpr(1)) { | ||
| 121 | + return false; | ||
| 122 | + } | ||
| 123 | + if (IsLastAxisBroadcast(tiling)) { | ||
| 124 | + return GetLastAxisBranch(tiling, node_info.input_dtype[0], params.const_rank) == | ||
| 125 | + ascendcapi_v2::LastAxisBranch::kFallback; | ||
| 126 | + } | ||
| 127 | + if (params.const_rank == -1 && ascendcperf_v2::HasUnknownNlastCondition(node_info, tiling)) { | ||
| 128 | + return false; | ||
| 129 | + } | ||
| 130 | + if (tiling.original_rank > 4U) { | ||
| 131 | + return false; | ||
| 132 | + } | ||
| 133 | + return GetNlastAxisBranch(tiling, node_info.input_dtype[0], params.const_rank) == | ||
| 134 | + ascendcapi_v2::NlastAxisBranch::kFallback; | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +af::Status BroadcastPerf(const NodeDetail &node_info, PerfOutputInfo &perf) { | ||
| 138 | + GE_ASSERT_TRUE(node_info.broadcast_node_params.valid, "Broadcast parameters are invalid."); | ||
| 139 | + GE_ASSERT_SUCCESS(ValidateBroadcastNodeDetail(node_info)); | ||
| 140 | + if (node_info.broadcast_node_params.is_scalar) { | ||
| 141 | + return BuildScalarPerf(node_info, perf); | ||
| 142 | + } | ||
| 143 | + GE_ASSERT_TRUE(!node_info.input_dtype.empty() && !node_info.output_dtype.empty(), | ||
| 144 | + "Broadcast dtype information is missing."); | ||
| 145 | + const auto ¶ms = node_info.broadcast_node_params; | ||
| 146 | + const auto src_inputs = MakeParamInputs(node_info.input_dims, node_info.repeats); | ||
| 147 | + const auto dst_inputs = MakeParamInputs(node_info.output_dims, node_info.output_dims); | ||
| 148 | + const Expr raw_src_size = ResolveBroadcastSize(params.src_shape, src_inputs); | ||
| 149 | + const Expr raw_dst_size = ResolveBroadcastSize(params.dst_shape, dst_inputs); | ||
| 150 | + BroadcastTilingInfo tiling; | ||
| 151 | + GE_ASSERT_SUCCESS(ascendcapi_v2::BuildBroadcastTiling(params.src_shape, params.dst_shape, node_info.input_dtype[0], | ||
| 152 | + src_inputs, dst_inputs, tiling)); | ||
| 153 | + if (raw_src_size == raw_dst_size) { | ||
| 154 | + return BuildEqualSizePerf(node_info, perf); | ||
| 155 | + } | ||
| 156 | + if (raw_src_size == CreateExpr(1)) { | ||
| 157 | + tiling.src_size = raw_src_size; | ||
| 158 | + tiling.dst_size = raw_dst_size; | ||
| 159 | + return BuildSingleElementPerf(node_info, tiling, perf); | ||
| 160 | + } | ||
| 161 | + if (ascendcapi_v2::IsLastAxisBroadcast(tiling)) { | ||
| 162 | + const auto branch = | ||
| 163 | + ascendcapi_v2::GetLastAxisBranch(tiling, node_info.input_dtype[0], node_info.broadcast_node_params.const_rank); | ||
| 164 | + if (branch == ascendcapi_v2::LastAxisBranch::kFallback && params.const_rank != -1) { | ||
| 165 | + return af::FAILED; | ||
| 166 | + } | ||
| 167 | + return BuildLastAxisPerf(node_info, tiling, perf); | ||
| 168 | + } | ||
| 169 | + const auto branch = | ||
| 170 | + ascendcapi_v2::GetNlastAxisBranch(tiling, node_info.input_dtype[0], node_info.broadcast_node_params.const_rank); | ||
| 171 | + if (branch == ascendcapi_v2::NlastAxisBranch::kFallback && params.const_rank != -1 && tiling.original_rank <= 4U) { | ||
| 172 | + return af::FAILED; | ||
| 173 | + } | ||
| 174 | + return BuildNlastAxisPerf(node_info, tiling, perf); | ||
| 175 | +} | ||
| 176 | + | ||
| 177 | +} // namespace ascendcperf_v2 | ||
| 178 | +} // namespace att | ||
| @@ -0,0 +1,21 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under terms of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +#ifndef AUTOFUSE_ASCENDC_BROADCAST_API_PERF_V2_H_ | ||
| 8 | +#define AUTOFUSE_ASCENDC_BROADCAST_API_PERF_V2_H_ | ||
| 9 | + | ||
| 10 | +#include "api_perf_register/api_perf.h" | ||
| 11 | + | ||
| 12 | +namespace att { | ||
| 13 | +namespace ascendcperf_v2 { | ||
| 14 | + | ||
| 15 | +af::Status BroadcastPerf(const NodeDetail &node_info, PerfOutputInfo &perf); | ||
| 16 | +bool IsBroadcastFallback(const NodeDetail &node_info); | ||
| 17 | + | ||
| 18 | +} // namespace ascendcperf_v2 | ||
| 19 | +} // namespace att | ||
| 20 | + | ||
| 21 | +#endif // AUTOFUSE_ASCENDC_BROADCAST_API_PERF_V2_H_ | ||
| @@ -0,0 +1,675 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under terms of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 of the License. | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +#include "broadcast_last_axis_perf_v2.h" | ||
| 8 | + | ||
| 9 | +#include "base/att_const_values.h" | ||
| 10 | +#include "common/checker.h" | ||
| 11 | + | ||
| 12 | +namespace att { | ||
| 13 | +namespace ascendcapi_v2 { | ||
| 14 | +namespace { | ||
| 15 | + | ||
| 16 | +bool IsTrue(const Expr &lhs, CondType condition, const Expr &rhs) { | ||
| 17 | + af::TriBool result = af::TriBool::kUnknown; | ||
| 18 | + if (condition == CondType::K_EQ) { | ||
| 19 | + result = af::SymbolicUtils::StaticCheckEq(lhs, rhs); | ||
| 20 | + } | ||
| 21 | + if (condition == CondType::K_LT) { | ||
| 22 | + result = af::SymbolicUtils::StaticCheckLt(lhs, rhs); | ||
| 23 | + } | ||
| 24 | + if (condition == CondType::K_LE) { | ||
| 25 | + result = af::SymbolicUtils::StaticCheckLe(lhs, rhs); | ||
| 26 | + } | ||
| 27 | + if (condition == CondType::K_GT) { | ||
| 28 | + result = af::SymbolicUtils::StaticCheckGt(lhs, rhs); | ||
| 29 | + } | ||
| 30 | + return result == af::TriBool::kTrue; | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +bool IsB8(const std::string &dtype) { | ||
| 34 | + return dtype == kUInt8 || dtype == kInt8; | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +bool IsZero(const Expr &value) { | ||
| 38 | + return af::SymbolicUtils::StaticCheckEq(value, CreateExpr(0)) == af::TriBool::kTrue; | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +LastAxisBranch ClassifyRankTwo(const BroadcastTilingInfo &tiling, const std::string &dtype, const Expr &vl, | ||
| 42 | + const Expr &half_vl, const Expr &block, int32_t const_rank) { | ||
| 43 | + const Expr last = tiling.dst_shape[1U]; | ||
| 44 | + if (IsTrue(last, CondType::K_EQ, block) && !IsB8(dtype)) { | ||
| 45 | + return LastAxisBranch::kE2B; | ||
| 46 | + } | ||
| 47 | + if (IsTrue(last, CondType::K_LT, half_vl)) { | ||
| 48 | + return IsTrue(tiling.dst_size, CondType::K_LT, vl) ? LastAxisBranch::kGatherOne : LastAxisBranch::kGatherTwo; | ||
| 49 | + } | ||
| 50 | + if (IsTrue(last, CondType::K_LE, vl)) { | ||
| 51 | + return LastAxisBranch::kLessThanVlUnaligned; | ||
| 52 | + } | ||
| 53 | + if (IsTrue(af::sym::Mod(last, block), CondType::K_EQ, CreateExpr(0))) { | ||
| 54 | + return LastAxisBranch::kLargerThanVlAligned; | ||
| 55 | + } | ||
| 56 | + return const_rank == -1 ? LastAxisBranch::kDynamicLargerThanVlUnaligned : LastAxisBranch::kLargerThanVlUnaligned; | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +LastAxisBranch ClassifyRankThree(const BroadcastTilingInfo &tiling, const std::string &dtype, const Expr &vl, | ||
| 60 | + const Expr &half_vl, const Expr &block, int32_t const_rank) { | ||
| 61 | + const Expr last = tiling.dst_shape[2U]; | ||
| 62 | + if (IsZero(block)) { | ||
| 63 | + return LastAxisBranch::kFallback; | ||
| 64 | + } | ||
| 65 | + const Expr default_block_num = vl / block; | ||
| 66 | + if (!IsB8(dtype) && tiling.src_stride.size() > 1U && !IsZero(tiling.src_stride[1]) && | ||
| 67 | + IsTrue(last, CondType::K_EQ, block) && IsTrue(tiling.dst_shape[1] * last, CondType::K_GT, half_vl) && | ||
| 68 | + IsTrue(af::sym::Mod(tiling.dst_shape[1], default_block_num), CondType::K_EQ, CreateExpr(0))) { | ||
| 69 | + return IsTrue(tiling.dst_shape[1] * last, CondType::K_GT, vl) ? LastAxisBranch::kE2BLargerThanVl | ||
| 70 | + : LastAxisBranch::kE2BLessThanVl; | ||
| 71 | + } | ||
| 72 | + if (IsTrue(last, CondType::K_LT, half_vl)) { | ||
| 73 | + return LastAxisBranch::kGatherWrapper; | ||
| 74 | + } | ||
| 75 | + if (IsTrue(last, CondType::K_LE, vl)) { | ||
| 76 | + if (IsTrue(af::sym::Mod(last, block), CondType::K_EQ, CreateExpr(0))) { | ||
| 77 | + return LastAxisBranch::kLessThanVlAligned; | ||
| 78 | + } | ||
| 79 | + return const_rank == -1 ? LastAxisBranch::kDynamicLessThanVlUnaligned : LastAxisBranch::kLessThanVlUnaligned; | ||
| 80 | + } | ||
| 81 | + if (IsTrue(af::sym::Mod(last, block), CondType::K_EQ, CreateExpr(0))) { | ||
| 82 | + return LastAxisBranch::kLargerThanVlAligned; | ||
| 83 | + } | ||
| 84 | + return const_rank == -1 ? LastAxisBranch::kDynamicLargerThanVlUnaligned : LastAxisBranch::kLargerThanVlUnaligned; | ||
| 85 | +} | ||
| 86 | + | ||
| 87 | +LastAxisBranch ClassifyRankFour(const BroadcastTilingInfo &tiling, const std::string &dtype, const Expr &vl, | ||
| 88 | + const Expr &half_vl, const Expr &block, int32_t const_rank) { | ||
| 89 | + const Expr last = tiling.dst_shape[3U]; | ||
| 90 | + if (IsZero(block)) { | ||
| 91 | + return LastAxisBranch::kFallback; | ||
| 92 | + } | ||
| 93 | + const Expr default_block_num = vl / block; | ||
| 94 | + if (!IsB8(dtype) && tiling.src_stride.size() > 2U && !IsZero(tiling.src_stride[2]) && | ||
| 95 | + IsTrue(last, CondType::K_EQ, block) && | ||
| 96 | + IsTrue(af::sym::Mod(tiling.dst_shape[2], default_block_num), CondType::K_EQ, CreateExpr(0))) { | ||
| 97 | + return LastAxisBranch::kE2B; | ||
| 98 | + } | ||
| 99 | + if (IsTrue(last, CondType::K_LT, half_vl) && !IsB8(dtype)) { | ||
| 100 | + return LastAxisBranch::kGatherWrapperForFourDim; | ||
| @@ -0,0 +1,45 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under terms of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 of the License. | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +#ifndef AUTOFUSE_ASCENDC_BROADCAST_LAST_AXIS_PERF_V2_H_ | ||
| 8 | +#define AUTOFUSE_ASCENDC_BROADCAST_LAST_AXIS_PERF_V2_H_ | ||
| 9 | + | ||
| 10 | +#include "broadcast_perf_utils_v2.h" | ||
| 11 | + | ||
| 12 | +namespace att { | ||
| 13 | +namespace ascendcapi_v2 { | ||
| 14 | + | ||
| 15 | +enum class LastAxisBranch { | ||
| 16 | + kE2B, | ||
| 17 | + kE2BLessThanVl, | ||
| 18 | + kE2BLargerThanVl, | ||
| 19 | + kGatherOne, | ||
| 20 | + kGatherTwo, | ||
| 21 | + kGatherWrapper, | ||
| 22 | + kGatherWrapperForFourDim, | ||
| 23 | + kLessThanVlAligned, | ||
| 24 | + kLessThanVlUnaligned, | ||
| 25 | + kLargerThanVlAligned, | ||
| 26 | + kLargerThanVlUnaligned, | ||
| 27 | + kDynamicLessThanVlUnaligned, | ||
| 28 | + kDynamicLargerThanVlUnaligned, | ||
| 29 | + kFallback, | ||
| 30 | +}; | ||
| 31 | + | ||
| 32 | +LastAxisBranch GetLastAxisBranch(const BroadcastTilingInfo &tiling, const std::string &dtype, int32_t const_rank); | ||
| 33 | + | ||
| 34 | +} // namespace ascendcapi_v2 | ||
| 35 | + | ||
| 36 | +namespace ascendcperf_v2 { | ||
| 37 | + | ||
| 38 | +ascendcapi_v2::LastAxisBranch GetLastAxisPerfBranch(const NodeDetail &node_info); | ||
| 39 | +af::Status BuildLastAxisPerf(const NodeDetail &node_info, const ascendcapi_v2::BroadcastTilingInfo &tiling, | ||
| 40 | + PerfOutputInfo &perf); | ||
| 41 | + | ||
| 42 | +} // namespace ascendcperf_v2 | ||
| 43 | +} // namespace att | ||
| 44 | + | ||
| 45 | +#endif // AUTOFUSE_ASCENDC_BROADCAST_LAST_AXIS_PERF_V2_H_ | ||
| @@ -0,0 +1,50 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under terms of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 of the License. | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +#ifndef AUTOFUSE_ASCENDC_BROADCAST_NLAST_AXIS_PERF_V2_H_ | ||
| 8 | +#define AUTOFUSE_ASCENDC_BROADCAST_NLAST_AXIS_PERF_V2_H_ | ||
| 9 | + | ||
| 10 | +#include "broadcast_perf_utils_v2.h" | ||
| 11 | + | ||
| 12 | +namespace att { | ||
| 13 | +namespace ascendcapi_v2 { | ||
| 14 | + | ||
| 15 | +enum class NlastAxisBranch { | ||
| 16 | + kGather, | ||
| 17 | + kGatherWrapperForFourDim, | ||
| 18 | + kGatherOne, | ||
| 19 | + kGatherTwo, | ||
| 20 | + kGatherBOne, | ||
| 21 | + kGatherBTwo, | ||
| 22 | + kLessThanVlAligned, | ||
| 23 | + kLessThanVlUnaligned, | ||
| 24 | + kLargerThanVlAlignedWithBlock, | ||
| 25 | + kLargerThanVlAlignedWithVl, | ||
| 26 | + kLargerThanVlUnaligned, | ||
| 27 | + kDynamicGather, | ||
| 28 | + kDynamicLessThanVlAligned, | ||
| 29 | + kDynamicLessThanVlUnaligned, | ||
| 30 | + kDynamicLargerThanVlAlignedWithBlock, | ||
| 31 | + kDynamicLargerThanVlUnaligned, | ||
| 32 | + kB64MoreDimGather, | ||
| 33 | + kFallback, | ||
| 34 | +}; | ||
| 35 | + | ||
| 36 | +NlastAxisBranch GetNlastAxisBranch(const BroadcastTilingInfo &tiling, const std::string &dtype, int32_t const_rank); | ||
| 37 | + | ||
| 38 | +} // namespace ascendcapi_v2 | ||
| 39 | + | ||
| 40 | +namespace ascendcperf_v2 { | ||
| 41 | + | ||
| 42 | +ascendcapi_v2::NlastAxisBranch GetNlastAxisPerfBranch(const NodeDetail &node_info); | ||
| 43 | +bool HasUnknownNlastCondition(const NodeDetail &node_info, const ascendcapi_v2::BroadcastTilingInfo &tiling); | ||
| 44 | +af::Status BuildNlastAxisPerf(const NodeDetail &node_info, const ascendcapi_v2::BroadcastTilingInfo &tiling, | ||
| 45 | + PerfOutputInfo &perf); | ||
| 46 | + | ||
| 47 | +} // namespace ascendcperf_v2 | ||
| 48 | +} // namespace att | ||
| 49 | + | ||
| 50 | +#endif // AUTOFUSE_ASCENDC_BROADCAST_NLAST_AXIS_PERF_V2_H_ | ||
| @@ -0,0 +1,279 @@ | |||
| 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 of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +#include "broadcast_perf_utils_v2.h" | ||
| 8 | + | ||
| 9 | +#include "base/att_const_values.h" | ||
| 10 | +#include "common/checker.h" | ||
| 11 | +#include "../perf_param_v2.h" | ||
| 12 | + | ||
| 13 | +#include <algorithm> | ||
| 14 | + | ||
| 15 | +namespace att { | ||
| 16 | +namespace ascendcapi_v2 { | ||
| 17 | +namespace { | ||
| 18 | +constexpr uint32_t kBlockBytes = 32U; | ||
| 19 | +constexpr uint32_t kVectorBytes = 256U; | ||
| 20 | + | ||
| 21 | +af::TriBool CheckCondition(CondType type, const Expr &lhs, const Expr &rhs) { | ||
| 22 | + switch (type) { | ||
| 23 | + case CondType::K_EQ: | ||
| 24 | + return af::SymbolicUtils::StaticCheckEq(lhs, rhs); | ||
| 25 | + case CondType::K_LT: | ||
| 26 | + return af::SymbolicUtils::StaticCheckLt(lhs, rhs); | ||
| 27 | + case CondType::K_GT: | ||
| 28 | + return af::SymbolicUtils::StaticCheckGt(lhs, rhs); | ||
| 29 | + case CondType::K_LE: | ||
| 30 | + return af::SymbolicUtils::StaticCheckLe(lhs, rhs); | ||
| 31 | + case CondType::K_GE: | ||
| 32 | + return af::SymbolicUtils::StaticCheckGe(lhs, rhs); | ||
| 33 | + default: | ||
| 34 | + return af::TriBool::kUnknown; | ||
| 35 | + } | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +void CollapseRank(std::vector<Expr> &src, std::vector<Expr> &dst) { | ||
| 39 | + std::vector<Expr> collapsed_src; | ||
| 40 | + std::vector<Expr> collapsed_dst; | ||
| 41 | + for (size_t i = 0U; i < src.size();) { | ||
| 42 | + const bool broadcast = src[i] == CreateExpr(1) && dst[i] != CreateExpr(1); | ||
| 43 | + Expr src_product = broadcast ? CreateExpr(1) : src[i]; | ||
| 44 | + Expr dst_product = dst[i]; | ||
| 45 | + ++i; | ||
| 46 | + while (i < src.size() && | ||
| 47 | + ((broadcast && src[i] == CreateExpr(1) && dst[i] != CreateExpr(1)) || (!broadcast && src[i] == dst[i]))) { | ||
| 48 | + dst_product = dst_product * dst[i]; | ||
| 49 | + if (!broadcast) { | ||
| 50 | + src_product = src_product * src[i]; | ||
| 51 | + } | ||
| 52 | + ++i; | ||
| 53 | + } | ||
| 54 | + collapsed_src.push_back(src_product); | ||
| 55 | + collapsed_dst.push_back(dst_product); | ||
| 56 | + } | ||
| 57 | + src = std::move(collapsed_src); | ||
| 58 | + dst = std::move(collapsed_dst); | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +void BuildStrides(const std::vector<Expr> &src, const std::vector<Expr> &dst, std::vector<Expr> &src_stride, | ||
| 62 | + std::vector<Expr> &dst_stride) { | ||
| 63 | + src_stride.assign(src.size(), CreateExpr(0)); | ||
| 64 | + dst_stride.assign(dst.size(), CreateExpr(0)); | ||
| 65 | + Expr src_step = CreateExpr(1); | ||
| 66 | + Expr dst_step = CreateExpr(1); | ||
| 67 | + for (size_t i = src.size(); i > 0U; --i) { | ||
| 68 | + const size_t index = i - 1U; | ||
| 69 | + dst_stride[index] = dst_step; | ||
| 70 | + dst_step = dst_step * dst[index]; | ||
| 71 | + if (src[index] == CreateExpr(1) && dst[index] != CreateExpr(1)) { | ||
| 72 | + continue; | ||
| 73 | + } | ||
| 74 | + src_stride[index] = src_step; | ||
| 75 | + src_step = src_step * src[index]; | ||
| 76 | + } | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +af::Status ApplyB64Tiling(const std::string &dtype, BroadcastTilingInfo &tiling) { | ||
| 80 | + if (dtype != kUInt64 && dtype != kInt64) { | ||
| 81 | + return af::SUCCESS; | ||
| 82 | + } | ||
| 83 | + if (tiling.src_size == tiling.dst_size) { | ||
| 84 | + return af::SUCCESS; | ||
| 85 | + } | ||
| 86 | + const size_t last = tiling.rank - 1U; | ||
| 87 | + if (tiling.src_shape[last] == CreateExpr(1) && tiling.dst_shape[last] != CreateExpr(1)) { | ||
| 88 | + if (tiling.rank < 9U) { | ||
| 89 | + tiling.src_shape.push_back(kSymTwo); | ||
| 90 | + tiling.dst_shape.push_back(kSymTwo); | ||
| 91 | + ++tiling.rank; | ||
| 92 | + } else { | ||
| 93 | + tiling.loop_num = tiling.dst_shape[0]; | ||
| 94 | + } | ||
| 95 | + } else { | ||
| 96 | + tiling.src_shape[last] = tiling.src_shape[last] * kSymTwo; | ||
| 97 | + tiling.dst_shape[last] = tiling.dst_shape[last] * kSymTwo; | ||
| 98 | + } | ||
| 99 | + tiling.src_size = tiling.src_size * kSymTwo; | ||
| 100 | + tiling.dst_size = tiling.dst_size * kSymTwo; | ||
| 101 | + return af::SUCCESS; | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +bool HasParamExprInputs(const std::vector<ascir_param::ParamExprLeaf> &leaves, const ParamExprInputs &inputs) { | ||
| 105 | + return leaves.size() == inputs.semantic.size() && leaves.size() == inputs.size.size() && | ||
| 106 | + leaves.size() == inputs.actual_size.size(); | ||
| 107 | +} | ||
| 108 | +} // namespace | ||
| 109 | + | ||
| 110 | +af::Status GetDtypeByteSize(const std::string &dtype, Expr &byte_size) { | ||
| 111 | + const auto iter = kDataTypeSizeMap.find(dtype); | ||
| 112 | + GE_ASSERT_TRUE(iter != kDataTypeSizeMap.end(), "Unsupported broadcast dtype[%s].", dtype.c_str()); | ||
| 113 | + byte_size = iter->second; | ||
| 114 | + return af::SUCCESS; | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +af::Status GetEffectiveHelperDtype(const std::string &dtype, std::string &helper_dtype) { | ||
| 118 | + Expr byte_size; | ||
| 119 | + GE_ASSERT_SUCCESS(GetDtypeByteSize(dtype, byte_size)); | ||
| 120 | + helper_dtype = (dtype == kUInt64 || dtype == kInt64) ? kUInt32 : dtype; | ||
| 121 | + return af::SUCCESS; | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +af::Status GetBroadcastVectorElements(const std::string &dtype, Expr &vl, Expr &half_vl, Expr &block_elements) { | ||
| 125 | + Expr byte_size; | ||
| 126 | + GE_ASSERT_SUCCESS(GetDtypeByteSize(dtype, byte_size)); | ||
| 127 | + if (af::SymbolicUtils::StaticCheckEq(byte_size, CreateExpr(0)) == af::TriBool::kTrue) { | ||
| 128 | + GELOGW("Broadcast dtype byte size cannot be zero."); | ||
| 129 | + return af::SUCCESS; | ||
| 130 | + } | ||
| 131 | + vl = CreateExpr(kVectorBytes) / byte_size; | ||
| 132 | + half_vl = vl / kSymTwo; | ||
| 133 | + block_elements = CreateExpr(kBlockBytes) / byte_size; | ||
| 134 | + return af::SUCCESS; | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +af::Status GetBroadcastTilingVectorElements(const std::string &dtype, Expr &vl, Expr &half_vl, Expr &block_elements) { | ||
| 138 | + std::string helper_dtype; | ||
| 139 | + GE_ASSERT_SUCCESS(GetEffectiveHelperDtype(dtype, helper_dtype)); | ||
| 140 | + return GetBroadcastVectorElements(helper_dtype, vl, half_vl, block_elements); | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +af::Status CeilDiv(const Expr &value, const Expr &divisor, Expr &result) { | ||
| 144 | + if (af::SymbolicUtils::StaticCheckEq(divisor, CreateExpr(0)) == af::TriBool::kTrue) { | ||
| 145 | + GELOGW("Broadcast ceil divisor cannot be zero."); | ||
| 146 | + return af::SUCCESS; | ||
| 147 | + } | ||
| 148 | + result = af::sym::Ceiling(value / divisor); | ||
| 149 | + return af::SUCCESS; | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +Expr ResolveParamExprLeaf(const ascir_param::ParamExprLeaf &leaf, const Expr &semantic_expr, const Expr &size_expr, | ||
| 153 | + const Expr &actual_size_expr) { | ||
| 154 | + if (leaf.role == ascir_param::ParamExprRole::kSize) { | ||
| 155 | + return size_expr; | ||
| 156 | + } | ||
| 157 | + if (leaf.role == ascir_param::ParamExprRole::kActualSize) { | ||
| 158 | + return actual_size_expr; | ||
| 159 | + } | ||
| 160 | + return semantic_expr; | ||
| 161 | +} | ||
| 162 | + | ||
| 163 | +Expr ShapeProduct(const std::vector<Expr> &shape) { | ||
| 164 | + Expr product = CreateExpr(1); | ||
| 165 | + for (const auto &dim : shape) { | ||
| 166 | + product = product * dim; | ||
| 167 | + } | ||
| 168 | + return product; | ||
| 169 | +} | ||
| 170 | + | ||
| 171 | +af::Status BuildBroadcastTiling(const std::vector<ascir_param::ParamExprLeaf> &src, | ||
| 172 | + const std::vector<ascir_param::ParamExprLeaf> &dst, const std::string &dtype, | ||
| 173 | + const ParamExprInputs &src_inputs, const ParamExprInputs &dst_inputs, | ||
| 174 | + BroadcastTilingInfo &tiling) { | ||
| 175 | + GE_ASSERT_TRUE(!src.empty() && src.size() == dst.size(), "Broadcast shapes must have the same non-zero rank."); | ||
| 176 | + GE_ASSERT_TRUE(HasParamExprInputs(src, src_inputs) && HasParamExprInputs(dst, dst_inputs), | ||
| 177 | + "Broadcast parameter expression inputs must match rank."); | ||
| 178 | + tiling.original_src_shape.clear(); | ||
| 179 | + tiling.original_dst_shape.clear(); | ||
| 180 | + for (size_t i = 0U; i < src.size(); ++i) { | ||
| 181 | + tiling.original_src_shape.push_back( | ||
| 182 | + ResolveParamExprLeaf(src[i], src_inputs.semantic[i], src_inputs.size[i], src_inputs.actual_size[i])); | ||
| 183 | + tiling.original_dst_shape.push_back( | ||
| 184 | + ResolveParamExprLeaf(dst[i], dst_inputs.semantic[i], dst_inputs.size[i], dst_inputs.actual_size[i])); | ||
| 185 | + } | ||
| 186 | + tiling.original_rank = src.size(); | ||
| 187 | + tiling.src_shape = tiling.original_src_shape; | ||
| 188 | + tiling.dst_shape = tiling.original_dst_shape; | ||
| 189 | + tiling.src_size = ShapeProduct(tiling.original_src_shape); | ||
| 190 | + tiling.dst_size = ShapeProduct(tiling.original_dst_shape); | ||
| 191 | + if (tiling.src_shape.size() > 4U) { | ||
| 192 | + CollapseRank(tiling.src_shape, tiling.dst_shape); | ||
| 193 | + } | ||
| 194 | + tiling.folded_rank = tiling.dst_shape.size(); | ||
| 195 | + if (tiling.original_rank == 4U && tiling.dst_shape[0] == CreateExpr(1) && | ||
| 196 | + tiling.original_src_shape[0] == CreateExpr(1)) { | ||
| 197 | + tiling.src_shape.erase(tiling.src_shape.begin()); | ||
| 198 | + tiling.dst_shape.erase(tiling.dst_shape.begin()); | ||
| 199 | + } | ||
| 200 | + tiling.rank = tiling.dst_shape.size(); | ||
| 201 | + GE_ASSERT_SUCCESS(ApplyB64Tiling(dtype, tiling)); | ||
| 202 | + const bool loop_src_stride_zero = | ||
| 203 | + tiling.loop_num != CreateExpr(0) && tiling.src_shape[0] == CreateExpr(1) && tiling.dst_shape[0] != CreateExpr(1); | ||
| 204 | + if (tiling.loop_num != CreateExpr(0)) { | ||
| 205 | + tiling.src_shape.erase(tiling.src_shape.begin()); | ||
| 206 | + tiling.dst_shape.erase(tiling.dst_shape.begin()); | ||
| 207 | + tiling.src_shape.push_back(kSymTwo); | ||
| 208 | + tiling.dst_shape.push_back(kSymTwo); | ||
| 209 | + } | ||
| 210 | + BuildStrides(tiling.src_shape, tiling.dst_shape, tiling.src_stride, tiling.dst_stride); | ||
| 211 | + if (tiling.loop_num != CreateExpr(0)) { | ||
| 212 | + tiling.src_stride.push_back(loop_src_stride_zero ? CreateExpr(0) : ShapeProduct(tiling.src_shape)); | ||
| 213 | + } | ||
| 214 | + return af::SUCCESS; | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +bool IsLastAxisBroadcast(const BroadcastTilingInfo &tiling) { | ||
| 218 | + return tiling.rank > 0U && tiling.src_stride.size() >= tiling.rank && | ||
| 219 | + tiling.src_stride[tiling.rank - 1U] == CreateExpr(0); | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +af::Status AddBroadcastDataCopyPerf(const std::string &dtype, const Expr &repeat_time, VfCostAccumulator &acc) { | ||
| 223 | + return AddVfInstructPerf(kLoad, dtype, repeat_time, 1U, acc); | ||
| 224 | +} | ||
| 225 | + | ||
| 226 | +af::Status AddVfInstructPerf(const std::string &instruct, const std::string &dtype, const Expr &repeat_time, | ||
| 227 | + uint32_t instruct_count, VfCostAccumulator &acc) { | ||
| 228 | + std::string helper_dtype; | ||
| 229 | + GE_ASSERT_SUCCESS(GetEffectiveHelperDtype(dtype, helper_dtype)); | ||
| 230 | + if (instruct == kLoad) { | ||
| 231 | + acc.load_count += instruct_count; | ||
| 232 | + } | ||
| 233 | + static const PerfParamTableV2 perf_table; | ||
| 234 | + const auto &entries = perf_table.GetVfInstructPerfTable(instruct); | ||
| 235 | + for (uint32_t i = 0U; i < instruct_count; ++i) { | ||
| 236 | + for (const auto &entry : entries) { | ||
| 237 | + if (std::find(entry.support_data_types.begin(), entry.support_data_types.end(), helper_dtype) == | ||
| 238 | + entry.support_data_types.end()) { | ||
| 239 | + continue; | ||
| 240 | + } | ||
| 241 | + acc.max_latency = af::sym::Max(acc.max_latency, CreateExpr(entry.latency)); | ||
| 242 | + acc.throughput = acc.throughput + CreateExpr(entry.throughput) * repeat_time; | ||
| 243 | + break; | ||
| 244 | + } | ||
| 245 | + } | ||
| 246 | + return af::SUCCESS; | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +Expr GetVfCost(const VfCostAccumulator &acc, bool include_head) { | ||
| 250 | + Expr cost = acc.max_latency + acc.throughput; | ||
| 251 | + if (include_head) { | ||
| 252 | + static const PerfParamTableV2 perf_table; | ||
| 253 | + cost = cost + perf_table.GetVectorFunctionHeadCost(); | ||
| 254 | + } | ||
| 255 | + cost.Simplify(); | ||
| 256 | + return cost; | ||
| 257 | +} | ||
| 258 | + | ||
| 259 | +af::Status BuildBroadcastTernary(const std::string &name, CondType condition, const Expr &lhs, const Expr &rhs, | ||
| 260 | + const Expr &true_value, const Expr &false_value, TernaryOpMap &ternary_ops, | ||
| 261 | + Expr &result) { | ||
| 262 | + const auto check = CheckCondition(condition, lhs, rhs); | ||
| 263 | + if (check == af::TriBool::kTrue) { | ||
| 264 | + result = true_value; | ||
| 265 | + return af::SUCCESS; | ||
| 266 | + } | ||
| 267 | + if (check == af::TriBool::kFalse) { | ||
| 268 | + result = false_value; | ||
| 269 | + return af::SUCCESS; | ||
| 270 | + } | ||
| 271 | + GetPerfVar(name, result, ternary_ops); | ||
| 272 | + TernaryOp ternary(condition, lhs, rhs, true_value, false_value); | ||
| 273 | + ternary.SetVariable(result); | ||
| 274 | + ternary_ops[result] = ternary; | ||
| 275 | + return af::SUCCESS; | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +} // namespace ascendcapi_v2 | ||
| 279 | +} // namespace att | ||
| @@ -0,0 +1,74 @@ | |||
| 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 of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +#ifndef AUTOFUSE_ASCENDC_BROADCAST_PERF_UTILS_V2_H_ | ||
| 8 | +#define AUTOFUSE_ASCENDC_BROADCAST_PERF_UTILS_V2_H_ | ||
| 9 | + | ||
| 10 | +#include <cstddef> | ||
| 11 | +#include <cstdint> | ||
| 12 | +#include <string> | ||
| 13 | +#include <vector> | ||
| 14 | + | ||
| 15 | +#include "api_perf_register/api_perf.h" | ||
| 16 | +#include "ascir_node_param/ascir_node_param.h" | ||
| 17 | +#include "gen_model_info/api_perf_register/utils/vf_perf_utils.h" | ||
| 18 | + | ||
| 19 | +namespace att { | ||
| 20 | +namespace ascendcapi_v2 { | ||
| 21 | + | ||
| 22 | +struct BroadcastTilingInfo { | ||
| 23 | + std::vector<Expr> original_src_shape; | ||
| 24 | + std::vector<Expr> original_dst_shape; | ||
| 25 | + std::vector<Expr> src_shape; | ||
| 26 | + std::vector<Expr> dst_shape; | ||
| 27 | + std::vector<Expr> src_stride; | ||
| 28 | + std::vector<Expr> dst_stride; | ||
| 29 | + Expr src_size{CreateExpr(1)}; | ||
| 30 | + Expr dst_size{CreateExpr(1)}; | ||
| 31 | + Expr loop_num{CreateExpr(0)}; | ||
| 32 | + size_t original_rank{0U}; | ||
| 33 | + size_t folded_rank{0U}; | ||
| 34 | + size_t rank{0U}; | ||
| 35 | +}; | ||
| 36 | + | ||
| 37 | +struct VfCostAccumulator { | ||
| 38 | + Expr max_latency{CreateExpr(0)}; | ||
| 39 | + Expr throughput{CreateExpr(0)}; | ||
| 40 | + uint32_t load_count{0U}; | ||
| 41 | +}; | ||
| 42 | + | ||
| 43 | +struct ParamExprInputs { | ||
| 44 | + std::vector<Expr> semantic; | ||
| 45 | + std::vector<Expr> size; | ||
| 46 | + std::vector<Expr> actual_size; | ||
| 47 | +}; | ||
| 48 | + | ||
| 49 | +af::Status GetDtypeByteSize(const std::string &dtype, Expr &byte_size); | ||
| 50 | +af::Status GetEffectiveHelperDtype(const std::string &dtype, std::string &helper_dtype); | ||
| 51 | +af::Status GetBroadcastVectorElements(const std::string &dtype, Expr &vl, Expr &half_vl, Expr &block_elements); | ||
| 52 | +af::Status GetBroadcastTilingVectorElements(const std::string &dtype, Expr &vl, Expr &half_vl, Expr &block_elements); | ||
| 53 | +af::Status CeilDiv(const Expr &value, const Expr &divisor, Expr &result); | ||
| 54 | +Expr ResolveParamExprLeaf(const ascir_param::ParamExprLeaf &leaf, const Expr &semantic_expr, const Expr &size_expr, | ||
| 55 | + const Expr &actual_size_expr); | ||
| 56 | +Expr ShapeProduct(const std::vector<Expr> &shape); | ||
| 57 | +af::Status BuildBroadcastTiling(const std::vector<ascir_param::ParamExprLeaf> &src, | ||
| 58 | + const std::vector<ascir_param::ParamExprLeaf> &dst, const std::string &dtype, | ||
| 59 | + const ParamExprInputs &src_inputs, const ParamExprInputs &dst_inputs, | ||
| 60 | + BroadcastTilingInfo &tiling); | ||
| 61 | +bool IsLastAxisBroadcast(const BroadcastTilingInfo &tiling); | ||
| 62 | + | ||
| 63 | +af::Status AddVfInstructPerf(const std::string &instruct, const std::string &dtype, const Expr &repeat_time, | ||
| 64 | + uint32_t instruct_count, VfCostAccumulator &acc); | ||
| 65 | +af::Status AddBroadcastDataCopyPerf(const std::string &dtype, const Expr &repeat_time, VfCostAccumulator &acc); | ||
| 66 | +Expr GetVfCost(const VfCostAccumulator &acc, bool include_head = true); | ||
| 67 | +af::Status BuildBroadcastTernary(const std::string &name, CondType condition, const Expr &lhs, const Expr &rhs, | ||
| 68 | + const Expr &true_value, const Expr &false_value, TernaryOpMap &ternary_ops, | ||
| 69 | + Expr &result); | ||
| 70 | + | ||
| 71 | +} // namespace ascendcapi_v2 | ||
| 72 | +} // namespace att | ||
| 73 | + | ||
| 74 | +#endif // AUTOFUSE_ASCENDC_BROADCAST_PERF_UTILS_V2_H_ | ||
| @@ -10,6 +10,7 @@ | |||
| 10 | #include "perf_param_v2.h" | 10 | #include "perf_param_v2.h" |
| 11 | #include "nddma_model.h" | 11 | #include "nddma_model.h" |
| 12 | #include "v35/att/api_perf_register/ascir_reduce_api_perf_v2.h" | 12 | #include "v35/att/api_perf_register/ascir_reduce_api_perf_v2.h" |
| 13 | +#include "v35/att/api_perf_register/ascendc_api_perf/broadcast_api_perf_v2.h" | ||
| 13 | #include "v35/att/api_perf_register/ascendc_regbase_perf.h" | 14 | #include "v35/att/api_perf_register/ascendc_regbase_perf.h" |
| 14 | #include "api_perf_register/api_perf_factory.h" | 15 | #include "api_perf_register/api_perf_factory.h" |
| 15 | #include "api_perf_register/ascendc_api_perf.h" | 16 | #include "api_perf_register/ascendc_api_perf.h" |
| @@ -556,6 +557,65 @@ af::Status TransposeApi([[maybe_unused]] const std::vector<TensorShapeInfo> &inp | |||
| 556 | GE_ASSERT_SUCCESS(ascendcperf_v2::TransposePerf(node_info, perf_res)); | 557 | GE_ASSERT_SUCCESS(ascendcperf_v2::TransposePerf(node_info, perf_res)); |
| 557 | return af::SUCCESS; | 558 | return af::SUCCESS; |
| 558 | } | 559 | } |
| 560 | + | ||
| 561 | +af::Status ValidateBroadcastBasic(const std::vector<TensorShapeInfo> &input_shapes, | ||
| 562 | + const std::vector<TensorShapeInfo> &output_shapes) { | ||
| 563 | + GE_ASSERT_TRUE(input_shapes.size() == 1U && output_shapes.size() == 1U, | ||
| 564 | + "Broadcast requires exactly one input and one output shape."); | ||
| 565 | + GE_ASSERT_TRUE(!input_shapes[0].data_type.empty() && !output_shapes[0].data_type.empty(), | ||
| 566 | + "Broadcast input/output dtype is missing."); | ||
| 567 | + return af::SUCCESS; | ||
| 568 | +} | ||
| 569 | + | ||
| 570 | +af::Status ValidateBroadcastShapes(const std::vector<TensorShapeInfo> &input_shapes, | ||
| 571 | + const std::vector<TensorShapeInfo> &output_shapes, const NodeInfo &node) { | ||
| 572 | + if (node.broadcast_node_params.is_scalar) { | ||
| 573 | + GE_ASSERT_TRUE(node.broadcast_node_params.duplicate_count.IsValid(), "Broadcast scalar actual size is missing."); | ||
| 574 | + return af::SUCCESS; | ||
| 575 | + } | ||
| 576 | + GE_ASSERT_TRUE(!output_shapes[0].dims.empty(), "Broadcast output shape is missing."); | ||
| 577 | + GE_ASSERT_TRUE(!input_shapes[0].dims.empty()); | ||
| 578 | + GE_ASSERT_TRUE(input_shapes[0].dims.size() == output_shapes[0].dims.size(), | ||
| 579 | + "Broadcast input/output shape ranks are different."); | ||
| 580 | + GE_ASSERT_TRUE(input_shapes[0].repeats.size() == input_shapes[0].dims.size(), | ||
| 581 | + "Broadcast input repeats length is invalid."); | ||
| 582 | + return af::SUCCESS; | ||
| 583 | +} | ||
| 584 | + | ||
| 585 | +af::Status LegacyBroadcastPerf(const std::vector<TensorShapeInfo> &input_shapes, | ||
| 586 | + const std::vector<TensorShapeInfo> &output_shapes, const NodeInfo &node, | ||
| 587 | + PerfOutputInfo &perf_res) { | ||
| 588 | + const auto legacy_perf = GetPerfFunc(kBroadcast); | ||
| 589 | + GE_ASSERT_NOTNULL(legacy_perf, "Legacy Broadcast performance function is unavailable."); | ||
| 590 | + return legacy_perf(input_shapes, output_shapes, node, perf_res); | ||
| 591 | +} | ||
| 592 | + | ||
| 593 | +af::Status BroadcastApiV2([[maybe_unused]] const std::vector<TensorShapeInfo> &input_shapes, | ||
| 594 | + [[maybe_unused]] const std::vector<TensorShapeInfo> &output_shapes, const NodeInfo &node, | ||
| 595 | + PerfOutputInfo &perf_res) { | ||
| 596 | + GE_ASSERT_SUCCESS(ValidateBroadcastBasic(input_shapes, output_shapes)); | ||
| 597 | + if (!node.broadcast_node_params.valid || | ||
| 598 | + (!node.broadcast_node_params.is_scalar && | ||
| 599 | + (node.broadcast_node_params.src_shape.empty() || node.broadcast_node_params.dst_shape.empty()))) { | ||
| 600 | + return LegacyBroadcastPerf(input_shapes, output_shapes, node, perf_res); | ||
| 601 | + } | ||
| 602 | + GE_ASSERT_SUCCESS(ValidateBroadcastShapes(input_shapes, output_shapes, node)); | ||
| 603 | + NodeDetail node_info; | ||
| 604 | + GE_ASSERT_SUCCESS(SetNodeDetail(input_shapes, output_shapes, node_info)); | ||
| 605 | + node_info.broadcast_node_params = node.broadcast_node_params; | ||
| 606 | + if (!input_shapes.empty()) { | ||
| 607 | + node_info.repeats = input_shapes[0].repeats; | ||
| 608 | + } | ||
| 609 | + const auto new_status = ascendcperf_v2::BroadcastPerf(node_info, perf_res); | ||
| 610 | + if (new_status == af::SUCCESS) { | ||
| 611 | + return af::SUCCESS; | ||
| 612 | + } | ||
| 613 | + if (!ascendcperf_v2::IsBroadcastFallback(node_info)) { | ||
| 614 | + return new_status; | ||
| 615 | + } | ||
| 616 | + // Only a classified unsupported implementation branch is delegated to the legacy model. | ||
| 617 | + return LegacyBroadcastPerf(input_shapes, output_shapes, node, perf_res); | ||
| 618 | +} | ||
| 559 | } // namespace ascir_v2 | 619 | } // namespace ascir_v2 |
| 560 | 620 | ||
| 561 | REGISTER_EVAL_FUNC_TAG(kStore, V2, ascir_v2::StoreApiV2); | 621 | REGISTER_EVAL_FUNC_TAG(kStore, V2, ascir_v2::StoreApiV2); |
| @@ -599,6 +659,7 @@ REGISTER_EVAL_FUNC_TAG(kCast, V2, ascir_v2::CastApi); | |||
| 599 | REGISTER_EVAL_FUNC_TAG(kSum, V2, ascir_reduce_v2::SumApi); | 659 | REGISTER_EVAL_FUNC_TAG(kSum, V2, ascir_reduce_v2::SumApi); |
| 600 | REGISTER_EVAL_FUNC_TAG(kRemovePad, V2, ascir_v2::RemovePadApi); | 660 | REGISTER_EVAL_FUNC_TAG(kRemovePad, V2, ascir_v2::RemovePadApi); |
| 601 | REGISTER_EVAL_FUNC_TAG(kWhere, V2, ascir_v2::WhereApi); | 661 | REGISTER_EVAL_FUNC_TAG(kWhere, V2, ascir_v2::WhereApi); |
| 662 | +REGISTER_EVAL_FUNC_TAG(kBroadcast, V2, ascir_v2::BroadcastApiV2); | ||
| 602 | REGISTER_EVAL_FUNC_TAG(kPow, V2, ascir_v2::PowApi); | 663 | REGISTER_EVAL_FUNC_TAG(kPow, V2, ascir_v2::PowApi); |
| 603 | REGISTER_EVAL_FUNC_TAG(kErf, V2, ascir_v2::ErfApi); | 664 | REGISTER_EVAL_FUNC_TAG(kErf, V2, ascir_v2::ErfApi); |
| 604 | REGISTER_EVAL_FUNC_TAG(kTanh, V2, ascir_v2::TanhApi); | 665 | REGISTER_EVAL_FUNC_TAG(kTanh, V2, ascir_v2::TanhApi); |
| @@ -623,7 +684,8 @@ ApiPerfRegister<ApiPerf> indirect_load_api_perf_v2(ApiPerfRegisterV2(kIndirectLo | |||
| 623 | &tiling_schedule_config_table_v2)); | 684 | &tiling_schedule_config_table_v2)); |
| 624 | ApiPerfRegister<ApiPerf> abs_api_perf_v2(ApiPerfRegisterV2(kAbs, kAbs + "V2", nullptr, &perf_param_table_v2, | 685 | ApiPerfRegister<ApiPerf> abs_api_perf_v2(ApiPerfRegisterV2(kAbs, kAbs + "V2", nullptr, &perf_param_table_v2, |
| 625 | &tiling_schedule_config_table_v2)); | 686 | &tiling_schedule_config_table_v2)); |
| 626 | -ApiPerfRegister<ApiPerf> broadcast_api_perf_v2(ApiPerfRegisterV2(kBroadcast, kBroadcast, nullptr, &perf_param_table_v2, | 687 | +ApiPerfRegister<ApiPerf> broadcast_api_perf_v2(ApiPerfRegisterV2(kBroadcast, GetPerfFunc(kBroadcast + "V2"), nullptr, |
| 688 | + &perf_param_table_v2, | ||
| 627 | &tiling_schedule_config_table_v2)); | 689 | &tiling_schedule_config_table_v2)); |
| 628 | ApiPerfRegister<ApiPerf> cast_api_perf_v2(ApiPerfRegisterV2(kCast, kCast + "V2", nullptr, &perf_param_table_v2, | 690 | ApiPerfRegister<ApiPerf> cast_api_perf_v2(ApiPerfRegisterV2(kCast, kCast + "V2", nullptr, &perf_param_table_v2, |
| 629 | &tiling_schedule_config_table_v2)); | 691 | &tiling_schedule_config_table_v2)); |
| @@ -15,6 +15,7 @@ | |||
| 15 | #include "api_call/utils/api_call_utils.h" | 15 | #include "api_call/utils/api_call_utils.h" |
| 16 | #include "api_call/utils/api_call_factory.h" | 16 | #include "api_call/utils/api_call_factory.h" |
| 17 | #include "api_call/broadcast/broadcast_api_call.h" | 17 | #include "api_call/broadcast/broadcast_api_call.h" |
| 18 | +#include "ascir_node_param/ascir_node_param.h" | ||
| 18 | #include "codegen/expression_convert_struct.h" | 19 | #include "codegen/expression_convert_struct.h" |
| 19 | #include "reg_api_call_utils.h" | 20 | #include "reg_api_call_utils.h" |
| 20 | 21 | ||
| @@ -22,15 +23,115 @@ namespace codegen { | |||
| 22 | using namespace std; | 23 | using namespace std; |
| 23 | using namespace ascgen_utils; | 24 | using namespace ascgen_utils; |
| 24 | 25 | ||
| 26 | +namespace { | ||
| 27 | +constexpr const char *kAscirNodeParams = "AscirNodeParams"; | ||
| 28 | +constexpr const char *kCompactPddingMode = "AscendC::PaddingMode::Compact"; | ||
| 29 | + | ||
| 30 | +af::Status ResetBroadcastNodeParams(const af::AscNodePtr &node, ascir_param::BroadcastNodeParams *&broadcast_params) { | ||
| 31 | + GE_ASSERT_NOTNULL(node); | ||
| 32 | + auto params = ascir_param::GetAscirNodeParams(node); | ||
| 33 | + if (params == nullptr) { | ||
| 34 | + auto op_desc = node->GetOpDesc(); | ||
| 35 | + GE_ASSERT_NOTNULL(op_desc); | ||
| 36 | + params = std::make_shared<ascir_param::AscirNodeParams>(); | ||
| 37 | + GE_ASSERT_TRUE(op_desc->SetExtAttr(kAscirNodeParams, params), "Node:%s SetExtAttr failed", node->GetNamePtr()); | ||
| 38 | + } | ||
| 39 | + auto *specific_params = std::get_if<ascir_param::BroadcastNodeParams>(¶ms->specific_params); | ||
| 40 | + if (specific_params == nullptr) { | ||
| 41 | + params->specific_params = ascir_param::BroadcastNodeParams{}; | ||
| 42 | + specific_params = std::get_if<ascir_param::BroadcastNodeParams>(¶ms->specific_params); | ||
| 43 | + } | ||
| 44 | + GE_ASSERT_NOTNULL(specific_params, "Broadcast specific params is null, node[%s].", node->GetNamePtr()); | ||
| 45 | + params->api_name = node->GetType(); | ||
| 46 | + params->status = ascir_param::ParamBuildStatus::kBuilt; | ||
| 47 | + *specific_params = ascir_param::BroadcastNodeParams{}; | ||
| 48 | + broadcast_params = specific_params; | ||
| 49 | + return af::SUCCESS; | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +std::vector<ascir_param::ParamExprLeaf> GetBroadcastShape(const TPipe &tpipe, const Tensor &input, const Tensor &output, | ||
| 53 | + bool is_src, bool is_compact_padding) { | ||
| 54 | + std::vector<ascir_param::ParamExprLeaf> shape; | ||
| 55 | + const auto axis_count = input.vectorized_axis.size(); | ||
| 56 | + shape.reserve(axis_count); | ||
| 57 | + for (size_t pos = 0UL; pos < axis_count; ++pos) { | ||
| 58 | + if (is_src && | ||
| 59 | + af::SymbolicUtils::StaticCheckEq(input.axis_size[input.vectorized_axis_pos[pos]], | ||
| 60 | + output.axis_size[output.vectorized_axis_pos[pos]]) != af::TriBool::kTrue) { | ||
| 61 | + shape.push_back({af::Symbol(1U), ascir_param::ParamExprRole::kSemantic}); | ||
| 62 | + continue; | ||
| 63 | + } | ||
| 64 | + const auto axis_id = output.vectorized_axis[pos]; | ||
| 65 | + auto role = tpipe.tiler.GetAxis(axis_id).type != ascir::Axis::Type::kAxisTypeTileInner || | ||
| 66 | + output.vectorized_axis[0] == axis_id | ||
| 67 | + ? ascir_param::ParamExprRole::kActualSize | ||
| 68 | + : ascir_param::ParamExprRole::kSize; | ||
| 69 | + if (pos != axis_count - 1UL) { | ||
| 70 | + shape.push_back({output.axis_size[output.vectorized_axis_pos[pos]], role}); | ||
| 71 | + continue; | ||
| 72 | + } | ||
| 73 | + size_t pre_pos = std::numeric_limits<size_t>::max(); | ||
| 74 | + for (size_t i = 0UL; i < pos; ++i) { | ||
| 75 | + if (output.vectorized_strides[i] != 0) { | ||
| 76 | + pre_pos = i; | ||
| 77 | + } | ||
| 78 | + } | ||
| 79 | + const auto expr = pre_pos == std::numeric_limits<size_t>::max() ? output.axis_size[output.vectorized_axis_pos[pos]] | ||
| 80 | + : output.vectorized_strides[pre_pos]; | ||
| 81 | + if (pre_pos != std::numeric_limits<size_t>::max() && is_compact_padding) { | ||
| 82 | + role = ascir_param::ParamExprRole::kActualSize; | ||
| 83 | + } | ||
| 84 | + shape.push_back({expr, role}); | ||
| 85 | + } | ||
| 86 | + return shape; | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +af::Status GetBroadcastScalarDuplicateCount(const TPipe &tpipe, const Tensor &output, af::Expression &count) { | ||
| 90 | + const auto parse_tiling_expr = [](std::string value) { | ||
| 91 | + size_t pos = 0UL; | ||
| 92 | + while ((pos = value.find("t->", pos)) != std::string::npos) { | ||
| 93 | + value.erase(pos, 3UL); | ||
| 94 | + } | ||
| 95 | + return af::Expression::Parse(value.c_str()); | ||
| 96 | + }; | ||
| 97 | + count = af::Expression::Parse("0"); | ||
| 98 | + if (output.vectorized_strides.size() != output.vectorized_axis.size()) { | ||
| 99 | + count = af::Expression::Parse("1"); | ||
| 100 | + for (const auto axis_pos : output.vectorized_axis_pos) { | ||
| 101 | + const auto axis_size_str = tpipe.tiler.Size(output.axis_size[axis_pos]); | ||
| 102 | + const auto axis_size = parse_tiling_expr(axis_size_str); | ||
| 103 | + GE_ASSERT_TRUE(axis_size.IsValid(), "Broadcast scalar output axis size is invalid: %s", axis_size_str.c_str()); | ||
| 104 | + count = af::sym::Mul(count, axis_size); | ||
| 105 | + } | ||
| 106 | + return af::SUCCESS; | ||
| 107 | + } | ||
| 108 | + for (size_t i = 0UL; i < output.vectorized_axis.size(); ++i) { | ||
| 109 | + if (output.vectorized_strides[i] == 0) { | ||
| 110 | + continue; | ||
| 111 | + } | ||
| 112 | + const auto axis_pos = output.vectorized_axis_pos[i]; | ||
| 113 | + const auto axis_size_str = tpipe.tiler.Size(output.axis_size[axis_pos]); | ||
| 114 | + const auto axis_size = parse_tiling_expr(axis_size_str); | ||
| 115 | + GE_ASSERT_TRUE(axis_size.IsValid(), "Broadcast scalar output axis size is invalid: %s", axis_size_str.c_str()); | ||
| 116 | + auto term = af::sym::Sub(axis_size, af::Expression::Parse("1")); | ||
| 117 | + if (output.vectorized_strides[i] != 1) { | ||
| 118 | + const auto stride = parse_tiling_expr(tpipe.tiler.Size(output.vectorized_strides[i])); | ||
| 119 | + GE_ASSERT_TRUE(stride.IsValid(), "Broadcast scalar output stride is invalid."); | ||
| 120 | + term = af::sym::Mul(term, stride); | ||
| 121 | + } | ||
| 122 | + count = af::sym::Add(count, term); | ||
| 123 | + } | ||
| 124 | + count = af::sym::Add(count, af::Expression::Parse("1")); | ||
| 125 | + return af::SUCCESS; | ||
| 126 | +} | ||
| 127 | + | ||
| 128 | +} // namespace | ||
| 129 | + | ||
| 25 | static void GenParams(const TPipe &tpipe, const Tensor &input, const Tensor &output, std::stringstream &ss, bool is_src, | 130 | static void GenParams(const TPipe &tpipe, const Tensor &input, const Tensor &output, std::stringstream &ss, bool is_src, |
| 26 | - bool has_transpose) { | 131 | + bool is_compact_padding) { |
| 27 | // 只保证在仅对张量尾轴做32B对齐的场景下有效,若对中间轴做了对齐,则还需要增加处理逻辑 | 132 | // 只保证在仅对张量尾轴做32B对齐的场景下有效,若对中间轴做了对齐,则还需要增加处理逻辑 |
| 28 | auto vectorized_axis_size = input.vectorized_axis.size(); | 133 | auto vectorized_axis_size = input.vectorized_axis.size(); |
| 29 | const char *shape_prefix = is_src ? "src_shape_" : "dst_shape_"; | 134 | const char *shape_prefix = is_src ? "src_shape_" : "dst_shape_"; |
| 30 | - DataCopyParams data_copy_param; | ||
| 31 | - (void)CalculateDmaParams(tpipe, output, output, data_copy_param); | ||
| 32 | - constexpr char kCompactPddingMode[] = "AscendC::PaddingMode::Compact"; | ||
| 33 | - std::string padding_mode = GetPaddingMode(output, data_copy_param, has_transpose); | ||
| 34 | ss << "const uint32_t " << shape_prefix << input.id << "_brc_to_" << output.id << "[" << vectorized_axis_size | 135 | ss << "const uint32_t " << shape_prefix << input.id << "_brc_to_" << output.id << "[" << vectorized_axis_size |
| 35 | << "] = {"; | 136 | << "] = {"; |
| 36 | const char *sep = ""; | 137 | const char *sep = ""; |
| @@ -47,7 +148,6 @@ static void GenParams(const TPipe &tpipe, const Tensor &input, const Tensor &out | |||
| 47 | continue; | 148 | continue; |
| 48 | } | 149 | } |
| 49 | } | 150 | } |
| 50 | - // 非尾轴 | ||
| 51 | if (pos != vectorized_axis_size - 1UL) { | 151 | if (pos != vectorized_axis_size - 1UL) { |
| 52 | GetOneAxisSize(tpipe, output, pos, ss); | 152 | GetOneAxisSize(tpipe, output, pos, ss); |
| 53 | ss << ")"; | 153 | ss << ")"; |
| @@ -68,7 +168,7 @@ static void GenParams(const TPipe &tpipe, const Tensor &input, const Tensor &out | |||
| 68 | ascir::AxisId axis_id = output.vectorized_axis[pos]; | 168 | ascir::AxisId axis_id = output.vectorized_axis[pos]; |
| 69 | auto last_dim_size = output.vectorized_strides[pre_pos]; | 169 | auto last_dim_size = output.vectorized_strides[pre_pos]; |
| 70 | if (tpipe.tiler.GetAxis(axis_id).type != ascir::Axis::Type::kAxisTypeTileInner || | 170 | if (tpipe.tiler.GetAxis(axis_id).type != ascir::Axis::Type::kAxisTypeTileInner || |
| 71 | - output.vectorized_axis[0] == axis_id || padding_mode == kCompactPddingMode) { | 171 | + output.vectorized_axis[0] == axis_id || is_compact_padding) { |
| 72 | ss << tpipe.tiler.ActualSize(last_dim_size); | 172 | ss << tpipe.tiler.ActualSize(last_dim_size); |
| 73 | } else { | 173 | } else { |
| 74 | ss << tpipe.tiler.Size(last_dim_size); | 174 | ss << tpipe.tiler.Size(last_dim_size); |
| @@ -85,13 +185,19 @@ Status BroadcastRegApiCall::Generate(const TPipe &tpipe, const std::vector<ascir | |||
| 85 | const auto &x = inputs[0].get(); | 185 | const auto &x = inputs[0].get(); |
| 86 | const auto &y = outputs[0].get(); | 186 | const auto &y = outputs[0].get(); |
| 87 | (void)RegisterBasicDumpParam(this->api_name_, inputs, outputs); | 187 | (void)RegisterBasicDumpParam(this->api_name_, inputs, outputs); |
| 88 | - | ||
| 89 | if (IsBroadcastConstantTensor(x)) { | 188 | if (IsBroadcastConstantTensor(x)) { |
| 90 | int64_t id = -1; | 189 | int64_t id = -1; |
| 91 | BroadcastScalar(tpipe, current_axis, x, y, id, result, false); | 190 | BroadcastScalar(tpipe, current_axis, x, y, id, result, false); |
| 191 | + ascir_param::BroadcastNodeParams *broadcast_params = nullptr; | ||
| 192 | + if (ResetBroadcastNodeParams(this->node, broadcast_params) == af::SUCCESS && broadcast_params != nullptr) { | ||
| 193 | + broadcast_params->valid = true; | ||
| 194 | + broadcast_params->is_scalar = true; | ||
| 195 | + broadcast_params->const_rank = -1; | ||
| 196 | + GE_ASSERT_SUCCESS(GetBroadcastScalarDuplicateCount(tpipe, y, broadcast_params->duplicate_count)); | ||
| 197 | + broadcast_params->duplicate_count_role = ascir_param::ParamExprRole::kActualSize; | ||
| 198 | + } | ||
| 92 | return af::SUCCESS; | 199 | return af::SUCCESS; |
| 93 | } | 200 | } |
| 94 | - | ||
| 95 | size_t min_vectorized_axis_size = 1UL; | 201 | size_t min_vectorized_axis_size = 1UL; |
| 96 | size_t max_vectorized_axis_size = 9UL; | 202 | size_t max_vectorized_axis_size = 9UL; |
| 97 | if (x.vectorized_axis.size() < min_vectorized_axis_size || x.vectorized_axis.size() > max_vectorized_axis_size) { | 203 | if (x.vectorized_axis.size() < min_vectorized_axis_size || x.vectorized_axis.size() > max_vectorized_axis_size) { |
| @@ -111,15 +217,16 @@ Status BroadcastRegApiCall::Generate(const TPipe &tpipe, const std::vector<ascir | |||
| 111 | x.vectorized_axis.size(), y.vectorized_axis.size()); | 217 | x.vectorized_axis.size(), y.vectorized_axis.size()); |
| 112 | return af::FAILED; | 218 | return af::FAILED; |
| 113 | } | 219 | } |
| 114 | - | ||
| 115 | std::stringstream ss; | 220 | std::stringstream ss; |
| 116 | // 生成参数 const uint32_t *dst_shape; | 221 | // 生成参数 const uint32_t *dst_shape; |
| 117 | std::stringstream params_name; | 222 | std::stringstream params_name; |
| 118 | - auto has_transpose = IsGraphHasTransposeNode(this->node); | 223 | + DataCopyParams data_copy_param; |
| 119 | - GenParams(tpipe, x, y, ss, false, has_transpose); | 224 | + (void)CalculateDmaParams(tpipe, y, y, data_copy_param); |
| 225 | + const auto has_transpose = IsGraphHasTransposeNode(this->node); | ||
| 226 | + const bool is_compact_padding = GetPaddingMode(y, data_copy_param, has_transpose) == kCompactPddingMode; | ||
| 227 | + GenParams(tpipe, x, y, ss, false, is_compact_padding); | ||
| 120 | // 生成参数 const uint32_t *src_shape; | 228 | // 生成参数 const uint32_t *src_shape; |
| 121 | - GenParams(tpipe, x, y, ss, true, has_transpose); | 229 | + GenParams(tpipe, x, y, ss, true, is_compact_padding); |
| 122 | - | ||
| 123 | std::string dtype_name; | 230 | std::string dtype_name; |
| 124 | Tensor::DtypeName(x.dtype, dtype_name); | 231 | Tensor::DtypeName(x.dtype, dtype_name); |
| 125 | ss << this->api_name_ << "<" << dtype_name << "," << x.vectorized_axis.size() << ">("; | 232 | ss << this->api_name_ << "<" << dtype_name << "," << x.vectorized_axis.size() << ">("; |
| @@ -127,10 +234,16 @@ Status BroadcastRegApiCall::Generate(const TPipe &tpipe, const std::vector<ascir | |||
| 127 | ss << y << "[" << tpipe.tiler.TensorVectorizedOffset(current_axis, y) << "], "; | 234 | ss << y << "[" << tpipe.tiler.TensorVectorizedOffset(current_axis, y) << "], "; |
| 128 | // 传入参数 const LocalTensor<T> &src; | 235 | // 传入参数 const LocalTensor<T> &src; |
| 129 | ss << x << "[" << tpipe.tiler.TensorVectorizedOffset(current_axis, x) << "], "; | 236 | ss << x << "[" << tpipe.tiler.TensorVectorizedOffset(current_axis, x) << "], "; |
| 130 | - | ||
| 131 | ss << "dst_shape_" << x.id << "_brc_to_" << y.id << ", src_shape_" << x.id << "_brc_to_" << y.id << ");\n"; | 237 | ss << "dst_shape_" << x.id << "_brc_to_" << y.id << ", src_shape_" << x.id << "_brc_to_" << y.id << ");\n"; |
| 132 | 238 | ||
| 133 | result = ss.str(); | 239 | result = ss.str(); |
| 240 | + ascir_param::BroadcastNodeParams *broadcast_params = nullptr; | ||
| 241 | + if (ResetBroadcastNodeParams(this->node, broadcast_params) == af::SUCCESS && broadcast_params != nullptr) { | ||
| 242 | + broadcast_params->valid = true; | ||
| 243 | + broadcast_params->const_rank = static_cast<int32_t>(x.vectorized_axis.size()); | ||
| 244 | + broadcast_params->dst_shape = GetBroadcastShape(tpipe, x, y, false, is_compact_padding); | ||
| 245 | + broadcast_params->src_shape = GetBroadcastShape(tpipe, x, y, true, is_compact_padding); | ||
| 246 | + } | ||
| 134 | return af::SUCCESS; | 247 | return af::SUCCESS; |
| 135 | } | 248 | } |
| 136 | 249 | ||