已合并
fix: 支持Nddma Brc缓存建模和多Group无依赖场景Group间建模 #1960
fix: 支持Nddma Brc缓存建模和多Group无依赖场景Group间建模 #1960
已合并
zhang_shengjie创建于 9月3日
共 12 个文件变更+1211-11
@@ -0,0 +1,27 @@
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 ATT_GROUP_PERF_CONFIG_H_
12+#define ATT_GROUP_PERF_CONFIG_H_
13+ 
14+namespace att {
15+ 
16+// Values are from two cold 2048x1024, block_dim=52, g0 -> SyncAll -> g1 samples:
17+// edge median 3208.7/8510.2 cycles and p95 5358.1/9845.5 cycles. They are deliberately
18+// conservative and must be revalidated with the profile matrix before changing.
19+struct SerializedEdgePenaltyConfig {
20+ static constexpr double kPenaltyRatio = 0.10;
21+ static constexpr double kPenaltyCapCycles = 8000.0;
22+ static constexpr const char *kModelVersion = "serialized_edge_v1";
23+};
24+ 
25+} // namespace att
26+ 
27+#endif // ATT_GROUP_PERF_CONFIG_H_
@@ -0,0 +1,207 @@
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 "cache_guard_count.h"
11+ 
12+#include <algorithm>
13+#include <cmath>
14+#include <limits>
15+ 
16+namespace att {
17+namespace {
18+bool IsPositiveConstantOrSymbol(const Expr &expr, uint64_t &value) {
19+ if (!expr.IsConstExpr()) {
20+ return expr.IsValid();
21+ }
22+ if (expr.GetExprType() == af::ExprType::kExprConstantInteger) {
23+ int64_t signed_value = 0;
24+ if (!expr.GetConstValue(signed_value) || signed_value <= 0) {
25+ return false;
26+ }
27+ value = static_cast<uint64_t>(signed_value);
28+ return true;
29+ }
30+ if (expr.GetExprType() == af::ExprType::kExprConstantRealDouble) {
31+ double real_value = 0.0;
32+ if (!expr.GetConstValue(real_value) || !std::isfinite(real_value) || real_value <= 0.0 ||
33+ real_value >= std::ldexp(1.0, std::numeric_limits<uint64_t>::digits)) {
34+ return false;
35+ }
36+ double integral = 0.0;
37+ if (std::fabs(std::modf(real_value, &integral)) > std::numeric_limits<double>::epsilon()) {
38+ return false;
39+ }
40+ value = static_cast<uint64_t>(real_value);
41+ return value > 0U;
42+ }
43+ return false;
44+}
45+ 
46+bool GetIntegerConstant(const Expr &expr, uint64_t &value) {
47+ return expr.IsConstExpr() && expr.GetExprType() == af::ExprType::kExprConstantInteger && expr.GetConstValue(value);
48+}
49+ 
50+bool HasSafeBlockRange(uint64_t block, uint64_t inner, uint64_t extent) {
51+ return block <= std::numeric_limits<uint64_t>::max() / inner &&
52+ block * inner <= std::numeric_limits<uint64_t>::max() - extent;
53+}
54+ 
55+bool EquivalentExpr(const Expr &lhs, const Expr &rhs) {
56+ if (!lhs.IsValid() || !rhs.IsValid()) {
57+ return false;
58+ }
59+ return lhs.Simplify() == rhs.Simplify();
60+}
61+ 
62+// A block split generated by the scheduler has
63+// block_dim = ceil(period / block_inner_extent)
64+// and the guarded loop uses the same block-inner extent (the final block is
65+// clipped by the codegen tail calculation). This is the only symbolic
66+// uniformity proof currently accepted. It avoids enumerating a symbolic
67+// block_dim while retaining the parser/scheduler positivity contract for
68+// axis repeats; arbitrary symbolic expressions still take the legacy path.
69+bool IsCanonicalPartition(const CacheGuardInfo &info, const Expr &block_dim) {
70+ if (info.kind != CacheGuardKind::kFusedBroadcast || !info.period.IsValid() || !info.block_inner_extent.IsValid() ||
71+ !info.loop_extent.IsValid()) {
72+ return false;
73+ }
74+ if (!EquivalentExpr(info.loop_extent, info.block_inner_extent)) {
75+ return false;
76+ }
77+ const Expr expected = af::sym::Ceiling(af::sym::Div(info.period, info.block_inner_extent));
78+ return EquivalentExpr(block_dim, expected);
79+}
80+ 
81+uint64_t CountConstantHits(const CacheGuardInfo &info, uint64_t block) {
82+ uint64_t period = 0U;
83+ uint64_t inner = 0U;
84+ uint64_t extent = 0U;
85+ if (!GetIntegerConstant(info.loop_extent, extent) || extent == 0U) {
86+ return 0U;
87+ }
88+ if (info.kind == CacheGuardKind::kOriginBroadcast) {
89+ return 1U;
90+ }
91+ (void)GetIntegerConstant(info.period, period);
92+ (void)GetIntegerConstant(info.block_inner_extent, inner);
93+ if (!HasSafeBlockRange(block, inner, extent)) {
94+ return 0U;
95+ }
96+ const uint64_t base = block * inner;
97+ if (base >= period) {
98+ return 0U;
99+ }
100+ const uint64_t effective_extent = std::min(extent, period - base);
101+ const uint64_t last = (base + effective_extent - 1U) / period;
102+ const uint64_t before = (base == 0U) ? 0U : (base - 1U) / period;
103+ const uint64_t multiples = last - before + ((base == 0U) ? 1U : 0U);
104+ return multiples + ((base % period == 0U) ? 0U : 1U);
105+}
106+} // namespace
107+ 
108+bool ValidateCacheGuardInfo(const CacheGuardInfo &info) {
109+ if (!info.address_invariant || !info.guard_axis.IsValid() || !info.loop_extent.IsValid()) {
110+ return false;
111+ }
112+ if (info.kind == CacheGuardKind::kNone) {
113+ return true;
114+ }
115+ uint64_t unused = 0U;
116+ if (!IsPositiveConstantOrSymbol(info.loop_extent, unused) ||
117+ (!info.loop_extent.IsConstExpr() && !info.positive_range_proven)) {
118+ return false;
119+ }
120+ if (info.kind == CacheGuardKind::kOriginBroadcast) {
121+ return true;
122+ }
123+ if (info.kind != CacheGuardKind::kFusedBroadcast || !info.period.IsValid() || !info.block_inner_extent.IsValid() ||
124+ !IsPositiveConstantOrSymbol(info.period, unused) ||
125+ !IsPositiveConstantOrSymbol(info.block_inner_extent, unused) ||
126+ ((!info.period.IsConstExpr() || !info.block_inner_extent.IsConstExpr()) && !info.positive_range_proven)) {
127+ return false;
128+ }
129+ uint64_t period = 0U;
130+ uint64_t inner = 0U;
131+ const bool period_safe = !info.period.IsConstExpr() || GetIntegerConstant(info.period, period);
132+ const bool inner_safe = !info.block_inner_extent.IsConstExpr() || GetIntegerConstant(info.block_inner_extent, inner);
133+ return period_safe && inner_safe &&
134+ (!info.period.IsConstExpr() || !info.block_inner_extent.IsConstExpr() ||
135+ inner <= std::numeric_limits<uint64_t>::max() / period);
136+}
137+ 
138+Expr CountGuardHits(const CacheGuardInfo &info, const Expr &block_idx) {
139+ uint64_t extent = 0U;
140+ if (GetIntegerConstant(info.loop_extent, extent) && extent == 0U) {
141+ return CreateExpr(0U);
142+ }
143+ if (!ValidateCacheGuardInfo(info) || !block_idx.IsValid()) {
144+ return CreateExpr(0U);
145+ }
146+ if (info.kind == CacheGuardKind::kNone) {
147+ return CreateExpr(0U);
148+ }
149+ uint64_t block = 0U;
150+ uint64_t period = 0U;
151+ uint64_t inner = 0U;
152+ if (GetIntegerConstant(block_idx, block) && GetIntegerConstant(info.period, period) &&
153+ GetIntegerConstant(info.block_inner_extent, inner) && GetIntegerConstant(info.loop_extent, extent)) {
154+ if (!HasSafeBlockRange(block, inner, extent)) {
155+ return CreateExpr(0U);
156+ }
157+ return CreateExpr(CountConstantHits(info, block));
158+ }
159+ if (info.kind == CacheGuardKind::kOriginBroadcast) {
160+ return CreateExpr(1U);
161+ }
162+ Expr base = af::sym::Mul(block_idx, info.block_inner_extent);
163+ Expr effective_extent = af::sym::Min(info.loop_extent, af::sym::Max(CreateExpr(0U), af::sym::Sub(info.period, base)));
164+ Expr modulo_hits = af::sym::Sub(af::sym::Ceiling(af::sym::Div(af::sym::Add(base, effective_extent), info.period)),
165+ af::sym::Ceiling(af::sym::Div(base, info.period)));
166+ Expr first_offset = af::sym::Mod(base, info.period);
167+ Expr has_work = af::sym::Ceiling(af::sym::Div(effective_extent, af::sym::Add(effective_extent, CreateExpr(1U))));
168+ return af::sym::Add(
169+ modulo_hits,
170+ af::sym::Mul(has_work, af::sym::Floor(af::sym::Div(
171+ af::sym::Add(first_offset, af::sym::Sub(info.period, CreateExpr(1U))), info.period))));
172+}
173+ 
174+bool IsBlockCountUniform(const CacheGuardInfo &info, const Expr &block_dim) {
175+ if (!ValidateCacheGuardInfo(info) || !block_dim.IsValid()) {
176+ return false;
177+ }
178+ if (info.kind == CacheGuardKind::kNone || info.kind == CacheGuardKind::kOriginBroadcast) {
179+ return true;
180+ }
181+ if (IsCanonicalPartition(info, block_dim)) {
182+ return true;
183+ }
184+ uint64_t blocks = 0U;
185+ uint64_t period = 0U;
186+ uint64_t inner = 0U;
187+ uint64_t extent = 0U;
188+ if (!GetIntegerConstant(block_dim, blocks) || blocks > 1000000U || !GetIntegerConstant(info.period, period) ||
189+ !GetIntegerConstant(info.block_inner_extent, inner) || !GetIntegerConstant(info.loop_extent, extent) ||
190+ blocks == 0U) {
191+ return false;
192+ }
193+ if (!HasSafeBlockRange(blocks - 1U, inner, extent)) {
194+ return false;
195+ }
196+ const uint64_t first = CountConstantHits(info, 0U);
197+ for (uint64_t block = 1U; block < blocks; ++block) {
198+ if (!HasSafeBlockRange(block, inner, extent)) {
199+ return false;
200+ }
201+ if (CountConstantHits(info, block) != first) {
202+ return false;
203+ }
204+ }
205+ return true;
206+}
207+} // namespace att
@@ -0,0 +1,36 @@
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 EXPR_GEN_CACHE_GUARD_COUNT_H_
11+#define EXPR_GEN_CACHE_GUARD_COUNT_H_
12+ 
13+#include "base/base_types.h"
14+ 
15+namespace att {
16+enum class CacheGuardKind { kNone, kOriginBroadcast, kFusedBroadcast };
17+ 
18+struct CacheGuardInfo {
19+ CacheGuardKind kind{CacheGuardKind::kNone};
20+ Expr guard_axis;
21+ Expr period;
22+ Expr block_inner_extent;
23+ Expr loop_extent;
24+ bool address_invariant{false};
25+ // Set only when metadata came from parser/scheduler axis repeats whose
26+ // positive range is guaranteed by the scheduling contract. Generic
27+ // symbolic expressions must leave this false and take the legacy path.
28+ bool positive_range_proven{false};
29+};
30+ 
31+Expr CountGuardHits(const CacheGuardInfo &info, const Expr &block_idx);
32+bool IsBlockCountUniform(const CacheGuardInfo &info, const Expr &block_dim);
33+bool ValidateCacheGuardInfo(const CacheGuardInfo &info);
34+} // namespace att
35+ 
36+#endif // EXPR_GEN_CACHE_GUARD_COUNT_H_
@@ -42,7 +42,103 @@ void InsertAxis(const SubAxis *cur_dim, const NodeInfo &node_info,
42 }42 }
43 }43 }
44}44}
45+ 
46+bool AxisStableLess(const SubAxis *lhs, const SubAxis *rhs) {
47+ if (lhs->id != rhs->id) {
48+ return lhs->id < rhs->id;
49+ }
50+ return lhs->name < rhs->name;
51+}
52+ 
53+bool IsBindMultiCoreInner(const SubAxis *axis) {
54+ return axis != nullptr && axis->is_bind_multi_core && axis->axis_type == AxisPosition::INNER;
55+}
56+ 
57+bool SharesParent(const SubAxis *lhs, const SubAxis *rhs) {
58+ for (const auto *parent : lhs->parent_axis) {
59+ if (parent == nullptr) {
60+ continue;
61+ }
62+ for (const auto *other_parent : rhs->parent_axis) {
63+ if (other_parent != nullptr &&
64+ (other_parent == parent || (!parent->name.empty() && parent->name == other_parent->name))) {
65+ return true;
66+ }
67+ }
68+ }
69+ return false;
70+}
45} // namespace71} // namespace
72+ 
73+bool ExeTimePassManager::TryBuildCacheGuardInfo(const NodeInfo &node, CacheGuardInfo &info) const {
74+ info = CacheGuardInfo{};
75+ if (node.is_cv_ub_fusion || (node.exec_condition != af::ExecuteCondition::kCacheBlockSplitOriginBroadcastAxis &&
76+ node.exec_condition != af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis)) {
77+ return false;
78+ }
79+ 
80+ std::vector<const SubAxis *> block_inner_axes;
81+ for (const auto *axis : node.loop_axes) {
82+ if (IsBindMultiCoreInner(axis)) {
83+ block_inner_axes.emplace_back(axis);
84+ }
85+ }
86+ std::sort(block_inner_axes.begin(), block_inner_axes.end(), AxisStableLess);
87+ // NDDMA is issued once per block-inner iteration; trailing vector axes are
88+ // consumed inside that operation and must not disable its cache guard.
89+ const bool nddma_with_trailing_vector_axis = node.node_type == "Nddma";
90+ if (block_inner_axes.size() != 1U || node.loop_axes.empty() ||
91+ (!nddma_with_trailing_vector_axis && node.loop_axes.back() != block_inner_axes.front())) {
92+ return false;
93+ }
94+ const auto *block_inner = block_inner_axes.front();
95+ if (!block_inner->repeat.IsValid()) {
96+ return false;
97+ }
98+ 
99+ info.guard_axis = af::Symbol(block_inner->name.c_str());
100+ info.loop_extent = block_inner->repeat;
101+ info.address_invariant = true;
102+ // Parser axis repeats are constrained to positive tile sizes by the
103+ // scheduler. Record that provenance so symbolic modulo/division is only
104+ // emitted for metadata produced by this adapter, never for arbitrary
105+ // caller-provided expressions.
106+ info.positive_range_proven = true;
107+ if (node.exec_condition == af::ExecuteCondition::kCacheBlockSplitOriginBroadcastAxis) {
108+ info.kind = CacheGuardKind::kOriginBroadcast;
109+ return ValidateCacheGuardInfo(info);
110+ }
111+ 
112+ std::vector<const SubAxis *> outer_parents;
113+ for (const auto *parent : block_inner->parent_axis) {
114+ if (parent != nullptr && parent->axis_type == AxisPosition::OUTER && !parent->is_bind_multi_core) {
115+ outer_parents.emplace_back(parent);
116+ }
117+ }
118+ if (tuning_space_ != nullptr) {
119+ for (const auto &axis_holder : tuning_space_->sub_axes) {
120+ const auto *axis = axis_holder.get();
121+ if (axis == nullptr || axis->axis_type != AxisPosition::OUTER || axis->is_bind_multi_core) {
122+ continue;
123+ }
124+ const bool direct_parent =
125+ std::find(axis->parent_axis.begin(), axis->parent_axis.end(), block_inner) != axis->parent_axis.end();
126+ if ((direct_parent || SharesParent(axis, block_inner)) &&
127+ std::find(outer_parents.begin(), outer_parents.end(), axis) == outer_parents.end()) {
128+ outer_parents.emplace_back(axis);
129+ }
130+ }
131+ }
132+ std::sort(outer_parents.begin(), outer_parents.end(), AxisStableLess);
133+ if (outer_parents.size() != 1U || !outer_parents.front()->repeat.IsValid()) {
134+ return false;
135+ }
136+ info.kind = CacheGuardKind::kFusedBroadcast;
137+ info.period = outer_parents.front()->repeat;
138+ info.block_inner_extent = block_inner->repeat;
139+ return ValidateCacheGuardInfo(info);
140+}
141+ 
46void ExeTimePassManager::AddBAxis(const std::string &dim_name, const Expr &repeat, const Expr &stride,142void ExeTimePassManager::AddBAxis(const std::string &dim_name, const Expr &repeat, const Expr &stride,
47 const NodeInfo &node_info) {143 const NodeInfo &node_info) {
48 TensorPtr output_tensor = node_info.outputs[0];144 TensorPtr output_tensor = node_info.outputs[0];
@@ -77,7 +173,8 @@ void ExeTimePassManager::AddRAxis(const std::string &dim_name, const Expr &repea
77 173 
78void ExeTimePassManager::CheckBroadcast(const NodeInfo &node) {174void ExeTimePassManager::CheckBroadcast(const NodeInfo &node) {
79 const std::string kNodeBroadcast = "Broadcast";175 const std::string kNodeBroadcast = "Broadcast";
80- if (node.node_type == kNodeBroadcast || ascgen_utils::IsGeneralizeBrcInlineScene(node.node_ptr)) {176+ if (node.node_type == kNodeBroadcast ||
177+ (node.node_ptr != nullptr && ascgen_utils::IsGeneralizeBrcInlineScene(node.node_ptr))) {
81 auto &tensor = node.inputs[0];178 auto &tensor = node.inputs[0];
82 for (size_t i = 0; i < tensor->dim_info.size(); i++) {179 for (size_t i = 0; i < tensor->dim_info.size(); i++) {
83 AddBAxis(tensor->dim_info[i]->name, tensor->repeat[i], tensor->stride[i], node);180 AddBAxis(tensor->dim_info[i]->name, tensor->repeat[i], tensor->stride[i], node);
@@ -189,6 +286,30 @@ bool ExeTimePassManager::GetRLoop(const NodeInfo &node, Expr &r_loop) const {
189 return false;286 return false;
190}287}
191 288 
289+Expr ExeTimePassManager::GetBlockDimExpr() const {
290+ if (tuning_space_ == nullptr || tuning_space_->block_dims.empty()) {
291+ return Expr();
292+ }
293+ Expr block_dim;
294+ bool has_block_dim = false;
295+ for (const auto &block_axes : tuning_space_->block_dims) {
296+ Expr group_dim = CreateExpr(1U);
297+ for (const auto *axis : block_axes) {
298+ if (axis == nullptr) {
299+ return Expr();
300+ }
301+ Expr axis_dim = axis->repeat;
302+ if (!axis_dim.IsValid()) {
303+ return Expr();
304+ }
305+ group_dim = af::sym::Mul(group_dim, axis_dim);
306+ }
307+ block_dim = has_block_dim ? af::sym::Max(block_dim, group_dim) : group_dim;
308+ has_block_dim = true;
309+ }
310+ return block_dim.Simplify();
311+}
312+ 
192/*313/*
193brc缓存执行逻辑:314brc缓存执行逻辑:
1941.递归计算节点的输入信息,将每一个节点与一个或多个Data节点绑定3151.递归计算节点的输入信息,将每一个节点与一个或多个Data节点绑定
@@ -243,6 +364,20 @@ TernaryOp ExeTimePassManager::HandleReduceOrNormalSplit(const NodeInfo &node, co
243}364}
244 365 
245TernaryOp ExeTimePassManager::UpdateNodeExeTime(const NodeInfo &node, const Expr &exe_time) const {366TernaryOp ExeTimePassManager::UpdateNodeExeTime(const NodeInfo &node, const Expr &exe_time) const {
367+ const bool is_cache_guard = node.exec_condition == af::ExecuteCondition::kCacheBlockSplitOriginBroadcastAxis ||
368+ node.exec_condition == af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis;
369+ CacheGuardInfo guard_info;
370+ if (TryBuildCacheGuardInfo(node, guard_info) && guard_info.address_invariant) {
371+ const auto block_dim = GetBlockDimExpr();
372+ if (IsBlockCountUniform(guard_info, block_dim)) {
373+ return TernaryOp(CountGuardHits(guard_info, CreateExpr(0U)));
374+ }
375+ if (is_cache_guard) {
376+ GELOGD("Node[%s] cache guard exe-time fallback: block hit count is not uniform.", node.name.c_str());
377+ }
378+ } else if (is_cache_guard) {
379+ GELOGD("Node[%s] cache guard exe-time fallback: metadata unavailable or address is variant.", node.name.c_str());
380+ }
246 if (brc_buf_node_.find(node.name) == brc_buf_node_.end()) {381 if (brc_buf_node_.find(node.name) == brc_buf_node_.end()) {
247 return TernaryOp(exe_time);382 return TernaryOp(exe_time);
248 }383 }
@@ -14,6 +14,7 @@
14#include <set>14#include <set>
15#include "util/ternary_op.h"15#include "util/ternary_op.h"
16#include "parser/tuning_space.h"16#include "parser/tuning_space.h"
17+#include "expr_gen/cache_guard_count.h"
17 18 
18namespace att {19namespace att {
19class ExeTimePassManager {20class ExeTimePassManager {
@@ -39,6 +40,9 @@ class ExeTimePassManager {
39 // 获取需要处理的节点40 // 获取需要处理的节点
40 TernaryOp UpdateNodeExeTime(const NodeInfo &node, const Expr &exe_time) const;41 TernaryOp UpdateNodeExeTime(const NodeInfo &node, const Expr &exe_time) const;
41 42 
43+ // 从 parser 已有的轴关系中重建 cache guard 元数据;无法唯一确定时返回 false。
44+ bool TryBuildCacheGuardInfo(const NodeInfo &node, CacheGuardInfo &info) const;
45+ 
42 private:46 private:
43 void CheckReduce(const NodeInfo &node);47 void CheckReduce(const NodeInfo &node);
44 void CheckBroadcast(const NodeInfo &node);48 void CheckBroadcast(const NodeInfo &node);
@@ -47,6 +51,7 @@ class ExeTimePassManager {
47 void UpdateBufNode(const std::vector<NodeInfo> &nodes);51 void UpdateBufNode(const std::vector<NodeInfo> &nodes);
48 void GenLog(const std::string &type_name, const std::map<std::string, std::set<std::string>> &axis_list) const;52 void GenLog(const std::string &type_name, const std::map<std::string, std::set<std::string>> &axis_list) const;
49 bool GetRLoop(const NodeInfo &node, Expr &r_loop) const;53 bool GetRLoop(const NodeInfo &node, Expr &r_loop) const;
54+ Expr GetBlockDimExpr() const;
50 bool CheckAxisSplit(const NodeInfo &node, const SubAxis *axis, Expr *fused_axis) const;55 bool CheckAxisSplit(const NodeInfo &node, const SubAxis *axis, Expr *fused_axis) const;
51 TernaryOp HandleBroadcastSplit(const NodeInfo &node, const Expr &exe_time, const Expr &fused_exe_time) const;56 TernaryOp HandleBroadcastSplit(const NodeInfo &node, const Expr &exe_time, const Expr &fused_exe_time) const;
52 TernaryOp HandleReduceOrNormalSplit(const NodeInfo &node, const Expr &exe_time, const SubAxis *axis, bool r_split,57 TernaryOp HandleReduceOrNormalSplit(const NodeInfo &node, const Expr &exe_time, const SubAxis *axis, bool r_split,
@@ -40,6 +40,21 @@ bool HasComputeType(const ascir::ImplGraph &impl_graph, const af::ComputeType co
40 return false;40 return false;
41}41}
42 42 
43+bool ShouldPreserveCacheCondition(const af::AscNodePtr &node, const bool graph_has_reduce) {
44+ if (!graph_has_reduce) {
45+ return true;
46+ }
47+ if (node == nullptr) {
48+ return false;
49+ }
50+ // Codegen evaluates cache eligibility per node in a Reduce graph. Keep the
51+ // condition for data movement/broadcast nodes so ATT models the same Nddma
52+ // (Load converted to Nddma) execution count; reduction nodes retain the
53+ // historical conservative behavior.
54+ const auto &type = node->GetType();
55+ return type == kLoad || type == "Nddma" || type == kBroadcast;
56+}
57+ 
43// 通过输出获取当前节点单次执行涉及到的轴的范围58// 通过输出获取当前节点单次执行涉及到的轴的范围
44std::vector<int64_t> GetNodeVectorizedAxis(const af::AscNodePtr &ge_node, int64_t loop_axis_id) {59std::vector<int64_t> GetNodeVectorizedAxis(const af::AscNodePtr &ge_node, int64_t loop_axis_id) {
45 std::vector<int64_t> total_vectorized_axis;60 std::vector<int64_t> total_vectorized_axis;
@@ -783,7 +798,9 @@ af::Status AscendGraphParser::ConvertNodeInfos(const af::AscNodePtr &ge_node, co
783 NodeInfo node_info;798 NodeInfo node_info;
784 node_info.name = ge_node->GetName();799 node_info.name = ge_node->GetName();
785 node_info.node_type = ge_node->GetType();800 node_info.node_type = ge_node->GetType();
786- node_info.exec_condition = use_cache_flag ? attrs.exec_condition : af::ExecuteCondition::kNoCache;801+ const bool preserve_cache_condition =
802+ use_cache_flag || ShouldPreserveCacheCondition(ge_node, /*graph_has_reduce=*/!use_cache_flag);
803+ node_info.exec_condition = preserve_cache_condition ? attrs.exec_condition : af::ExecuteCondition::kNoCache;
787 if (kUnitMap.find(ge_node->attr.api.unit) == kUnitMap.end()) {804 if (kUnitMap.find(ge_node->attr.api.unit) == kUnitMap.end()) {
788 node_info.node_unit = "UnitInvalid";805 node_info.node_unit = "UnitInvalid";
789 } else {806 } else {
@@ -21,6 +21,7 @@
21#include "mmpa/mmpa_api.h"21#include "mmpa/mmpa_api.h"
22#include "base_types_printer.h"22#include "base_types_printer.h"
23#include "base/att_const_values.h"23#include "base/att_const_values.h"
24+#include "base/att_group_perf_config.h"
24#include "common_utils.h"25#include "common_utils.h"
25#include "generator_utils/tilingdata_gen_utils.h"26#include "generator_utils/tilingdata_gen_utils.h"
26#include "tiling_data_gen/tiling_data_generator.h"27#include "tiling_data_gen/tiling_data_generator.h"
@@ -3591,12 +3592,102 @@ std::string TilingCodeGenImpl::GenPerfUpdateCode(const std::vector<std::string>
3591 return update_code;3592 return update_code;
3592}3593}
3593 3594 
3595+std::vector<std::pair<size_t, size_t>> TilingCodeGenImpl::GetSerializedGroupEdges(
3596+ const std::map<size_t, std::pair<std::string, std::string>> &graph_info,
3597+ const std::map<size_t, std::map<size_t, std::map<std::string, af::Expression>>> &var_relation,
3598+ const bool enable_group_parallel) const {
3599+ if (enable_group_parallel) {
3600+ return {};
3601+ }
3602+ 
3603+ std::set<std::pair<size_t, size_t>> edge_set;
3604+ for (const auto &dst_relation : var_relation) {
3605+ const size_t dst_id = dst_relation.first;
3606+ if (graph_info.find(dst_id) == graph_info.end()) {
3607+ continue;
3608+ }
3609+ for (const auto &src_relation : dst_relation.second) {
3610+ const size_t src_id = src_relation.first;
3611+ if (!src_relation.second.empty() && src_id != dst_id && graph_info.find(src_id) != graph_info.end()) {
3612+ edge_set.emplace(src_id, dst_id);
3613+ }
3614+ }
3615+ }
3616+ return {edge_set.begin(), edge_set.end()};
3617+}
3618+ 
3619+std::vector<std::pair<size_t, size_t>> TilingCodeGenImpl::GetWorkspaceSharedGroupEdges(
3620+ const size_t asc_graph_id, const size_t impl_graph_id,
3621+ const std::map<size_t, std::pair<std::string, std::string>> &graph_info) const {
3622+ std::map<size_t, std::set<int64_t>> group_workspace_ids;
3623+ for (const auto &model_info : tiling_model_info_) {
3624+ const auto &ident = model_info.schedule_group_ident;
3625+ if (ident.asc_graph_id != asc_graph_id || ident.impl_graph_id != impl_graph_id ||
3626+ graph_info.find(ident.group_id) == graph_info.end()) {
3627+ continue;
3628+ }
3629+ auto &tensor_ids = group_workspace_ids[ident.group_id];
3630+ for (const auto &workspace : model_info.workspace_size_map) {
3631+ tensor_ids.insert(workspace.first);
3632+ }
3633+ }
3634+ std::vector<std::pair<size_t, size_t>> edges;
3635+ for (auto lhs = group_workspace_ids.begin(); lhs != group_workspace_ids.end(); ++lhs) {
3636+ auto rhs = lhs;
3637+ for (++rhs; rhs != group_workspace_ids.end(); ++rhs) {
3638+ const auto &smaller = (lhs->second.size() <= rhs->second.size()) ? lhs->second : rhs->second;
3639+ const auto &larger = (lhs->second.size() <= rhs->second.size()) ? rhs->second : lhs->second;
3640+ const bool shared = std::any_of(smaller.cbegin(), smaller.cend(),
3641+ [&larger](const int64_t tensor_id) { return larger.count(tensor_id) != 0U; });
3642+ if (shared) {
3643+ edges.emplace_back(lhs->first, rhs->first);
3644+ }
3645+ }
3646+ }
3647+ return edges;
3648+}
3649+ 
3650+std::string TilingCodeGenImpl::GenSerializedEdgePenalty(const std::vector<std::string> &groups_perf,
3651+ const std::vector<std::pair<size_t, size_t>> &serialized_edges,
3652+ const std::string &indent) const {
3653+ std::vector<std::pair<size_t, size_t>> valid_edges;
3654+ valid_edges.reserve(serialized_edges.size());
3655+ for (const auto &edge : serialized_edges) {
3656+ if (edge.first >= groups_perf.size() || edge.second >= groups_perf.size()) {
3657+ return {};
3658+ }
3659+ valid_edges.emplace_back(edge);
3660+ }
3661+ if (valid_edges.empty()) {
3662+ return {};
3663+ }
3664+ 
3665+ const std::string ratio = std::to_string(SerializedEdgePenaltyConfig::kPenaltyRatio);
3666+ const std::string cap = std::to_string(SerializedEdgePenaltyConfig::kPenaltyCapCycles);
3667+ std::string code = indent + "double serialized_edge_base = 0.0;\n";
3668+ for (const auto &edge : valid_edges) {
3669+ code += indent + "serialized_edge_base += std::max(" + groups_perf[edge.first] + ", " + groups_perf[edge.second] +
3670+ ");\n";
3671+ }
3672+ code += indent + "double serialized_edge_penalty = std::min(" + cap + ", " + ratio + " * serialized_edge_base);\n";
3673+ code += indent + "cur_perf += serialized_edge_penalty;\n";
3674+ code += indent +
3675+ "OP_LOGD(OP_NAME, \"serialized_edge_model=" + std::string(SerializedEdgePenaltyConfig::kModelVersion) +
3676+ " serialized_edge_penalty=min(" + cap + ", " + ratio +
3677+ " * serialized_edge_base)=%lf (base=%lf)\", serialized_edge_penalty, serialized_edge_base);\n";
3678+ return code;
3679+}
3680+ 
3594void TilingCodeGenImpl::GenBestPerfUpdateCode(const size_t asc_graph_id, const size_t impl_graph_id,3681void TilingCodeGenImpl::GenBestPerfUpdateCode(const size_t asc_graph_id, const size_t impl_graph_id,
3595 const std::vector<std::string> &assign_max_block_num,3682 const std::vector<std::string> &assign_max_block_num,
3596- const std::string &indent) {3683+ const std::string &indent, const std::string &objective_log) {
3597 std::string tiling_key_prefix = "graph" + std::to_string(asc_graph_id) + "_";3684 std::string tiling_key_prefix = "graph" + std::to_string(asc_graph_id) + "_";
3598- tiling_func_.AddLine(indent + "OP_LOGI(OP_NAME, \"The value of graph" + std::to_string(asc_graph_id) + "_result" +3685+ if (objective_log.empty()) {
3599- std::to_string(impl_graph_id) + " is %lf\", cur_perf);");3686+ tiling_func_.AddLine(indent + "OP_LOGI(OP_NAME, \"The value of graph" + std::to_string(asc_graph_id) + "_result" +
3687+ std::to_string(impl_graph_id) + " is %lf\", cur_perf);");
3688+ } else {
3689+ tiling_func_.AddLine(objective_log);
3690+ }
3600 tiling_func_.AddLine(indent + "if (IsEqual(best_perf, -1) || cur_perf < best_perf) {");3691 tiling_func_.AddLine(indent + "if (IsEqual(best_perf, -1) || cur_perf < best_perf) {");
3601 tiling_func_.AddLine(indent + " best_perf = cur_perf;");3692 tiling_func_.AddLine(indent + " best_perf = cur_perf;");
3602 for (const auto &code : assign_max_block_num) {3693 for (const auto &code : assign_max_block_num) {
@@ -3617,14 +3708,58 @@ void TilingCodeGenImpl::GenBestPerfUpdateCode(const size_t asc_graph_id, const s
3617 3708 
3618af::Status TilingCodeGenImpl::GenUpdatePerf(const size_t asc_graph_id, const size_t impl_graph_id,3709af::Status TilingCodeGenImpl::GenUpdatePerf(const size_t asc_graph_id, const size_t impl_graph_id,
3619 const std::vector<std::string> &groups_perf,3710 const std::vector<std::string> &groups_perf,
3711+ const std::vector<std::pair<size_t, size_t>> &serialized_edges,
3620 const std::vector<std::string> &groups_block_num,3712 const std::vector<std::string> &groups_block_num,
3621 const std::vector<std::string> &assign_max_block_num) {3713 const std::vector<std::string> &assign_max_block_num) {
3714+ std::string objective_log;
3622 if (!IsScheduleResultEnableParallel(asc_graph_id, impl_graph_id)) {3715 if (!IsScheduleResultEnableParallel(asc_graph_id, impl_graph_id)) {
3623 tiling_func_.AddLine(GenSumAllGroupsPerf(groups_perf));3716 tiling_func_.AddLine(GenSumAllGroupsPerf(groups_perf));
3717+ tiling_func_.AddLine(" double group_sum = cur_perf;");
3718+ bool edge_model_enabled = false;
3719+ if (!serialized_edges.empty()) {
3720+ const auto penalty_code = GenSerializedEdgePenalty(groups_perf, serialized_edges, " ");
3721+ if (!penalty_code.empty()) {
3722+ edge_model_enabled = true;
3723+ tiling_func_.AddLine(penalty_code);
3724+ tiling_func_.AddLine(
3725+ " OP_LOGD(OP_NAME, \"objective=group_sum(%lf)+edge_penalty(%lf)=%lf\", group_sum, "
3726+ "serialized_edge_penalty, cur_perf);");
3727+ }
3728+ }
3729+ 
3730+ std::string group_formula;
3731+ for (size_t i = 0U; i < groups_perf.size(); ++i) {
3732+ if (i != 0U) {
3733+ group_formula += " + ";
3734+ }
3735+ group_formula += "group" + std::to_string(i);
3736+ }
3737+ std::string edge_base_formula = "0";
3738+ if (edge_model_enabled) {
3739+ edge_base_formula.clear();
3740+ for (size_t i = 0U; i < serialized_edges.size(); ++i) {
3741+ if (i != 0U) {
3742+ edge_base_formula += " + ";
3743+ }
3744+ edge_base_formula += "max(group" + std::to_string(serialized_edges[i].first) + ", group" +
3745+ std::to_string(serialized_edges[i].second) + ")";
3746+ }
3747+ }
3748+ objective_log = " OP_LOGI(OP_NAME, \"The value of graph" + std::to_string(asc_graph_id) + "_result" +
3749+ std::to_string(impl_graph_id) + " is %lf objective(group_sum(" + group_formula +
3750+ ")=%lf + edge_model(" + std::string(SerializedEdgePenaltyConfig::kModelVersion) +
3751+ ", base=" + edge_base_formula + "=%lf, penalty=min(" +
3752+ std::to_string(SerializedEdgePenaltyConfig::kPenaltyCapCycles) + ", " +
3753+ std::to_string(SerializedEdgePenaltyConfig::kPenaltyRatio) + "*base)=%lf)=%lf\", cur_perf, ";
3754+ if (edge_model_enabled) {
3755+ objective_log += "group_sum, serialized_edge_base, serialized_edge_penalty, cur_perf);";
3756+ } else {
3757+ objective_log += "group_sum, 0.0, 0.0, cur_perf);";
3758+ }
3624 } else {3759 } else {
3625 tiling_func_.AddLine(GenPerfUpdateCode(groups_perf, groups_block_num, " "));3760 tiling_func_.AddLine(GenPerfUpdateCode(groups_perf, groups_block_num, " "));
3626 }3761 }
3627- GenBestPerfUpdateCode(asc_graph_id, impl_graph_id, assign_max_block_num, " ");3762+ GenBestPerfUpdateCode(asc_graph_id, impl_graph_id, assign_max_block_num, " ", objective_log);
3628 tiling_func_.AddLine(" }"); // 闭合 GetScheduleResult 函数3763 tiling_func_.AddLine(" }"); // 闭合 GetScheduleResult 函数
3629 return af::SUCCESS;3764 return af::SUCCESS;
3630}3765}
@@ -3811,11 +3946,12 @@ af::Status TilingCodeGenImpl::GenSingleGroupScheduleResult(
3811 const size_t asc_graph_id, const size_t impl_graph_id,3946 const size_t asc_graph_id, const size_t impl_graph_id,
3812 const std::map<size_t, std::pair<std::string, std::string>> &graph_info,3947 const std::map<size_t, std::pair<std::string, std::string>> &graph_info,
3813 const std::map<std::string, std::set<std::string>> &hardware_map) {3948 const std::map<std::string, std::set<std::string>> &hardware_map) {
3814- const auto var_relation = var_relations_[asc_graph_id][impl_graph_id];3949+ const auto &var_relation = var_relations_[asc_graph_id][impl_graph_id];
3815 std::string check_cond;3950 std::string check_cond;
3816 std::vector<std::string> assign_max_block_num;3951 std::vector<std::string> assign_max_block_num;
3817 std::vector<std::string> groups_perf;3952 std::vector<std::string> groups_perf;
3818 std::vector<std::string> groups_block_num;3953 std::vector<std::string> groups_block_num;
3954+ std::map<size_t, size_t> group_perf_positions;
3819 for (const auto &group_info : graph_info) {3955 for (const auto &group_info : graph_info) {
3820 auto [input_vars_set_code, need_update_second_group_input_vars] =3956 auto [input_vars_set_code, need_update_second_group_input_vars] =
3821 ProcessVarRelations(graph_info, var_relation, group_info.first);3957 ProcessVarRelations(graph_info, var_relation, group_info.first);
@@ -3834,6 +3970,7 @@ af::Status TilingCodeGenImpl::GenSingleGroupScheduleResult(
3834 "Gen schedule group do tiling failed, graph id[%zu], impl id[%zu]", asc_graph_id,3970 "Gen schedule group do tiling failed, graph id[%zu], impl id[%zu]", asc_graph_id,
3835 impl_graph_id);3971 impl_graph_id);
3836 groups_perf.emplace_back(GenGetScheduleGroupPerf(group_info.second.first, group_info.second.second));3972 groups_perf.emplace_back(GenGetScheduleGroupPerf(group_info.second.first, group_info.second.second));
3973+ group_perf_positions[group_info.first] = groups_perf.size() - 1U;
3837 assign_max_block_num.emplace_back(GenCurMaxBlockDim(group_info.second.second, groups_block_num, cur_block));3974 assign_max_block_num.emplace_back(GenCurMaxBlockDim(group_info.second.second, groups_block_num, cur_block));
3838 groups_block_num.emplace_back(GenGetCurBlockDim(group_info.second.second));3975 groups_block_num.emplace_back(GenGetCurBlockDim(group_info.second.second));
3839 }3976 }
@@ -3842,8 +3979,27 @@ af::Status TilingCodeGenImpl::GenSingleGroupScheduleResult(
3842 impl_graph_id);3979 impl_graph_id);
3843 GE_ASSERT_EQ(groups_block_num.size(), groups_perf.size());3980 GE_ASSERT_EQ(groups_block_num.size(), groups_perf.size());
3844 tiling_func_.AddLine(" if (" + (check_cond.empty() ? "true" : check_cond) + ") {");3981 tiling_func_.AddLine(" if (" + (check_cond.empty() ? "true" : check_cond) + ") {");
3845- GE_ASSERT_SUCCESS(GenUpdatePerf(asc_graph_id, impl_graph_id, groups_perf, groups_block_num, assign_max_block_num),3982+ const bool enable_group_parallel = IsScheduleResultEnableParallel(asc_graph_id, impl_graph_id);
3846- "Gen update perf failed, asc_graph_id %zu impl_graph_id %zu", asc_graph_id, impl_graph_id);3983+ std::vector<std::pair<size_t, size_t>> group_edges =
3984+ GetSerializedGroupEdges(graph_info, var_relation, enable_group_parallel);
3985+ if (!enable_group_parallel) {
3986+ const auto workspace_edges = GetWorkspaceSharedGroupEdges(asc_graph_id, impl_graph_id, graph_info);
3987+ std::set<std::pair<size_t, size_t>> merged_edges(group_edges.cbegin(), group_edges.cend());
3988+ merged_edges.insert(workspace_edges.cbegin(), workspace_edges.cend());
3989+ group_edges.assign(merged_edges.cbegin(), merged_edges.cend());
3990+ }
3991+ std::vector<std::pair<size_t, size_t>> serialized_edges;
3992+ serialized_edges.reserve(group_edges.size());
3993+ for (const auto &edge : group_edges) {
3994+ const auto src_pos = group_perf_positions.find(edge.first);
3995+ const auto dst_pos = group_perf_positions.find(edge.second);
3996+ if (src_pos != group_perf_positions.end() && dst_pos != group_perf_positions.end()) {
3997+ serialized_edges.emplace_back(src_pos->second, dst_pos->second);
3998+ }
3999+ }
4000+ GE_ASSERT_SUCCESS(
4001+ GenUpdatePerf(asc_graph_id, impl_graph_id, groups_perf, serialized_edges, groups_block_num, assign_max_block_num),
4002+ "Gen update perf failed, asc_graph_id %zu impl_graph_id %zu", asc_graph_id, impl_graph_id);
3847 GenGetScheduleResultTail(graph_info);4003 GenGetScheduleResultTail(graph_info);
3848 return af::SUCCESS;4004 return af::SUCCESS;
3849}4005}
@@ -141,6 +141,7 @@ class TilingCodeGenImpl {
141 const std::map<std::string, std::set<std::string>> &hardware_map);141 const std::map<std::string, std::set<std::string>> &hardware_map);
142 af::Status GenUpdatePerf(const size_t asc_graph_id, const size_t impl_graph_id,142 af::Status GenUpdatePerf(const size_t asc_graph_id, const size_t impl_graph_id,
143 const std::vector<std::string> &groups_perf,143 const std::vector<std::string> &groups_perf,
144+ const std::vector<std::pair<size_t, size_t>> &serialized_edges,
144 const std::vector<std::string> &groups_block_num,145 const std::vector<std::string> &groups_block_num,
145 const std::vector<std::string> &assign_max_block_num);146 const std::vector<std::string> &assign_max_block_num);
146 void GenGetMaxScoreIndex(const AscGraphNamepspaceMap &namespace_map);147 void GenGetMaxScoreIndex(const AscGraphNamepspaceMap &namespace_map);
@@ -320,9 +321,20 @@ class TilingCodeGenImpl {
320 // 辅助函数:生成perf更新代码(消除GenUpdatePerf和GenGetScheduleResultPerfAndTail的重复)321 // 辅助函数:生成perf更新代码(消除GenUpdatePerf和GenGetScheduleResultPerfAndTail的重复)
321 static std::string GenPerfUpdateCode(const std::vector<std::string> &groups_perf,322 static std::string GenPerfUpdateCode(const std::vector<std::string> &groups_perf,
322 const std::vector<std::string> &groups_block_num, const std::string &indent);323 const std::vector<std::string> &groups_block_num, const std::string &indent);
324+ std::vector<std::pair<size_t, size_t>> GetSerializedGroupEdges(
325+ const std::map<size_t, std::pair<std::string, std::string>> &graph_info,
326+ const std::map<size_t, std::map<size_t, std::map<std::string, af::Expression>>> &var_relation,
327+ bool enable_group_parallel) const;
328+ std::vector<std::pair<size_t, size_t>> GetWorkspaceSharedGroupEdges(
329+ const size_t asc_graph_id, const size_t impl_graph_id,
330+ const std::map<size_t, std::pair<std::string, std::string>> &graph_info) const;
331+ std::string GenSerializedEdgePenalty(const std::vector<std::string> &groups_perf,
332+ const std::vector<std::pair<size_t, size_t>> &serialized_edges,
333+ const std::string &indent) const;
323 // 辅助函数:生成best perf更新代码334 // 辅助函数:生成best perf更新代码
324 void GenBestPerfUpdateCode(const size_t asc_graph_id, const size_t impl_graph_id,335 void GenBestPerfUpdateCode(const size_t asc_graph_id, const size_t impl_graph_id,
325- const std::vector<std::string> &assign_max_block_num, const std::string &indent);336+ const std::vector<std::string> &assign_max_block_num, const std::string &indent,
337+ const std::string &objective_log = {});
326 338 
327 // 为所有group生成cache line冲突检测helper函数339 // 为所有group生成cache line冲突检测helper函数
328 af::Status GenConflictGroupHelpers(const size_t asc_graph_id, const size_t impl_graph_id,340 af::Status GenConflictGroupHelpers(const size_t asc_graph_id, const size_t impl_graph_id,
@@ -8,14 +8,289 @@
8 * See LICENSE in the root of the software repository for the full text of the License.8 * See LICENSE in the root of the software repository for the full text of the License.
9 */9 */
10#include <iostream>10#include <iostream>
11+#include <limits>
11#include "gtest/gtest.h"12#include "gtest/gtest.h"
12#include "test_brc_buf_graph.h"13#include "test_brc_buf_graph.h"
13#include "parser/ascend_graph_parser.h"14#include "parser/ascend_graph_parser.h"
14#include "expr_gen/arg_list_manager.h"15#include "expr_gen/arg_list_manager.h"
15#include "expr_gen/exe_time_pass.h"16#include "expr_gen/exe_time_pass.h"
17+#include "expr_gen/cache_guard_count.h"
16#include "graph_construct_utils.h"18#include "graph_construct_utils.h"
17 19 
18namespace att {20namespace att {
21+namespace {
22+Expr Symbol(const char *name) {
23+ return af::Expression::Parse(name);
24+}
25+ 
26+CacheGuardInfo MakeOriginGuard(const Expr &guard_axis, const Expr &loop_extent) {
27+ return {CacheGuardKind::kOriginBroadcast, guard_axis, Expr(), Expr(), loop_extent, true, true};
28+}
29+ 
30+CacheGuardInfo MakeFusedGuard(const Expr &guard_axis, const Expr &period, const Expr &block_inner_extent,
31+ const Expr &loop_extent) {
32+ return {CacheGuardKind::kFusedBroadcast, guard_axis, period, block_inner_extent, loop_extent, true, true};
33+}
34+ 
35+uint64_t EvaluateConst(const Expr &expr) {
36+ uint64_t value = 0;
37+ EXPECT_TRUE(expr.GetConstValue(value));
38+ return value;
39+}
40+ 
41+TuningSpacePtr MakeGuardTuningSpace(const Expr &period, const Expr &inner_extent, const Expr &block_dim) {
42+ auto tuning_space = std::make_shared<TuningSpace>();
43+ auto origin = std::make_unique<SubAxis>();
44+ origin->id = 1;
45+ origin->name = "guarded";
46+ origin->repeat = inner_extent;
47+ auto outer = std::make_unique<SubAxis>();
48+ outer->id = 2;
49+ outer->name = "outer";
50+ outer->axis_type = AxisPosition::OUTER;
51+ outer->repeat = period;
52+ auto inner = std::make_unique<SubAxis>();
53+ inner->id = 3;
54+ inner->name = "block_inner";
55+ inner->axis_type = AxisPosition::INNER;
56+ inner->is_bind_multi_core = true;
57+ inner->repeat = inner_extent;
58+ inner->orig_axis = {origin.get()};
59+ // Parser split outputs reference their common source axis through parent_axis.
60+ inner->parent_axis = {origin.get()};
61+ outer->parent_axis = {origin.get()};
62+ auto block = std::make_unique<SubAxis>();
63+ block->id = 4;
64+ block->name = "block";
65+ block->axis_type = AxisPosition::OUTER;
66+ block->repeat = block_dim;
67+ tuning_space->sub_axes.emplace_back(std::move(origin));
68+ tuning_space->sub_axes.emplace_back(std::move(outer));
69+ tuning_space->sub_axes.emplace_back(std::move(inner));
70+ tuning_space->sub_axes.emplace_back(std::move(block));
71+ tuning_space->block_dims = {{tuning_space->sub_axes[3].get()}};
72+ NodeInfo node;
73+ node.name = "cast0";
74+ node.exec_condition = af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis;
75+ node.loop_axes = {tuning_space->sub_axes[2].get()};
76+ tuning_space->node_infos.emplace_back(node);
77+ return tuning_space;
78+}
79+} // namespace
80+ 
81+TEST(CacheGuardInfoTest, OriginGuardCountsOneHit) {
82+ CacheGuardInfo info = MakeOriginGuard(Symbol("i"), Symbol("19"));
83+ EXPECT_EQ(EvaluateConst(CountGuardHits(info, Symbol("0"))), 1);
84+}
85+ 
86+TEST(CacheGuardInfoTest, FusedGuardCountsPeriodHitsPerBlock) {
87+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("4"), Symbol("2"), Symbol("6"));
88+ EXPECT_EQ(EvaluateConst(CountGuardHits(info, Symbol("0"))), 1);
89+ EXPECT_EQ(EvaluateConst(CountGuardHits(info, Symbol("1"))), 1);
90+}
91+ 
92+TEST(CacheGuardInfoTest, FusedGuardReturnsZeroBeyondPeriod) {
93+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("4"), Symbol("2"), Symbol("2"));
94+ EXPECT_EQ(EvaluateConst(CountGuardHits(info, Symbol("3"))), 0);
95+}
96+ 
97+TEST(CacheGuardInfoTest, InvalidPeriodDoesNotBuildModulo) {
98+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("0"), Symbol("2"), Symbol("6"));
99+ EXPECT_FALSE(ValidateCacheGuardInfo(info));
100+}
101+ 
102+TEST(CacheGuardInfoTest, BlockDependentCountFallsBackUnlessUniform) {
103+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("5"), Symbol("2"), Symbol("6"));
104+ EXPECT_FALSE(IsBlockCountUniform(info, Symbol("54")));
105+}
106+ 
107+TEST(CacheGuardInfoTest, SymbolicCanonicalBlockSplitProvesUniformity) {
108+ const auto period = Symbol("period");
109+ const auto inner = Symbol("inner");
110+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), period, inner, inner);
111+ const auto block_dim = af::sym::Ceiling(af::sym::Div(period, inner));
112+ EXPECT_TRUE(IsBlockCountUniform(info, block_dim));
113+}
114+ 
115+TEST(CacheGuardInfoTest, ArbitrarySymbolicBlockDimFallsBack) {
116+ CacheGuardInfo info = MakeFusedGuard(Symbol("period"), Symbol("inner"), Symbol("inner"), Symbol("inner"));
117+ EXPECT_FALSE(IsBlockCountUniform(info, Symbol("block_dim")));
118+}
119+ 
120+TEST(CacheGuardInfoTest, UnitExtentTailMakesBlockCountNonUniform) {
121+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("5"), Symbol("2"), Symbol("1"));
122+ EXPECT_FALSE(IsBlockCountUniform(info, Symbol("4")));
123+}
124+ 
125+TEST(CacheGuardInfoTest, UnitPeriodTailMakesBlockCountNonUniform) {
126+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("1"), Symbol("2"), Symbol("2"));
127+ EXPECT_FALSE(IsBlockCountUniform(info, Symbol("2")));
128+}
129+ 
130+TEST(CacheGuardInfoTest, UnitPeriodUnitExtentCountsSingleHit) {
131+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("1"), Symbol("2"), Symbol("1"));
132+ EXPECT_EQ(EvaluateConst(CountGuardHits(info, Symbol("0"))), 1);
133+}
134+ 
135+TEST(CacheGuardInfoTest, RejectsNonPositiveLoopExtent) {
136+ EXPECT_FALSE(ValidateCacheGuardInfo(MakeOriginGuard(Symbol("i"), Symbol("0"))));
137+ EXPECT_FALSE(ValidateCacheGuardInfo(MakeOriginGuard(Symbol("i"), Symbol("-1"))));
138+}
139+ 
140+TEST(CacheGuardInfoTest, AllowsMixedSymbolicAndConstantMetadata) {
141+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("period"), Symbol("2"), Symbol("extent"));
142+ EXPECT_TRUE(ValidateCacheGuardInfo(info));
143+}
144+ 
145+TEST(CacheGuardInfoTest, RejectsUnprovenSymbolicMetadata) {
146+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("period"), Symbol("2"), Symbol("extent"));
147+ info.positive_range_proven = false;
148+ EXPECT_FALSE(ValidateCacheGuardInfo(info));
149+}
150+ 
151+TEST(CacheGuardInfoTest, RejectsNonIntegerConstantMetadata) {
152+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), Symbol("2.5"), Symbol("2"), Symbol("6"));
153+ EXPECT_FALSE(ValidateCacheGuardInfo(info));
154+}
155+ 
156+TEST(CacheGuardInfoTest, NoneGuardSafelyReturnsZero) {
157+ CacheGuardInfo info;
158+ EXPECT_EQ(EvaluateConst(CountGuardHits(info, Symbol("0"))), 0);
159+}
160+ 
161+TEST(CacheGuardInfoTest, OverflowingBlockRangeIsNotUniform) {
162+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), CreateExpr(1U),
163+ CreateExpr(std::numeric_limits<uint64_t>::max() / 2U), CreateExpr(2U));
164+ EXPECT_FALSE(IsBlockCountUniform(info, CreateExpr(3U)));
165+}
166+ 
167+TEST(CacheGuardInfoTest, LargeLoopExtentUsesConstantTimeCounting) {
168+ CacheGuardInfo info = MakeFusedGuard(Symbol("i"), CreateExpr(4U), CreateExpr(2U), CreateExpr(1000000000000UL));
169+ EXPECT_EQ(EvaluateConst(CountGuardHits(info, CreateExpr(0U))), 1);
170+}
171+ 
172+TEST(CacheGuardInfoTest, RejectsRealExtentBeyondUint64) {
173+ EXPECT_FALSE(ValidateCacheGuardInfo(MakeOriginGuard(Symbol("i"), Symbol("18446744073709551616.0"))));
174+}
175+ 
176+TEST(CacheGuardInfoTest, ReconstructsFusedGuardMetadataFromBlockAxes) {
177+ auto tuning_space = std::make_shared<TuningSpace>();
178+ auto origin = std::make_unique<SubAxis>();
179+ origin->id = 1;
180+ origin->name = "guarded";
181+ origin->repeat = Symbol("19");
182+ auto outer = std::make_unique<SubAxis>();
183+ outer->id = 2;
184+ outer->name = "outer";
185+ outer->axis_type = AxisPosition::OUTER;
186+ outer->repeat = Symbol("19");
187+ auto inner = std::make_unique<SubAxis>();
188+ inner->id = 3;
189+ inner->name = "block_inner";
190+ inner->axis_type = AxisPosition::INNER;
191+ inner->is_bind_multi_core = true;
192+ inner->repeat = Symbol("19");
193+ inner->orig_axis = {origin.get()};
194+ inner->parent_axis = {origin.get()};
195+ outer->parent_axis = {origin.get()};
196+ tuning_space->sub_axes.emplace_back(std::move(origin));
197+ tuning_space->sub_axes.emplace_back(std::move(outer));
198+ tuning_space->sub_axes.emplace_back(std::move(inner));
199+ 
200+ NodeInfo node;
201+ node.name = "fused";
202+ node.exec_condition = af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis;
203+ node.loop_axes = {tuning_space->sub_axes[2].get()};
204+ ExeTimePassManager manager(tuning_space);
205+ CacheGuardInfo info;
206+ ASSERT_TRUE(manager.TryBuildCacheGuardInfo(node, info));
207+ EXPECT_EQ(info.kind, CacheGuardKind::kFusedBroadcast);
208+ EXPECT_EQ(Str(info.period), "19");
209+ EXPECT_EQ(Str(info.block_inner_extent), "19");
210+ EXPECT_EQ(Str(info.loop_extent), "19");
211+ EXPECT_TRUE(info.positive_range_proven);
212+ 
213+ // The block-outer sibling shares the source parent but is not the guard period.
214+ auto block_outer = std::make_unique<SubAxis>();
215+ block_outer->id = 4;
216+ block_outer->name = "block_outer";
217+ block_outer->axis_type = AxisPosition::OUTER;
218+ block_outer->is_bind_multi_core = true;
219+ block_outer->repeat = Symbol("54");
220+ block_outer->parent_axis = {tuning_space->sub_axes[0].get()};
221+ tuning_space->sub_axes.emplace_back(std::move(block_outer));
222+ ASSERT_TRUE(manager.TryBuildCacheGuardInfo(node, info));
223+ EXPECT_EQ(Str(info.period), "19");
224+ 
225+ auto second_outer = std::make_unique<SubAxis>();
226+ second_outer->id = 5;
227+ second_outer->name = "outer_2";
228+ second_outer->axis_type = AxisPosition::OUTER;
229+ second_outer->repeat = Symbol("19");
230+ second_outer->parent_axis = {tuning_space->sub_axes[0].get()};
231+ tuning_space->sub_axes.emplace_back(std::move(second_outer));
232+ EXPECT_FALSE(manager.TryBuildCacheGuardInfo(node, info));
233+}
234+ 
235+TEST(CacheGuardInfoTest, ReconstructsOriginGuardAndRejectsUnsafeAxes) {
236+ auto tuning_space = std::make_shared<TuningSpace>();
237+ auto inner = std::make_unique<SubAxis>();
238+ inner->id = 1;
239+ inner->name = "block_inner";
240+ inner->axis_type = AxisPosition::INNER;
241+ inner->is_bind_multi_core = true;
242+ inner->repeat = Symbol("19");
243+ tuning_space->sub_axes.emplace_back(std::move(inner));
244+ NodeInfo node;
245+ node.name = "origin";
246+ node.exec_condition = af::ExecuteCondition::kCacheBlockSplitOriginBroadcastAxis;
247+ node.loop_axes = {tuning_space->sub_axes[0].get()};
248+ ExeTimePassManager manager(tuning_space);
249+ CacheGuardInfo info;
250+ ASSERT_TRUE(manager.TryBuildCacheGuardInfo(node, info));
251+ EXPECT_EQ(info.kind, CacheGuardKind::kOriginBroadcast);
252+ EXPECT_EQ(Str(info.loop_extent), "19");
253+ 
254+ node.exec_condition = af::ExecuteCondition::kNoCache;
255+ EXPECT_FALSE(manager.TryBuildCacheGuardInfo(node, info));
256+ node.exec_condition = af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis;
257+ EXPECT_FALSE(manager.TryBuildCacheGuardInfo(node, info));
258+ tuning_space->sub_axes[0]->is_bind_multi_core = false;
259+ EXPECT_FALSE(manager.TryBuildCacheGuardInfo(node, info));
260+}
261+ 
262+TEST(CacheGuardInfoTest, RejectsGuardWhenBlockInnerIsNotLoopLeaf) {
263+ auto tuning_space = MakeGuardTuningSpace(Symbol("4"), Symbol("2"), Symbol("2"));
264+ auto trailing_axis = std::make_unique<SubAxis>();
265+ trailing_axis->id = 5;
266+ trailing_axis->name = "trailing_inner";
267+ trailing_axis->axis_type = AxisPosition::INNER;
268+ trailing_axis->repeat = Symbol("2");
269+ tuning_space->sub_axes.emplace_back(std::move(trailing_axis));
270+ auto &node = tuning_space->node_infos.front();
271+ node.loop_axes.emplace_back(tuning_space->sub_axes.back().get());
272+ ExeTimePassManager manager(tuning_space);
273+ CacheGuardInfo info;
274+ EXPECT_FALSE(manager.TryBuildCacheGuardInfo(node, info));
275+}
276+ 
277+TEST(CacheGuardInfoTest, NddmaGuardAllowsTrailingVectorAxis) {
278+ auto tuning_space = MakeGuardTuningSpace(Symbol("4"), Symbol("2"), Symbol("2"));
279+ auto trailing_axis = std::make_unique<SubAxis>();
280+ trailing_axis->id = 5;
281+ trailing_axis->name = "trailing_vector";
282+ trailing_axis->axis_type = AxisPosition::INNER;
283+ trailing_axis->repeat = Symbol("2");
284+ tuning_space->sub_axes.emplace_back(std::move(trailing_axis));
285+ auto &node = tuning_space->node_infos.front();
286+ node.node_type = "Nddma";
287+ node.loop_axes.emplace_back(tuning_space->sub_axes.back().get());
288+ ExeTimePassManager manager(tuning_space);
289+ CacheGuardInfo info;
290+ EXPECT_TRUE(manager.TryBuildCacheGuardInfo(node, info));
291+ EXPECT_EQ(info.kind, CacheGuardKind::kFusedBroadcast);
292+}
293+ 
19class TestExeTimePass : public ::testing::Test {294class TestExeTimePass : public ::testing::Test {
20 public:295 public:
21 static void TearDownTestCase() {296 static void TearDownTestCase() {
@@ -28,6 +303,58 @@ class TestExeTimePass : public ::testing::Test {
28 void TearDown() override {}303 void TearDown() override {}
29};304};
30 305 
306+TEST_F(TestExeTimePass, FusedGuardUsesHitCount) {
307+ auto tuning_space = MakeGuardTuningSpace(Symbol("4"), Symbol("2"), Symbol("2"));
308+ const auto &node = tuning_space->node_infos.front();
309+ ExeTimePassManager exe_mgr(tuning_space);
310+ auto exe = exe_mgr.UpdateNodeExeTime(node, Symbol("19"));
311+ EXPECT_EQ(exe.GetTernaryOpStr(), "1");
312+}
313+ 
314+TEST_F(TestExeTimePass, SymbolicCanonicalBlockSplitUsesHitCount) {
315+ const auto period = Symbol("period");
316+ const auto inner = Symbol("inner");
317+ auto tuning_space = MakeGuardTuningSpace(period, inner, af::sym::Ceiling(af::sym::Div(period, inner)));
318+ const auto &node = tuning_space->node_infos.front();
319+ ExeTimePassManager exe_mgr(tuning_space);
320+ const auto exe = exe_mgr.UpdateNodeExeTime(node, Symbol("19"));
321+ EXPECT_NE(exe.GetTernaryOpStr(), "19");
322+}
323+ 
324+TEST_F(TestExeTimePass, OriginGuardUsesSingleHit) {
325+ auto tuning_space = MakeGuardTuningSpace(Symbol("4"), Symbol("2"), Symbol("2"));
326+ auto &node = tuning_space->node_infos.front();
327+ node.exec_condition = af::ExecuteCondition::kCacheBlockSplitOriginBroadcastAxis;
328+ ExeTimePassManager exe_mgr(tuning_space);
329+ auto exe = exe_mgr.UpdateNodeExeTime(node, Symbol("19"));
330+ EXPECT_EQ(exe.GetTernaryOpStr(), "1");
331+}
332+ 
333+TEST_F(TestExeTimePass, TailBlockUsesSingleGuardHit) {
334+ auto tuning_space = MakeGuardTuningSpace(Symbol("1024"), Symbol("19"), Symbol("54"));
335+ const auto &node = tuning_space->node_infos.front();
336+ ExeTimePassManager exe_mgr(tuning_space);
337+ auto exe = exe_mgr.UpdateNodeExeTime(node, Symbol("19"));
338+ EXPECT_EQ(exe.GetTernaryOpStr(), "1");
339+}
340+ 
341+TEST_F(TestExeTimePass, NonUniformGuardFallsBackToLegacyRepeat) {
342+ auto tuning_space = MakeGuardTuningSpace(Symbol("5"), Symbol("6"), Symbol("54"));
343+ const auto &node = tuning_space->node_infos.front();
344+ ExeTimePassManager exe_mgr(tuning_space);
345+ auto exe = exe_mgr.UpdateNodeExeTime(node, Symbol("19"));
346+ EXPECT_EQ(exe.GetTernaryOpStr(), "19");
347+}
348+ 
349+TEST_F(TestExeTimePass, MissingMetadataFallsBackToLegacyRepeat) {
350+ auto tuning_space = MakeGuardTuningSpace(Symbol("4"), Symbol("2"), Symbol("54"));
351+ auto &node = tuning_space->node_infos.front();
352+ node.exec_condition = af::ExecuteCondition::kConditionInvalid;
353+ ExeTimePassManager exe_mgr(tuning_space);
354+ auto exe = exe_mgr.UpdateNodeExeTime(node, Symbol("19"));
355+ EXPECT_EQ(exe.GetTernaryOpStr(), "19");
356+}
357+ 
31void GetExeTime(const TuningSpacePtr &tuning_space, TernaryOp &exe_cast0, TernaryOp &exe_store) {358void GetExeTime(const TuningSpacePtr &tuning_space, TernaryOp &exe_cast0, TernaryOp &exe_store) {
32 ExeTimePassManager exe_mgr(tuning_space);359 ExeTimePassManager exe_mgr(tuning_space);
33 Expr exe_time;360 Expr exe_time;
@@ -267,17 +267,29 @@ TEST_F(TestAscendGraphParser, test_gather_graph_parse) {
267TEST_F(TestAscendGraphParser, test_reduce_graph_parse) {267TEST_F(TestAscendGraphParser, test_reduce_graph_parse) {
268 af::AscGraph graph1("reduce_graph");268 af::AscGraph graph1("reduce_graph");
269 ASSERT_EQ(af::ascir::cg::BuildReduceAscendGraphND(graph1), af::SUCCESS);269 ASSERT_EQ(af::ascir::cg::BuildReduceAscendGraphND(graph1), af::SUCCESS);
270+ auto load1_node = graph1.FindNode("load1");
271+ ASSERT_NE(load1_node, nullptr);
272+ load1_node->GetOpDesc()->SetType("Nddma");
273+ load1_node->attr.type = "Nddma";
274+ load1_node->attr.sched.exec_condition = af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis;
270 att::TuningSpacePtr tuning_space = std::make_shared<att::TuningSpace>();275 att::TuningSpacePtr tuning_space = std::make_shared<att::TuningSpace>();
271 EXPECT_NE(tuning_space, nullptr);276 EXPECT_NE(tuning_space, nullptr);
272 att::AscendGraphParser ascend_graph_parser(tuning_space);277 att::AscendGraphParser ascend_graph_parser(tuning_space);
273 EXPECT_EQ(ascend_graph_parser.GraphParser(graph1), af::SUCCESS);278 EXPECT_EQ(ascend_graph_parser.GraphParser(graph1), af::SUCCESS);
274 int32_t cache_count = 0;279 int32_t cache_count = 0;
280+ int32_t load_cache_count = 0;
275 for (const auto &node_info : ascend_graph_parser.tuning_space_->node_infos) {281 for (const auto &node_info : ascend_graph_parser.tuning_space_->node_infos) {
276 if (node_info.exec_condition == af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis) {282 if (node_info.exec_condition == af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis) {
277 cache_count++;283 cache_count++;
284+ if (node_info.name == "load1") {
285+ load_cache_count++;
286+ }
278 }287 }
279 }288 }
280- EXPECT_EQ(cache_count, 0);289+ // Reduce nodes remain conservatively uncached, while codegen-compatible
290+ // Load/Nddma/Broadcast nodes retain their explicit cache condition.
291+ EXPECT_EQ(cache_count, 1);
292+ EXPECT_EQ(load_cache_count, 1);
281}293}
282 294 
283extern AxisPosition ConvertAxisType(const af::Axis::Type &type);295extern AxisPosition ConvertAxisType(const af::Axis::Type &type);
@@ -42,6 +42,7 @@ const std::string op_name = "OpTest";
42 42 
43namespace att {43namespace att {
44namespace {44namespace {
45+ 
45size_t CountSubstr(const std::string &text, const std::string &pattern) {46size_t CountSubstr(const std::string &text, const std::string &pattern) {
46 size_t count = 0U;47 size_t count = 0U;
47 size_t pos = text.find(pattern);48 size_t pos = text.find(pattern);
@@ -100,6 +101,229 @@ class MockTilingCodeGenerator : public TilingCodeGenerator {
100 101 
101class GeneratorUT : public testing::Test {};102class GeneratorUT : public testing::Test {};
102 103 
104+TEST(GeneratorUT, SerializedEdgeDiscoveryIsDeterministicAndDeduplicated) {
105+ TilingCodeGenConfig config;
106+ TilingModelInfo model_infos{CreateModelInfo()};
107+ ScoreFuncs score_funcs;
108+ MockHighPerfTilingCodeGenImpl gen_impl("test", config, model_infos, score_funcs, false);
109+ const std::map<size_t, std::pair<std::string, std::string>> graph_info = {
110+ {0U, {"ScheduleResult0", "group0"}}, {1U, {"ScheduleResult1", "group1"}}, {2U, {"ScheduleResult2", "group2"}}};
111+ std::map<size_t, std::map<size_t, std::map<std::string, af::Expression>>> var_relation;
112+ var_relation[1U][0U]["dep"] = CreateExpr(1);
113+ var_relation[1U][0U]["duplicate"] = CreateExpr(2);
114+ var_relation[1U][1U]["self"] = CreateExpr(3);
115+ var_relation[1U][99U]["unknown"] = CreateExpr(4);
116+ var_relation[0U][1U]["reverse"] = CreateExpr(5);
117+ 
118+ EXPECT_EQ(gen_impl.GetSerializedGroupEdges(graph_info, var_relation, false),
119+ (std::vector<std::pair<size_t, size_t>>{{0U, 1U}, {1U, 0U}}));
120+ EXPECT_TRUE(gen_impl.GetSerializedGroupEdges(graph_info, var_relation, true).empty());
121+}
122+ 
123+TEST(GeneratorUT, SerializedEdgeDiscoveryIgnoresEmptyInnerRelation) {
124+ TilingCodeGenConfig config;
125+ TilingModelInfo model_infos{CreateModelInfo()};
126+ ScoreFuncs score_funcs;
127+ MockHighPerfTilingCodeGenImpl gen_impl("test", config, model_infos, score_funcs, false);
128+ const std::map<size_t, std::pair<std::string, std::string>> graph_info = {{0U, {"ScheduleResult0", "group0"}},
129+ {1U, {"ScheduleResult1", "group1"}}};
130+ std::map<size_t, std::map<size_t, std::map<std::string, af::Expression>>> var_relation;
131+ var_relation[1U][0U];
132+ 
133+ EXPECT_TRUE(gen_impl.GetSerializedGroupEdges(graph_info, var_relation, false).empty());
134+ 
135+ var_relation[1U][0U]["dep"] = CreateExpr(1);
136+ EXPECT_EQ(gen_impl.GetSerializedGroupEdges(graph_info, var_relation, false),
137+ (std::vector<std::pair<size_t, size_t>>{{0U, 1U}}));
138+}
139+ 
140+TEST(GeneratorUT, SerializedEdgePenaltyEmitsCappedExpressionAndModelLog) {
141+ TilingCodeGenConfig config;
142+ TilingModelInfo model_infos{CreateModelInfo()};
143+ ScoreFuncs score_funcs;
144+ MockHighPerfTilingCodeGenImpl gen_impl("test", config, model_infos, score_funcs, false);
145+ const std::string source = gen_impl.GenSerializedEdgePenalty({"g0_perf", "g1_perf"}, {{0U, 1U}}, " ");
146+ EXPECT_NE(source.find("serialized_edge_base"), std::string::npos);
147+ EXPECT_NE(source.find("min(8000.0"), std::string::npos);
148+ EXPECT_NE(source.find("0.10"), std::string::npos);
149+ EXPECT_NE(source.find("max(g0_perf, g1_perf)"), std::string::npos);
150+ EXPECT_NE(source.find("serialized_edge_base += std::max(g0_perf, g1_perf)"), std::string::npos);
151+ EXPECT_NE(source.find("std::min(8000.000000, 0.100000 * serialized_edge_base)"), std::string::npos);
152+ EXPECT_NE(source.find("serialized_edge_model=serialized_edge_v1"), std::string::npos);
153+ EXPECT_TRUE(gen_impl.GenSerializedEdgePenalty({"g0_perf"}, {{0U, 1U}}, " ").empty());
154+}
155+ 
156+namespace {
157+struct ScheduleResultSourceCase {
158+ TilingModelInfo model_infos;
159+ std::map<size_t, std::pair<std::string, std::string>> graph_info;
160+ std::map<std::string, std::set<std::string>> hardware_map;
161+};
162+ 
163+ScheduleResultSourceCase MakeTwoGroupScheduleResultSourceCase(const bool enable_group_parallel) {
164+ ScheduleResultSourceCase test_case;
165+ auto group0 = CreateModelInfo();
166+ group0.schedule_group_ident = {0U, 0U, 0U};
167+ group0.enable_group_parallel = enable_group_parallel;
168+ auto group1 = CreateModelInfo();
169+ group1.schedule_group_ident = {0U, 0U, 1U};
170+ group1.enable_group_parallel = enable_group_parallel;
171+ test_case.model_infos = {group0, group1};
172+ test_case.graph_info = {{0U, {"ScheduleResult0", "group0"}}, {1U, {"ScheduleResult1", "group1"}}};
173+ test_case.hardware_map = {{"group0", {"block_dim"}}, {"group1", {"block_dim"}}};
174+ return test_case;
175+}
176+} // namespace
177+ 
178+TEST(GeneratorUT, GenUpdatePerfAddsSerializedEdgeToNonParallelObjective) {
179+ const auto test_case = MakeTwoGroupScheduleResultSourceCase(false);
180+ ScoreFuncs score_funcs;
181+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
182+ gen_impl.var_relations_[0U][0U][1U][0U]["dep"] = CreateExpr(1);
183+ 
184+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
185+ const std::string source = gen_impl.tiling_func_.GetOutputStr();
186+ EXPECT_NE(source.find("group_sum"), std::string::npos);
187+ EXPECT_NE(source.find("serialized_edge_penalty"), std::string::npos);
188+ EXPECT_NE(source.find("objective=group_sum"), std::string::npos);
189+ EXPECT_NE(source.find("The value of graph0_result0 is %lf objective(group_sum(group0 + group1)=%lf + edge_model("),
190+ std::string::npos);
191+ EXPECT_NE(source.find("edge_model(serialized_edge_v1"), std::string::npos);
192+ EXPECT_NE(source.find("base=max(group0, group1)=%lf, penalty=min(8000.000000, 0.100000*base)"), std::string::npos);
193+}
194+ 
195+TEST(GeneratorUT, GenUpdatePerfSingleGroupHasNoSerializedEdgePenalty) {
196+ auto test_case = MakeTwoGroupScheduleResultSourceCase(false);
197+ test_case.graph_info.erase(1U);
198+ test_case.hardware_map.erase("group1");
199+ test_case.model_infos.resize(1U);
200+ ScoreFuncs score_funcs;
201+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
202+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
203+ const std::string source = gen_impl.tiling_func_.GetOutputStr();
204+ EXPECT_EQ(source.find("serialized_edge_penalty"), std::string::npos);
205+ EXPECT_NE(source.find("edge_model(serialized_edge_v1, base=0=%lf, penalty=min(8000.000000, 0.100000*base)=%lf)=%lf"),
206+ std::string::npos);
207+}
208+ 
209+TEST(GeneratorUT, GenUpdatePerfParallelHasNoSerializedEdgePenalty) {
210+ const auto test_case = MakeTwoGroupScheduleResultSourceCase(true);
211+ ScoreFuncs score_funcs;
212+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
213+ gen_impl.var_relations_[0U][0U][1U][0U]["dep"] = CreateExpr(1);
214+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
215+ EXPECT_EQ(gen_impl.tiling_func_.GetOutputStr().find("serialized_edge_penalty"), std::string::npos);
216+}
217+ 
218+TEST(GeneratorUT, GenUpdatePerfEmptyRelationKeepsGroupSumCode) {
219+ const auto test_case = MakeTwoGroupScheduleResultSourceCase(false);
220+ ScoreFuncs score_funcs;
221+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
222+ gen_impl.var_relations_[0U][0U][1U][0U];
223+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
224+ const std::string source = gen_impl.tiling_func_.GetOutputStr();
225+ EXPECT_NE(source.find("cur_perf = ScheduleResult0::GetPerf(group0_tiling_data);"), std::string::npos);
226+ EXPECT_NE(source.find("cur_perf += ScheduleResult1::GetPerf(group1_tiling_data);"), std::string::npos);
227+ EXPECT_EQ(source.find("serialized_edge_penalty"), std::string::npos);
228+}
229+ 
230+TEST(GeneratorUT, GenUpdatePerfMapsNonContiguousGroupIdsToPerfPositions) {
231+ auto test_case = MakeTwoGroupScheduleResultSourceCase(false);
232+ test_case.graph_info = {{2U, {"ScheduleResult0", "group0"}}, {5U, {"ScheduleResult1", "group1"}}};
233+ test_case.model_infos[0].schedule_group_ident.group_id = 2U;
234+ test_case.model_infos[1].schedule_group_ident.group_id = 5U;
235+ ScoreFuncs score_funcs;
236+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
237+ gen_impl.var_relations_[0U][0U][5U][2U]["dep"] = CreateExpr(1);
238+ 
239+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
240+ const std::string source = gen_impl.tiling_func_.GetOutputStr();
241+ EXPECT_NE(source.find("serialized_edge_base += std::max(ScheduleResult0::GetPerf(group0_tiling_data), "),
242+ std::string::npos);
243+ EXPECT_NE(source.find("ScheduleResult1::GetPerf(group1_tiling_data));"), std::string::npos);
244+}
245+ 
246+TEST(GeneratorUT, GenUpdatePerfAddsSerializedEdgeFromSharedWorkspace) {
247+ auto test_case = MakeTwoGroupScheduleResultSourceCase(false);
248+ test_case.model_infos[0].workspace_size_map.emplace(5, CreateExpr(1024));
249+ test_case.model_infos[1].workspace_size_map.emplace(5, CreateExpr(1024));
250+ ScoreFuncs score_funcs;
251+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
252+ 
253+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
254+ const std::string source = gen_impl.tiling_func_.GetOutputStr();
255+ EXPECT_NE(source.find("serialized_edge_penalty"), std::string::npos);
256+ EXPECT_NE(source.find("objective=group_sum"), std::string::npos);
257+ EXPECT_NE(source.find("serialized_edge_base += std::max(ScheduleResult0::GetPerf(group0_tiling_data), "),
258+ std::string::npos);
259+}
260+ 
261+TEST(GeneratorUT, GenUpdatePerfDistinctWorkspaceHasNoSerializedEdgePenalty) {
262+ auto test_case = MakeTwoGroupScheduleResultSourceCase(false);
263+ test_case.model_infos[0].workspace_size_map.emplace(5, CreateExpr(1024));
264+ test_case.model_infos[1].workspace_size_map.emplace(7, CreateExpr(2048));
265+ ScoreFuncs score_funcs;
266+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
267+ 
268+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
269+ const std::string source = gen_impl.tiling_func_.GetOutputStr();
270+ EXPECT_NE(source.find("cur_perf = ScheduleResult0::GetPerf(group0_tiling_data);"), std::string::npos);
271+ EXPECT_NE(source.find("cur_perf += ScheduleResult1::GetPerf(group1_tiling_data);"), std::string::npos);
272+ EXPECT_EQ(source.find("serialized_edge_penalty"), std::string::npos);
273+}
274+ 
275+TEST(GeneratorUT, GenUpdatePerfParallelSharedWorkspaceHasNoSerializedEdgePenalty) {
276+ auto test_case = MakeTwoGroupScheduleResultSourceCase(true);
277+ test_case.model_infos[0].workspace_size_map.emplace(5, CreateExpr(1024));
278+ test_case.model_infos[1].workspace_size_map.emplace(5, CreateExpr(1024));
279+ ScoreFuncs score_funcs;
280+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
281+ 
282+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
283+ EXPECT_EQ(gen_impl.tiling_func_.GetOutputStr().find("serialized_edge_penalty"), std::string::npos);
284+}
285+ 
286+TEST(GeneratorUT, GenUpdatePerfWorkspaceEdgeDeduplicatedWithVarRelationEdge) {
287+ auto test_case = MakeTwoGroupScheduleResultSourceCase(false);
288+ test_case.model_infos[0].workspace_size_map.emplace(5, CreateExpr(1024));
289+ test_case.model_infos[1].workspace_size_map.emplace(5, CreateExpr(1024));
290+ ScoreFuncs score_funcs;
291+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
292+ gen_impl.var_relations_[0U][0U][1U][0U]["dep"] = CreateExpr(1);
293+ 
294+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
295+ const std::string source = gen_impl.tiling_func_.GetOutputStr();
296+ EXPECT_EQ(CountSubstr(source, "serialized_edge_base += std::max("), 1U);
297+}
298+ 
299+TEST(GeneratorUT, GenUpdatePerfInvalidEdgeDoesNotEmitUndefinedPenaltyLog) {
300+ auto model_info = CreateModelInfo();
301+ model_info.schedule_group_ident = {0U, 0U, 0U};
302+ ScoreFuncs score_funcs;
303+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), {model_info}, score_funcs, false);
304+ 
305+ ASSERT_EQ(gen_impl.GenUpdatePerf(0U, 0U, {"g0_perf"}, {{0U, 1U}}, {"g0_block"}, {}), af::SUCCESS);
306+ const std::string source = gen_impl.tiling_func_.GetOutputStr();
307+ EXPECT_EQ(source.find("serialized_edge_penalty"), std::string::npos);
308+ EXPECT_EQ(source.find("objective=group_sum"), std::string::npos);
309+}
310+ 
311+TEST(GeneratorUT, GenUpdatePerfMissingHardwareGroupSkipsSerializedEdge) {
312+ auto test_case = MakeTwoGroupScheduleResultSourceCase(false);
313+ test_case.graph_info = {{2U, {"ScheduleResult0", "group0"}}, {5U, {"ScheduleResult1", "group1"}}};
314+ test_case.model_infos[0].schedule_group_ident.group_id = 2U;
315+ test_case.model_infos[1].schedule_group_ident.group_id = 5U;
316+ test_case.hardware_map.erase("group1");
317+ ScoreFuncs score_funcs;
318+ MockHighPerfTilingCodeGenImpl gen_impl("test", TilingCodeGenConfig(), test_case.model_infos, score_funcs, false);
319+ gen_impl.var_relations_[0U][0U][5U][2U]["dep"] = CreateExpr(1);
320+ 
321+ ASSERT_EQ(gen_impl.GenSingleGroupScheduleResult(0U, 0U, test_case.graph_info, test_case.hardware_map), af::SUCCESS);
322+ const std::string source = gen_impl.tiling_func_.GetOutputStr();
323+ EXPECT_EQ(source.find("serialized_edge_penalty"), std::string::npos);
324+ EXPECT_EQ(source.find("objective=group_sum"), std::string::npos);
325+}
326+ 
103TEST(GeneratorUT, SourceDependenciesMergeAndRenderIncludes) {327TEST(GeneratorUT, SourceDependenciesMergeAndRenderIncludes) {
104 autofuse::GeneratedCode target;328 autofuse::GeneratedCode target;
105 autofuse::RequireSystemHeader(target.dependencies, "vector");329 autofuse::RequireSystemHeader(target.dependencies, "vector");
@@ -6154,6 +6154,48 @@ TEST(CodegenKernel, CacheGuardIsOnlyClosedWhenAConditionIsGenerated) {
6154 EXPECT_EQ(std::count(result.begin(), result.end(), '{'), std::count(result.begin(), result.end(), '}'));6154 EXPECT_EQ(std::count(result.begin(), result.end(), '{'), std::count(result.begin(), result.end(), '}'));
6155}6155}
6156 6156 
6157+TEST(CodegenKernel, CacheGuardPredicateUsesFirstMiddleAndLastTileForNineteen) {
6158+ af::SizeVar outer_size(af::Symbol("outer_size"));
6159+ af::SizeVar block_inner_extent(af::Symbol("19"));
6160+ af::Axis outer{.id = 0, .name = "outer", .size = outer_size.expr};
6161+ af::Axis guarded{.id = 1, .name = "guarded", .size = af::Symbol("19")};
6162+ af::Axis block_inner{
6163+ .id = 2, .name = "block_inner", .type = af::Axis::Type::kAxisTypeBlockInner, .size = block_inner_extent.expr};
6164+ 
6165+ codegen::Tiler tiler;
6166+ tiler.AddSizeVar(outer_size);
6167+ tiler.AddSizeVar(block_inner_extent);
6168+ tiler.AddAxis(outer);
6169+ tiler.AddAxis(guarded);
6170+ tiler.AddAxis(block_inner);
6171+ tiler.axis_map.at(outer.id).is_split_b = true;
6172+ tiler.axis_map.at(guarded.id).is_split_b = true;
6173+ 
6174+ codegen::Loop loop(guarded.id);
6175+ auto fused_call = new MockApiCall("fused_call");
6176+ fused_call->enable_cache = true;
6177+ fused_call->exec_condition = af::ExecuteCondition::kCacheBlockSplitFusedBroadcastAxis;
6178+ fused_call->unit = af::ComputeUnit::kUnitVector;
6179+ auto origin_call = new MockApiCall("origin_call");
6180+ origin_call->enable_cache = true;
6181+ origin_call->exec_condition = af::ExecuteCondition::kCacheBlockSplitOriginBroadcastAxis;
6182+ origin_call->unit = af::ComputeUnit::kUnitVector;
6183+ loop.AddCall(fused_call);
6184+ loop.AddCall(origin_call);
6185+ 
6186+ codegen::TPipe tpipe("tpipe", tiler);
6187+ std::string result;
6188+ ASSERT_EQ(loop.Generate(tiler, tpipe, result), af::SUCCESS);
6189+ 
6190+ // Tile 0 is covered by the explicit axis < 1 term; tiles 9 and 18 exercise the
6191+ // middle and tail positions of the period-19 predicate respectively.
6192+ EXPECT_NE(result.find("bool enable_cache_fused_brc_axis = (guarded < 1) || ((block_dim * block_inner_axis_size + "
6193+ "guarded) % guarded_loop_size < 1);"),
6194+ std::string::npos);
6195+ EXPECT_NE(result.find("bool enable_cache_origin_brc_axis = (guarded < 1);"), std::string::npos);
6196+ EXPECT_EQ(std::count(result.begin(), result.end(), '{'), std::count(result.begin(), result.end(), '}'));
6197+}
6198+ 
6157TEST(CodegenKernel, ReduceDoubleTileUsesReduceSpecificCacheCondition) {6199TEST(CodegenKernel, ReduceDoubleTileUsesReduceSpecificCacheCondition) {
6158 af::SizeVar outer_size(af::Symbol("outer_size"));6200 af::SizeVar outer_size(af::Symbol("outer_size"));
6159 af::SizeVar tile0_size(af::Symbol("tile0_size"));6201 af::SizeVar tile0_size(af::Symbol("tile0_size"));