MLIR 23.0.0git
TensorTilingInterfaceImpl.cpp
Go to the documentation of this file.
1//===- TensorTilingInterface.cpp - Tiling Interface models *- C++ ------*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
18
19using namespace mlir;
20using namespace mlir::tensor;
21
22namespace {
23
24struct PadOpTiling : public TilingInterface::ExternalModel<PadOpTiling, PadOp> {
25 using Base = TilingInterface::ExternalModel<PadOpTiling, PadOp>;
26 // Inherit the defaulted hint-bearing overloads; this op does not require the
27 // hint.
28 using Base::generateResultTileValue;
29 using Base::getTiledImplementation;
30
31 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op) const {
32 auto padOp = cast<PadOp>(op);
33 SmallVector<utils::IteratorType> iteratorTypes(
34 padOp.getResultType().getRank(), utils::IteratorType::parallel);
35 return iteratorTypes;
36 }
37
38 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &b) const {
39 ReifiedRankedShapedTypeDims reifiedShapes;
40 (void)reifyResultShapes(b, op, reifiedShapes);
41 OpFoldResult zero = b.getIndexAttr(0);
42 OpFoldResult one = b.getIndexAttr(1);
43 // Initialize all the ranges to {zero, one, one}. All the `ub`s are
44 // overwritten.
45 SmallVector<Range> loopRanges(reifiedShapes[0].size(), {zero, one, one});
46 for (const auto &ub : enumerate(reifiedShapes[0]))
47 loopRanges[ub.index()].size = ub.value();
48 return loopRanges;
49 }
50
51 FailureOr<TilingResult>
52 getTiledImplementation(Operation *op, OpBuilder &b,
53 ArrayRef<OpFoldResult> offsets,
54 ArrayRef<OpFoldResult> sizes) const {
55 FailureOr<TilingResult> result =
56 tensor::bubbleUpPadSlice(b, cast<PadOp>(op), offsets, sizes);
57 if (failed(result))
58 return failure();
59 return result.value();
60 }
61
62 LogicalResult
63 getResultTilePosition(Operation *op, OpBuilder &b, unsigned resultNumber,
64 ArrayRef<OpFoldResult> offsets,
65 ArrayRef<OpFoldResult> sizes,
66 SmallVector<OpFoldResult> &resultOffsets,
67 SmallVector<OpFoldResult> &resultSizes) const {
68 resultOffsets.assign(offsets.begin(), offsets.end());
69 resultSizes.assign(sizes.begin(), sizes.end());
70 return success();
71 }
72
73 LogicalResult getIterationDomainTileFromResultTile(
74 Operation *op, OpBuilder &b, unsigned resultNumber,
75 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
76 SmallVectorImpl<OpFoldResult> &iterDomainOffsets,
77 SmallVectorImpl<OpFoldResult> &iterDomainSizes) const {
78 iterDomainOffsets.assign(offsets.begin(), offsets.end());
79 iterDomainSizes.assign(sizes.begin(), sizes.end());
80 return success();
81 }
82
83 FailureOr<TilingResult>
84 generateResultTileValue(Operation *op, OpBuilder &b, unsigned resultNumber,
85 ArrayRef<OpFoldResult> offsets,
86 ArrayRef<OpFoldResult> sizes) const {
87 return getTiledImplementation(op, b, offsets, sizes);
88 }
89};
90
91} // namespace
92
93FailureOr<TilingResult> tensor::bubbleUpPadSlice(OpBuilder &b,
94 tensor::PadOp padOp,
97 bool generateZeroSliceGuard) {
98 // Only constant padding value supported.
99 Value padValue = padOp.getConstantPaddingValue();
100 if (!padValue)
101 return failure();
102
103 // Helper variables and functions for various arithmetic operations. These
104 // are used extensively for computing new offset/length and padding values.
105 Location loc = padOp->getLoc();
106 AffineExpr dim0, dim1;
107 bindDims(b.getContext(), dim0, dim1);
108 // Subtract two integers.
109 auto subMap = AffineMap::get(2, 0, {dim0 - dim1});
110 auto sub = [&](OpFoldResult v1, OpFoldResult v2) {
111 return affine::makeComposedFoldedAffineApply(b, loc, subMap, {v1, v2});
112 };
113 // Take the minimum of two integers.
114 auto idMap = AffineMap::getMultiDimIdentityMap(2, b.getContext());
115 auto min = [&](OpFoldResult v1, OpFoldResult v2) {
116 return affine::makeComposedFoldedAffineMin(b, loc, idMap, {v1, v2});
117 };
118 // Take the maximum of two integers.
119 auto max = [&](OpFoldResult v1, OpFoldResult v2) {
120 return affine::makeComposedFoldedAffineMax(b, loc, idMap, {v1, v2});
121 };
122 // Zero index-typed integer.
123 OpFoldResult zero = b.getIndexAttr(0);
124
125 // Compute new offsets, lengths, low padding, high padding.
126 SmallVector<OpFoldResult> newOffsets, newLengths;
127 SmallVector<OpFoldResult> newLows, newHighs;
128 // Set to true if the original data source is not read at all.
129 bool hasZeroLen = false;
130 // Same as hasZeroLen, but for dynamic dimension sizes. This condition
131 // is true if the original data source turns out to be unused at runtime.
132 Value dynHasZeroLenCond;
133
134 int64_t rank = padOp.getSourceType().getRank();
135 // Only unit stride supported.
136 SmallVector<OpFoldResult> newStrides(rank, b.getIndexAttr(1));
137 for (unsigned dim = 0; dim < rank; ++dim) {
138 auto low = padOp.getMixedLowPad()[dim];
139 bool hasLowPad = !isZeroInteger(low);
140 auto high = padOp.getMixedHighPad()[dim];
141 bool hasHighPad = !isZeroInteger(high);
142 auto offset = offsets[dim];
143 auto length = sizes[dim];
144 // If the dim has no padding, we dont need to calculate new values for that
145 // dim as the exisiting ones are correct even after the pattern.
146 if (!hasLowPad && !hasHighPad) {
147 newOffsets.push_back(offset);
148 newLengths.push_back(length);
149 newLows.push_back(low);
150 newHighs.push_back(high);
151 continue;
152 }
153
154 auto srcSize = tensor::getMixedSize(b, loc, padOp.getSource(), dim);
155
156 // The new amount of low padding is `low - offset`. Except for the case
157 // where none of the low padding is read. In that case, the new amount of
158 // low padding is zero.
159 //
160 // Optimization: If low = 0, then newLow = 0.
161 OpFoldResult newLow = hasLowPad ? max(zero, sub(low, offset)) : zero;
162 newLows.push_back(newLow);
163
164 // Start reading the data from position `offset - low`. Since the original
165 // read may have started in the low padding zone, this value could be
166 // negative. Therefore, start reading from:
167 //
168 // max(offset - low, 0)
169 //
170 // The original read could also have started in the high padding zone.
171 // In that case, set the offset to the end of source tensor. The new
172 // ExtractSliceOp length will be zero in that case. (Effectively reading
173 // no data from the source.)
174 //
175 // Optimization: If low = 0, then the formula can be simplified.
176 OpFoldResult newOffset = hasLowPad
177 ? min(max(sub(offset, low), zero), srcSize)
178 : min(offset, srcSize);
179 newOffsets.push_back(newOffset);
180
181 // The original ExtractSliceOp was reading until position `offset +
182 // length`. Therefore, the corresponding position within the source tensor
183 // is:
184 //
185 // offset + length - low
186 //
187 // In case the original ExtractSliceOp stopped reading within the low
188 // padding zone, this value can be negative. In that case, the end
189 // position of the read should be zero. (Similar to newOffset.)
190 //
191 // The original read could also have stopped in the high padding zone.
192 // In that case, set the end positition of the read should be the end of
193 // the source tensor. (Similar to newOffset.)
194 // srcSize - newOffset represents how much length we have available
195 // and length - newLow represents how much length we want at most.
196 // Note that there are many ways to order this indexing math to compute
197 // newLength, but we want to make sure that the final affine.min ops in the
198 // sequence are bounding the index to as small a value as possible. If
199 // ValueBoundsOpInterface is used, this calculation will get upper bounds
200 // from the affine.min ops, so we want to use the smallest known value to
201 // set the bound at the end of the computation sequence. In this case, the
202 // index will be upper bounded by length - newLow.
203 OpFoldResult newLength = min(sub(srcSize, newOffset), sub(length, newLow));
204 // Optimization: If low = 0, then newLow = 0. then newLength >= 0 assuming
205 // length >= 0.
206 if (hasLowPad)
207 newLength = max(newLength, zero);
208 newLengths.push_back(newLength);
209
210 // Check if newLength is zero. In that case, no SubTensorOp should be
211 // executed.
212 if (isZeroInteger(newLength)) {
213 hasZeroLen = true;
214 } else if (!hasZeroLen) {
215 Value check = arith::CmpIOp::create(
216 b, loc, arith::CmpIPredicate::eq,
217 getValueOrCreateConstantIndexOp(b, loc, newLength),
219 dynHasZeroLenCond =
220 dynHasZeroLenCond
221 ? arith::OrIOp::create(b, loc, check, dynHasZeroLenCond)
222 : check;
223 }
224
225 // The amount of high padding is simply the number of elements remaining,
226 // so that the result has the same length as the original ExtractSliceOp.
227 // As an optimization, if the original high padding is zero, then the new
228 // high padding must also be zero.
229 OpFoldResult newHigh =
230 hasHighPad ? sub(sub(length, newLength), newLow) : zero;
231 newHighs.push_back(newHigh);
232 }
233
234 // The shape of the result can be obtained from the sizes passed in.
235 SmallVector<Value> dynDims;
237 dispatchIndexOpFoldResults(sizes, dynDims, shape);
238 RankedTensorType resultType =
239 RankedTensorType::get(shape, padOp.getResultType().getElementType());
240
241 // Insert cast to ensure that types match. (May be folded away.)
242 auto castResult = [&](Value val) -> Value {
243 if (resultType == val.getType())
244 return val;
245 return tensor::CastOp::create(b, loc, resultType, val);
246 };
247
248 // In cases where the original data source is unused: Emit a GenerateOp and
249 // do not generate a SliceOp. (The result shape of the SliceOp would
250 // have a dimension of size 0, the semantics of which is unclear.)
251 auto createGenerateOp = [&]() {
252 // Create GenerateOp.
253 auto generateOp = tensor::GenerateOp::create(
254 b, loc, resultType, dynDims,
255 [&](OpBuilder &builder, Location gLoc, ValueRange indices) {
256 tensor::YieldOp::create(builder, gLoc, padValue);
257 });
258 return generateOp;
259 };
260
261 // Emit a SliceOp and a PadOp. Should not be used in cases where
262 // the result shape of the new SliceOp has a zero dimension.
263 auto createPadOfExtractSlice = [&]() {
264 // Create pad(extract_slice(x)).
265 auto newSliceOp = tensor::ExtractSliceOp::create(
266 b, loc, padOp.getSource(), newOffsets, newLengths, newStrides);
267 auto newPadOp = PadOp::create(
268 b, loc, Type(), newSliceOp, newLows, newHighs,
269 /*nofold=*/padOp.getNofold(),
270 getPrunedAttributeList(padOp, PadOp::getAttributeNames()));
271
272 // Copy region to new PadOp.
273 IRMapping bvm;
274 padOp.getRegion().cloneInto(&newPadOp.getRegion(), bvm);
275
276 // Cast result and return.
277 return std::make_tuple(newPadOp, newSliceOp);
278 };
279
280 // Rewrite extract_slice(pad(x)) into a GenerateOp it is statically known that
281 // the original data source x is not used.
282 if (hasZeroLen) {
283 Operation *generateOp = createGenerateOp();
284 return TilingResult{{generateOp},
285 {castResult(generateOp->getResult(0))},
286 /*generatedSlices=*/{}};
287 }
288
289 // If there are dynamic dimensions: Generate an scf.if check to avoid
290 // creating SliceOps with result dimensions of size 0 at runtime.
291 if (generateZeroSliceGuard && dynHasZeroLenCond) {
292 Operation *thenOp;
293 Operation *elseOp;
294 Operation *sliceOp;
295 auto result = scf::IfOp::create(
296 b, loc, dynHasZeroLenCond,
297 /*thenBuilder=*/
298 [&](OpBuilder &b, Location loc) {
299 thenOp = createGenerateOp();
300 scf::YieldOp::create(b, loc, castResult(thenOp->getResult(0)));
301 },
302 /*elseBuilder=*/
303 [&](OpBuilder &b, Location loc) {
304 std::tie(elseOp, sliceOp) = createPadOfExtractSlice();
305 scf::YieldOp::create(b, loc, castResult(elseOp->getResult(0)));
306 });
307 return TilingResult{
308 {elseOp}, SmallVector<Value>(result->getResults()), {sliceOp}};
309 }
310
311 auto [newPadOp, sliceOp] = createPadOfExtractSlice();
312 return TilingResult{
313 {newPadOp}, {castResult(newPadOp->getResult(0))}, {sliceOp}};
314}
315
317 DialectRegistry &registry) {
318 registry.addExtension(+[](MLIRContext *ctx, TensorDialect *dialect) {
319 tensor::PadOp::attachInterface<PadOpTiling>(*ctx);
320 });
321}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
static LogicalResult getResultTilePosition(RewriterBase &rewriter, ReductionTilingStrategy reductionStrategy, int64_t index, Value tiledResult, TilingInterface op, ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, ValueRange ivs, ArrayRef< OpFoldResult > numThreads, ArrayRef< OpFoldResult > givenTileSizes, const SetVector< unsigned > &reductionDims, SmallVector< OpFoldResult > &resultOffset, SmallVector< OpFoldResult > &resultSize)
static FailureOr< TilingResult > getTiledImplementation(RewriterBase &rewriter, TilingInterface op, ReductionTilingStrategy reductionStrategy, ValueRange regionIterArg, ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, ValueRange ivs, ArrayRef< OpFoldResult > numThreads, ArrayRef< OpFoldResult > givenTileSizes, ArrayRef< InnerTileAlignment > innerTileAlignments, const SetVector< unsigned > &reductionDims)
Base type for affine expression.
Definition AffineExpr.h:68
static AffineMap getMultiDimIdentityMap(unsigned numDims, MLIRContext *context)
Returns an AffineMap with 'numDims' identity result dim exprs.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class helps build Operations.
Definition Builders.h:209
This class represents a single result from folding an operation.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
OpFoldResult makeComposedFoldedAffineMax(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands)
Constructs an AffineMinOp that computes a maximum across the results of applying map to operands,...
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Constructs an AffineApplyOp that applies map to operands after composing the map with the maps of any...
OpFoldResult makeComposedFoldedAffineMin(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands)
Constructs an AffineMinOp that computes a minimum across the results of applying map to operands,...
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition Matchers.h:344
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
FailureOr< TilingResult > bubbleUpPadSlice(OpBuilder &b, tensor::PadOp padOp, ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, bool generateZeroSliceGuard=true)
Bubbles up a slice of this pad by taking the slice first and then performing the padding.
void registerTilingInterfaceExternalModels(mlir::DialectRegistry &registry)
Registers external models for Tiling interface for tensor ops.
OpFoldResult getMixedSize(OpBuilder &builder, Location loc, Value value, int64_t dim)
Return the dimension of the given tensor value.
Definition TensorOps.cpp:60
Include the generated interface declarations.
LogicalResult reifyResultShapes(OpBuilder &b, Operation *op, ReifiedRankedShapedTypeDims &reifiedReturnShapes)
Reify the shape of the result of an operation (typically in terms of the shape of its operands).
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:311
SmallVector< SmallVector< OpFoldResult > > ReifiedRankedShapedTypeDims
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
SmallVector< NamedAttribute > getPrunedAttributeList(Operation *op, ArrayRef< StringRef > elidedAttrs)
Container for result values of tiling.