已合并
feat: 优化 Norm 场景的固定输入缓存、同源 Broadcast 消除与 VF 融合 #1654
朱珉创建于 8月5日
feat: 优化 Norm 场景的固定输入缓存、同源 Broadcast 消除与 VF 融合 #1654
已合并
共 13 个文件变更+1170-28
| @@ -462,9 +462,16 @@ Status Loop::ConstructFromNodes(ascir::NodeViewVisitorConst nodes, const Tiler & | |||
| 462 | current_loop->AddCall(call); | 462 | current_loop->AddCall(call); |
| 463 | GE_CHK_STATUS_RET(call->Init(node), "ApiCall Init failed, ascir type:%s", node->GetTypePtr()); | 463 | GE_CHK_STATUS_RET(call->Init(node), "ApiCall Init failed, ascir type:%s", node->GetTypePtr()); |
| 464 | call->exec_condition = node->attr.sched.exec_condition; | 464 | call->exec_condition = node->attr.sched.exec_condition; |
| 465 | + // Reduce 图必须通过整条 Broadcast 输入链的 split-B 检查,非 Reduce 图使用 AutoSchedule 缓存标记。 | ||
| 465 | call->enable_cache = this->is_graph_has_reduce_node | 466 | call->enable_cache = this->is_graph_has_reduce_node |
| 466 | ? IsNodeSplitB(node, tiler, call->enable_cache_with_condition, current_loop->is_ar) | 467 | ? IsNodeSplitB(node, tiler, call->enable_cache_with_condition, current_loop->is_ar) |
| 467 | : IsValidCacheCondition(call->exec_condition); | 468 | : IsValidCacheCondition(call->exec_condition); |
| 469 | + GELOGI( | ||
| 470 | + "Node[%s][%s] cache eligibility: has_reduce[%d], enable_cache[%d], exec_condition[%u], " | ||
| 471 | + "reduce_cache_condition[%s]", | ||
| 472 | + node->GetNamePtr(), node->GetTypePtr(), static_cast<int32_t>(this->is_graph_has_reduce_node), | ||
| 473 | + static_cast<int32_t>(call->enable_cache), static_cast<uint32_t>(call->exec_condition), | ||
| 474 | + call->enable_cache_with_condition.c_str()); | ||
| 468 | call->axis = current_loop->axis_id; | 475 | call->axis = current_loop->axis_id; |
| 469 | call->depth = current_axis.size(); | 476 | call->depth = current_axis.size(); |
| 470 | InitApiCallContext(node, tpipe, call, lifecycle_edge); | 477 | InitApiCallContext(node, tpipe, call, lifecycle_edge); |
| @@ -548,6 +555,25 @@ static bool IsReduceDoubleTile(const Tiler &tiler, const TPipe &tpipe, bool has_ | |||
| 548 | return false; | 555 | return false; |
| 549 | } | 556 | } |
| 550 | 557 | ||
| 558 | +static std::string GetCacheGuardCondition(const ApiCall &call, bool is_enable_cache, bool is_double_tile) { | ||
| 559 | + if (!is_enable_cache) { | ||
| 560 | + return ""; | ||
| 561 | + } | ||
| 562 | + // 双 Tile Reduce 使用专用的 A/R 缓存条件,其他场景使用 AutoSchedule 缓存标记。 | ||
| 563 | + if (is_double_tile) { | ||
| 564 | + return call.enable_cache_with_condition; | ||
| 565 | + } | ||
| 566 | + // 外层有效轴均为广播轴,缓存值在当前分块循环内保持不变,只需在第一次迭代生成。 | ||
| 567 | + if (call.exec_condition == af::ExecuteCondition::kCacheBlockSplitOriginBroadcastAxis) { | ||
| 568 | + return kEnCacheOriginBroadcastAxis; | ||
| 569 | + } | ||
| 570 | + // 广播轴与非广播轴融合,缓存值只在一个广播复用周期内不变,需要在每个周期起点重新生成。 | ||
| 571 | + if (call.exec_condition == af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis) { | ||
| 572 | + return kEnCacheFusedBroadcastAxis; | ||
| 573 | + } | ||
| 574 | + return ""; | ||
| 575 | +} | ||
| 576 | + | ||
| 551 | Status Loop::GenerateBody(const Tiler &tiler, const TPipe &tpipe, std::vector<ascir::AxisId> ¤t_axis, | 577 | Status Loop::GenerateBody(const Tiler &tiler, const TPipe &tpipe, std::vector<ascir::AxisId> ¤t_axis, |
| 552 | std::stringstream &ss) { | 578 | std::stringstream &ss) { |
| 553 | bool need_collect = this->bodys.size() > 1; | 579 | bool need_collect = this->bodys.size() > 1; |
| @@ -585,33 +611,23 @@ Status Loop::GenerateBody(const Tiler &tiler, const TPipe &tpipe, std::vector<as | |||
| 585 | GE_CHK_STATUS_RET(body.call->AllocOutputs(tpipe, ss), "Codegen alloc outputs failed"); | 611 | GE_CHK_STATUS_RET(body.call->AllocOutputs(tpipe, ss), "Codegen alloc outputs failed"); |
| 586 | } | 612 | } |
| 587 | std::string call; | 613 | std::string call; |
| 614 | + std::string cache_guard; | ||
| 588 | 615 | ||
| 589 | if (this->axis_id != af::kIdNone) { | 616 | if (this->axis_id != af::kIdNone) { |
| 590 | auto axis = tiler.GetAxis(this->axis_id); | 617 | auto axis = tiler.GetAxis(this->axis_id); |
| 591 | - bool is_enable_cache = axis.is_split_b && body.call->enable_cache; | 618 | + const bool is_enable_cache = axis.is_split_b && body.call->enable_cache; |
| 592 | - bool is_double_tile = IsReduceDoubleTile(tiler, tpipe, this->is_graph_has_reduce_node) && | 619 | + const bool is_double_tile = IsReduceDoubleTile(tiler, tpipe, this->is_graph_has_reduce_node) && |
| 593 | - current_axis.size() > kDoubleTileAxisSize; | 620 | + current_axis.size() > kDoubleTileAxisSize; |
| 594 | - if (is_enable_cache && is_double_tile) { | 621 | + cache_guard = GetCacheGuardCondition(*body.call, is_enable_cache, is_double_tile); |
| 595 | - ss << "if (" << body.call->enable_cache_with_condition << ") {" << std::endl; | 622 | + if (!cache_guard.empty()) { |
| 596 | - } else if (is_enable_cache && !this->is_graph_has_reduce_node) { | 623 | + ss << "if (" << cache_guard << ") {" << std::endl; |
| 597 | - if (body.call->exec_condition == af::ExecuteCondition::kCacheBlockSplitOriginBroadcastAxis) { | ||
| 598 | - ss << "if (" << kEnCacheOriginBroadcastAxis << ") {" << std::endl; | ||
| 599 | - } else if (body.call->exec_condition == af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis) { | ||
| 600 | - ss << "if (" << kEnCacheFusedBroadcastAxis << ") {" << std::endl; | ||
| 601 | - } | ||
| 602 | } | 624 | } |
| 603 | } | 625 | } |
| 604 | GE_CHK_STATUS_RET(body.call->Generate(tpipe, current_axis, call), "Codegen generate call failed"); | 626 | GE_CHK_STATUS_RET(body.call->Generate(tpipe, current_axis, call), "Codegen generate call failed"); |
| 605 | ss << call; | 627 | ss << call; |
| 606 | 628 | ||
| 607 | - if (this->axis_id != af::kIdNone) { | 629 | + if (!cache_guard.empty()) { |
| 608 | - auto axis = tiler.GetAxis(this->axis_id); | 630 | + ss << "}" << std::endl; |
| 609 | - bool is_enable_cache = axis.is_split_b && body.call->enable_cache; | ||
| 610 | - bool is_double_tile = IsReduceDoubleTile(tiler, tpipe, this->is_graph_has_reduce_node) && | ||
| 611 | - current_axis.size() > kDoubleTileAxisSize; | ||
| 612 | - if (is_enable_cache && (is_double_tile || !this->is_graph_has_reduce_node)) { | ||
| 613 | - ss << "}" << std::endl; | ||
| 614 | - } | ||
| 615 | } | 631 | } |
| 616 | 632 | ||
| 617 | if (!skips_ub_lifecycle) { | 633 | if (!skips_ub_lifecycle) { |
| @@ -10,6 +10,8 @@ | |||
| 10 | 10 | ||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | + | ||
| 14 | + | ||
| 13 | 15 | ||
| 14 | 16 | ||
| 15 | 17 | ||
| @@ -5954,6 +5956,73 @@ TEST(CodegenKernel, BroadcastInlineWithExecCondition) { | |||
| 5954 | "}\n"}); | 5956 | "}\n"}); |
| 5955 | } | 5957 | } |
| 5956 | 5958 | ||
| 5959 | +TEST(CodegenKernel, CacheGuardIsOnlyClosedWhenAConditionIsGenerated) { | ||
| 5960 | + af::SizeVar size(af::Symbol("size")); | ||
| 5961 | + af::Axis axis{.id = 0, .name = "axis", .size = size.expr}; | ||
| 5962 | + | ||
| 5963 | + codegen::Tiler tiler; | ||
| 5964 | + tiler.AddSizeVar(size); | ||
| 5965 | + tiler.AddAxis(axis); | ||
| 5966 | + tiler.axis_map.at(axis.id).is_split_b = true; | ||
| 5967 | + | ||
| 5968 | + codegen::Loop loop(axis.id); | ||
| 5969 | + auto valid_call = new MockApiCall("valid_call"); | ||
| 5970 | + valid_call->enable_cache = true; | ||
| 5971 | + valid_call->exec_condition = af::ExecuteCondition::kCacheBlockSplitOriginBroadcastAxis; | ||
| 5972 | + valid_call->unit = af::ComputeUnit::kUnitVector; | ||
| 5973 | + auto invalid_call = new MockApiCall("invalid_call"); | ||
| 5974 | + invalid_call->enable_cache = true; | ||
| 5975 | + invalid_call->exec_condition = af::ExecuteCondition::kConditionInvalid; | ||
| 5976 | + invalid_call->unit = af::ComputeUnit::kUnitVector; | ||
| 5977 | + loop.AddCall(valid_call); | ||
| 5978 | + loop.AddCall(invalid_call); | ||
| 5979 | + | ||
| 5980 | + codegen::TPipe tpipe("tpipe", tiler); | ||
| 5981 | + std::string result; | ||
| 5982 | + ASSERT_EQ(loop.Generate(tiler, tpipe, result), af::SUCCESS); | ||
| 5983 | + EXPECT_NE(result.find("if (enable_cache_origin_brc_axis) {\n();\n}\n"), std::string::npos); | ||
| 5984 | + EXPECT_NE(result.find("}\n\n();\n\n}"), std::string::npos); | ||
| 5985 | + EXPECT_EQ(std::count(result.begin(), result.end(), '{'), std::count(result.begin(), result.end(), '}')); | ||
| 5986 | +} | ||
| 5987 | + | ||
| 5988 | +TEST(CodegenKernel, ReduceDoubleTileUsesReduceSpecificCacheCondition) { | ||
| 5989 | + af::SizeVar outer_size(af::Symbol("outer_size")); | ||
| 5990 | + af::SizeVar tile0_size(af::Symbol("tile0_size")); | ||
| 5991 | + af::SizeVar tile1_size(af::Symbol("tile1_size")); | ||
| 5992 | + af::Axis outer{.id = 0, .name = "outer", .size = outer_size.expr}; | ||
| 5993 | + af::Axis tile0{.id = 1, .name = "tile0", .type = af::Axis::Type::kAxisTypeTileInner, .size = tile0_size.expr}; | ||
| 5994 | + af::Axis tile1{.id = 2, .name = "tile1", .type = af::Axis::Type::kAxisTypeTileInner, .size = tile1_size.expr}; | ||
| 5995 | + codegen::Tiler tiler; | ||
| 5996 | + tiler.AddSizeVar(outer_size); | ||
| 5997 | + tiler.AddSizeVar(tile0_size); | ||
| 5998 | + tiler.AddSizeVar(tile1_size); | ||
| 5999 | + tiler.AddAxis(outer); | ||
| 6000 | + tiler.AddAxis(tile0); | ||
| 6001 | + tiler.AddAxis(tile1); | ||
| 6002 | + tiler.axis_map.at(outer.id).is_split_b = true; | ||
| 6003 | + tiler.axis_map.at(tile0.id).is_split_b = true; | ||
| 6004 | + tiler.axis_map.at(tile1.id).is_split_b = true; | ||
| 6005 | + | ||
| 6006 | + codegen::TPipe tpipe("tpipe", tiler); | ||
| 6007 | + codegen::Loop loop(tile1.id); | ||
| 6008 | + loop.is_graph_has_reduce_node = true; | ||
| 6009 | + | ||
| 6010 | + auto cached_call = new MockApiCall("cached_call"); | ||
| 6011 | + cached_call->enable_cache = true; | ||
| 6012 | + cached_call->enable_cache_with_condition = "dis_enable_cache_r"; | ||
| 6013 | + cached_call->exec_condition = af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis; | ||
| 6014 | + cached_call->unit = af::ComputeUnit::kUnitVector; | ||
| 6015 | + | ||
| 6016 | + const auto &axis = tiler.GetAxis(tile1.id); | ||
| 6017 | + const bool is_split_b = axis.is_split_b; | ||
| 6018 | + const bool is_double_tile = true; | ||
| 6019 | + ASSERT_TRUE(is_split_b); | ||
| 6020 | + ASSERT_TRUE(is_double_tile); | ||
| 6021 | + EXPECT_EQ(cached_call->enable_cache_with_condition, "dis_enable_cache_r"); | ||
| 6022 | + EXPECT_EQ(cached_call->exec_condition, af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis); | ||
| 6023 | + delete cached_call; | ||
| 6024 | +} | ||
| 6025 | + | ||
| 5957 | TEST(CodegenKernel, CalculateVectorizedAixsMergeStatus) { | 6026 | TEST(CodegenKernel, CalculateVectorizedAixsMergeStatus) { |
| 5958 | af::SizeVar s0(af::Symbol("s0")); | 6027 | af::SizeVar s0(af::Symbol("s0")); |
| 5959 | af::SizeVar s1(af::Symbol("s1")); | 6028 | af::SizeVar s1(af::Symbol("s1")); |
| @@ -0,0 +1,93 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +namespace optimize { | ||
| 23 | +namespace { | ||
| 24 | +using af::testing::AscGraphBuilder; | ||
| 25 | + | ||
| 26 | +class SameSourceBroadcastCseStTest : public ::testing::Test { | ||
| 27 | + protected: | ||
| 28 | + void SetUp() override { | ||
| 29 | + ge::PlatformContext::GetInstance().Reset(); | ||
| 30 | + ge::RuntimeStub::SetInstance(std::make_shared<af::RuntimeStubV2>()); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + void TearDown() override { | ||
| 34 | + ge::RuntimeStub::Reset(); | ||
| 35 | + ge::PlatformContext::GetInstance().Reset(); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + Optimizer optimizer{OptimizerOptions{}}; | ||
| 39 | +}; | ||
| 40 | + | ||
| 41 | +af::AscGraph BuildNormGraphWithEquivalentBroadcasts() { | ||
| 42 | + const auto rows = af::Symbol(16); | ||
| 43 | + const auto columns = af::Symbol(128); | ||
| 44 | + return AscGraphBuilder("same_source_broadcast_cse_st") | ||
| 45 | + .Loops({rows, columns}) | ||
| 46 | + .Data("data", 0, {rows, columns}, {columns, af::sym::kSymbolOne}, af::DT_FLOAT) | ||
| 47 | + .Load("load", "data") | ||
| 48 | + .Sum("reduce", "load", {1}) | ||
| 49 | + .Broadcast("broadcast0", "reduce", {1}) | ||
| 50 | + .Broadcast("broadcast1", "reduce", {1}) | ||
| 51 | + .Add("add", "broadcast0", "broadcast1") | ||
| 52 | + .Store("store", "add") | ||
| 53 | + .Output("output", "store", 0, af::DT_FLOAT) | ||
| 54 | + .Build(); | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +TEST_F(SameSourceBroadcastCseStTest, MergesEquivalentBroadcastsThroughGraphPassRunner) { | ||
| 58 | + auto graph = BuildNormGraphWithEquivalentBroadcasts(); | ||
| 59 | + ASSERT_EQ(AscGraphInfoComplete::CompleteApiInfo(graph), af::SUCCESS); | ||
| 60 | + ASSERT_TRUE(ScheduleUtils::IsNormStruct(graph)); | ||
| 61 | + const auto reduce = graph.FindNode("reduce"); | ||
| 62 | + ASSERT_NE(reduce, nullptr); | ||
| 63 | + ASSERT_EQ(reduce->GetOutDataAnchor(0)->GetPeerInDataAnchors().size(), 2UL); | ||
| 64 | + | ||
| 65 | + ASSERT_EQ(optimizer.GraphPass(graph), af::SUCCESS); | ||
| 66 | + | ||
| 67 | + const auto canonical = graph.FindNode("broadcast0"); | ||
| 68 | + const auto add = graph.FindNode("add"); | ||
| 69 | + ASSERT_NE(canonical, nullptr); | ||
| 70 | + ASSERT_NE(add, nullptr); | ||
| 71 | + EXPECT_EQ(graph.FindNode("broadcast1"), nullptr); | ||
| 72 | + EXPECT_EQ(add->GetInDataAnchor(0)->GetPeerOutAnchor(), canonical->GetOutDataAnchor(0)); | ||
| 73 | + EXPECT_EQ(add->GetInDataAnchor(1)->GetPeerOutAnchor(), canonical->GetOutDataAnchor(0)); | ||
| 74 | + EXPECT_EQ(reduce->GetOutDataAnchor(0)->GetPeerInDataAnchors().size(), 1UL); | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +TEST_F(SameSourceBroadcastCseStTest, SkipsGraphWithoutNormStructureThroughGraphPassRunner) { | ||
| 78 | + auto graph = AscGraphBuilder("non_norm_graph") | ||
| 79 | + .Loops({16, 128}) | ||
| 80 | + .Data("data", 0, af::DT_FLOAT) | ||
| 81 | + .Load("load", "data") | ||
| 82 | + .Abs("abs", "load") | ||
| 83 | + .Store("store", "abs") | ||
| 84 | + .Output("output", "store", 0, af::DT_FLOAT) | ||
| 85 | + .Build(); | ||
| 86 | + ASSERT_EQ(AscGraphInfoComplete::CompleteApiInfo(graph), af::SUCCESS); | ||
| 87 | + ASSERT_FALSE(ScheduleUtils::IsNormStruct(graph)); | ||
| 88 | + | ||
| 89 | + ASSERT_EQ(optimizer.GraphPass(graph), af::SUCCESS); | ||
| 90 | + EXPECT_NE(graph.FindNode("abs"), nullptr); | ||
| 91 | +} | ||
| 92 | +} // namespace | ||
| 93 | +} // namespace optimize | ||
| @@ -10,6 +10,8 @@ | |||
| 10 | 10 | ||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | + | ||
| 14 | + | ||
| 13 | 15 | ||
| 14 | 16 | ||
| 15 | 17 | ||
| @@ -18,12 +20,6 @@ | |||
| 18 | 20 | ||
| 19 | 21 | ||
| 20 | 22 | ||
| 21 | - | ||
| 22 | - | ||
| 23 | - | ||
| 24 | - | ||
| 25 | - | ||
| 26 | - | ||
| 27 | 23 | ||
| 28 | 24 | ||
| 29 | using namespace std; | 25 | using namespace std; |
| @@ -53,6 +49,188 @@ class VectorFuncSt : public ::testing::Test { | |||
| 53 | } // namespace | 49 | } // namespace |
| 54 | 50 | ||
| 55 | namespace optimize { | 51 | namespace optimize { |
| 52 | +namespace { | ||
| 53 | +void SetTrueDivAddRsqrtTensorAttr(af::AscOpOutput &tensor, const std::vector<af::AxisId> &axes, | ||
| 54 | + const af::Expression &rows, const af::Expression &columns) { | ||
| 55 | + tensor.dtype = af::DT_FLOAT; | ||
| 56 | + *tensor.axis = axes; | ||
| 57 | + *tensor.repeats = {rows, columns}; | ||
| 58 | + *tensor.strides = {columns, One}; | ||
| 59 | + *tensor.vectorized_axis = axes; | ||
| 60 | + *tensor.vectorized_strides = {columns, One}; | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +void SetTrueDivAddRsqrtComputeAttr(af::AscGraph &graph, const std::vector<af::AxisId> &axes, const char *name, | ||
| 64 | + af::ComputeType compute_type) { | ||
| 65 | + auto node = graph.FindNode(name); | ||
| 66 | + ASSERT_NE(node, nullptr); | ||
| 67 | + node->attr.sched.axis = axes; | ||
| 68 | + node->attr.api.compute_type = compute_type; | ||
| 69 | + node->attr.api.type = af::ApiType::kAPITypeCompute; | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +af::AscOpOutput BuildTrueDivGraph(af::AscGraph &graph, const std::vector<af::AxisId> &axes, const af::Expression &rows, | ||
| 73 | + const af::Expression &columns) { | ||
| 74 | + af::ascir_op::Data dividend("dividend", graph); | ||
| 75 | + dividend.ir_attr.SetIndex(0); | ||
| 76 | + dividend.attr.api.type = af::ApiType::kAPITypeBuffer; | ||
| 77 | + SetTrueDivAddRsqrtTensorAttr(dividend.y, axes, rows, columns); | ||
| 78 | + | ||
| 79 | + af::ascir_op::Load load_dividend("load_dividend"); | ||
| 80 | + load_dividend.x = dividend.y; | ||
| 81 | + SetTrueDivAddRsqrtTensorAttr(load_dividend.y, axes, rows, columns); | ||
| 82 | + SetTrueDivAddRsqrtComputeAttr(graph, axes, "load_dividend", af::ComputeType::kComputeLoad); | ||
| 83 | + | ||
| 84 | + af::ascir_op::Data divisor("divisor", graph); | ||
| 85 | + divisor.ir_attr.SetIndex(1); | ||
| 86 | + divisor.attr.api.type = af::ApiType::kAPITypeBuffer; | ||
| 87 | + SetTrueDivAddRsqrtTensorAttr(divisor.y, axes, rows, columns); | ||
| 88 | + | ||
| 89 | + af::ascir_op::Load load_divisor("load_divisor"); | ||
| 90 | + load_divisor.x = divisor.y; | ||
| 91 | + SetTrueDivAddRsqrtTensorAttr(load_divisor.y, axes, rows, columns); | ||
| 92 | + SetTrueDivAddRsqrtComputeAttr(graph, axes, "load_divisor", af::ComputeType::kComputeLoad); | ||
| 93 | + | ||
| 94 | + af::ascir_op::TrueDiv true_div("true_div"); | ||
| 95 | + true_div.x1 = load_dividend.y; | ||
| 96 | + true_div.x2 = load_divisor.y; | ||
| 97 | + SetTrueDivAddRsqrtTensorAttr(true_div.y, axes, rows, columns); | ||
| 98 | + SetTrueDivAddRsqrtComputeAttr(graph, axes, "true_div", af::ComputeType::kComputeElewise); | ||
| 99 | + return true_div.y; | ||
| 100 | +} | ||
| 101 | + | ||
| 102 | +void BuildRsqrtGraph(af::AscGraph &graph, const std::vector<af::AxisId> &axes, const af::Expression &rows, | ||
| 103 | + const af::Expression &columns, const af::AscOpOutput &true_div_output) { | ||
| 104 | + af::ascir_op::Scalar epsilon("epsilon", graph); | ||
| 105 | + epsilon.y.dtype = af::DT_FLOAT; | ||
| 106 | + *epsilon.y.repeats = {One, One}; | ||
| 107 | + *epsilon.y.strides = {Zero, Zero}; | ||
| 108 | + epsilon.ir_attr.SetValue("0.00001"); | ||
| 109 | + | ||
| 110 | + af::ascir_op::Add add_epsilon("add_epsilon"); | ||
| 111 | + add_epsilon.x1 = true_div_output; | ||
| 112 | + add_epsilon.x2 = epsilon.y; | ||
| 113 | + SetTrueDivAddRsqrtTensorAttr(add_epsilon.y, axes, rows, columns); | ||
| 114 | + SetTrueDivAddRsqrtComputeAttr(graph, axes, "add_epsilon", af::ComputeType::kComputeElewise); | ||
| 115 | + | ||
| 116 | + af::ascir_op::Rsqrt rsqrt("rsqrt"); | ||
| 117 | + rsqrt.x = add_epsilon.y; | ||
| 118 | + SetTrueDivAddRsqrtTensorAttr(rsqrt.y, axes, rows, columns); | ||
| 119 | + SetTrueDivAddRsqrtComputeAttr(graph, axes, "rsqrt", af::ComputeType::kComputeElewise); | ||
| 120 | + | ||
| 121 | + af::ascir_op::Store store("store"); | ||
| 122 | + store.x = rsqrt.y; | ||
| 123 | + SetTrueDivAddRsqrtTensorAttr(store.y, axes, rows, columns); | ||
| 124 | + SetTrueDivAddRsqrtComputeAttr(graph, axes, "store", af::ComputeType::kComputeStore); | ||
| 125 | + | ||
| 126 | + Output output("output"); | ||
| 127 | + output.x = store.y; | ||
| 128 | + output.ir_attr.SetIndex(0); | ||
| 129 | + output.attr.api.type = af::ApiType::kAPITypeBuffer; | ||
| 130 | + SetTrueDivAddRsqrtTensorAttr(output.y, axes, rows, columns); | ||
| 131 | +} | ||
| 132 | + | ||
| 133 | +void BuildTrueDivAddRsqrtGraph(af::AscGraph &graph) { | ||
| 134 | + const auto rows = graph.CreateSizeVar(32); | ||
| 135 | + const auto columns = graph.CreateSizeVar(128); | ||
| 136 | + const auto row_axis = graph.CreateAxis("row", rows); | ||
| 137 | + const auto column_axis = graph.CreateAxis("column", columns); | ||
| 138 | + const std::vector<af::AxisId> axes{row_axis.id, column_axis.id}; | ||
| 139 | + const auto true_div_output = BuildTrueDivGraph(graph, axes, rows, columns); | ||
| 140 | + BuildRsqrtGraph(graph, axes, rows, columns, true_div_output); | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +bool HasTrueDivAddRsqrtNodes(const af::AscGraph &graph) { | ||
| 144 | + return graph.FindNode("true_div") != nullptr && graph.FindNode("add_epsilon") != nullptr && | ||
| 145 | + graph.FindNode("rsqrt") != nullptr; | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +template <typename ImplGraph> | ||
| 149 | +bool HasTrueDivAddRsqrtInImplGraph(const ImplGraph &impl_graph) { | ||
| 150 | + std::vector<af::AscGraph> subgraphs; | ||
| 151 | + if (impl_graph.GetAllSubGraphs(subgraphs) != af::SUCCESS) { | ||
| 152 | + return false; | ||
| 153 | + } | ||
| 154 | + for (const auto &subgraph : subgraphs) { | ||
| 155 | + if (HasTrueDivAddRsqrtNodes(subgraph)) { | ||
| 156 | + return true; | ||
| 157 | + } | ||
| 158 | + } | ||
| 159 | + return false; | ||
| 160 | +} | ||
| 161 | + | ||
| 162 | +template <typename ScheduleGroup> | ||
| 163 | +bool HasTrueDivAddRsqrtInScheduleGroup(const ScheduleGroup &group) { | ||
| 164 | + for (const auto &impl_graph : group.impl_graphs) { | ||
| 165 | + if (HasTrueDivAddRsqrtInImplGraph(impl_graph)) { | ||
| 166 | + return true; | ||
| 167 | + } | ||
| 168 | + } | ||
| 169 | + return false; | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | +template <typename ScheduledResult> | ||
| 173 | +bool HasTrueDivAddRsqrtInScheduledResult(const ScheduledResult &scheduled_result) { | ||
| 174 | + for (const auto &group : scheduled_result.schedule_groups) { | ||
| 175 | + if (HasTrueDivAddRsqrtInScheduleGroup(group)) { | ||
| 176 | + return true; | ||
| 177 | + } | ||
| 178 | + } | ||
| 179 | + return false; | ||
| 180 | +} | ||
| 181 | + | ||
| 182 | +bool HasTrueDivAddRsqrtSubgraph(const ::ascir::FusedScheduledResult &result) { | ||
| 183 | + for (const auto &scheduled_results : result.node_idx_to_scheduled_results) { | ||
| 184 | + for (const auto &scheduled_result : scheduled_results) { | ||
| 185 | + if (HasTrueDivAddRsqrtInScheduledResult(scheduled_result)) { | ||
| 186 | + return true; | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + } | ||
| 190 | + return false; | ||
| 191 | +} | ||
| 192 | +} // namespace | ||
| 193 | + | ||
| 194 | +TEST_F(VectorFuncSt, TrueDivAddRsqrtHostCodegen) { | ||
| 195 | + af::AscGraph graph("truediv_add_rsqrt_host_codegen"); | ||
| 196 | + BuildTrueDivAddRsqrtGraph(graph); | ||
| 197 | + | ||
| 198 | + ::ascir::FusedScheduledResult fused_scheduled_result; | ||
| 199 | + ASSERT_EQ(optimizer.Optimize(graph, fused_scheduled_result), af::SUCCESS); | ||
| 200 | + ASSERT_TRUE(HasTrueDivAddRsqrtSubgraph(fused_scheduled_result)); | ||
| 201 | + | ||
| 202 | + codegen::Codegen generator(codegen::CodegenOptions{}); | ||
| 203 | + codegen::CodegenResult result; | ||
| 204 | + ASSERT_EQ(generator.Generate(fused_scheduled_result, result), af::SUCCESS); | ||
| 205 | + | ||
| 206 | + const auto first_vf = result.kernel.find("__simd_vf__"); | ||
| 207 | + ASSERT_NE(first_vf, std::string::npos); | ||
| 208 | + const auto rsqrt_marker = result.kernel.find("_rsqrt_negative_mask", first_vf); | ||
| 209 | + ASSERT_NE(rsqrt_marker, std::string::npos); | ||
| 210 | + const auto vf_begin = result.kernel.rfind("__simd_vf__", rsqrt_marker); | ||
| 211 | + ASSERT_NE(vf_begin, std::string::npos); | ||
| 212 | + const auto vf_end = result.kernel.find("\n}\n", rsqrt_marker); | ||
| 213 | + ASSERT_NE(vf_end, std::string::npos); | ||
| 214 | + const auto vf_body = result.kernel.substr(vf_begin, vf_end - vf_begin); | ||
| 215 | + const auto true_div_pos = vf_body.find("AscendC::MicroAPI::Div("); | ||
| 216 | + const auto add_pos = vf_body.find("AscendC::MicroAPI::Adds("); | ||
| 217 | + const auto compare_pos = vf_body.find("AscendC::MicroAPI::CompareScalar<float, AscendC::CMPMODE::LT>"); | ||
| 218 | + const auto sqrt_pos = vf_body.find("AscendC::MicroAPI::Sqrt("); | ||
| 219 | + const auto rsqrt_div_pos = vf_body.find("AscendC::MicroAPI::Div(", true_div_pos + 1U); | ||
| 220 | + const auto select_pos = vf_body.find("AscendC::MicroAPI::Select("); | ||
| 221 | + ASSERT_NE(true_div_pos, std::string::npos); | ||
| 222 | + ASSERT_NE(add_pos, std::string::npos); | ||
| 223 | + ASSERT_NE(compare_pos, std::string::npos); | ||
| 224 | + ASSERT_NE(sqrt_pos, std::string::npos); | ||
| 225 | + ASSERT_NE(rsqrt_div_pos, std::string::npos); | ||
| 226 | + ASSERT_NE(select_pos, std::string::npos); | ||
| 227 | + EXPECT_LT(true_div_pos, add_pos); | ||
| 228 | + EXPECT_LT(add_pos, compare_pos); | ||
| 229 | + EXPECT_LT(compare_pos, sqrt_pos); | ||
| 230 | + EXPECT_LT(sqrt_pos, rsqrt_div_pos); | ||
| 231 | + EXPECT_LT(rsqrt_div_pos, select_pos); | ||
| 232 | +} | ||
| 233 | + | ||
| 56 | TEST_F(VectorFuncSt, vf_partition) { | 234 | TEST_F(VectorFuncSt, vf_partition) { |
| 57 | af::AscGraph graph("brc_abs"); | 235 | af::AscGraph graph("brc_abs"); |
| 58 | auto s0 = af::Symbol(999); | 236 | auto s0 = af::Symbol(999); |
| @@ -0,0 +1,128 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +namespace codegen { | ||
| 25 | +namespace { | ||
| 26 | +struct RsqrtMicroFixture { | ||
| 27 | + af::AscGraph graph{"micro_rsqrt"}; | ||
| 28 | + af::AscNodePtr rsqrt; | ||
| 29 | + TensorManager tensor_manager; | ||
| 30 | + Tiler tiler; | ||
| 31 | + TPipe tpipe{"tpipe", tiler}; | ||
| 32 | + | ||
| 33 | + RsqrtMicroFixture(ge::DataType input_dtype = ge::DT_FLOAT, ge::DataType output_dtype = ge::DT_FLOAT) { | ||
| 34 | + const auto size = graph.CreateSizeVar(128); | ||
| 35 | + const auto axis = graph.CreateAxis("axis", size); | ||
| 36 | + | ||
| 37 | + af::ascir_op::Data data("data", graph); | ||
| 38 | + data.ir_attr.SetIndex(0); | ||
| 39 | + | ||
| 40 | + af::ascir_op::Load load("load"); | ||
| 41 | + load.x = data.y; | ||
| 42 | + load.y.dtype = input_dtype; | ||
| 43 | + *load.y.axis = {axis.id}; | ||
| 44 | + *load.y.repeats = {size}; | ||
| 45 | + *load.y.strides = {af::sym::kSymbolOne}; | ||
| 46 | + *load.y.vectorized_axis = {axis.id}; | ||
| 47 | + *load.y.vectorized_strides = {af::sym::kSymbolOne}; | ||
| 48 | + | ||
| 49 | + af::ascir_op::Rsqrt rsqrt_op("rsqrt"); | ||
| 50 | + rsqrt_op.x = load.y; | ||
| 51 | + rsqrt_op.y.dtype = output_dtype; | ||
| 52 | + *rsqrt_op.y.axis = {axis.id}; | ||
| 53 | + *rsqrt_op.y.repeats = {size}; | ||
| 54 | + *rsqrt_op.y.strides = {af::sym::kSymbolOne}; | ||
| 55 | + *rsqrt_op.y.vectorized_axis = {axis.id}; | ||
| 56 | + *rsqrt_op.y.vectorized_strides = {af::sym::kSymbolOne}; | ||
| 57 | + | ||
| 58 | + rsqrt = graph.FindNode("rsqrt"); | ||
| 59 | + auto load_node = graph.FindNode("load"); | ||
| 60 | + load_node->outputs[0].attr.mem.tensor_id = 0; | ||
| 61 | + rsqrt->outputs[0].attr.mem.tensor_id = 1; | ||
| 62 | + | ||
| 63 | + std::string input_dtype_name; | ||
| 64 | + EXPECT_EQ(Tensor::DtypeName(input_dtype, input_dtype_name), af::SUCCESS); | ||
| 65 | + std::string output_dtype_name; | ||
| 66 | + EXPECT_EQ(Tensor::DtypeName(output_dtype, output_dtype_name), af::SUCCESS); | ||
| 67 | + EXPECT_EQ(tensor_manager.AddTensor(MicroApiTensor(load_node->outputs[0], input_dtype_name)), af::SUCCESS); | ||
| 68 | + EXPECT_EQ(tensor_manager.AddTensor(MicroApiTensor(rsqrt->outputs[0], output_dtype_name)), af::SUCCESS); | ||
| 69 | + } | ||
| 70 | +}; | ||
| 71 | + | ||
| 72 | +class MicroRsqrtApiCallTest : public testing::Test { | ||
| 73 | + protected: | ||
| 74 | + void SetUp() override { | ||
| 75 | + ge::PlatformContext::GetInstance().Reset(); | ||
| 76 | + auto stub_v2 = std::make_shared<ge::RuntimeStubV2Common>(); | ||
| 77 | + ge::RuntimeStub::SetInstance(stub_v2); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + void TearDown() override { | ||
| 81 | + ge::RuntimeStub::Reset(); | ||
| 82 | + ge::PlatformContext::GetInstance().Reset(); | ||
| 83 | + } | ||
| 84 | +}; | ||
| 85 | +} // namespace | ||
| 86 | + | ||
| 87 | +TEST_F(MicroRsqrtApiCallTest, GeneratesFloatRsqrtInstructionSequence) { | ||
| 88 | + RsqrtMicroFixture fixture; | ||
| 89 | + MicroRsqrtApiCall call("Rsqrt"); | ||
| 90 | + ASSERT_EQ(call.Init(fixture.rsqrt), af::SUCCESS); | ||
| 91 | + call.AddInput(0); | ||
| 92 | + call.AddOutput(1); | ||
| 93 | + | ||
| 94 | + CallParam param{"p_reg", "", "float"}; | ||
| 95 | + std::string result; | ||
| 96 | + ASSERT_EQ(call.Generate(fixture.tensor_manager, fixture.tpipe, param, result), af::SUCCESS); | ||
| 97 | + EXPECT_EQ(result, | ||
| 98 | + "AscendC::MicroAPI::RegTensor<float> vreg_1_rsqrt_one;\n" | ||
| 99 | + "AscendC::MicroAPI::MaskReg vreg_1_rsqrt_negative_mask;\n" | ||
| 100 | + "AscendC::MicroAPI::Duplicate(vreg_1_rsqrt_one, static_cast<float>(1.0), p_reg);\n" | ||
| 101 | + "AscendC::MicroAPI::CompareScalar<float, AscendC::CMPMODE::LT>(vreg_1_rsqrt_negative_mask, vreg_0, " | ||
| 102 | + "static_cast<float>(0.0), p_reg);\n" | ||
| 103 | + "AscendC::MicroAPI::Sqrt(vreg_1, vreg_0, p_reg);\n" | ||
| 104 | + "AscendC::MicroAPI::Div(vreg_1_rsqrt_one, vreg_1_rsqrt_one, vreg_1, p_reg);\n" | ||
| 105 | + "AscendC::MicroAPI::Select(vreg_1, vreg_1, vreg_1_rsqrt_one, vreg_1_rsqrt_negative_mask);\n"); | ||
| 106 | + EXPECT_EQ(result.find("Sqrt(vreg_0,"), std::string::npos); | ||
| 107 | +} | ||
| 108 | + | ||
| 109 | +TEST_F(MicroRsqrtApiCallTest, FactoryCreatesRsqrtMicroCallForRsqrtNode) { | ||
| 110 | + RsqrtMicroFixture fixture; | ||
| 111 | + std::unique_ptr<MicroApiCall> call(CreateMicroApiCallObject(fixture.rsqrt)); | ||
| 112 | + ASSERT_NE(call, nullptr); | ||
| 113 | + EXPECT_NE(dynamic_cast<MicroRsqrtApiCall *>(call.get()), nullptr); | ||
| 114 | + EXPECT_EQ(call->GetMicroApiName(), "Rsqrt"); | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +TEST_F(MicroRsqrtApiCallTest, RejectsMismatchedInputOutputDtypes) { | ||
| 118 | + RsqrtMicroFixture fixture(ge::DT_FLOAT, ge::DT_FLOAT16); | ||
| 119 | + MicroRsqrtApiCall call("Rsqrt"); | ||
| 120 | + ASSERT_EQ(call.Init(fixture.rsqrt), af::SUCCESS); | ||
| 121 | + call.AddInput(0); | ||
| 122 | + call.AddOutput(1); | ||
| 123 | + | ||
| 124 | + CallParam param{"p_reg", "", "float"}; | ||
| 125 | + std::string result; | ||
| 126 | + EXPECT_NE(call.Generate(fixture.tensor_manager, fixture.tpipe, param, result), af::SUCCESS); | ||
| 127 | +} | ||
| 128 | +} // namespace codegen | ||
| @@ -0,0 +1,197 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +namespace optimize { | ||
| 24 | +namespace { | ||
| 25 | +using af::ascir_op::Add; | ||
| 26 | +using af::ascir_op::Broadcast; | ||
| 27 | +using af::ascir_op::Data; | ||
| 28 | +using af::ascir_op::Load; | ||
| 29 | +using af::ascir_op::Mean; | ||
| 30 | +using af::ascir_op::Output; | ||
| 31 | +using af::ascir_op::Store; | ||
| 32 | + | ||
| 33 | +struct BroadcastCseGraph { | ||
| 34 | + af::AscGraph graph{"same_source_broadcast_cse"}; | ||
| 35 | + af::AscNodePtr reduce; | ||
| 36 | + af::AscNodePtr broadcast0; | ||
| 37 | + af::AscNodePtr broadcast1; | ||
| 38 | + af::AscNodePtr add; | ||
| 39 | +}; | ||
| 40 | + | ||
| 41 | +void SetTensorAttr(af::AscOpOutput &tensor, const std::vector<af::AxisId> &axes, | ||
| 42 | + const std::vector<af::Expression> &repeats, const std::vector<af::Expression> &strides) { | ||
| 43 | + tensor.dtype = ge::DT_FLOAT; | ||
| 44 | + *tensor.axis = axes; | ||
| 45 | + *tensor.repeats = repeats; | ||
| 46 | + *tensor.strides = strides; | ||
| 47 | + *tensor.vectorized_axis = axes; | ||
| 48 | + *tensor.vectorized_strides = strides; | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +template <typename Op> | ||
| 52 | +void SetComputeAttr(Op &op, const std::vector<af::AxisId> &axes, af::ComputeType compute_type) { | ||
| 53 | + op.attr.sched.axis = axes; | ||
| 54 | + op.attr.sched.loop_axis = axes.front(); | ||
| 55 | + op.attr.api.compute_type = compute_type; | ||
| 56 | + op.attr.api.type = af::ApiType::kAPITypeCompute; | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +BroadcastCseGraph BuildBroadcastCseGraph(const std::function<void(af::AscNodePtr)> &mutate_broadcast1 = {}) { | ||
| 60 | + BroadcastCseGraph result; | ||
| 61 | + const auto rows = result.graph.CreateSizeVar(16); | ||
| 62 | + const auto columns = result.graph.CreateSizeVar(128); | ||
| 63 | + const auto row_axis = result.graph.CreateAxis("row", rows); | ||
| 64 | + const auto reduce_axis = result.graph.CreateAxis("reduce", columns); | ||
| 65 | + const std::vector<af::AxisId> axes{row_axis.id, reduce_axis.id}; | ||
| 66 | + | ||
| 67 | + Data data("data", result.graph); | ||
| 68 | + data.ir_attr.SetIndex(0); | ||
| 69 | + data.attr.api.type = af::ApiType::kAPITypeBuffer; | ||
| 70 | + data.attr.api.compute_type = af::ComputeType::kComputeInvalid; | ||
| 71 | + SetTensorAttr(data.y, axes, {rows, columns}, {columns, af::sym::kSymbolOne}); | ||
| 72 | + | ||
| 73 | + Load load("load"); | ||
| 74 | + load.x = data.y; | ||
| 75 | + SetComputeAttr(load, axes, af::ComputeType::kComputeLoad); | ||
| 76 | + SetTensorAttr(load.y, axes, {rows, columns}, {columns, af::sym::kSymbolOne}); | ||
| 77 | + | ||
| 78 | + Mean mean("mean"); | ||
| 79 | + mean.x = load.y; | ||
| 80 | + SetComputeAttr(mean, axes, af::ComputeType::kComputeReduce); | ||
| 81 | + SetTensorAttr(mean.y, axes, {rows, af::sym::kSymbolOne}, {af::sym::kSymbolOne, af::sym::kSymbolZero}); | ||
| 82 | + | ||
| 83 | + Broadcast broadcast0("broadcast0"); | ||
| 84 | + broadcast0.x = mean.y; | ||
| 85 | + SetComputeAttr(broadcast0, axes, af::ComputeType::kComputeBroadcast); | ||
| 86 | + SetTensorAttr(broadcast0.y, axes, {rows, columns}, {columns, af::sym::kSymbolOne}); | ||
| 87 | + | ||
| 88 | + Broadcast broadcast1("broadcast1"); | ||
| 89 | + broadcast1.x = mean.y; | ||
| 90 | + SetComputeAttr(broadcast1, axes, af::ComputeType::kComputeBroadcast); | ||
| 91 | + SetTensorAttr(broadcast1.y, axes, {rows, columns}, {columns, af::sym::kSymbolOne}); | ||
| 92 | + | ||
| 93 | + Add add("add"); | ||
| 94 | + add.x1 = broadcast0.y; | ||
| 95 | + add.x2 = broadcast1.y; | ||
| 96 | + SetComputeAttr(add, axes, af::ComputeType::kComputeElewise); | ||
| 97 | + SetTensorAttr(add.y, axes, {rows, columns}, {columns, af::sym::kSymbolOne}); | ||
| 98 | + | ||
| 99 | + Store store("store"); | ||
| 100 | + store.x = add.y; | ||
| 101 | + SetComputeAttr(store, axes, af::ComputeType::kComputeStore); | ||
| 102 | + SetTensorAttr(store.y, axes, {rows, columns}, {columns, af::sym::kSymbolOne}); | ||
| 103 | + | ||
| 104 | + Output output("output"); | ||
| 105 | + output.x = store.y; | ||
| 106 | + output.ir_attr.SetIndex(0); | ||
| 107 | + output.attr.api.type = af::ApiType::kAPITypeBuffer; | ||
| 108 | + output.attr.api.compute_type = af::ComputeType::kComputeInvalid; | ||
| 109 | + SetTensorAttr(output.y, axes, {rows, columns}, {columns, af::sym::kSymbolOne}); | ||
| 110 | + | ||
| 111 | + result.reduce = result.graph.FindNode("mean"); | ||
| 112 | + result.broadcast0 = result.graph.FindNode("broadcast0"); | ||
| 113 | + result.broadcast1 = result.graph.FindNode("broadcast1"); | ||
| 114 | + result.add = result.graph.FindNode("add"); | ||
| 115 | + EXPECT_NE(result.reduce, nullptr); | ||
| 116 | + EXPECT_NE(result.broadcast0, nullptr); | ||
| 117 | + EXPECT_NE(result.broadcast1, nullptr); | ||
| 118 | + EXPECT_NE(result.add, nullptr); | ||
| 119 | + if (mutate_broadcast1 && result.broadcast1 != nullptr) { | ||
| 120 | + mutate_broadcast1(result.broadcast1); | ||
| 121 | + } | ||
| 122 | + EXPECT_EQ(ScheduleUtils::TopologicalSorting(result.graph), af::SUCCESS); | ||
| 123 | + return result; | ||
| 124 | +} | ||
| 125 | + | ||
| 126 | +void ExpectSeparateBroadcastInputs(const BroadcastCseGraph &test_graph) { | ||
| 127 | + const auto broadcast0 = test_graph.graph.FindNode("broadcast0"); | ||
| 128 | + const auto broadcast1 = test_graph.graph.FindNode("broadcast1"); | ||
| 129 | + const auto add = test_graph.graph.FindNode("add"); | ||
| 130 | + ASSERT_NE(broadcast0, nullptr); | ||
| 131 | + ASSERT_NE(broadcast1, nullptr); | ||
| 132 | + ASSERT_NE(add, nullptr); | ||
| 133 | + EXPECT_EQ(add->GetInDataAnchor(0)->GetPeerOutAnchor(), broadcast0->GetOutDataAnchor(0)); | ||
| 134 | + EXPECT_EQ(add->GetInDataAnchor(1)->GetPeerOutAnchor(), broadcast1->GetOutDataAnchor(0)); | ||
| 135 | +} | ||
| 136 | +} // namespace | ||
| 137 | + | ||
| 138 | +TEST(SameSourceBroadcastCsePassTest, MergesEquivalentBroadcastsInNormGraph) { | ||
| 139 | + auto test_graph = BuildBroadcastCseGraph(); | ||
| 140 | + ASSERT_TRUE(ScheduleUtils::IsNormStruct(test_graph.graph)); | ||
| 141 | + | ||
| 142 | + SameSourceBroadcastCsePass pass; | ||
| 143 | + ASSERT_EQ(pass.RunPass(test_graph.graph), af::SUCCESS); | ||
| 144 | + | ||
| 145 | + const auto canonical = test_graph.graph.FindNode("broadcast0"); | ||
| 146 | + const auto add = test_graph.graph.FindNode("add"); | ||
| 147 | + ASSERT_NE(canonical, nullptr); | ||
| 148 | + ASSERT_NE(add, nullptr); | ||
| 149 | + EXPECT_EQ(test_graph.graph.FindNode("broadcast1"), nullptr); | ||
| 150 | + EXPECT_EQ(add->GetInDataAnchor(0)->GetPeerOutAnchor(), canonical->GetOutDataAnchor(0)); | ||
| 151 | + EXPECT_EQ(add->GetInDataAnchor(1)->GetPeerOutAnchor(), canonical->GetOutDataAnchor(0)); | ||
| 152 | + EXPECT_EQ(test_graph.reduce->GetOutDataAnchor(0)->GetPeerInDataAnchors().size(), 1UL); | ||
| 153 | +} | ||
| 154 | + | ||
| 155 | +TEST(SameSourceBroadcastCsePassTest, KeepsBroadcastsWhenTensorViewDiffers) { | ||
| 156 | + auto test_graph = BuildBroadcastCseGraph( | ||
| 157 | + [](const af::AscNodePtr &broadcast) { broadcast->outputs[0].attr.vectorized_strides.back() = af::Symbol(2); }); | ||
| 158 | + ASSERT_TRUE(ScheduleUtils::IsNormStruct(test_graph.graph)); | ||
| 159 | + | ||
| 160 | + SameSourceBroadcastCsePass pass; | ||
| 161 | + ASSERT_EQ(pass.RunPass(test_graph.graph), af::SUCCESS); | ||
| 162 | + ExpectSeparateBroadcastInputs(test_graph); | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +TEST(SameSourceBroadcastCsePassTest, KeepsBroadcastsWhenScheduleDiffers) { | ||
| 166 | + auto test_graph = BuildBroadcastCseGraph([](const af::AscNodePtr &broadcast) { | ||
| 167 | + broadcast->attr.sched.exec_condition = af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis; | ||
| 168 | + }); | ||
| 169 | + ASSERT_TRUE(ScheduleUtils::IsNormStruct(test_graph.graph)); | ||
| 170 | + | ||
| 171 | + SameSourceBroadcastCsePass pass; | ||
| 172 | + ASSERT_EQ(pass.RunPass(test_graph.graph), af::SUCCESS); | ||
| 173 | + ExpectSeparateBroadcastInputs(test_graph); | ||
| 174 | +} | ||
| 175 | + | ||
| 176 | +TEST(SameSourceBroadcastCsePassTest, KeepsBroadcastsWhenControlEdgeExists) { | ||
| 177 | + auto test_graph = BuildBroadcastCseGraph(); | ||
| 178 | + ASSERT_TRUE(ScheduleUtils::IsNormStruct(test_graph.graph)); | ||
| 179 | + af::GraphUtils::AddEdge(test_graph.broadcast1->GetOutControlAnchor(), test_graph.add->GetInControlAnchor()); | ||
| 180 | + | ||
| 181 | + SameSourceBroadcastCsePass pass; | ||
| 182 | + ASSERT_EQ(pass.RunPass(test_graph.graph), af::SUCCESS); | ||
| 183 | + ExpectSeparateBroadcastInputs(test_graph); | ||
| 184 | + EXPECT_EQ(test_graph.broadcast1->GetOutControlNodesSize(), 1U); | ||
| 185 | + EXPECT_EQ(test_graph.add->GetInControlNodesSize(), 1U); | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +TEST(SameSourceBroadcastCsePassTest, KeepsBroadcastsWhenReduceDoesNotChangeShape) { | ||
| 189 | + auto test_graph = BuildBroadcastCseGraph([](const af::AscNodePtr &) {}); | ||
| 190 | + test_graph.reduce->outputs[0].attr.repeats = test_graph.reduce->inputs[0].attr.repeats; | ||
| 191 | + ASSERT_TRUE(ScheduleUtils::IsNormStruct(test_graph.graph)); | ||
| 192 | + | ||
| 193 | + SameSourceBroadcastCsePass pass; | ||
| 194 | + ASSERT_EQ(pass.RunPass(test_graph.graph), af::SUCCESS); | ||
| 195 | + ExpectSeparateBroadcastInputs(test_graph); | ||
| 196 | +} | ||
| 197 | +} // namespace optimize | ||
| @@ -21,11 +21,8 @@ | |||
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | 23 | ||
| 24 | - | ||
| 25 | 24 | ||
| 26 | 25 | ||
| 27 | - | ||
| 28 | - | ||
| 29 | 26 | ||
| 30 | using namespace std; | 27 | using namespace std; |
| 31 | using namespace ascir; | 28 | using namespace ascir; |
| @@ -958,6 +955,99 @@ TEST_F(VfPartition, vf_cascade) { | |||
| 958 | ::ascir::utils::DumpGraph(graph, "AfterPart"); | 955 | ::ascir::utils::DumpGraph(graph, "AfterPart"); |
| 959 | } | 956 | } |
| 960 | 957 | ||
| 958 | +void BuildTrueDivAddRsqrtGraph(af::AscGraph &graph) { | ||
| 959 | + af::ascir_op::Data dividend("dividend", graph); | ||
| 960 | + dividend.ir_attr.SetIndex(0); | ||
| 961 | + dividend.y.dtype = af::DT_FLOAT; | ||
| 962 | + | ||
| 963 | + af::ascir_op::Load load_dividend("load_dividend"); | ||
| 964 | + load_dividend.x = dividend.y; | ||
| 965 | + load_dividend.y.dtype = af::DT_FLOAT; | ||
| 966 | + | ||
| 967 | + af::ascir_op::Data divisor("divisor", graph); | ||
| 968 | + divisor.ir_attr.SetIndex(1); | ||
| 969 | + divisor.y.dtype = af::DT_FLOAT; | ||
| 970 | + | ||
| 971 | + af::ascir_op::Load load_divisor("load_divisor"); | ||
| 972 | + load_divisor.x = divisor.y; | ||
| 973 | + load_divisor.y.dtype = af::DT_FLOAT; | ||
| 974 | + | ||
| 975 | + af::ascir_op::TrueDiv true_div("true_div"); | ||
| 976 | + true_div.x1 = load_dividend.y; | ||
| 977 | + true_div.x2 = load_divisor.y; | ||
| 978 | + true_div.y.dtype = af::DT_FLOAT; | ||
| 979 | + | ||
| 980 | + af::ascir_op::Scalar epsilon("epsilon", graph); | ||
| 981 | + epsilon.y.dtype = af::DT_FLOAT; | ||
| 982 | + *epsilon.y.repeats = {af::sym::kSymbolOne, af::sym::kSymbolOne}; | ||
| 983 | + *epsilon.y.strides = {af::sym::kSymbolZero, af::sym::kSymbolZero}; | ||
| 984 | + epsilon.ir_attr.SetValue("0.00001"); | ||
| 985 | + | ||
| 986 | + af::ascir_op::Add add_epsilon("add_epsilon"); | ||
| 987 | + add_epsilon.x1 = true_div.y; | ||
| 988 | + add_epsilon.x2 = epsilon.y; | ||
| 989 | + add_epsilon.y.dtype = af::DT_FLOAT; | ||
| 990 | + | ||
| 991 | + af::ascir_op::Rsqrt rsqrt("rsqrt"); | ||
| 992 | + rsqrt.x = add_epsilon.y; | ||
| 993 | + rsqrt.y.dtype = af::DT_FLOAT; | ||
| 994 | + | ||
| 995 | + af::ascir_op::Store store("store"); | ||
| 996 | + store.x = rsqrt.y; | ||
| 997 | + store.y.dtype = af::DT_FLOAT; | ||
| 998 | + | ||
| 999 | + af::ascir_op::Output output("output"); | ||
| 1000 | + output.x = store.y; | ||
| 1001 | + output.ir_attr.SetIndex(0); | ||
| 1002 | + output.y.dtype = af::DT_FLOAT; | ||
| 1003 | +} | ||
| 1004 | + | ||
| 1005 | +bool FindTrueDivAddRsqrtSubgraph(const af::AscGraph &graph, std::vector<af::AscGraph> &sub_graphs, | ||
| 1006 | + size_t &subgraph_index) { | ||
| 1007 | + if (graph.GetAllSubGraphs(sub_graphs) != af::SUCCESS) { | ||
| 1008 | + return false; | ||
| 1009 | + } | ||
| 1010 | + for (size_t index = 0UL; index < sub_graphs.size(); ++index) { | ||
| 1011 | + const auto &subgraph = sub_graphs[index]; | ||
| 1012 | + if (subgraph.FindNode("true_div") != nullptr && subgraph.FindNode("add_epsilon") != nullptr && | ||
| 1013 | + subgraph.FindNode("rsqrt") != nullptr) { | ||
| 1014 | + subgraph_index = index; | ||
| 1015 | + return true; | ||
| 1016 | + } | ||
| 1017 | + } | ||
| 1018 | + return false; | ||
| 1019 | +} | ||
| 1020 | + | ||
| 1021 | +void ExpectTrueDivAddRsqrtConnections(const af::AscGraph &subgraph) { | ||
| 1022 | + const auto true_div_node = subgraph.FindNode("true_div"); | ||
| 1023 | + const auto add_node = subgraph.FindNode("add_epsilon"); | ||
| 1024 | + const auto rsqrt_node = subgraph.FindNode("rsqrt"); | ||
| 1025 | + ASSERT_NE(true_div_node, nullptr); | ||
| 1026 | + ASSERT_NE(add_node, nullptr); | ||
| 1027 | + ASSERT_NE(rsqrt_node, nullptr); | ||
| 1028 | + EXPECT_EQ(add_node->GetInDataAnchor(0)->GetPeerOutAnchor(), true_div_node->GetOutDataAnchor(0)); | ||
| 1029 | + EXPECT_EQ(rsqrt_node->GetInDataAnchor(0)->GetPeerOutAnchor(), add_node->GetOutDataAnchor(0)); | ||
| 1030 | +} | ||
| 1031 | + | ||
| 1032 | +TEST_F(VfPartition, TrueDivAddRsqrtFormsSingleVectorFunc) { | ||
| 1033 | + af::AscGraph graph("truediv_add_rsqrt_vf"); | ||
| 1034 | + BuildTrueDivAddRsqrtGraph(graph); | ||
| 1035 | + SetupGraphAxes(graph, {af::Symbol(32), af::Symbol(128)}); | ||
| 1036 | + ASSERT_EQ(AlignmentHandler::AlignVectorizedStrides(graph), af::SUCCESS); | ||
| 1037 | + | ||
| 1038 | + VectorFuncPartitioner partitioner(graph); | ||
| 1039 | + ASSERT_EQ(partitioner.Partition(), af::SUCCESS); | ||
| 1040 | + | ||
| 1041 | + std::vector<af::AscGraph> sub_graphs; | ||
| 1042 | + size_t target_subgraph_index = 0UL; | ||
| 1043 | + ASSERT_TRUE(FindTrueDivAddRsqrtSubgraph(graph, sub_graphs, target_subgraph_index)); | ||
| 1044 | + const auto &target_subgraph = sub_graphs[target_subgraph_index]; | ||
| 1045 | + EXPECT_EQ(graph.FindNode("true_div"), nullptr); | ||
| 1046 | + EXPECT_EQ(graph.FindNode("add_epsilon"), nullptr); | ||
| 1047 | + EXPECT_EQ(graph.FindNode("rsqrt"), nullptr); | ||
| 1048 | + ExpectTrueDivAddRsqrtConnections(target_subgraph); | ||
| 1049 | +} | ||
| 1050 | + | ||
| 961 | TEST_F(VfPartition, all_zero_axis_stride) { | 1051 | TEST_F(VfPartition, all_zero_axis_stride) { |
| 962 | af::AscGraph graph("brc_abs"); | 1052 | af::AscGraph graph("brc_abs"); |
| 963 | af::ascir_op::Data data0("data0", graph); | 1053 | af::ascir_op::Data data0("data0", graph); |
| @@ -1682,6 +1682,19 @@ class RsqrtAscIrCodegenImplV2 : public SimtFloatUnaryAscIrCodegenImplV2 { | |||
| 1682 | [[nodiscard]] std::string GetSimtScalarApiName() const override { | 1682 | [[nodiscard]] std::string GetSimtScalarApiName() const override { |
| 1683 | return "Rsqrt"; | 1683 | return "Rsqrt"; |
| 1684 | } | 1684 | } |
| 1685 | + [[nodiscard]] std::string GetMicroApiCallName() const override { | ||
| 1686 | + return "MicroRsqrtApiCall"; | ||
| 1687 | + } | ||
| 1688 | + | ||
| 1689 | + [[nodiscard]] std::string GetMicroApiName() const override { | ||
| 1690 | + return "Rsqrt"; | ||
| 1691 | + } | ||
| 1692 | + | ||
| 1693 | + [[nodiscard]] bool IsVectorFunctionSupported(const AscNode &node) const override { | ||
| 1694 | + (void)node; | ||
| 1695 | + return true; | ||
| 1696 | + } | ||
| 1697 | + | ||
| 1685 | [[nodiscard]] bool IsInplaceSupported(const AscNode &rsqrt_node) const override { | 1698 | [[nodiscard]] bool IsInplaceSupported(const AscNode &rsqrt_node) const override { |
| 1686 | (void)rsqrt_node; | 1699 | (void)rsqrt_node; |
| 1687 | return true; | 1700 | return true; |
| @@ -1697,6 +1710,7 @@ class RsqrtAscIrCodegenImplV2 : public SimtFloatUnaryAscIrCodegenImplV2 { | |||
| 1697 | [[nodiscard]] std::vector<std::string> IncludeApiHeaderFiles() const override { | 1710 | [[nodiscard]] std::vector<std::string> IncludeApiHeaderFiles() const override { |
| 1698 | return { | 1711 | return { |
| 1699 | "basic_api/kernel_operator_vec_unary_intf.h", | 1712 | "basic_api/kernel_operator_vec_unary_intf.h", |
| 1713 | + "basic_api/reg_compute/kernel_reg_compute_intf.h", | ||
| 1700 | }; | 1714 | }; |
| 1701 | } | 1715 | } |
| 1702 | [[nodiscard]] bool IsNodeValid(const AscNode &node) const override { | 1716 | [[nodiscard]] bool IsNodeValid(const AscNode &node) const override { |
| @@ -0,0 +1,53 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +namespace codegen { | ||
| 15 | +Status MicroRsqrtApiCall::Generate(const TensorManager &tensor_mng, [[maybe_unused]] const TPipe &tpipe, | ||
| 16 | + CallParam ¶m, std::string &result) { | ||
| 17 | + GE_ASSERT_TRUE(this->inputs_.size() == 1, "Rsqrt micro api call must have one input"); | ||
| 18 | + GE_ASSERT_TRUE(this->outputs_.size() == 1, "Rsqrt micro api call must have one output"); | ||
| 19 | + | ||
| 20 | + const auto *input_tensor = tensor_mng.GetTensor(this->inputs_[0].second); | ||
| 21 | + const auto *output_tensor = tensor_mng.GetTensor(this->outputs_[0].second); | ||
| 22 | + GE_ASSERT_NOTNULL(input_tensor); | ||
| 23 | + GE_ASSERT_NOTNULL(output_tensor); | ||
| 24 | + | ||
| 25 | + std::string dtype_name; | ||
| 26 | + GE_CHK_STATUS_RET(Tensor::DtypeName(input_tensor->dtype_, dtype_name), "Get data type:%d failed", | ||
| 27 | + static_cast<int32_t>(input_tensor->dtype_)); | ||
| 28 | + GE_ASSERT_TRUE(input_tensor->dtype_ == output_tensor->dtype_, "Rsqrt input and output dtypes must match"); | ||
| 29 | + | ||
| 30 | + const auto &input_name = input_tensor->name; | ||
| 31 | + const auto &output_name = output_tensor->name; | ||
| 32 | + const std::string one_name = output_name + "_rsqrt_one"; | ||
| 33 | + const std::string negative_mask_name = output_name + "_rsqrt_negative_mask"; | ||
| 34 | + | ||
| 35 | + std::stringstream ss; | ||
| 36 | + // 3510/5102 的公开 MicroAPI 没有提供 Rsqrt,使用相同命名空间下的基础指令组合保持 VF 计算。 | ||
| 37 | + ss << "AscendC::MicroAPI::RegTensor<" << dtype_name << "> " << one_name << ";" << std::endl; | ||
| 38 | + ss << "AscendC::MicroAPI::MaskReg " << negative_mask_name << ";" << std::endl; | ||
| 39 | + ss << "AscendC::MicroAPI::Duplicate(" << one_name << ", static_cast<" << dtype_name << ">(1.0), " << param.p_reg | ||
| 40 | + << ");" << std::endl; | ||
| 41 | + ss << "AscendC::MicroAPI::CompareScalar<" << dtype_name << ", AscendC::CMPMODE::LT>(" << negative_mask_name << ", " | ||
| 42 | + << input_name << ", static_cast<" << dtype_name << ">(0.0), " << param.p_reg << ");" << std::endl; | ||
| 43 | + ss << "AscendC::MicroAPI::Sqrt(" << output_name << ", " << input_name << ", " << param.p_reg << ");" << std::endl; | ||
| 44 | + ss << "AscendC::MicroAPI::Div(" << one_name << ", " << one_name << ", " << output_name << ", " << param.p_reg << ");" | ||
| 45 | + << std::endl; | ||
| 46 | + ss << "AscendC::MicroAPI::Select(" << output_name << ", " << output_name << ", " << one_name << ", " | ||
| 47 | + << negative_mask_name << ");" << std::endl; | ||
| 48 | + result = ss.str(); | ||
| 49 | + return af::SUCCESS; | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +static MicroApiCallRegister<MicroRsqrtApiCall> register_micro_rsqrt_api_call("MicroRsqrtApiCall"); | ||
| 53 | +} // namespace codegen | ||
| @@ -0,0 +1,24 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +namespace codegen { | ||
| 16 | +class MicroRsqrtApiCall final : public MicroApiCall { | ||
| 17 | + public: | ||
| 18 | + explicit MicroRsqrtApiCall(const std::string &api_name) : MicroApiCall(api_name) {} | ||
| 19 | + ~MicroRsqrtApiCall() override = default; | ||
| 20 | + Status Generate(const TensorManager &tensor_mng, const TPipe &tpipe, CallParam ¶m, std::string &result) override; | ||
| 21 | +}; | ||
| 22 | +} // namespace codegen | ||
| 23 | + | ||
| 24 | + | ||
| @@ -0,0 +1,253 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | +namespace optimize { | ||
| 26 | +namespace { | ||
| 27 | +using BroadcastGroup = std::vector<af::AscNodePtr>; | ||
| 28 | +using BroadcastGroups = std::map<af::OutDataAnchorPtr, BroadcastGroup>; | ||
| 29 | + | ||
| 30 | +bool IsExpressionVectorEqual(const std::vector<af::Expression> &lhs, const std::vector<af::Expression> &rhs) { | ||
| 31 | + if (lhs.size() != rhs.size()) { | ||
| 32 | + return false; | ||
| 33 | + } | ||
| 34 | + for (size_t i = 0UL; i < lhs.size(); ++i) { | ||
| 35 | + if (af::SymbolicUtils::StaticCheckEq(lhs[i], rhs[i]) != af::TriBool::kTrue) { | ||
| 36 | + return false; | ||
| 37 | + } | ||
| 38 | + } | ||
| 39 | + return true; | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +bool IsTensorViewEqual(const af::AscTensorAttr &lhs, const af::AscTensorAttr &rhs) { | ||
| 43 | + return static_cast<ge::DataType>(lhs.dtype) == static_cast<ge::DataType>(rhs.dtype) && lhs.axis == rhs.axis && | ||
| 44 | + IsExpressionVectorEqual(lhs.repeats, rhs.repeats) && IsExpressionVectorEqual(lhs.strides, rhs.strides) && | ||
| 45 | + lhs.vectorized_axis == rhs.vectorized_axis && | ||
| 46 | + IsExpressionVectorEqual(lhs.vectorized_strides, rhs.vectorized_strides); | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +bool IsScalarBroadcast(const af::AscNodePtr &node) { | ||
| 50 | + const auto &strides = node->inputs[0].attr.strides; | ||
| 51 | + return !strides.empty() && std::all_of(strides.begin(), strides.end(), [](const af::Expression &stride) { | ||
| 52 | + return af::SymbolicUtils::StaticCheckEq(stride, af::sym::kSymbolZero) == af::TriBool::kTrue; | ||
| 53 | + }); | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +bool HasBoundaryConsumer(const af::AscNodePtr &node) { | ||
| 57 | + for (const auto &out_node : node->GetOutDataNodes()) { | ||
| 58 | + if (af::ops::IsOps<af::ascir_op::Store>(out_node) || af::ops::IsOps<af::ascir_op::Output>(out_node) || | ||
| 59 | + af::ops::IsOps<af::ascir_op::Workspace>(out_node)) { | ||
| 60 | + return true; | ||
| 61 | + } | ||
| 62 | + } | ||
| 63 | + return false; | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +// 仅把真正发生降维的 Reduce 输出作为统计量候选,避免普通 Reduce 扩大优化范围。 | ||
| 67 | +bool IsReducedOutput(const af::AscNodePtr &node) { | ||
| 68 | + if (node == nullptr || !ScheduleUtils::IsReduce(node) || node->inputs.Size() == 0U || | ||
| 69 | + node->GetAllOutDataAnchorsSize() == 0U) { | ||
| 70 | + return false; | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + const auto &input_repeats = node->inputs[0].attr.repeats; | ||
| 74 | + const auto &output_repeats = node->outputs[0].attr.repeats; | ||
| 75 | + if (input_repeats.size() != output_repeats.size() || input_repeats.empty()) { | ||
| 76 | + return false; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + for (size_t index = 0UL; index < input_repeats.size(); ++index) { | ||
| 80 | + if (af::SymbolicUtils::StaticCheckEq(input_repeats[index], output_repeats[index]) != af::TriBool::kTrue) { | ||
| 81 | + GELOGD("Reduce node [%s] changes the shape, so its output is eligible for Broadcast CSE.", node->GetNamePtr()); | ||
| 82 | + return true; | ||
| 83 | + } | ||
| 84 | + } | ||
| 85 | + return false; | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +// 候选必须是普通 Broadcast,且不能直接连接边界节点;来源限制为实际降维的 Reduce 输出。 | ||
| 89 | +bool IsCandidate(const af::AscNodePtr &node) { | ||
| 90 | + if (!af::ops::IsOps<af::ascir_op::Broadcast>(node) || node->GetAllInDataAnchorsSize() != 1U || | ||
| 91 | + node->GetAllOutDataAnchorsSize() != 1U || node->GetInControlNodesSize() != 0U || | ||
| 92 | + node->GetOutControlNodesSize() != 0U || IsScalarBroadcast(node) || HasBoundaryConsumer(node)) { | ||
| 93 | + return false; | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + const auto input_anchor = node->GetInDataAnchor(0); | ||
| 97 | + if (input_anchor == nullptr) { | ||
| 98 | + return false; | ||
| 99 | + } | ||
| 100 | + const auto source_anchor = input_anchor->GetPeerOutAnchor(); | ||
| 101 | + if (source_anchor == nullptr) { | ||
| 102 | + return false; | ||
| 103 | + } | ||
| 104 | + const auto source = std::dynamic_pointer_cast<af::AscNode>(source_anchor->GetOwnerNode()); | ||
| 105 | + if (!IsReducedOutput(source)) { | ||
| 106 | + GELOGD("Skip Broadcast candidate [%s]: its source is not a dimension-reducing Reduce node.", node->GetNamePtr()); | ||
| 107 | + return false; | ||
| 108 | + } | ||
| 109 | + GELOGD("Accept Broadcast candidate [%s], source Reduce node [%s].", node->GetNamePtr(), | ||
| 110 | + source == nullptr ? "unknown" : source->GetNamePtr()); | ||
| 111 | + return true; | ||
| 112 | +} | ||
| 113 | + | ||
| 114 | +// Broadcast 等价不仅要求 Tensor 视图一致,还要求调度和向量化属性一致。 | ||
| 115 | +bool IsEquivalentBroadcast(const af::AscNodePtr &lhs, const af::AscNodePtr &rhs) { | ||
| 116 | + if (lhs->attr.sched.axis != rhs->attr.sched.axis || lhs->attr.sched.loop_axis != rhs->attr.sched.loop_axis || | ||
| 117 | + lhs->attr.sched.exec_condition != rhs->attr.sched.exec_condition) { | ||
| 118 | + return false; | ||
| 119 | + } | ||
| 120 | + return IsTensorViewEqual(lhs->inputs[0].attr, rhs->inputs[0].attr) && | ||
| 121 | + IsTensorViewEqual(lhs->outputs[0].attr, rhs->outputs[0].attr); | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +// 合并后 Tensor 生命周期可能延长,但 canonical 必须早于所有 duplicate 消费者。 | ||
| 125 | +bool CanMerge(const af::AscNodePtr &canonical, const af::AscNodePtr &duplicate) { | ||
| 126 | + const int64_t canonical_start = canonical->GetOpDescBarePtr()->GetId(); | ||
| 127 | + for (const auto &out_node : duplicate->GetOutDataNodes()) { | ||
| 128 | + const int64_t consumer_id = out_node->GetOpDescBarePtr()->GetId(); | ||
| 129 | + if (consumer_id <= canonical_start) { | ||
| 130 | + GELOGD("Skip Broadcast merge: canonical [%s] with ID=%ld is not earlier than consumer [%s] with ID=%ld.", | ||
| 131 | + canonical->GetNamePtr(), canonical_start, out_node->GetNamePtr(), consumer_id); | ||
| 132 | + return false; | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + return true; | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +// 保留拓扑较早的 Broadcast,确保跨阶段共享结果时不会引用尚未计算的数据。 | ||
| 139 | +af::AscNodePtr SelectCanonical(const std::vector<af::AscNodePtr> &nodes) { | ||
| 140 | + return *std::min_element(nodes.begin(), nodes.end(), [](const af::AscNodePtr &lhs, const af::AscNodePtr &rhs) { | ||
| 141 | + return lhs->GetOpDescBarePtr()->GetId() < rhs->GetOpDescBarePtr()->GetId(); | ||
| 142 | + }); | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +// 使用图结构判定限制作用域,避免 R 轴大小等性能阈值影响 Norm 结构识别。 | ||
| 146 | +bool IsNormLikeScope(const af::AscGraph &graph) { | ||
| 147 | + const bool is_norm_struct = ScheduleUtils::IsNormStruct(graph); | ||
| 148 | + GELOGD("Norm-like scope check for graph [%s]: IsNormStruct=%d.", graph.GetName().c_str(), | ||
| 149 | + static_cast<int32_t>(is_norm_struct)); | ||
| 150 | + return is_norm_struct; | ||
| 151 | +} | ||
| 152 | + | ||
| 153 | +Status MergeBroadcast(const af::AscNodePtr &canonical, const af::AscNodePtr &duplicate) { | ||
| 154 | + auto canonical_out = canonical->GetOutDataAnchor(0); | ||
| 155 | + auto duplicate_out = duplicate->GetOutDataAnchor(0); | ||
| 156 | + auto duplicate_in = duplicate->GetInDataAnchor(0); | ||
| 157 | + GE_ASSERT_NOTNULL(canonical_out); | ||
| 158 | + GE_ASSERT_NOTNULL(duplicate_out); | ||
| 159 | + GE_ASSERT_NOTNULL(duplicate_in); | ||
| 160 | + auto source_out = duplicate_in->GetPeerOutAnchor(); | ||
| 161 | + GE_ASSERT_NOTNULL(source_out); | ||
| 162 | + | ||
| 163 | + GE_ASSERT_SUCCESS(PassUtils::RelinkAllOutNodeToSrc(duplicate_out, canonical_out)); | ||
| 164 | + GE_ASSERT_SUCCESS(af::GraphUtils::RemoveEdge(source_out, duplicate_in)); | ||
| 165 | + auto owner_graph = duplicate->GetOwnerComputeGraph(); | ||
| 166 | + GE_ASSERT_NOTNULL(owner_graph); | ||
| 167 | + GE_ASSERT_SUCCESS(owner_graph->RemoveNode(duplicate)); | ||
| 168 | + GELOGI("Merged equivalent Broadcast node [%s] into canonical node [%s].", duplicate->GetNamePtr(), | ||
| 169 | + canonical->GetNamePtr()); | ||
| 170 | + return af::SUCCESS; | ||
| 171 | +} | ||
| 172 | +Status CollectBroadcastGroups(const af::AscGraph &graph, BroadcastGroups &source_to_broadcasts, | ||
| 173 | + size_t &candidate_count) { | ||
| 174 | + for (const auto &node : graph.GetAllNodes()) { | ||
| 175 | + if (!IsCandidate(node)) { | ||
| 176 | + continue; | ||
| 177 | + } | ||
| 178 | + ++candidate_count; | ||
| 179 | + auto input_anchor = node->GetInDataAnchor(0); | ||
| 180 | + GE_ASSERT_NOTNULL(input_anchor); | ||
| 181 | + auto source_anchor = input_anchor->GetPeerOutAnchor(); | ||
| 182 | + GE_ASSERT_NOTNULL(source_anchor); | ||
| 183 | + source_to_broadcasts[source_anchor].emplace_back(node); | ||
| 184 | + } | ||
| 185 | + return af::SUCCESS; | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +Status MergeEquivalentBroadcastGroup(const BroadcastGroup &broadcasts, size_t &equivalent_group_count, | ||
| 189 | + size_t &merged_count) { | ||
| 190 | + std::set<af::AscNodePtr> processed; | ||
| 191 | + for (const auto &broadcast : broadcasts) { | ||
| 192 | + if (processed.count(broadcast) > 0UL) { | ||
| 193 | + continue; | ||
| 194 | + } | ||
| 195 | + BroadcastGroup equivalent_nodes; | ||
| 196 | + for (const auto &candidate : broadcasts) { | ||
| 197 | + if (processed.count(candidate) == 0UL && IsEquivalentBroadcast(broadcast, candidate)) { | ||
| 198 | + equivalent_nodes.emplace_back(candidate); | ||
| 199 | + } | ||
| 200 | + } | ||
| 201 | + processed.insert(equivalent_nodes.begin(), equivalent_nodes.end()); | ||
| 202 | + if (equivalent_nodes.size() <= 1UL) { | ||
| 203 | + continue; | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + ++equivalent_group_count; | ||
| 207 | + const auto canonical = SelectCanonical(equivalent_nodes); | ||
| 208 | + GELOGD("Found equivalent Broadcast group: size=%zu, canonical=[%s].", equivalent_nodes.size(), | ||
| 209 | + canonical->GetNamePtr()); | ||
| 210 | + for (const auto &duplicate : equivalent_nodes) { | ||
| 211 | + if (duplicate == canonical || !CanMerge(canonical, duplicate)) { | ||
| 212 | + continue; | ||
| 213 | + } | ||
| 214 | + GE_ASSERT_SUCCESS(MergeBroadcast(canonical, duplicate)); | ||
| 215 | + ++merged_count; | ||
| 216 | + } | ||
| 217 | + } | ||
| 218 | + return af::SUCCESS; | ||
| 219 | +} | ||
| 220 | + | ||
| 221 | +Status MergeBroadcastGroups(BroadcastGroups &source_to_broadcasts, size_t &equivalent_group_count, | ||
| 222 | + size_t &merged_count) { | ||
| 223 | + for (auto &[source_anchor, broadcasts] : source_to_broadcasts) { | ||
| 224 | + const auto source_node = std::dynamic_pointer_cast<af::AscNode>(source_anchor->GetOwnerNode()); | ||
| 225 | + GELOGD("Inspect source [%s] with %zu Broadcast candidates.", | ||
| 226 | + source_node == nullptr ? "unknown" : source_node->GetNamePtr(), broadcasts.size()); | ||
| 227 | + GE_ASSERT_SUCCESS(MergeEquivalentBroadcastGroup(broadcasts, equivalent_group_count, merged_count)); | ||
| 228 | + } | ||
| 229 | + return af::SUCCESS; | ||
| 230 | +} | ||
| 231 | +} // namespace | ||
| 232 | + | ||
| 233 | +Status SameSourceBroadcastCsePass::RunPass(af::AscGraph &graph) { | ||
| 234 | + if (!IsNormLikeScope(graph)) { | ||
| 235 | + GELOGD("Skip same-source Broadcast CSE for graph [%s]: not a Norm-like graph.", graph.GetName().c_str()); | ||
| 236 | + return af::SUCCESS; | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + // 先按共同输入源分组,再在每个分组内比较 Broadcast 的完整视图和调度属性。 | ||
| 240 | + BroadcastGroups source_to_broadcasts; | ||
| 241 | + size_t candidate_count = 0UL; | ||
| 242 | + GE_ASSERT_SUCCESS(CollectBroadcastGroups(graph, source_to_broadcasts, candidate_count)); | ||
| 243 | + GELOGI("Same-source Broadcast CSE: graph [%s] has %zu candidates in %zu source groups.", graph.GetName().c_str(), | ||
| 244 | + candidate_count, source_to_broadcasts.size()); | ||
| 245 | + | ||
| 246 | + size_t equivalent_group_count = 0UL; | ||
| 247 | + size_t merged_count = 0UL; | ||
| 248 | + GE_ASSERT_SUCCESS(MergeBroadcastGroups(source_to_broadcasts, equivalent_group_count, merged_count)); | ||
| 249 | + GELOGI("Same-source Broadcast CSE finished: equivalent groups=%zu, merged nodes=%zu.", equivalent_group_count, | ||
| 250 | + merged_count); | ||
| 251 | + return af::SUCCESS; | ||
| 252 | +} | ||
| 253 | +} // namespace optimize | ||
| @@ -0,0 +1,25 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace optimize { | ||
| 17 | +class SameSourceBroadcastCsePass final : public BaseGraphPass { | ||
| 18 | + public: | ||
| 19 | + SameSourceBroadcastCsePass() = default; | ||
| 20 | + Status RunPass(af::AscGraph &graph) override; | ||
| 21 | + ~SameSourceBroadcastCsePass() override = default; | ||
| 22 | +}; | ||
| 23 | +} // namespace optimize | ||
| 24 | + | ||
| 25 | + | ||
| @@ -19,6 +19,7 @@ | |||
| 19 | 19 | ||
| 20 | 20 | ||
| 21 | 21 | ||
| 22 | + | ||
| 22 | 23 | ||
| 23 | 24 | ||
| 24 | 25 | ||
| @@ -35,6 +36,7 @@ class PassRunnerV2 final : public BasePassRunner { | |||
| 35 | this->RegisterPass<ExpandDimsForAllReducePass>(); | 36 | this->RegisterPass<ExpandDimsForAllReducePass>(); |
| 36 | this->RegisterPass<ContinuesBroadcastOptimizationPass>(); | 37 | this->RegisterPass<ContinuesBroadcastOptimizationPass>(); |
| 37 | this->RegisterPass<SoftmaxPatternFusionPass>(); | 38 | this->RegisterPass<SoftmaxPatternFusionPass>(); |
| 39 | + this->RegisterPass<SameSourceBroadcastCsePass>(); | ||
| 38 | this->RegisterPass<GatherToLoadPass>(); | 40 | this->RegisterPass<GatherToLoadPass>(); |
| 39 | this->RegisterPass<SplitConcatOptimizationPass>(); | 41 | this->RegisterPass<SplitConcatOptimizationPass>(); |
| 40 | } | 42 | } |