This is an archive of the discontinued LLVM Phabricator instance.

[mlir] Support partial folding of affine.min/max
ClosedPublic

Authored by ftynse on May 6 2020, 10:06 AM.

Details

Summary

Originally, these operations were folded only if all expressions in their
affine maps could be folded to a constant expression that can be then subject
to numeric min/max computation. This introduces a more advanced version that
partially folds the affine map by lifting individual constant expression in it
even if some of the expressions remain variable. The folding can update the
operation in place to use a simpler map. Note that this is not as powerful as
canonicalization, in particular this does not remove dimensions or symbols that
became useless. This allows for better composition of Linalg tiling and
promotion transformation, where the latter can handle some canonical forms of
affine.min that the folding can now produce.

Depends On D79497

Diff Detail

Event Timeline

ftynse created this revision.May 6 2020, 10:06 AM
Herald added a project: Restricted Project. · View Herald Transcript

partially folds the affine map by lifting individual constant expression in it

Thanks for the detailed summary, but I'm sorry this part isn't clear. canonicalizeMapAndOperands does propagate constants from the operands into the maps. It isn't clear if you are doing the same or whether you are taking min/max over the subset of the results that are constant: for eg. affine.min affine_map<(d0, d1, d2) -> (d0, d1, d2)> (2, 3, %N) is affine.min affine_map<(d0) -> (2, d0)> (%N). I don't see any test cases of the latter form - so didn't look at the code carefully.

mlir/include/mlir/IR/AffineMap.h
147

By 'Folds', did you mean propagates?

rriddle resigned from this revision.May 6 2020, 11:02 AM
andydavis1 accepted this revision.May 6 2020, 12:49 PM

Thanks Alex!

mlir/lib/Dialect/Affine/IR/AffineOps.cpp
2131–2132

Can you remove this TODO now?

2132

"returns failure"

2150

Seems like this block of code could be shared with AffineMin::Fold

mlir/lib/IR/AffineMap.cpp
263

Could this function also return if all results were constants? Would this allow you to avoid calling extractIntegerResults in affine.min/max.fold above?

This revision is now accepted and ready to land.May 6 2020, 12:49 PM
nicolasvasilache accepted this revision.May 6 2020, 2:01 PM
nicolasvasilache added inline comments.
mlir/lib/Dialect/Affine/IR/AffineOps.cpp
2137

How about a 2 steps impl?

if (llvm::any_of(...))
  return failure();
auto range = llvm::map_range();
results.assign(range.begin(), range.end());

I'd say the readability improvements are worth it, you choose.

2150

Or have the whole impl factored out and just take either std::min/max_element as a parameter?

ftynse marked an inline comment as done.May 6 2020, 2:22 PM

partially folds the affine map by lifting individual constant expression in it

Thanks for the detailed summary, but I'm sorry this part isn't clear. canonicalizeMapAndOperands does propagate constants from the operands into the maps. It isn't clear if you are doing the same or whether you are taking min/max over the subset of the results that are constant: for eg. affine.min affine_map<(d0, d1, d2) -> (d0, d1, d2)> (2, 3, %N) is affine.min affine_map<(d0) -> (2, d0)> (%N). I don't see any test cases of the latter form - so didn't look at the code carefully.

Canonicalization does more than just propagating the constants, it moves dimensions and symbols around, drops them, or does more aggressive simplifications of affine maps. The folding only looks if it can replace one AffineExpr (however complex) with an AffineConstantExpr by substituting the dimensions and symbols with constant operands. It will do the replacement if possible.

Taking a subset of results looks interesting, but this change doesn't do it either (I had a specific use case, TBH, where having any constant in a map suffices to overapproximate the result, so I did just that). I think there are more simplifications that we can do as foldings rather than canonicalizations, including operand/result removal, but leaving this for future work.

mlir/lib/Dialect/Affine/IR/AffineOps.cpp
2131–2132

My change doesn't do the folding given as example in the todo.

mlir/lib/IR/AffineMap.cpp
242

Same potential 2-step readability gains as above.

partially folds the affine map by lifting individual constant expression in it

Thanks for the detailed summary, but I'm sorry this part isn't clear. canonicalizeMapAndOperands does propagate constants from the operands into the maps. It isn't clear if you are doing the same or whether you are taking min/max over the subset of the results that are constant: for eg. affine.min affine_map<(d0, d1, d2) -> (d0, d1, d2)> (2, 3, %N) is affine.min affine_map<(d0) -> (2, d0)> (%N). I don't see any test cases of the latter form - so didn't look at the code carefully.

Canonicalization does more than just propagating the constants, it moves dimensions and symbols around, drops them, or does more aggressive simplifications of affine maps. The folding only looks if it can replace one AffineExpr (however complex) with an AffineConstantExpr by substituting the dimensions and symbols with constant operands. It will do the replacement if possible.

For all the in-place folding for AffineForOp / AffineIfOp, we just make a call to canonicalizeMap/Set/AndOperands and then do the bound folding (the order has to be fixed though - a TODO). On the other hand, these min/max ops have results unlike AffineFor/IfOp and so you are just using the constantFold on the map itself. But SimplifyAffineOp is already registered on min/max ops, and the composeAffineMapAndOperands there is already making a call to canonicalizeMapAndOperands and thus already doing the constant folding for the result expressions - so isn't -canonicalize already accomplishing what you are doing here as part of fold? (albeit via the pattern). You just need the simple min of constants in its folder?

ftynse updated this revision to Diff 262587.May 7 2020, 3:29 AM
ftynse marked 9 inline comments as done.

Address reviews

ftynse added inline comments.May 7 2020, 3:31 AM
mlir/lib/Dialect/Affine/IR/AffineOps.cpp
2137

I tried, and it's actually more verbose because of lambdas.

2150

taking an instantiation of an STL algorithm as a parameter looks very ugly, so I just went for an if(std::is_same)

mlir/lib/IR/AffineMap.cpp
242

It's even better with the refactoring suggested by Andy below.

This revision was automatically updated to reflect the committed changes.

partially folds the affine map by lifting individual constant expression in it

Thanks for the detailed summary, but I'm sorry this part isn't clear. canonicalizeMapAndOperands does propagate constants from the operands into the maps. It isn't clear if you are doing the same or whether you are taking min/max over the subset of the results that are constant: for eg. affine.min affine_map<(d0, d1, d2) -> (d0, d1, d2)> (2, 3, %N) is affine.min affine_map<(d0) -> (2, d0)> (%N). I don't see any test cases of the latter form - so didn't look at the code carefully.

Canonicalization does more than just propagating the constants, it moves dimensions and symbols around, drops them, or does more aggressive simplifications of affine maps. The folding only looks if it can replace one AffineExpr (however complex) with an AffineConstantExpr by substituting the dimensions and symbols with constant operands. It will do the replacement if possible.

For all the in-place folding for AffineForOp / AffineIfOp, we just make a call to canonicalizeMap/Set/AndOperands and then do the bound folding (the order has to be fixed though - a TODO). On the other hand, these min/max ops have results unlike AffineFor/IfOp and so you are just using the constantFold on the map itself. But SimplifyAffineOp is already registered on min/max ops, and the composeAffineMapAndOperands there is already making a call to canonicalizeMapAndOperands and thus already doing the constant folding for the result expressions - so isn't -canonicalize already accomplishing what you are doing here as part of fold? (albeit via the pattern). You just need the simple min of constants in its folder?

Looks like you might have missed this comment @ftynse Just curious to know what you think here in response.

ftynse added a comment.May 9 2020, 2:11 AM

partially folds the affine map by lifting individual constant expression in it

Thanks for the detailed summary, but I'm sorry this part isn't clear. canonicalizeMapAndOperands does propagate constants from the operands into the maps. It isn't clear if you are doing the same or whether you are taking min/max over the subset of the results that are constant: for eg. affine.min affine_map<(d0, d1, d2) -> (d0, d1, d2)> (2, 3, %N) is affine.min affine_map<(d0) -> (2, d0)> (%N). I don't see any test cases of the latter form - so didn't look at the code carefully.

Canonicalization does more than just propagating the constants, it moves dimensions and symbols around, drops them, or does more aggressive simplifications of affine maps. The folding only looks if it can replace one AffineExpr (however complex) with an AffineConstantExpr by substituting the dimensions and symbols with constant operands. It will do the replacement if possible.

For all the in-place folding for AffineForOp / AffineIfOp, we just make a call to canonicalizeMap/Set/AndOperands and then do the bound folding (the order has to be fixed though - a TODO). On the other hand, these min/max ops have results unlike AffineFor/IfOp and so you are just using the constantFold on the map itself. But SimplifyAffineOp is already registered on min/max ops, and the composeAffineMapAndOperands there is already making a call to canonicalizeMapAndOperands and thus already doing the constant folding for the result expressions - so isn't -canonicalize already accomplishing what you are doing here as part of fold? (albeit via the pattern). You just need the simple min of constants in its folder?

Looks like you might have missed this comment @ftynse Just curious to know what you think here in response.

Indeed, I missed that one somehow. I need this transformation to happen in during _folding_, not canonicalization. Folding is independent of the canonicalization pass and may happen if you use OpeationFolder when building operations, unlike canonicalization. Arguably, most of the transformations currently performed by affine canonicalizers can actually be done in folding.