[Mlir-commits] [mlir] [mlir][vector] Do not unroll vector.contract with scalar result (PR #232203)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sun Oct 18 04:30:58 PDT 2026


https://github.com/StarOne01 updated https://github.com/llvm/llvm-project/pull/232203

>From 8842ea15f3590c2fad30a1fe173ba8f9161def21 Mon Sep 17 00:00:00 2001
From: Prashanth <ping at starone01.me>
Date: Thu, 15 Oct 2026 12:39:41 +0530
Subject: [PATCH 1/2] [mlir][vector] add a check to UnrollContractionPattern to
 bail out if we have a scalar result and add contract to scalar test case

---
 .../Dialect/Vector/Transforms/VectorUnroll.cpp  |  3 +++
 .../Dialect/Vector/vector-unroll-options.mlir   | 17 +++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
index 0ea6bc78d3b0c4..89a4604925f86e 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
@@ -290,6 +290,9 @@ struct UnrollContractionPattern
 
   LogicalResult matchAndRewrite(vector::ContractionOp contractOp,
                                 PatternRewriter &rewriter) const override {
+    if (!isa<VectorType>(contractOp.getResultType()))
+      return rewriter.notifyMatchFailure(contractOp,
+                                         "scalar result not supported");
     auto targetShape = getTargetShape(options, contractOp);
     if (!targetShape)
       return failure();
diff --git a/mlir/test/Dialect/Vector/vector-unroll-options.mlir b/mlir/test/Dialect/Vector/vector-unroll-options.mlir
index 61a74744e7a72e..22fcb483ea1e57 100644
--- a/mlir/test/Dialect/Vector/vector-unroll-options.mlir
+++ b/mlir/test/Dialect/Vector/vector-unroll-options.mlir
@@ -935,3 +935,20 @@ func.func @masked_vector_multi_reduction(%v : vector<4x6xf32>, %acc: vector<4xf3
 // CHECK-LABEL: func @masked_vector_multi_reduction
 //  CHECK-NEXT:   %[[R:.*]] = vector.mask %{{.*}} { vector.multi_reduction <add>, {{.*}} : vector<4x6xf32> to vector<4xf32> }
 //  CHECK-NEXT:   return %[[R]]
+
+// -----
+
+#map = affine_map<(d0) -> (d0)>
+#map0 = affine_map<(d0) -> ()>
+
+func.func @vector_contract_to_scalar(%a: vector<4xf32>, %b: vector<4xf32>, %acc: f32) -> f32 {
+  %r = vector.contract {indexing_maps = [#map, #map, #map0], iterator_types = ["reduction"], kind = #vector.kind<add>} %a, %b, %acc : vector<4xf32>, vector<4xf32> into f32
+  return %r : f32
+}
+
+// CHECK-LABEL: func @vector_contract_to_scalar(
+// CHECK-SAME:    %[[A:.*]]: vector<4xf32>, %[[B:.*]]: vector<4xf32>, %[[ACC:.*]]: f32) -> f32 {
+// CHECK-NOT:     vector.extract_strided_slice
+// CHECK:         %[[R:.*]] = vector.contract
+// CHECK-SAME:      %[[A]], %[[B]], %[[ACC]] : vector<4xf32>, vector<4xf32> into f32
+// CHECK:         return %[[R]] : f32

>From 9ca14aea1d32a2de285d92cef4e808207842e4dc Mon Sep 17 00:00:00 2001
From: Prashanth <ping at starone01.me>
Date: Sun, 18 Oct 2026 08:59:04 +0530
Subject: [PATCH 2/2] [mlir][vector] enable full unrolling when we have a
 scalar result and add tests for 1dxf32, 2dxf32, 1dxi32, 1dxf16

---
 .../Vector/Transforms/VectorUnroll.cpp        | 25 ++++----
 .../Dialect/Vector/vector-unroll-options.mlir | 64 ++++++++++++++++---
 2 files changed, 69 insertions(+), 20 deletions(-)

diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
index 89a4604925f86e..5201f862379844 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
@@ -152,7 +152,6 @@ getUnrollOrder(unsigned numLoops, Operation *op,
 }
 
 namespace {
-
 struct UnrollTransferReadPattern
     : public OpRewritePattern<vector::TransferReadOp> {
   UnrollTransferReadPattern(MLIRContext *context,
@@ -290,13 +289,11 @@ struct UnrollContractionPattern
 
   LogicalResult matchAndRewrite(vector::ContractionOp contractOp,
                                 PatternRewriter &rewriter) const override {
-    if (!isa<VectorType>(contractOp.getResultType()))
-      return rewriter.notifyMatchFailure(contractOp,
-                                         "scalar result not supported");
+    bool scalarResult = !isa<VectorType>(contractOp.getResultType());
     auto targetShape = getTargetShape(options, contractOp);
     if (!targetShape)
       return failure();
-    auto dstVecType = cast<VectorType>(contractOp.getResultType());
+    auto dstVecType = dyn_cast<VectorType>(contractOp.getResultType());
     SmallVector<int64_t> originalSize = *contractOp.getShapeForUnroll();
 
     Location loc = contractOp.getLoc();
@@ -346,21 +343,28 @@ struct UnrollContractionPattern
       auto *accIt = accCache.find(accOffets);
       if (accIt != accCache.end())
         slicesOperands[2] = accIt->second;
+      else if (scalarResult)
+        slicesOperands[2] = contractOp.getAcc();
       else
         extractOperand(2, contractOp.getAcc(), accPermutationMap, accOffets);
-
-      SmallVector<int64_t> dstShape =
-          applyPermutationMap(dstAffineMap, ArrayRef<int64_t>(*targetShape));
-      auto targetType = VectorType::get(dstShape, dstVecType.getElementType());
+      Type targetType = contractOp.getResultType();
+      if (!scalarResult) {
+        SmallVector<int64_t> dstShape =
+            applyPermutationMap(dstAffineMap, ArrayRef<int64_t>(*targetShape));
+        targetType = VectorType::get(dstShape, dstVecType.getElementType());
+      }
       Operation *newOp = cloneOpWithOperandsAndTypes(
           rewriter, loc, contractOp, slicesOperands, targetType);
-
       SmallVector<int64_t> dstOffets =
           applyPermutationMap(dstAffineMap, ArrayRef<int64_t>(offsets));
       // Save the accumulated value untill all the loops are unrolled since
       // reduction loop keep updating the accumulator.
       accCache[dstOffets] = newOp->getResult(0);
     }
+    if (scalarResult) {
+      rewriter.replaceOp(contractOp, accCache.begin()->second);
+      return success();
+    }
     // Assemble back the accumulator into a single vector.
     Value result = arith::ConstantOp::create(rewriter, loc, dstVecType,
                                              rewriter.getZeroAttr(dstVecType));
@@ -1726,7 +1730,6 @@ struct UnrollDeinterleavePattern
 private:
   vector::UnrollVectorOptions options;
 };
-
 } // namespace
 
 void mlir::vector::populateVectorUnrollPatterns(
diff --git a/mlir/test/Dialect/Vector/vector-unroll-options.mlir b/mlir/test/Dialect/Vector/vector-unroll-options.mlir
index 22fcb483ea1e57..4129942e5bd3e8 100644
--- a/mlir/test/Dialect/Vector/vector-unroll-options.mlir
+++ b/mlir/test/Dialect/Vector/vector-unroll-options.mlir
@@ -940,15 +940,61 @@ func.func @masked_vector_multi_reduction(%v : vector<4x6xf32>, %acc: vector<4xf3
 
 #map = affine_map<(d0) -> (d0)>
 #map0 = affine_map<(d0) -> ()>
-
-func.func @vector_contract_to_scalar(%a: vector<4xf32>, %b: vector<4xf32>, %acc: f32) -> f32 {
-  %r = vector.contract {indexing_maps = [#map, #map, #map0], iterator_types = ["reduction"], kind = #vector.kind<add>} %a, %b, %acc : vector<4xf32>, vector<4xf32> into f32
-  return %r : f32
+module {
+  func.func @vector_contract_to_scalar_f32_from_1d(%a: vector<4xf32>, %b: vector<4xf32>, %acc: f32) -> f32 {
+    %r = vector.contract {indexing_maps = [#map, #map, #map0], iterator_types = ["reduction"], kind = #vector.kind<add>} %a, %b, %acc : vector<4xf32>, vector<4xf32> into f32
+    return %r : f32
+  }
 }
 
-// CHECK-LABEL: func @vector_contract_to_scalar(
+// CHECK-LABEL: func @vector_contract_to_scalar_f32_from_1d(
 // CHECK-SAME:    %[[A:.*]]: vector<4xf32>, %[[B:.*]]: vector<4xf32>, %[[ACC:.*]]: f32) -> f32 {
-// CHECK-NOT:     vector.extract_strided_slice
-// CHECK:         %[[R:.*]] = vector.contract
-// CHECK-SAME:      %[[A]], %[[B]], %[[ACC]] : vector<4xf32>, vector<4xf32> into f32
-// CHECK:         return %[[R]] : f32
+// CHECK:         %[[A0:.*]] = vector.extract_strided_slice %[[A]] offsets = [0], sizes = [2], strides = [1] : vector<4xf32> to vector<2xf32>
+// CHECK:         %[[B0:.*]] = vector.extract_strided_slice %[[B]] offsets = [0], sizes = [2], strides = [1] : vector<4xf32> to vector<2xf32>
+// CHECK:         %[[R0:.*]] = vector.contract {{.*}} %[[A0]], %[[B0]], %[[ACC]] : vector<2xf32>, vector<2xf32> into f32
+// CHECK:         %[[A1:.*]] = vector.extract_strided_slice %[[A]] offsets = [2], sizes = [2], strides = [1] : vector<4xf32> to vector<2xf32>
+// CHECK:         %[[B1:.*]] = vector.extract_strided_slice %[[B]] offsets = [2], sizes = [2], strides = [1] : vector<4xf32> to vector<2xf32>
+// CHECK:         %[[R1:.*]] = vector.contract {{.*}} %[[A1]], %[[B1]], %[[R0]] : vector<2xf32>, vector<2xf32> into f32
+// CHECK:         return %[[R1]] : f32
+
+// -----
+
+#map = affine_map<(d0) -> (d0)>
+#map0 = affine_map<(d0) -> ()>
+module {
+  func.func @vector_contract_to_scalar_f16(%a: vector<8xf16>, %b: vector<8xf16>, %acc: f16) -> f16 {
+    %r = vector.contract {indexing_maps = [#map, #map, #map0], iterator_types = ["reduction"], kind = #vector.kind<add>} %a, %b, %acc : vector<8xf16>, vector<8xf16> into f16
+    return %r : f16
+  }
+}
+
+// CHECK-LABEL: func @vector_contract_to_scalar_f16(
+// CHECK-SAME:    %[[A:.*]]: vector<8xf16>, %[[B:.*]]: vector<8xf16>, %[[ACC:.*]]: f16) -> f16 {
+// CHECK:         %[[A0:.*]] = vector.extract_strided_slice %[[A]] offsets = [0], sizes = [4], strides = [1] : vector<8xf16> to vector<4xf16>
+// CHECK:         %[[B0:.*]] = vector.extract_strided_slice %[[B]] offsets = [0], sizes = [4], strides = [1] : vector<8xf16> to vector<4xf16>
+// CHECK:         %[[R0:.*]] = vector.contract {{.*}} %[[A0]], %[[B0]], %[[ACC]] : vector<4xf16>, vector<4xf16> into f16
+// CHECK:         %[[A1:.*]] = vector.extract_strided_slice %[[A]] offsets = [4], sizes = [4], strides = [1] : vector<8xf16> to vector<4xf16>
+// CHECK:         %[[B1:.*]] = vector.extract_strided_slice %[[B]] offsets = [4], sizes = [4], strides = [1] : vector<8xf16> to vector<4xf16>
+// CHECK:         %[[R1:.*]] = vector.contract {{.*}} %[[A1]], %[[B1]], %[[R0]] : vector<4xf16>, vector<4xf16> into f16
+// CHECK:         return %[[R1]] : f16
+
+// -----
+
+#map = affine_map<(d0, d1) -> (d0, d1)>
+#map0 = affine_map<(d0,d1) -> ()>
+module {
+  func.func @vector_contract_to_scalar_f32_from_2d(%a: vector<4x4xf32>, %b: vector<4x4xf32>, %acc: f32) -> f32 {
+    %r = vector.contract {indexing_maps = [#map, #map, #map0], iterator_types = ["reduction", "reduction"], kind = #vector.kind<add>} %a, %b, %acc : vector<4x4xf32>, vector<4x4xf32> into f32
+    return %r : f32
+  }
+}
+
+// CHECK-LABEL: func @vector_contract_to_scalar_f32_from_2d(
+// CHECK-SAME:    %[[A:.*]]: vector<4x4xf32>, %[[B:.*]]: vector<4x4xf32>, %[[ACC:.*]]: f32) -> f32 {
+// CHECK:         %[[A0:.*]] = vector.extract_strided_slice %[[A]] offsets = [0, 0], sizes = [4, 2], strides = [1, 1] : vector<4x4xf32> to vector<4x2xf32>
+// CHECK:         %[[B0:.*]] = vector.extract_strided_slice %[[B]] offsets = [0, 0], sizes = [4, 2], strides = [1, 1] : vector<4x4xf32> to vector<4x2xf32>
+// CHECK:         %[[R0:.*]] = vector.contract {{.*}} %[[A0]], %[[B0]], %[[ACC]] : vector<4x2xf32>, vector<4x2xf32> into f32
+// CHECK:         %[[A1:.*]] = vector.extract_strided_slice %[[A]] offsets = [0, 2], sizes = [4, 2], strides = [1, 1] : vector<4x4xf32> to vector<4x2xf32>
+// CHECK:         %[[B1:.*]] = vector.extract_strided_slice %[[B]] offsets = [0, 2], sizes = [4, 2], strides = [1, 1] : vector<4x4xf32> to vector<4x2xf32>
+// CHECK:         %[[R1:.*]] = vector.contract {{.*}} %[[A1]], %[[B1]], %[[R0]] : vector<4x2xf32>, vector<4x2xf32> into f32
+// CHECK:         return %[[R1]] : f32



More information about the Mlir-commits mailing list