From 8efa723fd7b2ba3cb5699118cdf202b9602967e7 Mon Sep 17 00:00:00 2001 From: Melika Norouzbeygi Date: Mon, 27 Jul 2026 16:16:55 +0000 Subject: [PATCH 1/5] refactor(tile-fusion): add reusable whole-group cost evaluation --- .../Transforms/TileFusion/PTOFusionPlan.cpp | 87 +++++++++++++++---- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp b/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp index cacd31565c..783cae0af2 100644 --- a/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp +++ b/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp @@ -245,6 +245,31 @@ countConnectionsToGroup(const pto::FusionBlockAnalysis &blockAnalysis, return connections; } +static unsigned +countInternalEdges(const pto::FusionBlockAnalysis &blockAnalysis, + ArrayRef group) { + DenseSet memberIds; + for (const pto::FusionComputeNode *member : group) + memberIds.insert(member->id); + + return llvm::count_if(blockAnalysis.edges, + [&](const pto::FusionDFGEdge &edge) { + return memberIds.contains(edge.producerNode) && + memberIds.contains(edge.consumerNode); + }); +} + +static unsigned +countInternalConnections(const pto::FusionBlockAnalysis &blockAnalysis, + ArrayRef group) { + unsigned connections = 0; + for (auto [index, lhs] : llvm::enumerate(group)) + for (const pto::FusionComputeNode *rhs : group.drop_front(index + 1)) + if (nodesHaveDirectDataFlowConnection(blockAnalysis, *lhs, *rhs)) + ++connections; + return connections; +} + static GroupFootprint computeGroupFootprint(ArrayRef members) { DenseSet producedTiles; @@ -272,6 +297,22 @@ computeGroupFootprint(ArrayRef members) { return footprint; } +static PlanningCost +computePlanningCost(ArrayRef members, + unsigned connectionCount, int64_t loopMergeBenefit, + unsigned liveTileLimit, unsigned vfParameterLimit) { + GroupFootprint footprint = computeGroupFootprint(members); + + PlanningCost cost; + cost.dependencyBenefit = 4 * static_cast(connectionCount); + cost.loopMergeBenefit = loopMergeBenefit; + cost.liveTilePenalty = std::max( + 0, static_cast(footprint.liveTileCount) - liveTileLimit); + cost.vfParameterPenalty = std::max( + 0, static_cast(footprint.vfParameterCount) - vfParameterLimit); + return cost; +} + class CostModel { public: virtual ~CostModel() = default; @@ -280,6 +321,10 @@ class CostModel { const pto::FusionComputeNode &candidate) const = 0; + virtual PlanningCost + evaluateGroup(const PlanningContext &ctx, + ArrayRef members) const = 0; + virtual PlanningDecision evaluateAppend(const PlanningContext &ctx, ArrayRef currentGroup, @@ -304,6 +349,14 @@ class ConservativeGreedyCostModel final : public CostModel { return decision; } + PlanningCost evaluateGroup( + const PlanningContext &ctx, + ArrayRef members) const override { + return computePlanningCost( + members, countInternalEdges(ctx.blockAnalysis, members), + /*loopMergeBenefit=*/2, /*liveTileLimit=*/4, /*vfParameterLimit=*/6); + } + PlanningDecision evaluateAppend(const PlanningContext &ctx, ArrayRef currentGroup, @@ -331,16 +384,10 @@ class ConservativeGreedyCostModel final : public CostModel { SmallVector proposedGroup( currentGroup.begin(), currentGroup.end()); proposedGroup.push_back(&candidate); - GroupFootprint footprint = computeGroupFootprint(proposedGroup); - - decision.cost.dependencyBenefit = - 4 * static_cast( - countEdgesFromGroup(ctx.blockAnalysis, currentGroup, candidate)); - decision.cost.loopMergeBenefit = 2; - decision.cost.liveTilePenalty = - std::max(0, static_cast(footprint.liveTileCount) - 4); - decision.cost.vfParameterPenalty = std::max( - 0, static_cast(footprint.vfParameterCount) - 6); + decision.cost = computePlanningCost( + proposedGroup, + countEdgesFromGroup(ctx.blockAnalysis, currentGroup, candidate), + /*loopMergeBenefit=*/2, /*liveTileLimit=*/4, /*vfParameterLimit=*/6); decision.accept = decision.cost.total() > 0; return decision; } @@ -364,6 +411,15 @@ class ConservativeDAGGreedyCostModel final : public CostModel { return decision; } + PlanningCost evaluateGroup( + const PlanningContext &ctx, + ArrayRef members) const override { + return computePlanningCost( + members, countInternalConnections(ctx.blockAnalysis, members), + /*loopMergeBenefit=*/4, /*liveTileLimit=*/10, + /*vfParameterLimit=*/12); + } + PlanningDecision evaluateAppend(const PlanningContext &ctx, ArrayRef currentGroup, @@ -393,14 +449,9 @@ class ConservativeDAGGreedyCostModel final : public CostModel { SmallVector proposedGroup( currentGroup.begin(), currentGroup.end()); proposedGroup.push_back(&candidate); - GroupFootprint footprint = computeGroupFootprint(proposedGroup); - - decision.cost.dependencyBenefit = 4 * static_cast(connectionCount); - decision.cost.loopMergeBenefit = 4; - decision.cost.liveTilePenalty = std::max( - 0, static_cast(footprint.liveTileCount) - 10); - decision.cost.vfParameterPenalty = std::max( - 0, static_cast(footprint.vfParameterCount) - 12); + decision.cost = computePlanningCost( + proposedGroup, connectionCount, /*loopMergeBenefit=*/4, + /*liveTileLimit=*/10, /*vfParameterLimit=*/12); decision.accept = decision.cost.total() > 0; return decision; } From b228629fd55fb92a739d0628216a6a25a88a23e4 Mon Sep 17 00:00:00 2001 From: Melika Norouzbeygi Date: Mon, 27 Jul 2026 16:26:05 +0000 Subject: [PATCH 2/5] feat(tile-fusion): explore cost-ranked fusion group candidates --- .../Transforms/TileFusion/PTOFusionPlan.cpp | 120 ++++++++++++++---- 1 file changed, 96 insertions(+), 24 deletions(-) diff --git a/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp b/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp index 783cae0af2..1055e746e4 100644 --- a/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp +++ b/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp @@ -21,6 +21,7 @@ #include "llvm/ADT/StringSwitch.h" #include +#include namespace mlir { namespace pto { @@ -514,6 +515,56 @@ class ConservativeGreedyStrategyEngine final : public StrategyEngine { }; class ConservativeDAGGreedyStrategyEngine final : public StrategyEngine { + static constexpr unsigned kMaxCandidatesPerGroupSize = 64; + + struct GroupCandidate { + SmallVector members; + DenseSet memberIds; + PlanningCost cost; + }; + + static bool haveSameMembers(const GroupCandidate &lhs, + const GroupCandidate &rhs) { + if (lhs.members.size() != rhs.members.size()) + return false; + return llvm::equal( + lhs.members, rhs.members, + [](const pto::FusionComputeNode *lhsNode, + const pto::FusionComputeNode *rhsNode) { + return lhsNode->id == rhsNode->id; + }); + } + + static bool isBetterCandidate(const GroupCandidate &lhs, + const GroupCandidate &rhs) { + if (lhs.cost.total() != rhs.cost.total()) + return lhs.cost.total() > rhs.cost.total(); + if (lhs.members.size() != rhs.members.size()) + return lhs.members.size() > rhs.members.size(); + + for (auto [lhsNode, rhsNode] : llvm::zip(lhs.members, rhs.members)) { + if (lhsNode->blockOrder != rhsNode->blockOrder) + return lhsNode->blockOrder < rhsNode->blockOrder; + if (lhsNode->id != rhsNode->id) + return lhsNode->id < rhsNode->id; + } + return false; + } + + static void + addOrUpdateCandidate(SmallVectorImpl &candidates, + GroupCandidate candidate) { + auto existing = llvm::find_if(candidates, [&](const GroupCandidate &other) { + return haveSameMembers(candidate, other); + }); + if (existing == candidates.end()) { + candidates.push_back(std::move(candidate)); + return; + } + if (isBetterCandidate(candidate, *existing)) + *existing = std::move(candidate); + } + public: SmallVector planBlock(const PlanningContext &ctx, @@ -529,36 +580,57 @@ class ConservativeDAGGreedyStrategyEngine final : public StrategyEngine { if (!seedDecision.accept) continue; - SmallVector groupMembers; - DenseSet groupNodeIds; - groupMembers.push_back(&seed); - groupNodeIds.insert(seed.id); - - bool changed = true; - while (changed) { - changed = false; - for (const pto::FusionComputeNode &candidate : - ctx.blockAnalysis.computeNodes) { - if (assignedNodes.contains(candidate.id) || - groupNodeIds.contains(candidate.id)) - continue; - - PlanningDecision appendDecision = - costModel.evaluateAppend(ctx, groupMembers, candidate); - if (!appendDecision.accept) - continue; - - groupMembers.push_back(&candidate); - groupNodeIds.insert(candidate.id); - changed = true; + GroupCandidate seedCandidate; + seedCandidate.members.push_back(&seed); + seedCandidate.memberIds.insert(seed.id); + seedCandidate.cost = + costModel.evaluateGroup(ctx, seedCandidate.members); + + SmallVector frontier; + frontier.push_back(std::move(seedCandidate)); + std::optional bestCandidate; + + while (!frontier.empty()) { + SmallVector nextFrontier; + for (const GroupCandidate ¤t : frontier) { + for (const pto::FusionComputeNode &candidate : + ctx.blockAnalysis.computeNodes) { + if (assignedNodes.contains(candidate.id) || + current.memberIds.contains(candidate.id)) + continue; + + PlanningDecision appendDecision = + costModel.evaluateAppend(ctx, current.members, candidate); + if (!appendDecision.accept) + continue; + + GroupCandidate successor = current; + successor.members.push_back(&candidate); + successor.members = buildStableInGroupOrder(successor.members); + successor.memberIds.insert(candidate.id); + successor.cost = + costModel.evaluateGroup(ctx, successor.members); + addOrUpdateCandidate(nextFrontier, std::move(successor)); + } } + + llvm::stable_sort(nextFrontier, isBetterCandidate); + if (nextFrontier.size() > kMaxCandidatesPerGroupSize) + nextFrontier.resize(kMaxCandidatesPerGroupSize); + + for (const GroupCandidate &candidate : nextFrontier) + if (!bestCandidate || + isBetterCandidate(candidate, *bestCandidate)) + bestCandidate = candidate; + + frontier = std::move(nextFrontier); } - if (groupMembers.size() < 2) + if (!bestCandidate || bestCandidate->members.size() < 2) continue; PlannedFusionGroup group; - group.members = buildStableInGroupOrder(groupMembers); + group.members = bestCandidate->members; groups.push_back(group); for (const pto::FusionComputeNode *member : group.members) assignedNodes.insert(member->id); From 41b23f7f0ce8c25fb2f6e2f24633b6cf7b701feb Mon Sep 17 00:00:00 2001 From: Melika Norouzbeygi Date: Mon, 27 Jul 2026 17:06:16 +0000 Subject: [PATCH 3/5] test(tile-fusion): cover cost-guided fusion group search --- .../fusion_plan_cost_guided_search.pto | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 test/lit/tile_fusion/fusion_plan_cost_guided_search.pto diff --git a/test/lit/tile_fusion/fusion_plan_cost_guided_search.pto b/test/lit/tile_fusion/fusion_plan_cost_guided_search.pto new file mode 100644 index 0000000000..016778df0c --- /dev/null +++ b/test/lit/tile_fusion/fusion_plan_cost_guided_search.pto @@ -0,0 +1,115 @@ +// Copyright (c) 2026 Huawei Technologies Co., Ltd. +// This program is free software, you can redistribute it and/or modify it under the terms and conditions of +// CANN Open Software License Agreement Version 2.0 (the "License"). +// Please refer to the License for details. You may not use this file except in compliance with the License. +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +// See LICENSE in the root of the software repository for the full text of the License. + +// Guards cost-guided FusionPlan search across competing block-local DFG paths. +// +// RUN: ptoas --pto-arch=a5 --pto-level=level2 --enable-op-fusion --emit-pto-ir %s --mlir-print-ir-after=pto-fusion-plan -o /dev/null 2>&1 | FileCheck %s + +module { + // The six-op prefix has live-tile/VF-parameter footprint 12. The first + // branch is immediately profitable, but raises the footprint enough to + // reject both later appends: + // + // cost(prefix + first) = 24 + // cost(prefix + second + join) = 28 + // + // Candidate-group search must therefore choose the later, higher-cost path. + // The independent two-op component must still form the next fusion group. + func.func @prefer_higher_group_cost( + %arg0: !pto.tile_buf, + %arg1: !pto.tile_buf, + %arg2: !pto.tile_buf, + %arg3: !pto.tile_buf, + %arg4: !pto.tile_buf, + %arg5: !pto.tile_buf, + %arg6: !pto.tile_buf, + %arg7: !pto.tile_buf, + %arg8: !pto.tile_buf) { + %tmp0 = pto.alloc_tile : !pto.tile_buf + %tmp1 = pto.alloc_tile : !pto.tile_buf + %tmp2 = pto.alloc_tile : !pto.tile_buf + %tmp3 = pto.alloc_tile : !pto.tile_buf + %tmp4 = pto.alloc_tile : !pto.tile_buf + %tmp5 = pto.alloc_tile : !pto.tile_buf + %first = pto.alloc_tile : !pto.tile_buf + %second = pto.alloc_tile : !pto.tile_buf + %join = pto.alloc_tile : !pto.tile_buf + %independent0 = pto.alloc_tile : !pto.tile_buf + %independent1 = pto.alloc_tile : !pto.tile_buf + + pto.tadd ins(%arg0, %arg1 : !pto.tile_buf, !pto.tile_buf) outs(%tmp0 : !pto.tile_buf) + pto.tadd ins(%tmp0, %arg2 : !pto.tile_buf, !pto.tile_buf) outs(%tmp1 : !pto.tile_buf) + pto.tadd ins(%tmp1, %arg3 : !pto.tile_buf, !pto.tile_buf) outs(%tmp2 : !pto.tile_buf) + pto.tadd ins(%tmp2, %arg4 : !pto.tile_buf, !pto.tile_buf) outs(%tmp3 : !pto.tile_buf) + pto.tadd ins(%tmp3, %arg5 : !pto.tile_buf, !pto.tile_buf) outs(%tmp4 : !pto.tile_buf) + pto.texp ins(%tmp4 : !pto.tile_buf) outs(%tmp5 : !pto.tile_buf) + pto.texp ins(%tmp5 : !pto.tile_buf) outs(%first : !pto.tile_buf) + pto.tadd ins(%tmp5, %arg6 : !pto.tile_buf, !pto.tile_buf) outs(%second : !pto.tile_buf) + pto.tadd ins(%second, %tmp0 : !pto.tile_buf, !pto.tile_buf) outs(%join : !pto.tile_buf) + + pto.tadd ins(%arg7, %arg8 : !pto.tile_buf, !pto.tile_buf) outs(%independent0 : !pto.tile_buf) + pto.texp ins(%independent0 : !pto.tile_buf) outs(%independent1 : !pto.tile_buf) + return + } + + // Both branches have the same whole-group cost, and accepting either one + // makes the other append unprofitable. Stable block order must break the tie. + func.func @stable_equal_cost_tie( + %arg0: !pto.tile_buf, + %arg1: !pto.tile_buf, + %arg2: !pto.tile_buf, + %arg3: !pto.tile_buf, + %arg4: !pto.tile_buf, + %arg5: !pto.tile_buf, + %arg6: !pto.tile_buf) { + %tmp0 = pto.alloc_tile : !pto.tile_buf + %tmp1 = pto.alloc_tile : !pto.tile_buf + %tmp2 = pto.alloc_tile : !pto.tile_buf + %tmp3 = pto.alloc_tile : !pto.tile_buf + %tmp4 = pto.alloc_tile : !pto.tile_buf + %tmp5 = pto.alloc_tile : !pto.tile_buf + %first = pto.alloc_tile : !pto.tile_buf + %second = pto.alloc_tile : !pto.tile_buf + + pto.tadd ins(%arg0, %arg1 : !pto.tile_buf, !pto.tile_buf) outs(%tmp0 : !pto.tile_buf) + pto.tadd ins(%tmp0, %arg2 : !pto.tile_buf, !pto.tile_buf) outs(%tmp1 : !pto.tile_buf) + pto.tadd ins(%tmp1, %arg3 : !pto.tile_buf, !pto.tile_buf) outs(%tmp2 : !pto.tile_buf) + pto.tadd ins(%tmp2, %arg4 : !pto.tile_buf, !pto.tile_buf) outs(%tmp3 : !pto.tile_buf) + pto.tadd ins(%tmp3, %arg5 : !pto.tile_buf, !pto.tile_buf) outs(%tmp4 : !pto.tile_buf) + pto.tadd ins(%tmp4, %arg6 : !pto.tile_buf, !pto.tile_buf) outs(%tmp5 : !pto.tile_buf) + pto.texp ins(%tmp5 : !pto.tile_buf) outs(%first : !pto.tile_buf) + pto.texp ins(%tmp5 : !pto.tile_buf) outs(%second : !pto.tile_buf) + return + } +} + +// CHECK: // -----// IR Dump After FusionPlan (pto-fusion-plan) //----- // +// CHECK-LABEL: func.func @prefer_higher_group_cost( +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 0 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 1 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 2 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 3 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 4 : i64} +// CHECK: pto.texp{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 5 : i64} +// CHECK: pto.texp +// CHECK-NOT: pto.fusion.group_id +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 6 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 7 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 1 : i64, pto.fusion.order = 0 : i64} +// CHECK: pto.texp{{.*}}{pto.fusion.group_id = 1 : i64, pto.fusion.order = 1 : i64} + +// CHECK-LABEL: func.func @stable_equal_cost_tie( +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 0 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 1 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 2 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 3 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 4 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 5 : i64} +// CHECK: pto.texp{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 6 : i64} +// CHECK: pto.texp +// CHECK-NOT: pto.fusion.group_id From 815dc43a49fd23c02c2998bdb7ccf0e097ac216c Mon Sep 17 00:00:00 2001 From: Melika Norouzbeygi Date: Mon, 27 Jul 2026 18:38:20 +0000 Subject: [PATCH 4/5] fix: separate lit tests --- .../fusion_plan_cost_guided_search.pto | 40 -------------- .../fusion_plan_cost_guided_tie.pto | 55 +++++++++++++++++++ 2 files changed, 55 insertions(+), 40 deletions(-) create mode 100644 test/lit/tile_fusion/fusion_plan_cost_guided_tie.pto diff --git a/test/lit/tile_fusion/fusion_plan_cost_guided_search.pto b/test/lit/tile_fusion/fusion_plan_cost_guided_search.pto index 016778df0c..8552d5cab0 100644 --- a/test/lit/tile_fusion/fusion_plan_cost_guided_search.pto +++ b/test/lit/tile_fusion/fusion_plan_cost_guided_search.pto @@ -57,35 +57,6 @@ module { return } - // Both branches have the same whole-group cost, and accepting either one - // makes the other append unprofitable. Stable block order must break the tie. - func.func @stable_equal_cost_tie( - %arg0: !pto.tile_buf, - %arg1: !pto.tile_buf, - %arg2: !pto.tile_buf, - %arg3: !pto.tile_buf, - %arg4: !pto.tile_buf, - %arg5: !pto.tile_buf, - %arg6: !pto.tile_buf) { - %tmp0 = pto.alloc_tile : !pto.tile_buf - %tmp1 = pto.alloc_tile : !pto.tile_buf - %tmp2 = pto.alloc_tile : !pto.tile_buf - %tmp3 = pto.alloc_tile : !pto.tile_buf - %tmp4 = pto.alloc_tile : !pto.tile_buf - %tmp5 = pto.alloc_tile : !pto.tile_buf - %first = pto.alloc_tile : !pto.tile_buf - %second = pto.alloc_tile : !pto.tile_buf - - pto.tadd ins(%arg0, %arg1 : !pto.tile_buf, !pto.tile_buf) outs(%tmp0 : !pto.tile_buf) - pto.tadd ins(%tmp0, %arg2 : !pto.tile_buf, !pto.tile_buf) outs(%tmp1 : !pto.tile_buf) - pto.tadd ins(%tmp1, %arg3 : !pto.tile_buf, !pto.tile_buf) outs(%tmp2 : !pto.tile_buf) - pto.tadd ins(%tmp2, %arg4 : !pto.tile_buf, !pto.tile_buf) outs(%tmp3 : !pto.tile_buf) - pto.tadd ins(%tmp3, %arg5 : !pto.tile_buf, !pto.tile_buf) outs(%tmp4 : !pto.tile_buf) - pto.tadd ins(%tmp4, %arg6 : !pto.tile_buf, !pto.tile_buf) outs(%tmp5 : !pto.tile_buf) - pto.texp ins(%tmp5 : !pto.tile_buf) outs(%first : !pto.tile_buf) - pto.texp ins(%tmp5 : !pto.tile_buf) outs(%second : !pto.tile_buf) - return - } } // CHECK: // -----// IR Dump After FusionPlan (pto-fusion-plan) //----- // @@ -102,14 +73,3 @@ module { // CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 7 : i64} // CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 1 : i64, pto.fusion.order = 0 : i64} // CHECK: pto.texp{{.*}}{pto.fusion.group_id = 1 : i64, pto.fusion.order = 1 : i64} - -// CHECK-LABEL: func.func @stable_equal_cost_tie( -// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 0 : i64} -// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 1 : i64} -// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 2 : i64} -// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 3 : i64} -// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 4 : i64} -// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 5 : i64} -// CHECK: pto.texp{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 6 : i64} -// CHECK: pto.texp -// CHECK-NOT: pto.fusion.group_id diff --git a/test/lit/tile_fusion/fusion_plan_cost_guided_tie.pto b/test/lit/tile_fusion/fusion_plan_cost_guided_tie.pto new file mode 100644 index 0000000000..52edef0852 --- /dev/null +++ b/test/lit/tile_fusion/fusion_plan_cost_guided_tie.pto @@ -0,0 +1,55 @@ +// Copyright (c) 2026 Huawei Technologies Co., Ltd. +// This program is free software, you can redistribute it and/or modify it under the terms and conditions of +// CANN Open Software License Agreement Version 2.0 (the "License"). +// Please refer to the License for details. You may not use this file except in compliance with the License. +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +// See LICENSE in the root of the software repository for the full text of the License. + +// Guards the stable tie-break for equal-cost FusionPlan candidates. +// +// RUN: ptoas --pto-arch=a5 --pto-level=level2 --enable-op-fusion --emit-pto-ir %s --mlir-print-ir-after=pto-fusion-plan -o /dev/null 2>&1 | FileCheck %s + +module { + // Both branches have the same whole-group cost, and accepting either one + // makes the other append unprofitable. Stable block order must break the tie. + func.func @stable_equal_cost_tie( + %arg0: !pto.tile_buf, + %arg1: !pto.tile_buf, + %arg2: !pto.tile_buf, + %arg3: !pto.tile_buf, + %arg4: !pto.tile_buf, + %arg5: !pto.tile_buf, + %arg6: !pto.tile_buf) { + %tmp0 = pto.alloc_tile : !pto.tile_buf + %tmp1 = pto.alloc_tile : !pto.tile_buf + %tmp2 = pto.alloc_tile : !pto.tile_buf + %tmp3 = pto.alloc_tile : !pto.tile_buf + %tmp4 = pto.alloc_tile : !pto.tile_buf + %tmp5 = pto.alloc_tile : !pto.tile_buf + %first = pto.alloc_tile : !pto.tile_buf + %second = pto.alloc_tile : !pto.tile_buf + + pto.tadd ins(%arg0, %arg1 : !pto.tile_buf, !pto.tile_buf) outs(%tmp0 : !pto.tile_buf) + pto.tadd ins(%tmp0, %arg2 : !pto.tile_buf, !pto.tile_buf) outs(%tmp1 : !pto.tile_buf) + pto.tadd ins(%tmp1, %arg3 : !pto.tile_buf, !pto.tile_buf) outs(%tmp2 : !pto.tile_buf) + pto.tadd ins(%tmp2, %arg4 : !pto.tile_buf, !pto.tile_buf) outs(%tmp3 : !pto.tile_buf) + pto.tadd ins(%tmp3, %arg5 : !pto.tile_buf, !pto.tile_buf) outs(%tmp4 : !pto.tile_buf) + pto.tadd ins(%tmp4, %arg6 : !pto.tile_buf, !pto.tile_buf) outs(%tmp5 : !pto.tile_buf) + pto.texp ins(%tmp5 : !pto.tile_buf) outs(%first : !pto.tile_buf) + pto.texp ins(%tmp5 : !pto.tile_buf) outs(%second : !pto.tile_buf) + return + } +} + +// CHECK: // -----// IR Dump After FusionPlan (pto-fusion-plan) //----- // +// CHECK-LABEL: func.func @stable_equal_cost_tie( +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 0 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 1 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 2 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 3 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 4 : i64} +// CHECK: pto.tadd{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 5 : i64} +// CHECK: pto.texp{{.*}}{pto.fusion.group_id = 0 : i64, pto.fusion.order = 6 : i64} +// CHECK: pto.texp +// CHECK-NOT: pto.fusion.group_id From b183af7c4110083ceb2ce2617bb9567344e485a4 Mon Sep 17 00:00:00 2001 From: Melika Norouzbeygi Date: Tue, 28 Jul 2026 19:53:55 +0000 Subject: [PATCH 5/5] chore: add a comment describing the bounded search --- lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp b/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp index 1055e746e4..95612ebc24 100644 --- a/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp +++ b/lib/PTO/Transforms/TileFusion/PTOFusionPlan.cpp @@ -515,6 +515,9 @@ class ConservativeGreedyStrategyEngine final : public StrategyEngine { }; class ConservativeDAGGreedyStrategyEngine final : public StrategyEngine { + // Keep a bounded, deterministically ranked frontier to prevent exponential + // compile-time growth. This selects the best group among retained candidates; + // it does not guarantee the global optimum across every connected subset. static constexpr unsigned kMaxCandidatesPerGroupSize = 64; struct GroupCandidate {