Page MenuHomePhabricator

[mlir][Interfaces] Add ValueBoundsOpInterface and tensor dialect op impl

Authored by springerm on Mar 9 2023, 3:39 AM.



Ops can implement this interface to specify lower/upper bounds for their result values and block arguments. Bounds can be specified for:

  • Index-type values
  • Dimension sizes of shapes values

The bounds are added to a constraint set. Users can query this constraint set to compute bounds wrt. to a user-specified set of values. Only EQ bounds are supported at the moment.

A helper function is added to the Affine dialect to reify a computed bounds with an affine.apply op.

This revision also contains interface implementations for various tensor dialect ops, which illustrates how to implement this interface.


Depends On: D146869

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
springerm added inline comments.Mar 16 2023, 8:02 AM
117 ↗(On Diff #503721)

That is because reifyBounds can be used to reify non-constant bounds. I.e., the bound may be the result of an affine.apply op that computes the bound. You're using it to compute constant bounds (in which case no builder would be needed), but reifyBounds is more general.

I'm going to add a new option/overload for computing an explicitly constant bound (without a builder) in a subsequent revision.

303 ↗(On Diff #503721)
springerm updated this revision to Diff 505809.Mar 16 2023, 8:02 AM
springerm marked 2 inline comments as done.
springerm edited the summary of this revision. (Show Details)

address some comments and move to mlir/Interfaces

springerm retitled this revision from [mlir][linalg] Add ValueBoundsOpInterface and tensor dialect op impl to [mlir][Interfaces] Add ValueBoundsOpInterface and tensor dialect op impl.Mar 16 2023, 8:03 AM
springerm edited the summary of this revision. (Show Details)Mar 16 2023, 8:04 AM
springerm updated this revision to Diff 506110.Mar 17 2023, 9:24 AM

Remove loc

springerm added inline comments.Mar 20 2023, 4:28 AM

LGTM. All my comments have been resolved. Please, wait for Mehdi to provide feedback on the remaining open questions/secondary PRs.

I'm curious about @jpienaar 's thoughts on the overall direction here


I am not sure what you have in mind for D146414, I meant helping reviewing D120368 to get it to land.

460 ↗(On Diff #506945)

Can you commit this separately? It's not clear to me that it relates to the current patch

jpienaar added inline comments.Mar 29 2023, 9:37 PM

So value and dim may be provided as long as value is not index type? E.g., if value is an i32, then you can have both?


It is difficult to know what this function will do and stopCondition represents - it feels like base case of recursion, but its unclear here. Could you perhaps expand this to provide more information? How many stop conditions are actually here?


And ValueBoundsConstraintSet is not visible here so that one could use the stop condition type defined there?


That's just being conservative right? Could this be retained in more cases?


OOC are all BoundType's allowed?


I almost wonder if spelling out boundtype in name isn't more intuitive, e.g.,

addUpperBound, addLowerBound, addEquality (last one sounds less good than other two).

Ha, makes me wonder about operators boundset.bound(value) <= dim / boundset.bound(value)[dim] <= expr - which does also translate closer to

addBound(value, UB, expr) - so it reads add bound for value of upper bound with expr.

Seeing presburger in the API just feels weird to me.


These aren't bounds right? This feels natural with equality bounds/solution of constraint set.


Can we choose any other sentinel (-56) or differentiate with type (optional)? (-1 has been used in sentinel in too many places ...)


Mehdi commented on this as well, but this is first mention of column, might be worthwhile to introduce here.


Could this be along with destination style ops or in another header?


Could you expand here? Its not quite intuitive what the effect is (well and presents a nicely documented usage of the interface).


New form is

cast<Foo>(...) / isa<Foo>(...)



(your other change will improve this, I also toyed around with having a shaped.dim op that canonicalizes on input type without dependency on dialect


I'd really prefer if we did this in a more declarative manner so that this could be generated and we could even have a dynamic form of this (similar to how torch-mlir does shape functions in spirit). But more work is needed for that and we aren't there yet, and that can't be a blocker here.


This feels like exactly what we'd represent in the shape (and type inference) function.


Why declare this as an external interface inside the dialect directory? Are alternative implementations expected? This going to be optionally added?


Could we capture the error in op and surface it when queried or some such? Or is it assumed that verification would have caught all instances where bounds could fail?

springerm marked 12 inline comments as done.Mar 30 2023, 9:22 AM
springerm added inline comments.

i32 is not supported (added with D146870). Only index-type values and shaped values are supported.

I added an assertion in ValueBoundsConstraintSet::computeBound.


Added an example. Some of the next revisions also add overloads, so that users can use this interface without specifying a stopCondition.

  • D146296 makes the stopCondition optional for constant bounds.
  • D146306 adds an overload for a given list of dependencies.
  • D143910 adds an overload for a given list of independencies.

Correct. Adding new IR is OK. Expanded the comment.


Only EQ at the moment. There is an assertion in the implementation that LB/UB are not yet implemented. The other bound types are added in D145787 to keep this revision a bit smaller.


This is a nice API. And it allows us to specify inclusive/exclusive bounds via </<= etc.

I updated just this revision for now, will update the dependent ones later.


The getExpr function can be used to provide an expression that can be passed to addBound. (The overload that take an AffineExpr.)


I wanted to use std::optional but for some reason, an std::optional cannot be used as the key for a DenseMap:

DenseMap.h:469:22: error: no member named 'getEmptyKey' in 'llvm::DenseMapInfo<std::optional<long>>'

This must be in some place where both DestinationStyleOpInterface and ValueBoundsOpInterface are available. (Kind of like cross-dialect dependencies, but this one is cross-interfaces.)

ValueBoundsOpInterface seems a bit better than putting it in DestinationStyleOpInterface.h because when DestinationStyleOpInterface is a more "fundamental" interface.


What do you mean by "more declarative"?


It could be implemented directly on the op. Alternative interface implementations are not possible in MLIR (not even with external models).

The reason why I put it here is separation of concerns. So that TensorOps.cpp/ is not getting any longer. This keeps clean of any "optional/non-fundamental" interfaces and easy to understand. I think certain interfaces are very important and should be implemented on the op directly (e.g., OffsetSizeAndStrideOpInterface), other just clutter the file (like this one).


This is just a sanity check. This should always return success. If it fails, it indicates that there is a bug in FlatLinearConstraints or the ValueBoundsOpInterface implementation.

springerm updated this revision to Diff 509699.Mar 30 2023, 9:22 AM
springerm marked 8 inline comments as done.

address most comments

springerm added inline comments.Mar 31 2023, 3:33 AM

I updated a few more places in the internal API. kIndexValue is now just used when interacting with the DenseMap.

ubfx added a subscriber: ubfx.Apr 3 2023, 10:40 PM
jpienaar accepted this revision.Apr 5 2023, 2:58 PM

Nice, I like the new API too.


I meant something else: is this optional simulating a sum type? E.g., is only one of value or dim allowed to be set and an optional is being used? So a ValueOrDim bound is actually being passed in using more raw types (well a std::variant<Value, int64_t> is probably not much more higher level than std::optional, so raw perhaps not best word)


Something that could allow us to capture the information in one way but use both places rather than implement twice. I feel like if we had it such that you could run an abstract interpretation over the shape inference functions, you could have gotten some of these for free (and vice versa). [I'll probably prod at this in a month or so, so no blocker here, just me wishing that was done already :)]


Following new style preference/deprecation, dyn_cast<ShapedType>(value.getType()) here and above.


Update description ?

This revision is now accepted and ready to land.Apr 5 2023, 2:58 PM
dcaballe accepted this revision.Apr 5 2023, 5:43 PM
This revision was landed with ongoing or failed builds.Apr 5 2023, 5:57 PM
This revision was automatically updated to reflect the committed changes.
springerm marked 5 inline comments as done.

Thanks for the thorough review!


std::variant is like a union right? This is different here. This function can be called in two ways:

  • value has index type and dim = nullopt
  • value has a shaped type and dim != nullopt

So value is always needed and whether dim should be specified or not depends on value.getType().