Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions mlir/include/mlir/IR/AffineExpr.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ class AffineExpr {
/// floordiv, ceildiv, and mod is only allowed w.r.t constants.
bool isPureAffine() const;

/// Returns true if this expression is monotonicically increasing with respect
/// to the AffineDimExprs, i.e. increasing the value of any AffineDimExpr will
/// never decrease the value of the result.
bool isMonotonicallyIncreasing() const;

/// Returns the greatest known integral divisor of this affine expression. The
/// result is always positive.
int64_t getLargestKnownDivisor() const;
Expand Down
4 changes: 4 additions & 0 deletions mlir/include/mlir/IR/AffineMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,10 @@ class AffineMap {
/// Returns true if the AffineMap represents a symbol-less permutation map.
bool isPermutation() const;

// Returns true if every result is monotonically increasing.
// See AffineExpr::isMonotonicallyIncreasing().
bool isComponentWiseMonotonicallyIncreasing() const;

/// Returns the map consisting of the `resultPos` subset.
AffineMap getSubMap(ArrayRef<unsigned> resultPos) const;

Expand Down
7 changes: 5 additions & 2 deletions mlir/lib/Dialect/Linalg/Utils/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,11 @@ computeSliceParameters(OpBuilder &builder, Location loc, Value valueToTile,
sliceParams.strides.reserve(rank);
for (unsigned r = 0; r < rank; ++r) {
LLVM_DEBUG(llvm::dbgs() << "computeSliceParameters: for dim#" << r);
if (!isTiled(map.getSubMap({r}), tileSizes)) {
auto m = map.getSubMap({r});
// The offset & size computation below only handles the case when
// the map is monotonically increasing, i.e. the min and max values are
// attained at the lower and upper bounds of the iteration domain.
if (!isTiled(m, tileSizes) || !m.isComponentWiseMonotonicallyIncreasing()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC this will just silently not tile? I think it might be better to error out to avoid having users expect one thing and it silently doing something else?

Copy link
Contributor Author

@mgehre-amd mgehre-amd Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will take the full slice of that dimension of the input tensor, but it will tile the other dimensions of the input tensor (if their expressions are monotonic).

We could also error out in this case, and require the user to instead use a tile size of 0 for each dim with non-monotonic expression.
This makes sense to me if there is just a single op being tiled.

If we tile & fuse through a whole chain of ops, and only the last one has non-monotonic maps, maybe it would be nicer to allow tiling to succeed even the last op in the chain has some dimensions un-tiled?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we tile & fuse through a whole chain of ops, and only the last one has non-monotonic maps, maybe it would be nicer to allow tiling to succeed even the last op in the chain has some dimensions un-tiled?

I am not sure I can visualize this without an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that my check is too conservative (I actually want to tile affine_maps that contain mods) with safe tile sizes. I will abandon this PR.

sliceParams.offsets.push_back(builder.getIndexAttr(0));
OpFoldResult dim = createFoldedDimOp(builder, loc, valueToTile, r);
sliceParams.sizes.push_back(dim);
Expand All @@ -593,7 +597,6 @@ computeSliceParameters(OpBuilder &builder, Location loc, Value valueToTile,

// Tiling creates a new slice at the proper index, the slice step is 1
// (i.e. the op does not subsample, stepping occurs in the loop).
auto m = map.getSubMap({r});
LLVM_DEBUG(llvm::dbgs() << "computeSliceParameters: submap: " << m << "\n");
IRRewriter rewriter(builder);
OpFoldResult offset = makeComposedFoldedAffineApply(rewriter, loc, m, lbs);
Expand Down
36 changes: 36 additions & 0 deletions mlir/lib/IR/AffineExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,42 @@ bool AffineExpr::isPureAffine() const {
llvm_unreachable("Unknown AffineExpr");
}

static bool isNonNegativeConstant(AffineExpr expr) {
auto constant = dyn_cast<AffineConstantExpr>(expr);
return constant && constant.getValue() >= 0;
}

bool AffineExpr::isMonotonicallyIncreasing() const {
switch (getKind()) {
case AffineExprKind::SymbolId:
case AffineExprKind::DimId:
case AffineExprKind::Constant:
return true;
case AffineExprKind::Add: {
auto op = llvm::cast<AffineBinaryOpExpr>(*this);
return op.getLHS().isMonotonicallyIncreasing() &&
op.getRHS().isMonotonicallyIncreasing();
}
case AffineExprKind::Mul: {
// One operand must be a non-negative constant.
auto op = llvm::cast<AffineBinaryOpExpr>(*this);
return op.getLHS().isMonotonicallyIncreasing() &&
op.getRHS().isMonotonicallyIncreasing() &&
(isNonNegativeConstant(op.getLHS()) ||
isNonNegativeConstant(op.getRHS()));
}
case AffineExprKind::FloorDiv:
case AffineExprKind::CeilDiv: {
auto op = llvm::cast<AffineBinaryOpExpr>(*this);
return op.getLHS().isMonotonicallyIncreasing() &&
isNonNegativeConstant(op.getRHS());
}
case AffineExprKind::Mod:
return false;
}
llvm_unreachable("Unknown AffineExpr");
}

// Returns the greatest known integral divisor of this affine expression.
int64_t AffineExpr::getLargestKnownDivisor() const {
AffineBinaryOpExpr binExpr(nullptr);
Expand Down
5 changes: 5 additions & 0 deletions mlir/lib/IR/AffineMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,11 @@ bool AffineMap::isPermutation() const {
return isProjectedPermutation();
}

bool AffineMap::isComponentWiseMonotonicallyIncreasing() const {
return all_of(getResults(),
[](auto expr) { return expr.isMonotonicallyIncreasing(); });
}

AffineMap AffineMap::getSubMap(ArrayRef<unsigned> resultPos) const {
SmallVector<AffineExpr, 4> exprs;
exprs.reserve(resultPos.size());
Expand Down
32 changes: 32 additions & 0 deletions mlir/test/Dialect/Linalg/tile-tensors.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,35 @@ module attributes {transform.with_named_sequence} {
transform.yield
}
}

// -----

// CHECK-LABEL: func @non_monotonic_affine_expr
// CHECK-SAME: %[[ARG0:[a-zA-Z0-9_]+]]: tensor<?xf32>
func.func @non_monotonic_affine_expr(%arg0 : tensor<?xf32>) -> tensor<?xf32> {
%c0 = arith.constant 0 : index
%0 = tensor.dim %arg0, %c0 : tensor<?xf32>
%empty = tensor.empty(%0) : tensor<?xf32>

// CHECK: scf.for
// CHECK: %[[SIZE:[a-zA-Z0-9_]+]] = tensor.dim %[[ARG0]],
// CHECK: tensor.extract_slice %[[ARG0]][0] [%[[SIZE]]] [1] : tensor<?xf32> to tensor<?xf32>
%generic = linalg.generic
{indexing_maps = [affine_map<(d0) -> (d0 mod 3)>,
affine_map<(d0) -> (d0)>],
iterator_types = ["parallel"]}
ins(%arg0: tensor<?xf32>)
outs(%empty : tensor<?xf32>) {
^bb0(%in : f32, %out: f32):
linalg.yield %in : f32
} -> tensor<?xf32>
return %generic : tensor<?xf32>
}

module attributes {transform.with_named_sequence} {
transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
%0 = transform.structured.match ops{["linalg.generic"]} in %arg1 : (!transform.any_op) -> !transform.any_op
%1, %loop = transform.structured.tile_using_for %0 tile_sizes [100] : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
}
}
Loading