Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NFC] Make AggregateOpInterface part of mlir:: instead of linalg:: #70089

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Abhishek-Varma
Copy link
Contributor

@Abhishek-Varma Abhishek-Varma commented Oct 24, 2023

-- Currently, AggregateOpInterface is part of mlir::linalg:: namespace
so this commit makes it part of a generic mlir:: namespace.

Signed-off-by: Abhishek Varma abhishek@nod-labs.com

@llvmbot
Copy link
Collaborator

llvmbot commented Oct 24, 2023

@llvm/pr-subscribers-mlir-linalg

@llvm/pr-subscribers-mlir

Author: Abhishek Varma (Abhishek-Varma)

Changes

-- Currently, AggregateOpInterface is part of mlir::linalg:: namespace
so this commit makes it part of a generic mlir:: namespace.

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


Full diff: https://github.com/llvm/llvm-project/pull/70089.diff

2 Files Affected:

  • (modified) mlir/include/mlir/Dialect/Linalg/IR/LinalgInterfaces.td (-30)
  • (modified) mlir/include/mlir/Interfaces/DestinationStyleOpInterface.td (+30)
diff --git a/mlir/include/mlir/Dialect/Linalg/IR/LinalgInterfaces.td b/mlir/include/mlir/Dialect/Linalg/IR/LinalgInterfaces.td
index 44e82f452b3cef1..99b2520b5750da9 100644
--- a/mlir/include/mlir/Dialect/Linalg/IR/LinalgInterfaces.td
+++ b/mlir/include/mlir/Dialect/Linalg/IR/LinalgInterfaces.td
@@ -817,34 +817,4 @@ def LinalgStructuredInterface
   let verifyWithRegions = 1;
 }
 
-def AggregatedOpInterface : OpInterface<"AggregatedOpInterface"> {
-  let description = [{
-    Interface for decomposing aggregated operations into a sequence of simpler
-    ops.
-  }];
-  let cppNamespace = "::mlir::linalg";
-  let methods = [
-      InterfaceMethod<
-        /*desc=*/[{
-          Method to decompose the operation into simpler operations.
-
-          On success, this method returns one `Value` per result in the
-          original operation.
-          The order of the returned values must match the order of the
-          original values.
-          In other words, the returned vector can be used directly with
-          `RewriterBase::replaceOp(this, returnedValues)`.
-        }],
-        /*retType=*/"FailureOr<SmallVector<Value>>",
-        /*methodName=*/"decomposeOperation",
-        /*args=*/(ins
-            "OpBuilder &":$b),
-        /*methodBody=*/"",
-        /*defaultImplementation=*/[{
-          return {};
-        }]
-      >
-  ];
-}
-
 #endif // LINALG_IR_LINALGINTERFACES
diff --git a/mlir/include/mlir/Interfaces/DestinationStyleOpInterface.td b/mlir/include/mlir/Interfaces/DestinationStyleOpInterface.td
index 4c52d803e114762..fabe609d2190f4f 100644
--- a/mlir/include/mlir/Interfaces/DestinationStyleOpInterface.td
+++ b/mlir/include/mlir/Interfaces/DestinationStyleOpInterface.td
@@ -206,5 +206,35 @@ def DestinationStyleOpInterface : OpInterface<"DestinationStyleOpInterface"> {
   let verifyWithRegions = 1;
 }
 
+def AggregatedOpInterface : OpInterface<"AggregatedOpInterface"> {
+  let description = [{
+    Interface for decomposing aggregated operations into a sequence of simpler
+    ops.
+  }];
+  let cppNamespace = "::mlir";
+  let methods = [
+      InterfaceMethod<
+        /*desc=*/[{
+          Method to decompose the operation into simpler operations.
+
+          On success, this method returns one `Value` per result in the
+          original operation.
+          The order of the returned values must match the order of the
+          original values.
+          In other words, the returned vector can be used directly with
+          `RewriterBase::replaceOp(this, returnedValues)`.
+        }],
+        /*retType=*/"FailureOr<SmallVector<Value>>",
+        /*methodName=*/"decomposeOperation",
+        /*args=*/(ins
+            "OpBuilder &":$b),
+        /*methodBody=*/"",
+        /*defaultImplementation=*/[{
+          return {};
+        }]
+      >
+  ];
+}
+
 
 #endif // MLIR_DESTINATIONSTYLEOPINTERFACE

@Abhishek-Varma Abhishek-Varma force-pushed the nfc_aggregate_op_interface branch 3 times, most recently from c88b335 to 1f208cf Compare October 24, 2023 19:55
@qcolombet
Copy link
Collaborator

The change looks fine but what is your use case?

Also, we may want to also move the DecomposeInterfaceOp transform operation in the generic part.

@Abhishek-Varma
Copy link
Contributor Author

Abhishek-Varma commented Oct 25, 2023

The change looks fine but what is your use case?

Also, we may want to also move the DecomposeInterfaceOp transform operation in the generic part.

Hi @dcaballe

So, the use case occurs in IREE where we have operations from LinalgExt like Attention, WinogradInputTransformOp and WinogradOutputTransformOp which we decompose into finer set of operations.
The cleaner way to do that would be for them to adhere to AggregateOpInterface's contract and invoke their decompositions via decomposeOperation.
But since AggregateOpInterface is tightly bound to linalg currently, including it straightaway is not possible - so we're trying to move this at a generic namespace for anyone to be able to implement their operation's decomposition using this interface and therefore also reduce the extra number of decomposition passes one may require.

Also regarding the DecomposeInterfaceOp - can we take it up in a subsequent revision? A quick search shows that applyToOne has far too many users and perhaps it'd be better if we address it in a separate PR.

CC: @MaheshRavishankar

@MaheshRavishankar
Copy link
Contributor

The change looks fine but what is your use case?
Also, we may want to also move the DecomposeInterfaceOp transform operation in the generic part.

Hi @dcaballe

So, the use case occurs in IREE where we have operations from LinalgExt like Attention, WinogradInputTransformOp and WinogradOutputTransformOp which we decompose into finer set of operations. The cleaner way to do that would be for them to adhere to AggregateOpInterface's contract and invoke their decompositions via decomposeOperation. But since AggregateOpInterface is tightly bound to linalg currently, including it straightaway is not possible - so we're trying to move this at a generic namespace for anyone to be able to implement their operation's decomposition using this interface and therefore also reduce the extra number of decomposition passes one may require.

Also regarding the DecomposeInterfaceOp - can we take it up in a subsequent revision? A quick search shows that applyToOne has far too many users and perhaps it'd be better if we address it in a separate PR.

CC: @MaheshRavishankar

@qcolombet see comment above, but I think its more about, this doesnt need to be in Linalg, so avoiding unnecessary dependency on Linalg by moving this out.
W.R.T the transform dialect op, would be great if we could do it as a follow up.

def AggregatedOpInterface : OpInterface<"AggregatedOpInterface"> {
let description = [{
Interface for decomposing aggregated operations into a sequence of simpler
ops.
Copy link
Collaborator

@joker-eph joker-eph Oct 25, 2023

Choose a reason for hiding this comment

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

An "aggregated operations" isn't a terminology that I'm familiar with, so not a "standard MLIR term" I suspect.

So please provide a more extensive documentation for the interface, with examples preferably.
We should know what this interface really does, what is "decomposing", when it is used and why.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @joker-eph - I've added a description as requested. Can you please take a look and let me know if it sounds better?

-- Currently, AggregateOpInterface is part of mlir::linalg:: namespace
   so this commit makes it part of a generic mlir:: namespace.

Signed-off-by: Abhishek Varma <abhishek@nod-labs.com>
Interface.
`linalg::SoftmaxOp` is one such operation which makes use of this Interface
for implementing its decomposition.
}];
Copy link
Collaborator

@joker-eph joker-eph Oct 26, 2023

Choose a reason for hiding this comment

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

Thanks for adding a doc!

This seems pretty ambiguous and under-defined to me: the whole concept of MLIR and progressive lowering is that almost every operation will be decomposed, gradually, through many layer of abstractions: so most ops (above LLVM) are fitting this description.

I still don't know when I would use this interface and ask to "decompose" such an operation, what is the use-case?

(also the Aggregate naming confused me because Aggregate is how LLVM named "struct" types).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

also the Aggregate naming confused me because Aggregate is how LLVM named "struct" types).

Oh okay, I understand. We can change the naming here perhaps, in that case.

I still don't know when I would use this interface and ask to "decompose" such an operation, what is the use-case?

I understand your point. I'll try to explain the use-case which necessitates this NFC change.
So, the idea is to perform a similar operation like linalg::Softmax for a few other Linalg-Like operations in IREE by implementing the already defined upstream interface used by linalg::Softmax here on top-of-main and then instead of having separate individual passes for their decomposition, we only have one pass which would do the needful.

I'm not sure if we're discussing about removing this interface altogether from top-of-main now or we shouldn't move it from linalg:: namespace to a more generic namespace - since the Interface already exists.

Can you confirm once? @joker-eph

Also, perhaps @MaheshRavishankar can add a better context here if I missed any pointers pertaining to the use-case.

Copy link
Collaborator

Choose a reason for hiding this comment

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

For context, when this interface was first introduced the concept we wanted to capture is instructions that can be represented with a sequence of simpler instructions within the same dialect.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The same thing could be achieved with providing proper pattern rewriters, but we found that it was easier to discover when these particular patterns were part of the related "instruction" itself.

At the time we kept this interface within linalg because we would have expected that using that more broadly would warrant a proper RFC.

Copy link
Collaborator

Choose a reason for hiding this comment

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

See https://reviews.llvm.org/D154363 for more context

Copy link
Contributor

Choose a reason for hiding this comment

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

Echo-ing @qcolombet comments. All operations eventually get decomposed, you are right. As Quentin mentioned you could have a pattern rewrite that decomposes an operation into smaller parts. We might maybe need a better term for it, but this interface is meant to group together a set of operations that exhibit a common behavior. Some examples are Softmax (that is in tree), FlashAttention and few others (that are actually out-of-tree in IREE). These are all "concepts" that are common in typical ML programmer land, and maintaining these as an "aggregate operation" in the compiler on the input better represents the programmer intent. You could do several transformations on these aggregate operations themselves (like tiling), but at some point in the compilation flow, it is better to lower them into the decomposed sequence of operations (typically a DAG of linalg-like operations) so that the rest of the compilation flow works on the decomposed sequence (like reductions or matmuls, etc). All these operations seem to follow a similar compilation flow, and the goal is to have some interfaces that these operations can implement so that the compiler just works on these interfaces. There is an in-tree example for now, softmax. We can implement the same interface on FlashAttention etc. that are not in tree as of now. (We plan to move those in tree as well if that helps, just like Softmax was moved into tree by Quentin)

So if you have a better name we could use that. (Naming is hard :) ). Also note that this is just moving an interface that is already in tree, but within Linalg, out of Linalg dialect. There doesnt seem to be a reason for this interface to live in Linalg dialect.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also note that this is just moving an interface that is already in tree, but within Linalg, out of Linalg dialect. There doesnt seem to be a reason for this interface to live in Linalg dialect.

I assume our messages crossed and I expanded on this above?

Copy link
Contributor

Choose a reason for hiding this comment

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

I have a hard time seeing how this is something that composes across dialects or can just be reused generically. That seems quite "specific" to a specific pass at one given level of abstraction (and this is what makes it pretty contextual and not generic as I see it right now), and that's why it seems fine to me where it is.

For example would I implement this interface for the Arith dialect by splitting my fma into add + mul? This may be something I want for my use-case, but if I do this it'll be automatically picked up by the transformation when you apply it at the linalg level.

Yeah thats not the intent, but there is no way to codify it AFAICS. It is hard to predict where all these gets used. If there are issues with this, I am happy to go with one of two options

  1. We keep the interface in Linalg...
  2. We move the interface out of core into IREE... Since there we have more control of which ops implement the interface, we can prevent unintended uses of it.

Copy link
Collaborator

@joker-eph joker-eph Oct 27, 2023

Choose a reason for hiding this comment

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

I'm fine with these 2 options, but none of them is consistent with "land this PR" right now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

For example would I implement this interface for the Arith dialect by splitting my fma into add + mul? This may be something I want for my use-case, but if I do this it'll be automatically picked up by the transformation when you apply it at the linalg level.

That's a good point and I believe we wouldn't want that thing to be picked up at the linalg level. That being said, this is transformation specific, meaning we could have the transformation pick the level it wants or all levels if it is what the specific use case is. E.g., if op.implementThisInterface and op.isDialect(linalg). I'm not saying this is what we want (nor if it is even possible), but assuming it is, then that could be a way to have the interface being more controllable.

Not arguing either way for this PR (accept or reject), ultimately what I wanted to achieve with the original PR (admittedly without giving it much thoughts) was to do a better job at describing / discovering what is supported by the conversion patterns and what should be expanded before conversion. The idea of this interface was to capture what needed to be expanded.

Copy link
Contributor

@MaheshRavishankar MaheshRavishankar left a comment

Choose a reason for hiding this comment

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

This looks good to me. There is an outstanding isasue to better codify where this interface is to be used, but I would rather have this in tree and iterate, instead of building something outside of tree.

Copy link
Collaborator

@joker-eph joker-eph left a comment

Choose a reason for hiding this comment

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

I don't think this is ready to proceed.

@MaheshRavishankar
Copy link
Contributor

I don't think this is ready to proceed.

could you give some feedback, then how to proceed then. This interface is not being added. It is in the Linalg dialect (where it really does not belong). So this PR is only moving it.

@joker-eph
Copy link
Collaborator

joker-eph commented Oct 27, 2023

I don't think this is ready to proceed.

could you give some feedback, then how to proceed then. This interface is not being added. It is in the Linalg dialect (where it really does not belong). So this PR is only moving it.

I thought you concluded in the thread above where you wrote:

I am happy to go with one of two options

  1. We keep the interface in Linalg...
  2. We move the interface out of core into IREE... Since there we have more control of which ops implement the interface, we can prevent unintended uses of it.

I'm confused by your comment there?

Right now the "service" this interface provide is contextual: that is for a given op, the implementation you want from the will differ based on the pass, and this as-is can't be a generic interface, but looks like a pass-specific interface.

@MaheshRavishankar
Copy link
Contributor

I don't think this is ready to proceed.

could you give some feedback, then how to proceed then. This interface is not being added. It is in the Linalg dialect (where it really does not belong). So this PR is only moving it.

I thought you concluded in the thread above where you wrote:

I am happy to go with one of two options

  1. We keep the interface in Linalg...
  2. We move the interface out of core into IREE... Since there we have more control of which ops implement the interface, we can prevent unintended uses of it.

I'm confused by your comment there?

Right now the "service" this interface provide is contextual: that is for a given op, the implementation you want from the will differ based on the pass, and this as-is can't be a generic interface, but looks like a pass-specific interface.

so... leave it where it is, and drop this PR? IMO this is in the wrong place under Linalg... Id rather move it out of there, and be very prescriptive of which ops in tree to add this interface to (add a big red warning flag saying this is to be used with caution). But I am not tied to this. I am happy to leave it in Linalg. I dont want to move it to IREE because this has upstream dependencies, i.e. Softmax op in Linalg dialect (which also doesnt belong in Linalg, but thats a different story).

@MaheshRavishankar
Copy link
Contributor

While we resolve this, @Abhishek-Varma instead of moving this to mlir/Interfaces/... Maybe just split this particular interface out of LinalgInterface.td and add the AggregateOpInterface.td to mlir/Dialect/Linalg/IR/ itself. I think the original issue we were hitting was that we would have to pull in all of LinalgInterface to include this which causes issues. Having a separate .td/.h and .cpp in mlir/Linalg/* gets around that for now till we decide if and when to move this interface out of Linalg.

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

Successfully merging this pull request may close these issues.

5 participants