This patch adds a mixin for TileToForallOp to
_structured_transform_ops_ext.py with syntactic sugar for construction
such ops. First, the types of the results are made optional and filled
with common default values if omitted. Second, for num_threads and
tile_sizes, the three possible forms (static, dynamic, or packed), can
now all be given through the same respective argument, which gets
dispatched to the correct form-specific argument automatically.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Please verify the following assumptions that went into the design:
- Specifying num_threads is the common case and tile_sizes is not. This would justify the current design: former is a positional argument whereas the latter is a keyword-only argument. (Note that they can't be positional both at the same time because they have exactly the same possible types.)
- There are actually cases where we want to change the types of forall_op and tiled_op and there are several respective choices. If there no reasonable cases, then we can get away with fewer overloads and use the current default types always. If there are just two cases (the current default and "any type"), then a Boolean flag might be more appropriate.
- It is more common to change the type of forall_op than it is to change the type of tiled_op. If it is the other way around, it may be better to flip their order in the overloads.
mlir/python/mlir/dialects/_structured_transform_ops_ext.py | ||
---|---|---|
430–432 | I suppose this may be useful to other ops. Could we make it a standalone function from the start? | |
438–441 | Nit: it feels a bit magical that an operation is interpreted as packed and a singleton list of operations is interpreted as one value, but we can try and see how often people are confused with this. (In C++, I'd wrap them into different "tag" structures, but Python is more tolerant towards such type tricks). |
Specifying num_threads is the common case and tile_sizes is not. This would justify the current design: former is a positional argument whereas the latter is a keyword-only argument. (Note that they can't be positional both at the same time because they have exactly the same possible types.)
Per https://peps.python.org/pep-0020/ "explicit is better than implicit", so I would strongly suggest to have both as keyword arguments.
There are actually cases where we want to change the types of forall_op and tiled_op and there are several respective choices. If there no reasonable cases, then we can get away with fewer overloads and use the current default types always. If there are just two cases (the current default and "any type"), then a Boolean flag might be more appropriate.
There aren't that many cases that use the types extensively, but I'd argue that there should be. The point of the type system is to specify visible static preconditions on the ops, e.g., tiling only applies to tilable ops (though we don't have a type for this). The result type of a transform op is by default very open so the users can supply whatever type the following op needs and avoid a cast. I'd go with any_op as a default in case of the missing result type.
It is more common to change the type of forall_op than it is to change the type of tiled_op. If it is the other way around, it may be better to flip their order in the overloads.
The order *must* follow the order of results in the operation. Anything else without explicit keywords would be extremely error-prone. Furthermore, tile_to_forall already uses a different order of loop/op results than the "regular" tile. That should be fixed, but it shouldn't be your problem (fixes are certainly welcome).
The order *must* follow the order of results in the operation. Anything else without explicit keywords would be extremely error-prone. Furthermore, tile_to_forall already uses a different order of loop/op results than the "regular" tile. That should be fixed, but it shouldn't be your problem (fixes are certainly welcome).
The order currently doesn't match, let's use None for one of the arguments if need be, but not flip the order. Requesting changes for this one.
Yeah, makes sense, will do. What about the overload with just one type, though? I am thinking that maybe it shouldn't exist: either you specify all types (in __init__ order) or none. Otherwise, what's the most intuitive/import of the two to specify?
I'd always specify both for now. People either care about types or don't use them at all.
Addressing comments from @ftynse:
- Make _dispatch_mixed_values a free function.
- Make num_threads a kewword-only argument.
- Re-order tiled_op_type and loops_type to match the constructor/op definition.
- Make the default types AnyOpTypes.
I also did the following changes:
- Remove the overload with only one type. I don't see how a user could predict which of the two is meant if only one of them is given, so this seems error-prone. Also, if need be, such an overload could be added later.
- Format the changed lines with yapf.
- Remove a spurious print.
Format the changed lines with yapf.
We are now supposed to format with black: https://discourse.llvm.org/t/rfc-document-and-standardize-python-code-style/68257.
mlir/python/mlir/dialects/_structured_transform_ops_ext.py | ||
---|---|---|
438–441 | I am not a Python guru but I thought that this is a usual pattern. For example, the built-in open function accepts either a "path-like object", which in turn is an str, a bytes, or an os.Path, or an integer corresponding to a Unix file descriptor. All forms somehow identify the file that should be opened and the implementation of the function (supposedly) does some dispatching to interpret it. |
I suppose this may be useful to other ops. Could we make it a standalone function from the start?