[Mlir-commits] [mlir] [mlir][linalg] Account for preserved outs in fusion bounds (PR #228467)

Ziliang Zhang llvmlistbot at llvm.org
Fri Oct 2 07:51:52 PDT 2026


https://github.com/ziliangzl created https://github.com/llvm/llvm-project/pull/228467

When checking reduction fusion, include preserved producer outs in loop-bound coverage. This allows fusion when a missing loop dimension is defined by an out operand that remains on the fused op.

>From d5d1772dbf9f50e4579a3d31359377732f3124cb Mon Sep 17 00:00:00 2001
From: Ziliang Zhang <zzl.coding at gmail.com>
Date: Fri, 2 Oct 2026 12:20:11 +0800
Subject: [PATCH] [mlir][linalg] Account for preserved outs in fusion bounds

When checking reduction fusion, include preserved producer outs in loop-bound coverage. This allows fusion when a missing loop dimension is defined by an out operand that remains on the fused op.
---
 .../Linalg/Transforms/ElementwiseOpFusion.cpp | 15 +++++-
 .../Linalg/fusion-elementwise-ops.mlir        | 54 ++++++++++++++++---
 2 files changed, 60 insertions(+), 9 deletions(-)

diff --git a/mlir/lib/Dialect/Linalg/Transforms/ElementwiseOpFusion.cpp b/mlir/lib/Dialect/Linalg/Transforms/ElementwiseOpFusion.cpp
index 8add883163fe95c..aba1ba484d2bbd5 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/ElementwiseOpFusion.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/ElementwiseOpFusion.cpp
@@ -183,7 +183,7 @@ bool mlir::linalg::areElementwiseOpsFusable(OpOperand *fusedOperand) {
   // Ensure that the fusion does not remove size information required to
   // get the loop bounds. For non-reduction generics, this is trivially the
   // case due to the output operand. For reductions, we need to check that after
-  // the fusion, each loop dimension has at least one input that defines it.
+  // the fusion, each loop dimension has at least one operand that defines it.
   if ((consumer.getNumReductionLoops())) {
     BitVector coveredDims(consumer.getNumLoops(), false);
 
@@ -208,6 +208,19 @@ bool mlir::linalg::areElementwiseOpsFusable(OpOperand *fusedOperand) {
               operand, producerResultIndexMap, consumerIndexMap);
       addToCoveredDims(newIndexingMap);
     }
+
+    // Preserved producer outputs are also part of the fused op and may provide
+    // loop bounds that are not available from the producer inputs.
+    llvm::SmallDenseSet<int> preservedProducerResults =
+        getPreservedProducerResults(producer, consumer, fusedOperand);
+    for (const auto &operand : llvm::enumerate(producer.getDpsInitsMutable())) {
+      if (!preservedProducerResults.contains(operand.index()))
+        continue;
+      AffineMap newIndexingMap =
+          getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(
+              &operand.value(), producerResultIndexMap, consumerIndexMap);
+      addToCoveredDims(newIndexingMap);
+    }
     if (!coveredDims.all())
       return false;
   }
diff --git a/mlir/test/Dialect/Linalg/fusion-elementwise-ops.mlir b/mlir/test/Dialect/Linalg/fusion-elementwise-ops.mlir
index 4f0d4bb0d8f5dc8..e0264bbb6b09aad 100644
--- a/mlir/test/Dialect/Linalg/fusion-elementwise-ops.mlir
+++ b/mlir/test/Dialect/Linalg/fusion-elementwise-ops.mlir
@@ -777,20 +777,22 @@ func.func @fuse_scalar_constant(%arg0 : tensor<?x?xf32>) -> (tensor<?x?xf32>, te
 
 // -----
 
-// Fusing the broadcast into a reduction would require to insert extra knowledge
-// about the size of the reduction dimension. As long, as this is not
-// implemented, we check that two linalg operations remain.
-// TODO: Support this case in element-wise fusion.
+// The producer output is preserved because its indexing map is needed to
+// compute the reduction dimension's bound after fusion.
 
 #map0 = affine_map<(d0, d1) -> ()>
 #map1 = affine_map<(d0, d1) -> (d0, d1)>
 #map2 = affine_map<(d0, d1) -> (d1, d0)>
 #map3 = affine_map<(d0, d1) -> (d0)>
 
-// CHECK-LABEL: @no_fusion_missing_reduction_shape
-// CHECK: linalg.generic
-// CHECK: linalg.generic
-func.func @no_fusion_missing_reduction_shape(%arg0: tensor<f32>, %arg1: index) -> tensor<?xf32> {
+// CHECK-LABEL: @fusion_reduction_shape_from_preserved_out
+// CHECK: %[[FUSED:.+]]:2 = linalg.generic
+// CHECK-SAME: ins(%{{.+}} : tensor<f32>)
+// CHECK-SAME: outs(%{{.+}}, %{{.+}} : tensor<?x?xf32>, tensor<?xf32>)
+// CHECK-NOT: linalg.generic
+// CHECK: return %[[FUSED]]#1
+func.func @fusion_reduction_shape_from_preserved_out(
+    %arg0: tensor<f32>, %arg1: index) -> tensor<?xf32> {
   %cst = arith.constant 0xFF800000 : f32
   %4 = tensor.empty(%arg1, %arg1) : tensor<?x?xf32>
   %5 = linalg.generic {
@@ -815,6 +817,42 @@ func.func @no_fusion_missing_reduction_shape(%arg0: tensor<f32>, %arg1: index) -
 
 // -----
 
+#map0 = affine_map<(d0, d1) -> (d0)>
+#map1 = affine_map<(d0, d1) -> (d0, d1)>
+
+// CHECK-LABEL: @fusion_reduction_shape_from_used_producer_out
+// CHECK-SAME: %[[ARG0:.+]]: tensor<4xf32>,
+// CHECK-SAME: %[[ARG1:.+]]: tensor<4x8xf32>,
+// CHECK-SAME: %[[ARG2:.+]]: tensor<4xf32>
+// CHECK: %[[FUSED:.+]]:2 = linalg.generic
+// CHECK-SAME: ins(%[[ARG0]] : tensor<4xf32>)
+// CHECK-SAME: outs(%[[ARG1]], %[[ARG2]] : tensor<4x8xf32>, tensor<4xf32>)
+// CHECK-NOT: linalg.generic
+// CHECK: return %[[FUSED]]#1
+func.func @fusion_reduction_shape_from_used_producer_out(
+    %arg0: tensor<4xf32>, %arg1: tensor<4x8xf32>,
+    %arg2: tensor<4xf32>) -> tensor<4xf32> {
+  %0 = linalg.generic {
+      indexing_maps = [#map0, #map1],
+      iterator_types = ["parallel", "parallel"]}
+      ins(%arg0 : tensor<4xf32>) outs(%arg1 : tensor<4x8xf32>) {
+    ^bb0(%in: f32, %out: f32):
+      %2 = arith.addf %in, %out : f32
+      linalg.yield %2 : f32
+  } -> tensor<4x8xf32>
+  %1 = linalg.generic {
+      indexing_maps = [#map1, #map0],
+      iterator_types = ["parallel", "reduction"]}
+      ins(%0 : tensor<4x8xf32>) outs(%arg2 : tensor<4xf32>) {
+    ^bb0(%in: f32, %out: f32):
+      %2 = arith.addf %in, %out : f32
+      linalg.yield %2 : f32
+  } -> tensor<4xf32>
+  return %1 : tensor<4xf32>
+}
+
+// -----
+
 func.func @fusion_different_axes(%arg0 : tensor<5000xi64>, %arg1 : tensor<5000xi32>) -> tensor<5000xi32> {
   %c1_i32 = arith.constant 1 : i32
   %0 = linalg.generic {



More information about the Mlir-commits mailing list