[LinalgExt][Attention] Specialize generics to matmuls (#24668)

Specialize generic matmul to named one in decompose-attention to prevent
BubbleUpExpandShapes from propagating reshapes through contractions,
preserving codegen-friendly structure for microkernels.

Signed-off-by: Kamil Karwacki <karwacki@roofline.ai>
Co-authored-by: Thomas Ziereis <ziereis@roofline.ai>
diff --git a/compiler/src/iree/compiler/Dialect/LinalgExt/IR/AggregatedOpInterfaceImpl.cpp b/compiler/src/iree/compiler/Dialect/LinalgExt/IR/AggregatedOpInterfaceImpl.cpp
index b12f5a1..1b76613 100644
--- a/compiler/src/iree/compiler/Dialect/LinalgExt/IR/AggregatedOpInterfaceImpl.cpp
+++ b/compiler/src/iree/compiler/Dialect/LinalgExt/IR/AggregatedOpInterfaceImpl.cpp
@@ -16,6 +16,7 @@
 #include "mlir/Dialect/Affine/Utils.h"
 #include "mlir/Dialect/Arith/Utils/Utils.h"
 #include "mlir/Dialect/Linalg/IR/Linalg.h"
+#include "mlir/Dialect/Linalg/Transforms/Transforms.h"
 #include "mlir/Dialect/Linalg/Utils/Utils.h"
 #include "mlir/Dialect/Math/IR/Math.h"
 #include "mlir/Dialect/MemRef/IR/MemRef.h"
@@ -153,7 +154,7 @@
 
 static Value computeMatmul(OpBuilder &builder, Location loc, AffineMap lhsMap,
                            AffineMap rhsMap, AffineMap accMap, Value lhs,
-                           Value rhs, Value acc) {
+                           Value rhs, Value acc, bool specialize = false) {
 
   SmallVector<AffineMap> compressedMaps =
       compressUnusedDims(SmallVector<AffineMap>{lhsMap, rhsMap, accMap});
@@ -183,6 +184,19 @@
         linalg::YieldOp::create(b, loc, add);
       });
 
+  if (specialize) {
+    // Try to specialize the generic into a named op (e.g. batch_matmul).
+    // Named ops prevent BubbleUpExpandShapes from propagating reshapes through
+    // contractions, preserving codegen-friendly structure for microkernels.
+    IRRewriter rewriter(builder);
+    rewriter.setInsertionPoint(genericOp);
+    FailureOr<linalg::LinalgOp> specializedOp =
+        linalg::specializeGenericOp(rewriter, genericOp);
+    if (succeeded(specializedOp)) {
+      return specializedOp->getOperation()->getResult(0);
+    }
+  }
+
   return genericOp.getResult(0);
 }
 
@@ -313,7 +327,7 @@
                               SmallVector<OpFoldResult> iterationDomain,
                               Type sElementType, Region &elementwiseRegion,
                               DictionaryAttr qkAttrs, bool lowPrecision,
-                              bool useExp2) {
+                              bool useExp2, bool specialize = false) {
   MLIRContext *ctx = b.getContext();
   // If using exp2 for attention instead of the original exp, we have to
   // multiply the scale by log2(e). We use exp2 instead of exp as most platforms
@@ -358,7 +372,7 @@
   Value sZero = arith::ConstantOp::create(b, loc, b.getZeroAttr(sElementType));
   Value s = linalg::FillOp::create(b, loc, sZero, emptyS).getResult(0);
 
-  s = computeMatmul(b, loc, qMap, kMap, sMap, query, key, s);
+  s = computeMatmul(b, loc, qMap, kMap, sMap, query, key, s, specialize);
   if (qkAttrs) {
     s.getDefiningOp()->setAttrs(qkAttrs);
   }
@@ -437,7 +451,8 @@
   // ---- QK Matmul + elementwise math ----
   Value s = computeQKAndElementwise(
       loc, b, query, key, getScale(), mask, qMap, kMap, sMap, getMaskMap(),
-      sizes, f32Type, getRegion(), qkAttrs, lowPrecision, /*useExp2=*/true);
+      sizes, f32Type, getRegion(), qkAttrs, lowPrecision, /*useExp2=*/true,
+      /*specialize=*/true);
 
   // ---- Softmax ----
 
@@ -502,8 +517,8 @@
   }
 
   // result = P @ V + acc
-  Value result =
-      computeMatmul(b, loc, pMap, getValueMap(), accMap, p, value, accFill);
+  Value result = computeMatmul(b, loc, pMap, getValueMap(), accMap, p, value,
+                               accFill, /*specialize=*/true);
   if (pvAttrs) {
     result.getDefiningOp()->setAttrs(pvAttrs);
   }
diff --git a/compiler/src/iree/compiler/Dialect/LinalgExt/IR/BUILD.bazel b/compiler/src/iree/compiler/Dialect/LinalgExt/IR/BUILD.bazel
index d30d0d7..9480824 100644
--- a/compiler/src/iree/compiler/Dialect/LinalgExt/IR/BUILD.bazel
+++ b/compiler/src/iree/compiler/Dialect/LinalgExt/IR/BUILD.bazel
@@ -96,6 +96,7 @@
         "@llvm-project//mlir:InliningUtils",
         "@llvm-project//mlir:LinalgDialect",
         "@llvm-project//mlir:LinalgStructuredOpsIncGen",
+        "@llvm-project//mlir:LinalgTransforms",
         "@llvm-project//mlir:LinalgUtils",
         "@llvm-project//mlir:MathDialect",
         "@llvm-project//mlir:MemRefDialect",
diff --git a/compiler/src/iree/compiler/Dialect/LinalgExt/IR/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/LinalgExt/IR/CMakeLists.txt
index 50a477c..2621f05 100644
--- a/compiler/src/iree/compiler/Dialect/LinalgExt/IR/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Dialect/LinalgExt/IR/CMakeLists.txt
@@ -57,6 +57,7 @@
     MLIRInferTypeOpInterface
     MLIRLinalgDialect
     MLIRLinalgStructuredOpsIncGenLib
+    MLIRLinalgTransforms
     MLIRLinalgUtils
     MLIRMathDialect
     MLIRMemRefDialect
diff --git a/compiler/src/iree/compiler/Dialect/LinalgExt/IR/test/decompose_aggregate_op.mlir b/compiler/src/iree/compiler/Dialect/LinalgExt/IR/test/decompose_aggregate_op.mlir
index 85c2181..ff12285 100644
--- a/compiler/src/iree/compiler/Dialect/LinalgExt/IR/test/decompose_aggregate_op.mlir
+++ b/compiler/src/iree/compiler/Dialect/LinalgExt/IR/test/decompose_aggregate_op.mlir
@@ -107,12 +107,7 @@
 // CHECK: linalg.generic
 // CHECK:   arith.mulf
 // S = Q @ K
-// CHECK: linalg.generic
-// CHECK:   arith.extf
-// CHECK:   arith.extf
-// CHECK:   arith.mulf
-// CHECK:   arith.addf
-// CHECK:   linalg.yield
+// CHECK: linalg.batch_matmul
 // max = rowMax(S)
 // CHECK: linalg.generic
 // CHECK-NOT: arith.extf
@@ -139,13 +134,8 @@
 // CHECK-NOT: arith.extf
 // CHECK:   arith.truncf
 // CHECK:   linalg.yield
-// newAcc = P @ V
-// CHECK: linalg.generic
-// CHECK:   arith.extf
-// CHECK:   arith.extf
-// CHECK:   arith.mulf
-// CHECK:   arith.addf
-// CHECK:   linalg.yield
+// result = P @ V
+// CHECK: linalg.batch_matmul
 
 // -----