-
Notifications
You must be signed in to change notification settings - Fork 15k
[MLIR][Python] Support Python-defined passes in MLIR #156000
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
base: main
Are you sure you want to change the base?
Conversation
mlir/include/mlir-c/Rewrite.h
Outdated
MLIR_CAPI_EXPORTED MlirLogicalResult mlirApplyPatternsAndFoldGreedilyForOp( | ||
MlirOperation op, MlirFrozenRewritePatternSet patterns, | ||
MlirGreedyRewriteDriverConfig); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure which name is suitable.. So the current name is just a placeholder. And modifying mlirApplyPatternsAndFoldGreedily
seems a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a breaking change, but not sure how widely and also C API is really best effort stable (meaning, we try not to break it). I probably should have included a Module suffix on the original, and then this could have gone without. How many usages in the wild can you find of this call?
(Nit: ForOp suffix makes me think it's related to ForOp).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, I perform a simple check on public repos via GitHub search (https://github.com/search?q=mlirApplyPatternsAndFoldGreedily+&type=code) and there seems to be some use cases of this API so I'm not sure if we should change it or not (maybe leave to maintainers for this decision : ).
Currently I'm going to rename it to mlirApplyPatternsAndFoldGreedilyWithOp
to avoid such confusion with ForOp
as you mentioned : )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please extract this change in an independent PR? This is largely unrelated (your test python pass could just do another kind of rewrite right now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed in ca80408.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please extract this change in an independent PR? This is largely unrelated (your test python pass could just do another kind of rewrite right now).
Ahh got it.
I think I can do it via op/block/region/walk/erase APIs directly, but it maybe a little ugly since the normal rewriter API is not ported to python yet AFAIK?
✅ With the latest revision this PR passed the Python code formatter. |
✅ With the latest revision this PR passed the C/C++ code formatter. |
mlirTypeIDCreate(this), mlirStringRefCreate(name.data(), name.length()), | ||
mlirStringRefCreate(argument.data(), argument.length()), | ||
mlirStringRefCreate(description.data(), description.length()), | ||
mlirStringRefCreate(opName.data(), opName.size()), 0, nullptr, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dependent dialects are not yet supported.
mlir/lib/Bindings/Python/Pass.cpp
Outdated
|
||
#include "mlir-c/Bindings/Python/Interop.h" // This is expected after nanobind. | ||
#include "nanobind/trampoline.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The empty line is to workaround clang-format: otherwise it will report that "mlir-c/Bindings/Python/Interop.h" should be put before "mlir/Bindings/Python/Nanobind.h" : )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should do
// clang-format off
#include "mlir/Bindings/Python/Nanobind.h"
#include "mlir-c/Bindings/Python/Interop.h" // ON WINDOWS This is expected after nanobind.
// clang-format on
(the ON WINDOWS
isn't currently there but it should be because I discovered recently that it's in fact only windows that this matters for).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh thank you for your suggestion. It is done in c8c2fae.
@llvm/pr-subscribers-mlir Author: Twice (PragmaTwice) ChangesSTATUS: This PR is work-in-progress now :) It tries to close #155996. This PR exports a class This is a simple example of a Python-defined pass.
TODO list:
Full diff: https://github.com/llvm/llvm-project/pull/156000.diff 7 Files Affected:
|
nb::object deepcopy = copy.attr("deepcopy"); | ||
return deepcopy(obj).release().ptr(); | ||
}; | ||
callbacks.initialize = nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initialize
is not ported to python side yet.
auto passModule = | ||
m.def_submodule("passmanager", "MLIR Pass Management Bindings"); | ||
populatePassManagerSubmodule(passModule); | ||
populatePassSubmodule(passModule); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that pass
is a keyword in python so that name like mlir.pass.Pass
doesn't work. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, pass vs pass :-) I don't have a good suggestion given how established that is MLIR/compilers and Python side ... MlirPass or PyPass or pydefpass would be most obvious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently the full name of the base class is mlir.passmanager.Pass
. Is it good enough or we'd better to rename it with another module name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that PEP8 recommends appending a underscore in case of names of arguments / attributes clashing with reserved keywords, I feel mlir.pass_.Pass
is an option as it is not too surprising for me personally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think putting a trailing underscore in a namespace is a little awkward. How about mlir.passes
? or mlir.passinfra
? or I dunno something like that that I'm failing to come up with right now...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. For now, I will rename it to mlir.passes.Pass
. Let me know if anyone think of a better name : )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 01e68c5.
// Note that passmanager will take the ownership of the returned | ||
// object and release it when appropriate. | ||
MlirPass make() { | ||
auto *obj = nb::find(this).release().ptr(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About lifetime of the python object: here we increase the ref count by nb::find
(and by release()
we avoid decrease the count here), and when the ExternalPass
is destructed, callback.destructor
is called so that dec_ref()
is called for this object.
…dFoldGreedilyWithOp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to take another pass (not pun intended) but here are some initial non-nit comments.
auto passModule = | ||
m.def_submodule("passmanager", "MLIR Pass Management Bindings"); | ||
populatePassManagerSubmodule(passModule); | ||
populatePassSubmodule(passModule); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think putting a trailing underscore in a namespace is a little awkward. How about mlir.passes
? or mlir.passinfra
? or I dunno something like that that I'm failing to come up with right now...
mlir/lib/Bindings/Python/Pass.cpp
Outdated
|
||
#include "mlir-c/Bindings/Python/Interop.h" // This is expected after nanobind. | ||
#include "nanobind/trampoline.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should do
// clang-format off
#include "mlir/Bindings/Python/Nanobind.h"
#include "mlir-c/Bindings/Python/Interop.h" // ON WINDOWS This is expected after nanobind.
// clang-format on
(the ON WINDOWS
isn't currently there but it should be because I discovered recently that it's in fact only windows that this matters for).
//---------------------------------------------------------------------------- | ||
// Mapping of the Python-defined Pass interface | ||
//---------------------------------------------------------------------------- | ||
nb::class_<PyPassBase, PyPass>(m, "Pass") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't this backwards? cf <PyOperation, PyOperationBase>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh this is a quite interesting case that I just did some research on : )
Refer to the nanobind documentation (https://nanobind.readthedocs.io/en/latest/api_core.html#_CPPv4I0DpEN8nanobind6class_E):
template<typename T, typename ...Ts>
class class_ : public objectThe variable length parameter Ts is optional and can be used to specify the base class of T and/or an alias needed to realize trampoline classes.
So in the case you mentioned (<PyOperation, PyOperationBase>
), the parameter Ts
(PyOperationBase
) is a base class of T
(PyOperation
); and for the case here (<PyPassBase, PyPass>
), the Ts
(PyPass
) is a trampoline class of T
(PyPassBase
). So I think both of them is correct here.
For example, in the documentation of trampoline classes (https://nanobind.readthedocs.io/en/latest/classes.html#overriding-virtual-functions-in-python), we can see such an instance of nb::class_
:
nb::class_<Dog, PyDog /* <-- trampoline */>(m, "Dog")
And here PyDog
is a derived class of Dog
but also a trampoline class of Dog
: )
// Make an MlirPass instance on-the-fly that wraps this object. | ||
// Note that passmanager will take the ownership of the returned | ||
// object and release it when appropriate. | ||
MlirPass make() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason that in this design I add a new class PyPassBase
and lazily construct the MlirPass
while adding into the pass manager instead of just wrapping MlirPass
as a python object is that: the lifetime of MlirPass
is a bit tricky.
If we don't add it into the pass manager, the owership of MlirPass
should be in our hand (and we are responsible to release it finally), and after adding into a pass manager, the ownership is transfered into the manager, and delete myPassPtr
will be called when the manager is destructed on the C++ side. So, the ownership state of MlirPass
is changed between and after adding to pass manager.
By lazily constructing the MlirPass
only when adding to a pass manager, we can avoid to expose the MlirPass
object into the python world so that we don't need to care about how to interoperate between c++ delete passPtr
and python object ref-count. And also we can ensure that the lifetime of the MlirPass
is always handled by the pass manager.
This approach might seem a bit roundabout but works. I’m also open to trying other methods if there are any : ) (Maybe I can check how the Region/Block binding APIs work since the situation is similiar? Not sure yet.) WDYT?
based heavily on llvm#156000
based heavily on llvm#156000
Sorry for taking so long to review! Firstly, I want to repeat/reiterate/state that I'm fully in support of this functionality (and I believe many others are as well). Coincidentally, at $DAYJOB we want to provide/support something very similar to our users very soon. So thank you very much for the implementation! Secondly, I'd like to propose a slightly different alternative: #157369. The core of the difference being that instead of providing an API based on classes ( @PragmaTwice take a look - let me know what you think. If you agree/like the alternative, feel free to just copy-paste into this PR (no need to merge or whatever). |
based heavily on llvm#156000
based heavily on llvm#156000
based heavily on llvm#156000
Thanks for the patch! It really helps clarify what the pass API should look like. I’ll try to merge it into this PR. I’ve left a few minor comments over there (#157369). |
based heavily on llvm#156000
based heavily on llvm#156000
based heavily on llvm#156000
based heavily on llvm#156000
based heavily on llvm#156000
based heavily on llvm#156000
based heavily on llvm#156000
STATUS: This PR is ready-for-review but quite experimental :)
It tries to close #155996.
This PR exports a class
mlir.passmanager.Pass
for Python-side to use for defining new MLIR passes.This is a simple example of a Python-defined pass.
TODO list: