[RFC] ValueBoundsOpInterface: Compute bounds of index values/dyn. dim. sizes

Status update: I uploaded a revision that addresses some of the comments mentioned in here. ValueBoundsOpInterface no longer builds IR. It is now analysis only. There are two helpers (in the respective dialects) that manifest a computed bound in IR; either as an affine.apply op or as a combination of arith dialect ops.

tensorflow/tensorflow/compiler/mlir/tools/kernel_gen/transforms/same_shape_propagation.cc

This component builds equivalence classes of SSA values that have the same shape.

ValueBoundsOpInterface could be a complete upstream replacement for this component. Some issues with same_shape_propagation.cc that could be addressed with ValueBoundsOpInterface:

  • Hard-coded list of supported ops (not interface-based).
  • Can only handle cases where the shape of a result is the same as the shape of another value. No arithmetics possible. E.g., tensor.pad could not be handled by same_shape_propagation.cc.

There’s also this comment. ValueBoundsOpInterface would address that comment.

// A basic shape equality inference. This should be superceeded by a proper
// inference once available. Until then, we just build this out to the needs of
// the kernel generator project.

xla/xla/mlir_hlo/mhlo/analysis/shape_component_analysis.cc

This is a symbolic interpreter for MHLO ops. It can compute dimension sizes (GetShapeInfo) and values of SSA values (GetValueInfo).

The handling of SSA values is slightly different from ValueBoundsOpInterface: it can be used to compute the elements stored in a shape tensor, whereas ValueBoundsOpInterface only works on index/integer-typed SSA values at the moment.

There are many similarities in the design: both approaches use a worklist-driven approach to traverse the backward slice. Both utilize AffineExpr to encode bounds. The SymbolicExpr data structure is very similar to the combination of AffineMap and ValueDimList in ValueBoundsOpInterface.

shape_component_analysis.cc is currently not interface-based and has a hard-coded list of supported ops. If we add support for shape tensor values to ValueBoundsOpInterface, it could be an upstream replacement for shape_component_analysis.cc.

MLIR InferIntRangeInterface

This interface can be used to constant integer LB and UB for integer-typed SSA values. ValueBoundsOpInterface is a more generalized variant, which also compute non-constant bounds wrt. SSA values.

The data structures are different. InferIntRangeInterface uses a lattice. I’m not super familiar with this data structure. ValueBoundsOpInterface uses a constraint set (FlatLinearConstraints).

ValueBoundsOpInterface can be seen as a generalization of InferIntRangeInterface. I think we should try to merge the two. I think ValueBoundsConstraintSet::computeConstantBound is doing roughly the same as querying a single bound from the lattice. Maybe the authors of InferIntRangeInterface and related infrastructure could also weigh in a bit. @krzysz00 @Mogball

InferIntRangeInterface is aware of integer type ranges:

Bounds are tracked separately for the signed and unsigned
interpretations of a value, which ensures that the impact of
arithmetic overflows is correctly tracked during the analysis.

ValueBoundsOpInterface does currently not take this into account. The underlying constraint set is based on MPInt (similar to APInt). Once a bound is added to the constraint set, the type information is “erased”; there are no restrictions about how small/large a value can get.

In general, when analyzing a non-constant value, it may be impossible to know if a value overflows at runtime or not. For the special case where a constant bound is computed, it should be possible to take into account such overflows. But this will require some extra work.

I have a preliminary revision that adds support for branches to ValueBoundsOpInterface (arith.select, scf.if). These are tricky because their value bounds cannot be expressed as linear constraints. E.g., for %0 = arith.select %cond, %a, %b : index, the lower bound of constraint would be %0 >= min(lb(%a), lb(%b)). For such ops, ValueBoundsOpInterface can infer constant bounds (if there are any), so the constraint set-based approach can still do as well as the lattice-based approach (but no better).

MLIR ReifyRankedShapedTypeOpInterface

This is a special case of ValueBoundsOpInterface, where a bound is computed and reified as IR in terms of the op’s operands. The ReifyRankedShapedTypeOpInterface would no longer be needed. mlir::reifyResultShapes can directly query the ValueBoundsOpInterface. This would also allow dialects to implement the interface without depending on the arith/affine dialect.

There is a related discussion about inefficiencies when using ReifyRankedShapedTypeOpInterface to reify the shape of a value over a chain of multiple ops, which would be improved with ValueBoundsOpInterface: Is there a way to avoid creating instructions that are going to be CSE-ed anyway