Skip to content

Conversation

@Abhishek-Varma
Copy link
Contributor

@Abhishek-Varma Abhishek-Varma commented Oct 14, 2025

-- This commit adds matchers to infer which linalg.*conv/pooling* ops a given linalg.generic op is.
-- The goal is directed towards addressing the aim of [RFC] Op explosion in Linalg iteratively for *conv*/*pooling* ops.
-- It makes use of the matchers as part of linalg-specialize-generic-ops to specialize a linalg.generic to the corresponding linalg.*conv/pooling* op and thus demonstrates the matchers' working.

Signed-off-by: Abhishek Varma abhvarma@amd.com

@github-actions
Copy link

github-actions bot commented Oct 14, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds comprehensive pattern matching capabilities to identify and specialize generic linalg operations to specific convolution and pooling named operations. The implementation includes matchers for numerous convolution variants (1D, 2D, 3D), depthwise convolutions, and pooling operations across different data layouts, enabling automatic specialization of linalg.generic operations to their corresponding named operation counterparts.

  • Adds 45+ matcher functions for various convolution and pooling operation patterns
  • Implements specialization logic to convert linalg.generic operations to named convolution/pooling operations
  • Provides comprehensive test coverage with roundtrip tests for all supported operation variants

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
mlir/test/Dialect/Linalg/roundtrip-linalg-convolution-named-ops.mlir New test file with comprehensive roundtrip tests for all convolution/pooling operation variants
mlir/lib/Dialect/Linalg/Utils/Utils.cpp Core implementation of pattern matching logic for convolution and pooling operations
mlir/lib/Dialect/Linalg/Transforms/Specialize.cpp Integration of convolution matchers into the specialization pipeline
mlir/include/mlir/Dialect/Linalg/Utils/Utils.h Public API declarations for all convolution/pooling matcher functions

return %0 : tensor<?x?x?x?xf32>
}
// CHECK: @pooling_nhwc_min_unsigned
// CHECK: linalg.pooling_nhwc_min
Copy link

Copilot AI Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CHECK comment indicates linalg.pooling_nhwc_min but the function being tested is pooling_nhwc_min_unsigned. This appears to be a copy-paste error that should be corrected to match the actual expected operation name.

Suggested change
// CHECK: linalg.pooling_nhwc_min
// CHECK: linalg.pooling_nhwc_min_unsigned

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I wanted to flag this for max/min_unsigned ops - glad Copilot reminded me!

Step 1: Use the following input IR :-

func.func @pooling_nhwc_max_unsigned_float(%input: tensor<?x?x?x?xf32>, %filter: tensor<?x?xf32>, %init: tensor<?x?x?x?xf32>) -> tensor<?x?x?x?xf32> {
  %0 = linalg.pooling_nhwc_max_unsigned {dilations = dense<1> : tensor<2xi64>,
                                strides = dense<1> : tensor<2xi64>}
     ins (%input, %filter: tensor<?x?x?x?xf32>, tensor<?x?xf32>)
    outs (%init: tensor<?x?x?x?xf32>) -> tensor<?x?x?x?xf32>
  return %0 : tensor<?x?x?x?xf32>
}
func.func @pooling_nhwc_max_signed_float(%input: tensor<?x?x?x?xf32>, %filter: tensor<?x?xf32>, %init: tensor<?x?x?x?xf32>) -> tensor<?x?x?x?xf32> {
  %0 = linalg.pooling_nhwc_max {dilations = dense<1> : tensor<2xi64>,
                                strides = dense<1> : tensor<2xi64>}
     ins (%input, %filter: tensor<?x?x?x?xf32>, tensor<?x?xf32>)
    outs (%init: tensor<?x?x?x?xf32>) -> tensor<?x?x?x?xf32>
  return %0 : tensor<?x?x?x?xf32>
}

Step 2: Run mlir-opt --linalg-generalize-named-ops input.mlir

We'll observe the following :-

  func.func @pooling_nhwc_max_unsigned_float(%arg0: tensor<?x?x?x?xf32>, %arg1: tensor<?x?xf32>, %arg2: tensor<?x?x?x?xf32>) -> tensor<?x?x?x?xf32> {
    %0 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel", "reduction", "reduction"]} ins(%arg0, %arg1 : tensor<?x?x?x?xf32>, tensor<?x?xf32>) outs(%arg2 : tensor<?x?x?x?xf32>) {
    ^bb0(%in: f32, %in_0: f32, %out: f32):
      %1 = arith.maximumf %out, %in : f32
      linalg.yield %1 : f32
    } -> tensor<?x?x?x?xf32>
    return %0 : tensor<?x?x?x?xf32>
  }
  func.func @pooling_nhwc_max_signed_float(%arg0: tensor<?x?x?x?xf32>, %arg1: tensor<?x?xf32>, %arg2: tensor<?x?x?x?xf32>) -> tensor<?x?x?x?xf32> {
    %0 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel", "reduction", "reduction"]} ins(%arg0, %arg1 : tensor<?x?x?x?xf32>, tensor<?x?xf32>) outs(%arg2 : tensor<?x?x?x?xf32>) {
    ^bb0(%in: f32, %in_0: f32, %out: f32):
      %1 = arith.maximumf %out, %in : f32
      linalg.yield %1 : f32
    } -> tensor<?x?x?x?xf32>
    return %0 : tensor<?x?x?x?xf32>
  }

Since both the linalg.generic ops are IDENTICAL - there's no way to distinguish between these two and therefore we get linalg.pooling_nhwc_max (a less constrained version) for both the cases when using --linalg-specialize-generic-ops. Basically the matcher, currently, will match both - just the matter of which one is preferred more (as is demonstrated via Specialize.cpp).

NOTE: This only happens when the input is a float. In case of integer, the body of the linalg.generic has either arith.maxui or arith.maxsi - therefore let's us distinguish between linalg.pooling_nhwc_max_unsigned and linalg.pooling_nhwc_max respectively.

CC: @hanhanW @rengolin - let me know how do we want to proceed in this ops' case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surely linalg.pooling_nhwc_max_unsigned should only allow integers, no?

@rengolin
Copy link
Member

Could we avoid the explosion in exported function names? They're almost identical, modulo the one thing different.

@rengolin rengolin requested a review from javedabsar1 October 14, 2025 20:31
@hanhanW
Copy link
Contributor

hanhanW commented Oct 14, 2025

Thanks @Abhishek-Varma for the patch. I'll help review it. Let's add some motivation for the change; I'll add my thoughts below.

Could we avoid the explosion in exported function names? They're almost identical, modulo the one thing different.

The PR is related to [RFC] Op explosion in Linalg. We are making progress on matmul side in upstream. E.g., the transpose variants are removed from named ops; we have matchers for the deprecated ops. [https://github.com//pull/147961]. The revision aims to do something similar, especially for matcher parts. However, we don't have generic conv/depthwise_conv/pooling ops today. Thus, the implementation looks different. I did a quick survey in the beginning, and I felt that the "explosion" is likely needed in the transition period. They are stronger matchers comparing to isa<linalg::*Conv*>. I'm happy to learn more if you have an idea about avoiding the explosion. My naive thinking is that we'll need this anyway in the transition period, like other matmul matchers.

The motivation of the change: our goal is to enhance existing patterns to work with generic op form; it requires matchers. Hopefully, the matching logic can be reused when we have the generic conv/depthwise_conv/pooling ops.

Mirroring some context from IREE issue, and the plan of upstream pattern enhancement:

So I think there are two major tasks in the work stream:

  • Implement the matchers and use it in IREE and Vectorization.
  • Adapt the convolution patterns, that are mentioned in the issue, to accept generalized convolution ops.

@Abhishek-Varma
Copy link
Contributor Author

I tried exposing a single API, so now with just one function name exposed we should be able to achieve the goal of this PR :-

template <typename ConvOpTy>
bool isaConvolutionOpOfType(LinalgOp op,
                            SmallVector<int64_t> *dilations = nullptr,
                            SmallVector<int64_t> *strides = nullptr);

Let me know if this looks okay. Have updated the description of the PR slightly.

CC: @rengolin @hanhanW

@rengolin
Copy link
Member

Thanks @hanhanW for the context around IREE. This is one of the key design decisions behind the Linalg Forms.

Adding my side to it: While the background motivation for this particular PR is to improve IREE, the matchers themselves are useful on their own for all other upstream and downstream users to be able to convert between the forms and simplify their matching infrastructure.

I'll let @javedabsar1 review the code and approve, but I like your public API using just one template argument and two function arguments. While the (static) implementation is still very verbose, the outward facing design is very clean. We can improve the private implementation with time, independent of being able to use this already upstream. Thanks!

Copy link
Contributor

@banach-space banach-space left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working of this, clearly a lot of effort has gone into this!

Before we proceed, I suggest splitting this into multiple PRs. This is nearly 3k LOC and just impossible to review as a single PR. From what I can tell, splitting this into multiple PRs should be trivial:

  • PR1 - high-level structure (the required minimum) + 1 representative example (e.g. 1D+2D+3D depthwise conv, NWC format)
  • PR2 - Depthwise convs - NCW format
  • PR3 - Regular convs
  • ...

Or something similar.

Two other high-level point re testing:

How does it sound?

template <typename ConvOpTy>
bool isaConvolutionOpOfType(LinalgOp op, SmallVector<int64_t> *dilations,
SmallVector<int64_t> *strides) {
if constexpr (std::is_same_v<ConvOpTy, linalg::Conv1DOp>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, a switch statement will be easier to follow. Could you try this:

class TypeSwitch : public detail::TypeSwitchBase<TypeSwitch<T, ResultT>, T> {
?

return %0 : tensor<?x?x?x?xf32>
}
// CHECK: @pooling_nhwc_min_unsigned
// CHECK: linalg.pooling_nhwc_min
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surely linalg.pooling_nhwc_max_unsigned should only allow integers, no?

Abhishek-Varma added a commit to Abhishek-Varma/llvm-project that referenced this pull request Oct 16, 2025
-- This commit includes the basic infra/utilities to add matchers for
   linalg.*conv*/*pool* ops - such that given a `linalg.generic` op it
   identifies which linalg.*conv*/*pool* op it is.
-- It adds a few representative linalg.*conv*/*pool* ops to demo the
   matchers' capability and does so as part of `linalg-specialize-generic-ops`
   pass.
-- The goal is directed towards addressing the aim of
   [[RFC] Op explosion in Linalg](https://discourse.llvm.org/t/rfc-op-explosion-in-linalg/82863)
   iteratively for `*conv*/*pooling*` ops.
-- This is part-1 of a series of PRs aimed to add matchers for Convolution ops.
-- For further details, refer to llvm#163374 (review)

Signed-off-by: Abhishek Varma <abhvarma@amd.com>
Abhishek-Varma added a commit to Abhishek-Varma/llvm-project that referenced this pull request Oct 16, 2025
-- This commit includes the basic infra/utilities to add matchers for
   linalg.*conv*/*pool* ops - such that given a `linalg.generic` op it
   identifies which linalg.*conv*/*pool* op it is.
-- It adds a few representative linalg.*conv*/*pool* ops to demo the
   matchers' capability and does so as part of `linalg-specialize-generic-ops`
   pass.
-- The goal is directed towards addressing the aim of
   [[RFC] Op explosion in Linalg](https://discourse.llvm.org/t/rfc-op-explosion-in-linalg/82863)
   iteratively for `*conv*/*pooling*` ops.
-- This is part-1 of a series of PRs aimed to add matchers for Convolution ops.
-- For further details, refer to llvm#163374 (review)

Signed-off-by: Abhishek Varma <abhvarma@amd.com>
@Abhishek-Varma
Copy link
Contributor Author

Thank you!

@banach-space @javedabsar1 @rengolin @hanhanW - I've raised the first PR that aims to break the current PR into reviewable chunk.

Here is the PR's link : #163724

I'm closing this PR - we can shift any discussion henceforth to that PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants