已合并
feat: 优化 Norm 场景的固定输入缓存、同源 Broadcast 消除与 VF 融合 #1654
feat: 优化 Norm 场景的固定输入缓存、同源 Broadcast 消除与 VF 融合 #1654
已合并
朱珉创建于 8月5日
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_node466 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+ 
551Status Loop::GenerateBody(const Tiler &tiler, const TPipe &tpipe, std::vector<ascir::AxisId> &current_axis,577Status Loop::GenerateBody(const Tiler &tiler, const TPipe &tpipe, std::vector<ascir::AxisId> &current_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#include "gtest/gtest.h"11#include "gtest/gtest.h"
12 12 
13+#include <algorithm>
14+ 
13#include "node_utils_ex.h"15#include "node_utils_ex.h"
14#include "graph_utils.h"16#include "graph_utils.h"
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+ 
5957TEST(CodegenKernel, CalculateVectorizedAixsMergeStatus) {6026TEST(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+#include "gtest/gtest.h"
12+ 
13+#include "asc_graph_builder.h"
14+#include "ascgraph_info_complete.h"
15+#define private public
16+#include "optimize.h"
17+#undef private
18+#include "platform_context.h"
19+#include "runtime_stub.h"
20+#include "schedule_utils.h"
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#include "gtest/gtest.h"11#include "gtest/gtest.h"
12 12 
13+#include "codegen.h"
14+ 
13#include "ascendc_ir.h"15#include "ascendc_ir.h"
14#include "ascendc_ir_def.h"16#include "ascendc_ir_def.h"
15#include "ascir_ops.h"17#include "ascir_ops.h"
@@ -18,12 +20,6 @@
18#include "platform_context.h"20#include "platform_context.h"
19#undef private21#undef private
20#include "ascir_ops_utils.h"22#include "ascir_ops_utils.h"
21-#include "graph/ascendc_ir/utils/asc_graph_utils.h"
22-#include "graph/utils/graph_utils.h"
23-#include "attribute_group/attr_group_shape_env.h"
24-#include "fused_graph/fused_graph_unfolder.h"
25-#include "graph/debug/ge_attr_define.h"
26-#include "util/mem_utils.h"
27#include "runtime_stub.h"23#include "runtime_stub.h"
28 24 
29using namespace std;25using namespace std;
@@ -53,6 +49,188 @@ class VectorFuncSt : public ::testing::Test {
53} // namespace49} // namespace
54 50 
55namespace optimize {51namespace 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+ 
56TEST_F(VectorFuncSt, vf_partition) {234TEST_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+#include <memory>
12+#include <string>
13+ 
14+#include "gtest/gtest.h"
15+ 
16+#include "ascendc_ir.h"
17+#include "ascir_ops.h"
18+#include "codegen_kernel.h"
19+#include "micro_api_call/micro_api_call_factory.h"
20+#include "micro_api_call/micro_rsqrt_api_call.h"
21+#include "platform_context.h"
22+#include "runtime_stub.h"
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+#include <functional>
12+#include <string>
13+#include <vector>
14+ 
15+#include "gtest/gtest.h"
16+ 
17+#include "ascendc_ir.h"
18+#include "ascir_ops.h"
19+#include "graph/utils/graph_utils.h"
20+#include "optimize/graph_pass/same_source_broadcast_cse_pass.h"
21+#include "optimize/schedule_utils.h"
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#include "ascir_ops_utils.h"21#include "ascir_ops_utils.h"
22#include "schedule_utils.h"22#include "schedule_utils.h"
23#include "platform_context.h"23#include "platform_context.h"
24-#include "optimize/platformv2.h"
25#include "ascgraph_info_complete.h"24#include "ascgraph_info_complete.h"
26#include "runtime_stub.h"25#include "runtime_stub.h"
27-#include "optimize.h"
28-#include "asc_graph_builder.h"
29 26 
30using namespace std;27using namespace std;
31using namespace ascir;28using 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+ 
961TEST_F(VfPartition, all_zero_axis_stride) {1051TEST_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+#include "micro_api_call_factory.h"
11+ 
12+#include "micro_rsqrt_api_call.h"
13+ 
14+namespace codegen {
15+Status MicroRsqrtApiCall::Generate(const TensorManager &tensor_mng, [[maybe_unused]] const TPipe &tpipe,
16+ CallParam &param, 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+#ifndef __AUTOFUSE_MICRO_RSQRT_API_CALL_H__
11+#define __AUTOFUSE_MICRO_RSQRT_API_CALL_H__
12+ 
13+#include "micro_api_call.h"
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 &param, std::string &result) override;
21+};
22+} // namespace codegen
23+ 
24+#endif // __AUTOFUSE_MICRO_RSQRT_API_CALL_H__
@@ -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+#include "same_source_broadcast_cse_pass.h"
12+ 
13+#include <algorithm>
14+#include <map>
15+#include <set>
16+#include <vector>
17+ 
18+#include "ascir/meta/ascir_ops_utils.h"
19+#include "ascir_ops.h"
20+#include "graph/symbolizer/symbolic_utils.h"
21+#include "graph/utils/graph_utils.h"
22+#include "optimize/graph_pass/pass_utils.h"
23+#include "optimize/schedule_utils.h"
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+#ifndef OPTIMIZE_PLATFORM_V2_GRAPH_PASS_SAME_SOURCE_BROADCAST_CSE_PASS_H
12+#define OPTIMIZE_PLATFORM_V2_GRAPH_PASS_SAME_SOURCE_BROADCAST_CSE_PASS_H
13+ 
14+#include "optimize/graph_pass/base_graph_pass.h"
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+#endif // OPTIMIZE_PLATFORM_V2_GRAPH_PASS_SAME_SOURCE_BROADCAST_CSE_PASS_H
@@ -19,6 +19,7 @@
19#include "optimize/graph_pass/pow_equiv_substitution_pass.h"19#include "optimize/graph_pass/pow_equiv_substitution_pass.h"
20#include "optimize/graph_pass/masked_fill_input_reorder_pass.h"20#include "optimize/graph_pass/masked_fill_input_reorder_pass.h"
21#include "v35/optimize/graph_pass/continues_broadcast_optimization.h"21#include "v35/optimize/graph_pass/continues_broadcast_optimization.h"
22+#include "v35/optimize/graph_pass/same_source_broadcast_cse_pass.h"
22#include "v35/optimize/graph_pass/gather_to_load.h"23#include "v35/optimize/graph_pass/gather_to_load.h"
23#include "v35/optimize/graph_pass/softmax_pattern_fusion_pass.h"24#include "v35/optimize/graph_pass/softmax_pattern_fusion_pass.h"
24#include "v35/optimize/graph_pass/split_concat_optimization_pass.h"25#include "v35/optimize/graph_pass/split_concat_optimization_pass.h"
@@ -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 }