[Mlir-commits] [mlir] [mlir][Vector] Add a dedicated Transform Dialect op for `UnrollContractPattern` (PR #230543)

Andrzej WarzyƄski llvmlistbot at llvm.org
Fri Oct 9 08:00:44 PDT 2026


https://github.com/banach-space created https://github.com/llvm/llvm-project/pull/230543

Adds a dedicated Transform Dialect op to run `UnrollContractPattern`.
The new op is currently restricted to matmul-like `vector.contract` ops
and has limitations similar to those of:

  `transform.apply_patterns.gpu.unroll_vectors_subgroup_mma`

We could consider merging the two ops in the future.

While the new `transform.apply_patterns.vector.unroll_contract` op is
quite constrained, it suffices for my use case, so I have refrained from
generalising it. It takes three unroll sizes, corresponding to the M, N,
and K dimensions in GEMM/matmul terminology.

There are also a couple of minor NFC changes. For example,

  vector-multi-reduction-unrolling.mlir

is renamed to

  vector-multi-reduction-unroll.mlir

to align with the naming convention used by other tests for unrolling
patterns.


>From 4f35749f57f1861aa4cf73f0a9c5341b0c5d9cb8 Mon Sep 17 00:00:00 2001
From: Andrzej Warzynski <andrzej.warzynski at arm.com>
Date: Fri, 9 Oct 2026 13:22:58 +0000
Subject: [PATCH] [mlir][Vector] Add a dedicated Transform Dialect op for
 `UnrollContractPattern`

Adds a dedicated Transform Dialect op to run `UnrollContractPattern`.
The new op is currently restricted to matmul-like `vector.contract` ops
and has limitations similar to those of:

  `transform.apply_patterns.gpu.unroll_vectors_subgroup_mma`

We could consider merging the two ops in the future.

While the new `transform.apply_patterns.vector.unroll_contract` op is
quite constrained, it suffices for my use case, so I have refrained from
generalising it. It takes three unroll sizes, corresponding to the M, N,
and K dimensions in GEMM/matmul terminology.

There are also a couple of minor NFC changes. For example,

  vector-multi-reduction-unrolling.mlir

is renamed to

  vector-multi-reduction-unroll.mlir

to align with the naming convention used by other tests for unrolling
patterns.
---
 .../Vector/TransformOps/VectorTransformOps.td | 22 +++++++++
 .../Vector/Transforms/VectorRewritePatterns.h |  6 +++
 .../TransformOps/VectorTransformOps.cpp       | 32 +++++++++++++
 .../Vector/Transforms/VectorUnroll.cpp        | 22 ++++++++-
 .../Dialect/Vector/td/unroll-contract.mlir    | 13 +++++
 .../Vector/vector-contract-unroll.mlir        | 48 +++++++++++++++++++
 ...lir => vector-multi-reduction-unroll.mlir} |  0
 7 files changed, 141 insertions(+), 2 deletions(-)
 create mode 100644 mlir/test/Dialect/Vector/td/unroll-contract.mlir
 create mode 100644 mlir/test/Dialect/Vector/vector-contract-unroll.mlir
 rename mlir/test/Dialect/Vector/{vector-multi-reduction-unrolling.mlir => vector-multi-reduction-unroll.mlir} (100%)

diff --git a/mlir/include/mlir/Dialect/Vector/TransformOps/VectorTransformOps.td b/mlir/include/mlir/Dialect/Vector/TransformOps/VectorTransformOps.td
index 3bd53bb69c576d..1b965937d6e6d2 100644
--- a/mlir/include/mlir/Dialect/Vector/TransformOps/VectorTransformOps.td
+++ b/mlir/include/mlir/Dialect/Vector/TransformOps/VectorTransformOps.td
@@ -14,6 +14,7 @@ include "mlir/Dialect/Transform/Interfaces/TransformInterfaces.td"
 include "mlir/Dialect/Vector/Transforms/VectorTransformsBase.td"
 include "mlir/Interfaces/SideEffectInterfaces.td"
 include "mlir/IR/OpBase.td"
+include "mlir/Dialect/Transform/IR/TransformTypes.td"
 
 def ApplyVectorToLLVMConversionPatternsOp : Op<Transform_Dialect,
     "apply_conversion_patterns.vector.vector_to_llvm",
@@ -324,6 +325,27 @@ def ApplyUnrollFromElementsPatternsOp : Op<Transform_Dialect,
   let assemblyFormat = "attr-dict";
 }
 
+def ApplyUnrollContractPatternsOp : Op<Transform_Dialect,
+    "apply_patterns.vector.unroll_contract",
+    [DeclareOpInterfaceMethods<PatternDescriptorOpInterface>]> {
+  let description = [{
+    Apply unroll patterns for `vector.contract` using the given shapes as the
+    target shape. If no `vector.contract` Op is present in the target block, no
+    unrolling takes place.
+
+    This op assumes that the target `vector.contract` Op models a matrix
+    multiplication. Generalising this Op to other cases is left as a TODO.
+  }];
+
+  let arguments = (ins I64Attr:$m,
+                       I64Attr:$n,
+                       I64Attr:$k);
+
+  let assemblyFormat = [{
+    `[` $m `,` $n `,` $k `]` attr-dict
+  }];
+}
+
 def ApplyUnrollToElementsPatternsOp : Op<Transform_Dialect,
     "apply_patterns.vector.unroll_to_elements",
     [DeclareOpInterfaceMethods<PatternDescriptorOpInterface>]> {
diff --git a/mlir/include/mlir/Dialect/Vector/Transforms/VectorRewritePatterns.h b/mlir/include/mlir/Dialect/Vector/Transforms/VectorRewritePatterns.h
index 6363279f6826ba..9a67d40136c6dd 100644
--- a/mlir/include/mlir/Dialect/Vector/Transforms/VectorRewritePatterns.h
+++ b/mlir/include/mlir/Dialect/Vector/Transforms/VectorRewritePatterns.h
@@ -328,6 +328,12 @@ SmallVector<Value> sliceTransferIndices(ArrayRef<int64_t> elementOffsets,
                                         AffineMap permutationMap, Location loc,
                                         OpBuilder &builder);
 
+/// Unrolls 2 or more dimensional `vector.to_elements` ops by unrolling the
+/// outermost dimension of the operand.
+void populateVectorContractUnrollPatterns(RewritePatternSet &patterns,
+                                          const UnrollVectorOptions &options,
+                                          PatternBenefit benefit = 1);
+
 /// Unrolls 2 or more dimensional `vector.to_elements` ops by unrolling the
 /// outermost dimension of the operand.
 void populateVectorToElementsUnrollPatterns(RewritePatternSet &patterns,
diff --git a/mlir/lib/Dialect/Vector/TransformOps/VectorTransformOps.cpp b/mlir/lib/Dialect/Vector/TransformOps/VectorTransformOps.cpp
index c4c930738d2c92..0d9e44cb7b9e5f 100644
--- a/mlir/lib/Dialect/Vector/TransformOps/VectorTransformOps.cpp
+++ b/mlir/lib/Dialect/Vector/TransformOps/VectorTransformOps.cpp
@@ -168,6 +168,38 @@ void transform::ApplyUnrollFromElementsPatternsOp::populatePatterns(
   vector::populateVectorFromElementsUnrollPatterns(patterns);
 }
 
+void transform::ApplyUnrollContractPatternsOp::populatePatterns(
+    RewritePatternSet &patterns) {
+  int64_t m = getM();
+  int64_t n = getN();
+  int64_t k = getK();
+
+  auto nativeShapeFn =
+      [m, n, k](Operation *op) -> std::optional<SmallVector<int64_t>> {
+    return SmallVector<int64_t>({m, n, k});
+  };
+
+  vector::populateVectorContractUnrollPatterns(
+      patterns, vector::UnrollVectorOptions()
+                    .setNativeShapeFn(nativeShapeFn)
+                    .setFilterConstraint([](Operation *op) {
+                      auto contract = dyn_cast<vector::ContractionOp>(op);
+                      if (!contract)
+                        return failure();
+
+                      if (contract.getIteratorTypesArray() !=
+                          SmallVector<vector::IteratorType>{
+                              vector::IteratorType::parallel,
+                              vector::IteratorType::parallel,
+                              vector::IteratorType::reduction})
+                        return failure();
+                      auto type = dyn_cast<VectorType>(contract.getType());
+                      if (!type || type.getRank() != 2)
+                        return failure();
+                      return success();
+                    }));
+}
+
 void transform::ApplyUnrollToElementsPatternsOp::populatePatterns(
     RewritePatternSet &patterns) {
   vector::populateVectorToElementsUnrollPatterns(patterns);
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
index d58aedcc9d0234..abeeecbc5522e6 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
@@ -90,10 +90,17 @@ static Operation *cloneOpWithOperandsAndTypes(OpBuilder &builder, Location loc,
 static std::optional<SmallVector<int64_t>>
 getTargetShape(const vector::UnrollVectorOptions &options, Operation *op) {
   LDBG() << "Get unroll shape for op " << op->getName().getStringRef();
-  if (options.filterConstraint && failed(options.filterConstraint(op))) {
+
+  if (!options.filterConstraint) {
     LDBG() << "--no filter constraint -> BAIL";
     return std::nullopt;
   }
+
+  if (failed(options.filterConstraint(op))) {
+    LDBG() << "--filtering failed -> BAIL";
+    return std::nullopt;
+  }
+
   assert(options.nativeShape &&
          "vector unrolling expects the native shape or native"
          "shape call back function to be set");
@@ -284,8 +291,12 @@ struct UnrollContractionPattern
   LogicalResult matchAndRewrite(vector::ContractionOp contractOp,
                                 PatternRewriter &rewriter) const override {
     auto targetShape = getTargetShape(options, contractOp);
-    if (!targetShape)
+    if (!targetShape) {
+      // targetShape = SmallVector<int64_t>({1, 8, 4});
       return failure();
+    }
+    contractOp.getResultType();
+    // return failure();
     auto dstVecType = cast<VectorType>(contractOp.getResultType());
     SmallVector<int64_t> originalSize = *contractOp.getShapeForUnroll();
 
@@ -1728,6 +1739,13 @@ void mlir::vector::populateVectorUnrollPatterns(
                                           benefit);
 }
 
+void mlir::vector::populateVectorContractUnrollPatterns(
+    RewritePatternSet &patterns, const UnrollVectorOptions &options,
+    PatternBenefit benefit) {
+  patterns.add<UnrollContractionPattern>(patterns.getContext(), options,
+                                         benefit);
+}
+
 void mlir::vector::populateVectorToElementsUnrollPatterns(
     RewritePatternSet &patterns, PatternBenefit benefit) {
   patterns.add<UnrollToElements>(patterns.getContext(), UnrollVectorOptions(),
diff --git a/mlir/test/Dialect/Vector/td/unroll-contract.mlir b/mlir/test/Dialect/Vector/td/unroll-contract.mlir
new file mode 100644
index 00000000000000..c88a272d960639
--- /dev/null
+++ b/mlir/test/Dialect/Vector/td/unroll-contract.mlir
@@ -0,0 +1,13 @@
+module attributes {transform.with_named_sequence} {
+  transform.named_sequence @unroll_contract(%module_op: !transform.any_op {transform.readonly}) {
+
+    %func = transform.structured.match ops{["func.func"]} in %module_op : (!transform.any_op) -> !transform.any_op
+    %contract = transform.structured.match ops{["vector.contract"]} in %func : (!transform.any_op) -> !transform.any_op
+
+    transform.apply_patterns to %func {
+      transform.apply_patterns.vector.unroll_contract [1, 8, 4]
+    } : !transform.any_op
+
+    transform.yield
+  }
+}
diff --git a/mlir/test/Dialect/Vector/vector-contract-unroll.mlir b/mlir/test/Dialect/Vector/vector-contract-unroll.mlir
new file mode 100644
index 00000000000000..1f95532868fd86
--- /dev/null
+++ b/mlir/test/Dialect/Vector/vector-contract-unroll.mlir
@@ -0,0 +1,48 @@
+// RUN: mlir-opt %s -transform-preload-library='transform-library-paths=%p/td/unroll-contract.mlir' \
+// RUN: -transform-interpreter=entry-point=unroll_contract --split-input-file | FileCheck %s
+
+//===----------------------------------------------------------------------===//
+// Test  UnrollContractionPattern
+//===----------------------------------------------------------------------===//
+// CHECK-LABEL: @matmul
+func.func @matmul(%mask: vector<8x8x4xi1>, %lhs: vector<8x4xf32>, %rhs: vector<4x8xf32>, %acc: vector<8x8xf32>) -> vector<8x8xf32> {
+
+// CHECK-REPEAT-8: vector.contract  {{.*}} : vector<1x4xf32>, vector<4x8xf32> into vector<1x8xf32>
+  %res = vector.contract {
+    indexing_maps = [affine_map<(m, n, k) -> (m, k)>, affine_map<(m, n, k) -> (k, n)>, affine_map<(m, n, k) -> (m, n)>],
+    iterator_types = ["parallel", "parallel", "reduction"],
+    kind = #vector.kind<add>}
+    %lhs, %rhs, %acc : vector<8x4xf32>, vector<4x8xf32> into vector<8x8xf32>
+
+  return %res : vector<8x8xf32>
+}
+
+// -----
+
+// CHECK-LABEL: @negative_batched_matmul
+func.func @negative_batched_matmul(%mask: vector<1x8x8x4xi1>, %lhs: vector<1x8x4xf32>, %rhs: vector<1x4x8xf32>, %acc: vector<1x8x8xf32>) -> vector<1x8x8xf32> {
+
+// CHECK: vector.contract  {{.*}} : vector<1x8x4xf32>, vector<1x4x8xf32> into vector<1x8x8xf32>
+  %res = vector.contract {
+    indexing_maps = [affine_map<(b, m, n, k) -> (b, m, k)>, affine_map<(b, m, n, k) -> (b, k, n)>, affine_map<(b, m, n, k) -> (b, m, n)>],
+    iterator_types = ["parallel", "parallel", "parallel", "reduction"],
+    kind = #vector.kind<add>}
+    %lhs, %rhs, %acc : vector<1x8x4xf32>, vector<1x4x8xf32> into vector<1x8x8xf32>
+
+  return %res : vector<1x8x8xf32>
+}
+
+// -----
+
+// CHECK-LABEL: @negative_matmul_transpose_a
+func.func @negative_matmul_transpose_a(%mask: vector<8x8x4xi1>, %lhs: vector<4x8xf32>, %rhs: vector<4x8xf32>, %acc: vector<8x8xf32>) -> vector<8x8xf32> {
+
+// CHECK: vector.contract  {{.*}} : vector<4x8xf32>, vector<4x8xf32> into vector<8x8xf32>
+  %res = vector.contract {
+    indexing_maps = [affine_map<(k, m, n) -> (k, m)>, affine_map<(k, m, n) -> (k, n)>, affine_map<(k, m, n) -> (m, n)>],
+    iterator_types = ["reduction", "parallel", "parallel"],
+    kind = #vector.kind<add>}
+    %lhs, %rhs, %acc : vector<4x8xf32>, vector<4x8xf32> into vector<8x8xf32>
+
+  return %res : vector<8x8xf32>
+}
diff --git a/mlir/test/Dialect/Vector/vector-multi-reduction-unrolling.mlir b/mlir/test/Dialect/Vector/vector-multi-reduction-unroll.mlir
similarity index 100%
rename from mlir/test/Dialect/Vector/vector-multi-reduction-unrolling.mlir
rename to mlir/test/Dialect/Vector/vector-multi-reduction-unroll.mlir



More information about the Mlir-commits mailing list