MLIR 24.0.0git
Transforms.cpp
Go to the documentation of this file.
1//===- Transforms.cpp - Linalg transformations as patterns ----------------===//
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//
9// This file implements logic and helpers to expose Linalg transforms as rewrite
10// patterns.
11//
12//===----------------------------------------------------------------------===//
13
28#include "mlir/IR/AffineExpr.h"
31#include "mlir/Support/LLVM.h"
32#include "llvm/ADT/SmallVectorExtras.h"
33#include "llvm/ADT/TypeSwitch.h"
34#include "llvm/Support/Debug.h"
35#include "llvm/Support/DebugLog.h"
36#include "llvm/Support/InterleavedRange.h"
37#include "llvm/Support/raw_ostream.h"
38#include <type_traits>
39#include <utility>
40
41#define DEBUG_TYPE "linalg-transforms"
42
43using namespace mlir;
44using namespace mlir::linalg;
45
46//===----------------------------------------------------------------------===//
47// Transformations exposed as functional-style API calls.
48//===----------------------------------------------------------------------===//
49
50//===----------------------------------------------------------------------===//
51// peelLoop transformation.
52//===----------------------------------------------------------------------===//
53
54/// Try to peel and canonicalize loop `op` and return the new result.
55/// Also applies affine_min/max bounds simplification on the fly where relevant.
56// TODO: Add support for scf.parallel and affine.for loops.
58 Operation *op) {
60 .Case([&](scf::ForOp forOp) {
61 scf::ForOp partialIteration;
62 if (succeeded(scf::peelForLoopAndSimplifyBounds(rewriter, forOp,
63 partialIteration)))
64 return partialIteration->getResults();
65 assert(!partialIteration && "expected that loop was not peeled");
66 return forOp->getResults();
67 })
68 .Default([&](Operation *op) { return op->getResults(); });
69}
70
71/// Peel 'loops' and applies affine_min/max bounds simplification on the fly
72/// where relevant.
75 for (auto loopOp : loops)
76 peelLoop(rewriter, loopOp);
77}
78
79//===----------------------------------------------------------------------===//
80// pack transformation.
81//===----------------------------------------------------------------------===//
82
83#ifndef NDEBUG
84/// Return true if `map` has 0 or 1 result function of AffineDimExpr(dim).
86 bool found = false;
87 for (AffineExpr e : map.getResults()) {
88 if (!e.isFunctionOfDim(dim))
89 continue;
90 if (found)
91 return false;
92 found = true;
93 }
94 return true;
95}
96#endif // NDEBUG
97
99 return llvm::interleaved(ri, ", ", /*Prefix=*/"|", /*Suffix=*/"");
100}
101
102/// Return the index of the first result of `map` that is a function of
103/// AffineDimExpr(dim), std::nullopt otherwise.
104static std::optional<int64_t> getFirstResultIndexFunctionOf(AffineMap map,
105 int64_t dim) {
106 for (int64_t i = 0, e = map.getNumResults(); i < e; ++i) {
107 AffineExpr expr = map.getResult(i);
108 if (!expr.isFunctionOfDim(dim))
109 continue;
110 return i;
111 }
112 return std::nullopt;
113}
114
115/// Perform one step of packing of a LinalgOp's metadata along `dim` into the
116/// `newDim` at `iteratorTypes.size()` by:
117/// 1. Appending `iteratorTypes[newDim]`, equal to `iteratorTypes[dim]`.
118/// 2. Appending a `newDim` to the domain of every indexing map.
119/// 3. For each operand (i.e. for each map in `indexingMaps`), perform packing
120/// by potentially adding a `newDim` result to `map`.
121/// The preserved invariant is that `iteratorTypes.size()` is always equal to
122/// `map.getNumDims()` for every map in `indexingMaps`.
123///
124/// Update `indexingMaps` and `iteratorTypes` inplace as one step of the update.
125/// Return a vector that records the optional packing for each operand.
126/// Return failure if the packed indexing cannot be represented with a LinalgOp.
127///
128/// Further details:
129/// ================
130/// The current implementation of packing (i.e. data tiling) consists of
131/// rewriting a linearized strip-mined form into a higher-dimensional access.
132/// e.g. consider an access `A[I][f(j, k, l)]` and packing by 4; we rewrite
133/// `I` into `4 * i + ii`, where `0 <= ii < 4`.
134/// The access is further rewritten as `A[i][f(j, k, l)][ii]`.
135///
136/// This rewrite into higher dimensional access is not possible for general
137/// AffineExpr in Linalg atm, it is restricted to an AffineDimExpr:
138/// e.g. consider an access `A[I + J][f(j, k, l)]` and packing by 4; we
139/// rewrite `I + J` into `4 * i + ii + J`, where `0 <= ii < 4`.
140/// The rewrite of the access would be a form not representable in Linalg:
141/// `A[i + (ii + J) / 4][f(j, k, l)][(ii + J) % 4]`.
142/// Note however that as `J` and `ii` iterate, the accesses do not have a
143/// particular alignment, so packing does not achieve alignment in this case
144///
145/// In the future, we may want to consider a mixed-form that allows some
146/// alignment in the presence of multiple accesses:
147/// `A[I][f(j, k, l)]` and `B[I + J][f(j, k, l)]`
148/// And would rewrite accesses as:
149/// `A[i][f(j, k, l)][ii]` and `B[4 * i + ii + J][f(j, k, l)]`
150static FailureOr<SmallVector<std::optional<int64_t>>>
153 int64_t dim) {
154 int64_t newDim = iteratorTypes.size();
155 iteratorTypes.push_back(iteratorTypes[dim]);
156
157 SmallVector<std::optional<int64_t>> packedDimPerIndexingMap(
158 indexingMaps.size(), std::nullopt);
160 for (int64_t operandIdx = 0, e = indexingMaps.size(); operandIdx < e;
161 ++operandIdx) {
162 AffineMap map = indexingMaps[operandIdx];
163
164 // Add the `newDim` to map whatever the case.
165 assert(map.getNumDims() == newDim && "num dims invariant violation");
166 map = map.shiftDims(1, newDim);
167
168 // Get the at-most-1 index of the result that is a function of `dim`.
169 // If we can find one, we insert `AffineDimExpr(newDim)` to the map, which
170 // logically chunks dimension `dim` into `K * dim + newDim`, where the
171 // packing factor `K` is specified separately.
172 assert(hasAtMostOneResultFunctionOfDim(map, dim) &&
173 "num results invariant violation");
174 auto maybeOperandDimensionToPack = getFirstResultIndexFunctionOf(map, dim);
175 if (!maybeOperandDimensionToPack.has_value()) {
176 newMaps.push_back(map);
177 continue;
178 }
179
180 // We can only pack AffineDimExpr atm.
181 if (!isa<AffineDimExpr>(map.getResult(maybeOperandDimensionToPack.value())))
182 return failure();
183
184 // Add `newDim` to the results of the map.
185 map = map.insertResult(Builder(map.getContext()).getAffineDimExpr(newDim),
186 map.getNumResults());
187 newMaps.push_back(map);
188
189 // Record the that `operandIdx` is packed.
190 packedDimPerIndexingMap[operandIdx] = maybeOperandDimensionToPack;
191 }
192 indexingMaps = newMaps;
193
194 return packedDimPerIndexingMap;
195}
196
197namespace {
198
199/// Helper struct to encode packing along one dimension of a LinalgOp.
200struct PackedOperandsDim {
201 OpFoldResult packedSize;
202 SmallVector<std::optional<int64_t>> packedDimForEachOperand;
203};
204
205/// Helper struct to encode packing along all dimensions of a LinalgOp.
206struct PackedOperandsDimList {
207 void pushBack(PackedOperandsDim &&packedOperandsDims) {
208 spec.emplace_back(packedOperandsDims);
209 }
210 /// Return all the dims that have been packed for operand @ `operandPos`.
211 SmallVector<int64_t> extractPackedDimsForOperand(int64_t operandPos);
212 /// Return all the pack sizes by which an operand @ `operandPos` is packed.
213 SmallVector<OpFoldResult> extractPackSizesForOperand(int64_t operandPos);
214
215private:
216 SmallVector<PackedOperandsDim> spec;
217};
218
219} // namespace
220
221/// Rewrite index operations in a packed Linalg op to preserve their values in
222/// the original iteration space. Packing loop dimension `d` by `tileSize`
223/// splits its index into an outer and inner index, so the original index is
224/// `outer * tileSize + inner`.
225static void
226remapPackedIndexOps(RewriterBase &rewriter, LinalgOp packedLinalgOp,
227 ArrayRef<OpFoldResult> packedSizes,
228 ArrayRef<std::optional<int64_t>> packedLoopToInnerLoop) {
229 if (!packedLinalgOp.hasIndexSemantics())
230 return;
231
232 SmallVector<IndexOp> indexOps =
233 llvm::to_vector(packedLinalgOp.getBlock()->getOps<IndexOp>());
234 for (IndexOp indexOp : indexOps) {
235 unsigned dim = indexOp.getDim();
236 assert(dim < packedLoopToInnerLoop.size() && "invalid linalg.index dim");
237 std::optional<int64_t> innerDim = packedLoopToInnerLoop[dim];
238 if (!innerDim)
239 continue;
240
241 OpBuilder::InsertionGuard guard(rewriter);
242 rewriter.setInsertionPoint(indexOp);
243 Value outerIndex =
244 IndexOp::create(rewriter, indexOp.getLoc(), dim).getResult();
245 Value innerIndex =
246 IndexOp::create(rewriter, indexOp.getLoc(), *innerDim).getResult();
247
248 AffineExpr outer, inner, tileSize;
249 bindDims(rewriter.getContext(), outer, inner);
250 bindSymbols(rewriter.getContext(), tileSize);
252 rewriter, indexOp.getLoc(), outer * tileSize + inner,
253 {outerIndex, innerIndex, packedSizes[dim]});
255 rewriter, indexOp.getLoc(), originalIndex));
256 }
257}
258
259FailureOr<LowerPackResult> linalg::lowerPack(RewriterBase &rewriter,
260 linalg::PackOp packOp,
261 bool lowerPadLikeWithInsertSlice) {
262 // Pack/unpack memref transformations are unsupported. The memref forms
263 // are mainly for bufferization and scalar lowering. Other uses are not
264 // recommended, see #225650 for details.
265 if (!packOp.hasPureTensorSemantics())
266 return failure();
267
268 auto packedTensorType =
269 cast<RankedTensorType>(packOp->getResultTypes().front());
270
271 Location loc = packOp->getLoc();
272 OpBuilder::InsertionGuard g(rewriter);
273 rewriter.setInsertionPoint(packOp);
274
275 // 2. Compute the permutation vector to shuffle packed shape into the shape
276 // before any outer or inner permutations have been applied.
277 PackingMetadata packingMetadata;
278 SmallVector<int64_t> packedToStripMinedShapePerm =
279 getPackInverseDestPerm(packOp, packingMetadata);
280
281 // 3. Compute the stripMinedShape: this is the packed shape before any outer
282 // or inner permutations have been applied.
283 SmallVector<int64_t> stripMinedShape(packedTensorType.getShape());
284 applyPermutationToVector(stripMinedShape, packedToStripMinedShapePerm);
285
286 // Also compute the mixed (static+dynamic) strip-mined sizes for the
287 // expand_shape output. This is needed to support dynamic inner tile sizes,
288 // since the shapes cannot be inferred automatically when multiple dynamic
289 // dims appear in a single reassociation group during ExpandShapeOp
290 // construction.
291 SmallVector<OpFoldResult> stripMinedMixedSizes =
292 tensor::getMixedSizes(rewriter, loc, packOp.getDest());
293 applyPermutationToVector(stripMinedMixedSizes, packedToStripMinedShapePerm);
294
295 // 4. Pad the source of packOp to a shape we can expand into stripMinedShape.
296 SmallVector<OpFoldResult> lows(packOp.getSourceRank(),
297 rewriter.getIndexAttr(0));
298 SmallVector<OpFoldResult> highs(packOp.getSourceRank(),
299 rewriter.getIndexAttr(0));
300 for (auto [pos, innerSize] :
301 llvm::zip_equal(packOp.getInnerDimsPos(), packOp.getMixedTiles())) {
302 int outerPos =
303 packedToStripMinedShapePerm[packingMetadata.outerPositions[pos]];
304 OpFoldResult origSize =
305 tensor::getMixedSize(rewriter, loc, packOp.getSource(), pos);
306 OpFoldResult outerSize =
307 tensor::getMixedSize(rewriter, loc, packOp.getDest(), outerPos);
308 AffineExpr s0, d0, d1;
309 bindDims(rewriter.getContext(), d0, d1);
310 bindSymbols(rewriter.getContext(), s0);
311 auto map = AffineMap::get(/*dimCount=*/2, /*symbolCount=*/1, d0 * s0 - d1);
313 rewriter, loc, map, {outerSize, origSize, innerSize});
314 }
315 RankedTensorType collapsed = tensor::CollapseShapeOp::inferCollapsedType(
316 RankedTensorType::Builder(packedTensorType).setShape(stripMinedShape),
317 packingMetadata.reassociations);
318 Value paddingValue = packOp.getPaddingValue();
319 if (!paddingValue) {
320 paddingValue = arith::ConstantOp::create(
321 rewriter, loc, rewriter.getZeroAttr(getElementTypeOrSelf(collapsed)));
322 }
323 auto padOp =
324 tensor::PadOp::create(rewriter, loc, collapsed, packOp.getSource(), lows,
325 highs, paddingValue, /*nofold=*/false);
326
327 LDBG() << "insertPositions: "
328 << llvm::interleaved(packingMetadata.insertPositions);
329 LDBG() << "outerPositions: "
330 << llvm::interleaved(packingMetadata.outerPositions);
331 LDBG() << "packedShape: " << llvm::interleaved(packedTensorType.getShape());
332 LDBG() << "packedToStripMinedShapePerm: "
333 << llvm::interleaved(packedToStripMinedShapePerm);
334 LDBG() << "reassociations: "
335 << llvm::interleaved(llvm::map_range(packingMetadata.reassociations,
337 LDBG() << "stripMinedShape: " << llvm::interleaved(stripMinedShape);
338 LDBG() << "collapsed type: " << collapsed;
339
340 if (lowerPadLikeWithInsertSlice && packOp.isLikePad()) {
341 // Pack ops which operate as simple pads may not produce legal
342 // tensor.insert_slice operations when the packed type does not rank reduce
343 // to the padded type.
344 SliceVerificationResult rankReduces =
345 isRankReducedType(packedTensorType, padOp.getResultType());
346
347 if (rankReduces == SliceVerificationResult::Success) {
348 // This pack is just a plain pad.
349 // Just insert the pad in the higher ranked tensor.
350 // Offsets.
351 SmallVector<OpFoldResult> zeros(packOp.getDestRank(),
352 rewriter.getIndexAttr(0));
353 // Strides.
354 SmallVector<OpFoldResult> ones(packOp.getDestRank(),
355 rewriter.getIndexAttr(1));
357 tensor::getMixedSizes(rewriter, loc, packOp.getDest());
358
359 auto insertSliceOp = tensor::InsertSliceOp::create(
360 rewriter, loc, /*source=*/padOp, /*dest=*/packOp.getDest(),
361 /*offsets=*/zeros, sizes, /*strides=*/ones);
362
363 LDBG() << "insert_slice op: " << insertSliceOp;
364
365 rewriter.replaceOp(packOp, insertSliceOp->getResults());
366
367 return LowerPackResult{padOp, /*reshapeOp=*/nullptr,
368 /*transposeOp=*/nullptr};
369 }
370 }
371
372 // 5. Expand from the padded result to the stripMinedShape.
373 auto expandShapeResultType =
374 RankedTensorType::Builder(packedTensorType).setShape(stripMinedShape);
375 auto reshapeOp = tensor::ExpandShapeOp::create(
376 rewriter, loc, expandShapeResultType, padOp.getResult(),
377 packingMetadata.reassociations, stripMinedMixedSizes);
378
379 // 6. Transpose stripMinedShape to packedShape.
380 SmallVector<int64_t> transpPerm =
381 invertPermutationVector(packedToStripMinedShapePerm);
382 auto transposeOp = linalg::TransposeOp::create(
383 rewriter, loc, reshapeOp.getResult(), packOp.getDest(), transpPerm);
384
385 LDBG() << "reshape op: " << reshapeOp;
386 LDBG() << "transpPerm: " << llvm::interleaved(transpPerm);
387 LDBG() << "transpose op: " << transposeOp;
388
389 // 7. Replace packOp by transposeOp.
390 rewriter.replaceOp(packOp, transposeOp->getResults());
391
392 return LowerPackResult{padOp, reshapeOp, transposeOp};
393}
394
395FailureOr<LowerUnPackOpResult>
396linalg::lowerUnPack(RewriterBase &rewriter, linalg::UnPackOp unPackOp,
397 bool lowerUnpadLikeWithExtractSlice) {
398 // Pack/unpack memref transformations are unsupported. The memref forms
399 // are mainly for bufferization and scalar lowering. Other uses are not
400 // recommended, see #225650 for details.
401 if (!unPackOp.hasPureTensorSemantics())
402 return failure();
403
404 Location loc = unPackOp->getLoc();
405 OpBuilder::InsertionGuard g(rewriter);
406 rewriter.setInsertionPoint(unPackOp);
407
408 auto packedTensorType = cast<RankedTensorType>(unPackOp.getSourceType());
409 int64_t packedRank = packedTensorType.getRank();
410
411 OpFoldResult zero = rewriter.getIndexAttr(0), one = rewriter.getIndexAttr(1);
412 auto destTensorType = cast<RankedTensorType>(unPackOp.getDest().getType());
413 if (lowerUnpadLikeWithExtractSlice && unPackOp.isLikeUnPad()) {
414 // This unpack is just a plain unpad.
415 // Just extract the slice from the higher ranked tensor.
416 ArrayRef<int64_t> destShape = destTensorType.getShape();
417 // The inner dimensions stay the same as the destination tensor, but the
418 // outer ones are additional 1s.
419 SmallVector<OpFoldResult> sizes(packedRank - destShape.size(), one);
420 sizes.append(tensor::getMixedSizes(rewriter, loc, unPackOp.getDest()));
421
422 auto extractSliceOp = tensor::ExtractSliceOp::create(
423 rewriter, loc, destTensorType, unPackOp.getSource(),
424 SmallVector<OpFoldResult>(packedRank, zero), sizes,
425 SmallVector<OpFoldResult>(packedRank, one));
426
427 rewriter.replaceOp(unPackOp, extractSliceOp->getResults());
428
429 return LowerUnPackOpResult{/*emptyOp=*/nullptr, /*transposeOp=*/nullptr,
430 /*reshapeOp=*/nullptr, extractSliceOp,
431 /*copyOp=*/nullptr};
432 }
433
434 // 1. Compute the permutation vector to shuffle packed shape into the shape
435 // before any outer or inner permutations have been applied.
436 PackingMetadata packingMetadata;
437 SmallVector<int64_t> packedToStripMinedShapePerm =
438 getUnPackInverseSrcPerm(unPackOp, packingMetadata);
439
440 // 2. Compute the stripMinedShape: this is the packed shape without outer and
441 // inner permutations.
442 SmallVector<int64_t> stripMinedShape(packedTensorType.getShape());
443 applyPermutationToVector(stripMinedShape, packedToStripMinedShapePerm);
444
445 // 3. Transpose packedShape to stripMinedShape.
446 RankedTensorType stripMinedTensorType =
447 RankedTensorType::Builder(packedTensorType).setShape(stripMinedShape);
448 RankedTensorType collapsedType = tensor::CollapseShapeOp::inferCollapsedType(
449 stripMinedTensorType, packingMetadata.reassociations);
450
451 // Get dynamic dims from input tensor based on packedToStripMinedShapePerm
452 // permutation.
454 tensor::getMixedSizes(rewriter, loc, unPackOp.getSource());
455 applyPermutationToVector(dims, packedToStripMinedShapePerm);
456 auto emptyOp = tensor::EmptyOp::create(rewriter, loc, dims,
457 stripMinedTensorType.getElementType());
458 auto transposeOp =
459 linalg::TransposeOp::create(rewriter, loc, unPackOp.getSource(), emptyOp,
460 packedToStripMinedShapePerm);
461
462 LDBG() << "insertPositions: "
463 << llvm::interleaved(packingMetadata.insertPositions);
464 LDBG() << "packedShape: " << llvm::interleaved(packedTensorType.getShape());
465 LDBG() << "packedToStripMinedShapePerm: "
466 << llvm::interleaved(packedToStripMinedShapePerm);
467 LDBG() << "reassociations: "
468 << llvm::interleaved(llvm::map_range(packingMetadata.reassociations,
470 LDBG() << "stripMinedShape: " << llvm::interleaved(stripMinedShape);
471 LDBG() << "collapsed type: " << collapsedType;
472
473 // 4. Collapse from the stripMinedShape to the padded result.
474 auto reshapeOp = tensor::CollapseShapeOp::create(
475 rewriter, loc, collapsedType, transposeOp->getResult(0),
476 packingMetadata.reassociations);
477
478 // 5. ExtractSlice.
479 int64_t destRank = destTensorType.getRank();
480 auto extractSliceOp = tensor::ExtractSliceOp::create(
481 rewriter, loc, destTensorType, reshapeOp->getResult(0),
482 SmallVector<OpFoldResult>(destRank, zero),
483 tensor::getMixedSizes(rewriter, loc, unPackOp.getDest()),
484 SmallVector<OpFoldResult>(destRank, one));
485
486 // 6. Inject a copy to preserve DPS.
487 auto copyOp = linalg::CopyOp::create(
488 rewriter, loc, extractSliceOp->getResult(0), unPackOp.getDest());
489
490 // 7. Replace unPackOp by copyOp.
491 rewriter.replaceOp(unPackOp, copyOp->getResults());
492
493 return LowerUnPackOpResult{emptyOp, transposeOp, reshapeOp, extractSliceOp,
494 copyOp};
495}
496
498PackedOperandsDimList::extractPackedDimsForOperand(int64_t operandPos) {
500 for (auto &i : spec) {
501 if (!i.packedDimForEachOperand[operandPos].has_value())
502 continue;
503 res.push_back(i.packedDimForEachOperand[operandPos].value());
504 }
505 return res;
506}
507
508SmallVector<OpFoldResult>
509PackedOperandsDimList::extractPackSizesForOperand(int64_t operandPos) {
510 SmallVector<OpFoldResult> res;
511 for (auto &i : spec) {
512 if (!i.packedDimForEachOperand[operandPos].has_value())
513 continue;
514 res.push_back(i.packedSize);
515 }
516 return res;
517}
518
519/// Implement packing of a single LinalgOp by performing packing by
520/// `packedSizes`. There must be one packedSizes entry per `linalgOp` iterator.
521/// Return the packed Linalg op on success, failure otherwise.
522FailureOr<PackResult> linalg::pack(RewriterBase &rewriter,
523 linalg::LinalgOp linalgOp,
524 ArrayRef<OpFoldResult> packedSizes) {
525 if (packedSizes.size() != linalgOp.getNumLoops()) {
526 return rewriter.notifyMatchFailure(linalgOp,
527 "incorrect number of pack sizes");
528 }
529 if (!linalgOp.hasPureTensorSemantics()) {
530 return rewriter.notifyMatchFailure(
531 linalgOp, "expects LinalgOp with pure tensor semantics");
532 }
533
534 Location loc = linalgOp->getLoc();
535 SmallVector<AffineMap> indexingMaps = linalgOp.getIndexingMapsArray();
537 linalgOp.getIteratorTypesArray();
538 LDBG() << "Start packing: " << linalgOp;
539 LDBG() << "maps: " << llvm::interleaved(indexingMaps);
540 LDBG() << "iterators: " << llvm::interleaved(iteratorTypes);
541
544 // Step 1. Pack each dim of the LinalgOp metadata by packedSizes[i].
545 PackedOperandsDimList listOfPackedOperandsDim;
546 SmallVector<std::optional<int64_t>> packedLoopToInnerLoop(packedSizes.size());
547 for (int64_t i = 0, e = packedSizes.size(); i < e; ++i) {
548 std::optional<int64_t> maybeConstant = getConstantIntValue(packedSizes[i]);
549 // Skip tile sizes explicitly set to 0.
550 if (maybeConstant.has_value() && maybeConstant.value() == 0)
551 continue;
552
553 PackedOperandsDim packedOperandsDims;
554 packedOperandsDims.packedSize = packedSizes[i];
555 FailureOr<SmallVector<std::optional<int64_t>>>
556 maybePackedDimForEachOperand =
557 packLinalgMetadataOnce(indexingMaps, iteratorTypes, i);
558 if (failed(maybePackedDimForEachOperand))
559 return failure();
560 packedLoopToInnerLoop[i] = iteratorTypes.size() - 1;
561 packedOperandsDims.packedDimForEachOperand = *maybePackedDimForEachOperand;
562
563 LDBG() << "++++ After pack size #" << i << ": " << packedSizes[i];
564 LDBG() << "maps: " << llvm::interleaved(indexingMaps);
565 LDBG() << "iterators: " << llvm::interleaved(iteratorTypes);
566 LDBG() << "packedDimForEachOperand: "
567 << llvm::interleaved(packedOperandsDims.packedDimForEachOperand);
568
569 listOfPackedOperandsDim.pushBack(std::move(packedOperandsDims));
570 }
571
572 // Step 2. Propagate packing to all LinalgOp operands.
573 SmallVector<Value> inputsAndInits, results;
574 SmallVector<OpOperand *> initOperands =
575 llvm::to_vector(llvm::make_pointer_range(linalgOp.getDpsInitsMutable()));
576 SmallVector<OpOperand *> inputOperands = linalgOp.getDpsInputOperands();
577 for (const auto &operandsList : {inputOperands, initOperands}) {
578 for (OpOperand *opOperand : operandsList) {
579 int64_t pos = opOperand->getOperandNumber();
580 Value operand = opOperand->get();
581 SmallVector<int64_t> innerPos =
582 listOfPackedOperandsDim.extractPackedDimsForOperand(pos);
583 SmallVector<OpFoldResult> innerPackSizes =
584 listOfPackedOperandsDim.extractPackSizesForOperand(pos);
585 LDBG() << "operand: " << operand;
586 LDBG() << "innerPos: " << llvm::interleaved(innerPos);
587 LDBG() << "innerPackSizes: " << llvm::interleaved(innerPackSizes);
588 if (innerPackSizes.empty()) {
589 inputsAndInits.push_back(operand);
590 continue;
591 }
592 Value dest = linalg::PackOp::createDestinationTensor(
593 rewriter, loc, operand, innerPackSizes, innerPos,
594 /*outerDimsPerm=*/{});
595 ShapedType operandType = cast<ShapedType>(operand.getType());
596 bool areConstantTiles =
597 llvm::all_of(innerPackSizes, [](OpFoldResult tile) {
598 return getConstantIntValue(tile).has_value();
599 });
600 if (areConstantTiles && operandType.hasStaticShape() &&
601 !linalg::PackOp::requirePaddingValue(
602 operandType.getShape(), innerPos,
603 cast<ShapedType>(dest.getType()).getShape(), {},
604 innerPackSizes)) {
605 packOps.push_back(linalg::PackOp::create(rewriter, loc, operand, dest,
606 innerPos, innerPackSizes));
607 } else {
608 // TODO: value of the padding attribute should be determined by
609 // consumers.
610 auto zeroAttr =
611 rewriter.getZeroAttr(getElementTypeOrSelf(dest.getType()));
612 Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
613 packOps.push_back(linalg::PackOp::create(
614 rewriter, loc, operand, dest, innerPos, innerPackSizes, zero));
615 }
616 inputsAndInits.push_back(packOps.back().getResult());
617 }
618 }
619
620 // Step 3. Build the packed op, use the type of `inits` as result types.
621 ValueRange inputs =
622 ValueRange{inputsAndInits}.take_front(linalgOp.getNumDpsInputs());
623 ValueRange inits =
624 ValueRange{inputsAndInits}.take_back(linalgOp.getNumDpsInits());
625 auto packedLinalgOp =
626 linalg::GenericOp::create(rewriter, linalgOp.getLoc(), inits.getTypes(),
627 inputs, inits, indexingMaps, iteratorTypes);
628 packedLinalgOp.getRegion().takeBody(linalgOp->getRegion(0));
629 remapPackedIndexOps(rewriter, packedLinalgOp, packedSizes,
630 packedLoopToInnerLoop);
631
632 // Step 4. Propagate packing to all the op results.
633 for (OpResult result : packedLinalgOp->getResults()) {
634 int64_t resultNum = result.getResultNumber();
635 linalg::PackOp maybePackedInit =
636 inits[resultNum].getDefiningOp<linalg::PackOp>();
637 if (!maybePackedInit) {
638 results.push_back(result);
639 continue;
640 }
641 // Build the symmetrical UnPackOp to the existing PackOp.
642 unPackOps.push_back(linalg::UnPackOp::create(
643 rewriter, packedLinalgOp->getLoc(), result, maybePackedInit.getSource(),
644 maybePackedInit.getInnerDimsPos(), maybePackedInit.getMixedTiles()));
645 results.push_back(unPackOps.back().getResult());
646 }
647
648 // Step 5. Replace `linalgOp`.
649 rewriter.replaceOp(linalgOp, results);
650
651 // Return packedLinalgOp.
652 return PackResult{packOps,
653 cast<linalg::LinalgOp>(packedLinalgOp.getOperation()),
654 unPackOps};
655}
656
657//===----------------------------------------------------------------------===//
658// packTranspose transformation.
659//===----------------------------------------------------------------------===//
660
661/// Return a copy of `tensorType` after permutation by `permutationVector`.
662// Note: Should be a new method in of MemRef/RankedTensor/VectorType::Builder
663// but this would introduce a dependence on Dialect in IR.
664// TODO: Restructure.
665static RankedTensorType permuteShape(RankedTensorType tensorType,
666 ArrayRef<int64_t> permutationVector) {
667 SmallVector<int64_t> shape(tensorType.getShape());
668 applyPermutationToVector(shape, permutationVector);
669 return RankedTensorType::Builder(tensorType).setShape(shape);
670}
671
672/// Return a new GenericOp obtained by transposing opOperand by the permutation
673/// vector:
674/// - the corresponding indexing map is transposed by `permutation`
675/// - the corresponding operand value is replaced by `transposedValue`
676/// `linalgOp` is replaced by the return op in the process.
677/// Asserts that `transposedValue` is of the proper transposed ShapedType.
679 RewriterBase &rewriter, LinalgOp linalgOp, OpOperand &opOperand,
680 ArrayRef<int64_t> permutation, Value transposedValue) {
681 // Sanity check the operand.
682 assert(linalgOp == opOperand.getOwner() && "linalg op must own the operand");
683
684 // Sanity check of the expected transposed tensor type.
685 auto tensorType = permuteShape(
686 cast<RankedTensorType>(opOperand.get().getType()), permutation);
687 (void)tensorType;
688 assert(tensorType == transposedValue.getType() &&
689 "expected tensor type mismatch");
690
691 // Compute the transposed indexing map.
692 // Sigh unsigned pollution.
693 SmallVector<unsigned> tmpTransposition =
694 llvm::map_to_vector(permutation, [](int64_t i) -> unsigned { return i; });
695 AffineMap permutationMap =
696 AffineMap::getPermutationMap(tmpTransposition, rewriter.getContext());
697 AffineMap transposedMap =
698 permutationMap.compose(linalgOp.getMatchingIndexingMap(&opOperand));
699
700 // Set the transposed indexing map in the proper position.
701 SmallVector<AffineMap> indexingMaps = linalgOp.getIndexingMapsArray();
702 indexingMaps[linalgOp.getIndexingMapIndex(&opOperand)] = transposedMap;
703 // Set the transposedValue in the proper operand position.
704 SmallVector<Value> operands = linalgOp->getOperands();
705 operands[opOperand.getOperandNumber()] = transposedValue;
706
707 ValueRange operandsRef(operands);
708 auto transposedGenericOp = linalg::GenericOp::create(
709 rewriter,
710 /*location=*/linalgOp->getLoc(),
711 /*resultTensorTypes=*/
712 operandsRef.drop_front(linalgOp.getNumDpsInputs()).getTypes(),
713 /*inputs=*/operandsRef.take_front(linalgOp.getNumDpsInputs()),
714 /*outputs=*/operandsRef.drop_front(linalgOp.getNumDpsInputs()),
715 /*indexingMaps=*/indexingMaps,
716 /*iteratorTypes=*/linalgOp.getIteratorTypesArray());
717 transposedGenericOp.getRegion().takeBody(linalgOp->getRegion(0));
718 rewriter.replaceOp(linalgOp, transposedGenericOp->getResults());
719
720 return cast<linalg::LinalgOp>(transposedGenericOp.getOperation());
721}
722
723FailureOr<PackTransposeResult>
724linalg::packTranspose(RewriterBase &rewriter, linalg::PackOp packOp,
725 linalg::LinalgOp linalgOp, linalg::UnPackOp maybeUnPackOp,
726 ArrayRef<int64_t> outerPerm,
727 ArrayRef<int64_t> innerPerm) {
728 Location loc = linalgOp.getLoc();
729
730 // Step 1. Transpose packOp.
731 rewriter.setInsertionPoint(packOp);
732 linalg::PackOp transposedPackOp =
733 packOp.createTransposedClone(rewriter, loc, innerPerm, outerPerm);
734
735 if (packOp.hasPureBufferSemantics() || !packOp.getResult().hasOneUse())
736 return rewriter.notifyMatchFailure(linalgOp, "expect single pack use");
737
738 OpOperand &packUse = *packOp->getUses().begin();
739 if (packUse.getOwner() != linalgOp) {
740 return rewriter.notifyMatchFailure(
741 linalgOp, "not a single use by the LinalgOp target");
742 }
743 if (maybeUnPackOp &&
744 (!linalgOp.isDpsInit(&packUse) ||
745 maybeUnPackOp.getSource() != linalgOp.getTiedOpResult(&packUse))) {
746 return rewriter.notifyMatchFailure(linalgOp,
747 "not produced by the LinalgOp target");
748 }
749
750 // Step 2. Transpose linalgOp.
751 // transposedPackOp.getOuterDimsPerm() may be empty, in which case it is the
752 // identity. Don't rely on it.
753 int64_t numLeadingDims = packOp.getSourceRank();
754 int64_t numTrailingDims = packOp.getInnerDimsPos().size();
755 // Step 2.a. Compute the permutation on the whole operand.
756 // Leading part just reuse the outerPerm.
757 SmallVector<int64_t> permutation(outerPerm);
758 if (permutation.empty())
759 llvm::append_range(permutation, llvm::seq<int64_t>(0, numLeadingDims));
760 // Trailing part needs to reindex positions by `numLeadingDims`.
761 if (innerPerm.empty()) {
762 llvm::append_range(
763 permutation,
764 llvm::seq<int64_t>(numLeadingDims, numLeadingDims + numTrailingDims));
765 } else {
766 llvm::append_range(permutation,
767 llvm::map_range(innerPerm, [&](int64_t pos) {
768 return numLeadingDims + pos;
769 }));
770 }
771 if (!isPermutationVector(permutation))
772 return rewriter.notifyMatchFailure(linalgOp, "invalid permutation");
773
774 // Step 2.b. Save the transposedPackUse operand number in case we need to
775 // get the tied OpResult after `linalgOp` has been replaced.
776 int64_t packUseOperandNumber = packUse.getOperandNumber();
777 // Step 2.c. Actually perform the transposition.
778 rewriter.setInsertionPoint(linalgOp);
779 linalg::LinalgOp transposedLinalgOp = transposeOneLinalgOperandAndReplace(
780 rewriter, linalgOp, packUse, permutation, transposedPackOp.getResult());
781
782 // Step 3. Maybe transpose unPackOp.
783 linalg::UnPackOp transposedUnPackOp;
784 if (maybeUnPackOp) {
785 OpOperand &opOperand =
786 transposedLinalgOp->getOpOperand(packUseOperandNumber);
787 OpResult transposedResult = transposedLinalgOp.getTiedOpResult(&opOperand);
788 rewriter.setInsertionPoint(maybeUnPackOp);
789 transposedUnPackOp = maybeUnPackOp.createTransposedClone(
790 rewriter, loc, transposedResult, innerPerm, outerPerm);
791
792 rewriter.replaceOp(maybeUnPackOp, transposedUnPackOp->getResults());
793 }
794
795 // Step 4. Finally, replace packOp now that we don't need it anymore.
796 if (packOp.hasPureTensorSemantics())
797 rewriter.replaceOp(packOp, transposedPackOp->getResults());
798 else
799 rewriter.eraseOp(packOp);
800
801 return PackTransposeResult{transposedPackOp, transposedLinalgOp,
802 transposedUnPackOp};
803}
804
805//===----------------------------------------------------------------------===//
806// packMatmulGreedily transformation.
807//===----------------------------------------------------------------------===//
808
809/// Pack a LinalgOp by greedily inferring matmul dimensions (m, n, k) where m
810/// and n are proper parallel dimensions and k is a proper reduction
811/// dimension. Packing occurs by rewriting the op as a linalg.generic and
812/// calling linalg::pack by `mnkPackedSizes`. The order of the packed
813/// dimensions is customizable: the `mnkOrder` is a permutation of {0, 1, 2}
814/// to reorder {m, n, k} into one of the 8 possible forms. The outer
815/// dimensions of the operands are not permuted at this time, this is left for
816/// future work.
817FailureOr<PackResult>
818linalg::packMatmulGreedily(RewriterBase &rewriter, LinalgOp linalgOp,
819 ArrayRef<OpFoldResult> mnkPackedSizes,
820 ArrayRef<int64_t> mnkPaddedSizesNextMultipleOf,
821 ArrayRef<int64_t> mnkOrder) {
822 assert(mnkPackedSizes.size() == 3 && "unexpected num of packing sizes");
823 assert((mnkPaddedSizesNextMultipleOf.empty() ||
824 mnkPaddedSizesNextMultipleOf.size() == 3) &&
825 "num of packing sizes next multiple should be empty or of size 3");
826 assert(mnkOrder.size() == 3 && "unexpected mnkOrder size");
827 assert(isPermutationVector(mnkOrder) && "expected a permutation");
828
829 int64_t numLoops = linalgOp.getNumLoops();
830 if (numLoops <= 2) {
831 LDBG() << "need 3+ loops to find a matmul to pack, got " << numLoops
832 << " in: " << linalgOp;
833 return rewriter.notifyMatchFailure(
834 linalgOp, "need 3+ loops to find a matmul to pack");
835 }
836
837 // Locally adjust the desired iterator position of mnk and packing sizes.
838 int64_t numPackedDims = mnkPackedSizes.size();
839 SmallVector<int64_t> mmnnkkPos(numPackedDims);
840 for (int64_t i = 0, e = numPackedDims; i < e; ++i)
841 mmnnkkPos[i] = numLoops - numPackedDims + mnkOrder[i];
842 SmallVector<OpFoldResult> packedSizes(numPackedDims);
843 for (int64_t i = 0, e = numPackedDims; i < e; ++i)
844 packedSizes[mnkOrder[i]] = mnkPackedSizes[i];
845 SmallVector<int64_t> paddedSizesNextMultipleOf(numPackedDims);
846 for (int64_t i = 0, e = numPackedDims; i < e; ++i) {
847 paddedSizesNextMultipleOf[mnkOrder[i]] =
848 mnkPaddedSizesNextMultipleOf.empty() ? 0
849 : mnkPaddedSizesNextMultipleOf[i];
850 }
851
852 // 1. Infer dims that are important for matmul.
853 FailureOr<ContractionDimensions> maybeDimensions =
854 inferContractionDims(linalgOp);
855 if (failed(maybeDimensions)) {
856 LDBG() << "couldn't infer matmul iterators in: " << linalgOp;
857 return rewriter.notifyMatchFailure(linalgOp,
858 "couldn't infer matmul iterators");
859 }
860
861 // 2. Normalize linalgOp to an kmn-matmul-like with [red, par, par] most
862 // minor iterators. In cases with multiple options for m, n, k bias towards
863 // the most minor embedding.
864 // If we wanted a different normalization order, this is where it would have
865 // to plug a heuristic.
866 int64_t mPos = maybeDimensions->m.back(), nPos = maybeDimensions->n.back(),
867 kPos = maybeDimensions->k.back();
868 LDBG() << "Start packing generic op greedily with (m@" << mPos << ", n@"
869 << nPos << ", k@" << kPos << "): " << linalgOp;
870
871 // 2.a. Rewrite as a generic.
872 auto genericOp = dyn_cast<GenericOp>(linalgOp.getOperation());
873 if (!genericOp) {
874 FailureOr<LinalgOp> generalizeResult =
875 generalizeNamedOp(rewriter, linalgOp);
876 assert(succeeded(generalizeResult) &&
877 isa<GenericOp>(generalizeResult->getOperation()) &&
878 "unexpected failure generalizing op");
879 genericOp = cast<GenericOp>(generalizeResult->getOperation());
880 }
881
882 // 2.b. Interchange to move the dimensions (k, m, n) as most-minor
883 // iterators. Note that this only normalized the iteration order and does
884 // not change the indexings of any operand.
885 SmallVector<int64_t> permutation =
886 computePermutationVector(numLoops, {mPos, nPos, kPos}, mmnnkkPos);
887 LDBG() << "perm: " << llvm::interleaved(permutation);
888 // Sign .. unsigned pollution.
889 SmallVector<unsigned> unsignedPerm(permutation.begin(), permutation.end());
890 FailureOr<GenericOp> interchangeResult =
891 interchangeGenericOp(rewriter, genericOp, unsignedPerm);
892 assert(succeeded(interchangeResult) && "unexpected failure interchanging op");
893 genericOp = *interchangeResult;
894 LDBG() << "Generalized Op to pack: " << genericOp;
895
896 // At this point, the op iterators are normalized to {leading, k, m, n}.
897 // The layouts induced by packing will always be:
898 // - LHS{leading_lhs, kk, mm}
899 // - RHS{leading_rhs, kk, nn}
900 // - RES{leading_res, mm, nn}
901 // If we wanted to change the packed order, we would reorder (k, m, n) to
902 // something else above.
903 //
904 // Additional permutations of the outer dims of the operands (i.e.
905 // leading_lhs, leading_rhs and leading_res) could follow by computing the
906 // desired outerPerm for each operand.
907 // This is left for future work.
908
909 // TODO: this creates too much IR, go use reifyResultShapes.
910 SmallVector<Range, 4> loopRanges =
911 cast<LinalgOp>(genericOp.getOperation())
912 .createLoopRanges(rewriter, genericOp.getLoc());
913
914 // Add leading zeros to match numLoops, we only pack the last 3 dimensions
915 // post interchange.
916 LDBG() << "paddedSizesNextMultipleOf: "
917 << llvm::interleaved(paddedSizesNextMultipleOf);
918 LDBG() << "loopRanges: "
919 << llvm::interleaved(
920 llvm::map_range(loopRanges, [](Range r) { return r.size; }));
921 SmallVector<OpFoldResult> adjustedPackedSizes(numLoops - packedSizes.size(),
922 rewriter.getIndexAttr(0));
923 for (int64_t i = 0, e = numPackedDims; i < e; ++i) {
924 if (paddedSizesNextMultipleOf[i] == 0) {
925 adjustedPackedSizes.push_back(packedSizes[i]);
926 continue;
927 }
928 AffineExpr d0, s0;
929 bindDims(rewriter.getContext(), d0);
930 bindSymbols(rewriter.getContext(), s0);
931 adjustedPackedSizes.push_back(affine::makeComposedFoldedAffineApply(
932 rewriter, genericOp->getLoc(), d0.ceilDiv(s0) * s0,
933 {loopRanges[adjustedPackedSizes.size()].size,
934 rewriter.getIndexAttr(paddedSizesNextMultipleOf[i])}));
935 }
936 LDBG() << "adjustedPackedSizes: " << llvm::interleaved(adjustedPackedSizes);
937
938 // TODO: If we wanted to give the genericOp a name after packing, after
939 // calling `pack` would be a good time. One would still need to check that
940 // `containsMostMinorMatmul(packingRes->packedLinalgOp)` is true, since we
941 // also allow degenerate matmul cases (i.e. matvec, dot).
942 return pack(rewriter, genericOp, adjustedPackedSizes);
943}
944
945//===----------------------------------------------------------------------===//
946// Transformations exposed as rewrite patterns.
947//===----------------------------------------------------------------------===//
948
951 assert(!tileSizeComputationFunction && "tile sizes already set");
952 SmallVector<int64_t, 4> tileSizes(ts);
953 tileSizeComputationFunction = [tileSizes](OpBuilder &b, Operation *op) {
955 b.setInsertionPointToStart(
956 &op->getParentOfType<func::FuncOp>().getBody().front());
957 return llvm::map_to_vector<4>(tileSizes, [&](int64_t s) {
958 Value v = arith::ConstantIndexOp::create(b, op->getLoc(), s);
959 return v;
960 });
961 };
962 return *this;
963}
964
966 memref::CopyOp copyOp, PatternRewriter &rewriter) const {
967 return vectorizeCopy(rewriter, copyOp);
968}
969
970/// Filling `dest` using FillOp constant padding value if possible.
971/// Otherwise, generate a tensor::GenerateOp.
973 RewriterBase &rewriter, tensor::PadOp padOp, Value dest,
974 const SmallVector<Value> &dynSizes) const {
975 auto padValue = padOp.getConstantPaddingValue();
976 if (padValue) {
977 // Move the padding value defined inside the PadOp block to outside.
978 if (padValue.getParentBlock() == &padOp.getRegion().front())
979 rewriter.moveOpBefore(padValue.getDefiningOp(), padOp);
980 return FillOp::create(rewriter, padOp.getLoc(), padValue, dest).result();
981 }
982
983 // Fill could not be optimized: Lower to tensor::GenerateOp with region.
984 auto generateOp = tensor::GenerateOp::create(rewriter, padOp.getLoc(),
985 padOp.getResultType(), dynSizes);
986 // Copy region to new op.
987 IRMapping bvm;
988 padOp.getRegion().cloneInto(&generateOp.getRegion(), bvm);
989 return generateOp;
990}
991
992LogicalResult
994 PatternRewriter &rewriter) const {
995 // Given an OpFoldResult, return an index-typed value.
996 auto getIdxValue = [&](OpFoldResult ofr) {
997 if (auto val = llvm::dyn_cast_if_present<Value>(ofr))
998 return val;
1000 rewriter, padOp.getLoc(),
1001 cast<IntegerAttr>(cast<Attribute>(ofr)).getInt())
1002 .getResult();
1003 };
1004
1005 auto resultType = padOp.getResultType();
1006 // Compute size of EmptyOp. Any combination of static/dynamic is supported.
1007 SmallVector<Value> dynSizes;
1008 SmallVector<int64_t> staticSizes;
1009 for (unsigned dim = 0; dim < resultType.getRank(); ++dim) {
1010 if (resultType.isDynamicDim(dim)) {
1011 auto srcSize = getIdxValue(tensor::getMixedSize(rewriter, padOp.getLoc(),
1012 padOp.getSource(), dim));
1013 // Add low and high padding value.
1014 auto plusLow = rewriter.createOrFold<arith::AddIOp>(
1015 padOp.getLoc(), srcSize, getIdxValue(padOp.getMixedLowPad()[dim]));
1016 auto plusHigh = rewriter.createOrFold<arith::AddIOp>(
1017 padOp.getLoc(), plusLow, getIdxValue(padOp.getMixedHighPad()[dim]));
1018 dynSizes.push_back(plusHigh);
1019 }
1020 staticSizes.push_back(resultType.getDimSize(dim));
1021 }
1022
1023 // Init tensor and fill it with padding.
1024 Value emptyTensor =
1025 tensor::EmptyOp::create(rewriter, padOp.getLoc(), staticSizes,
1026 resultType.getElementType(), dynSizes);
1027 Value fill = createFillOrGenerateOp(rewriter, padOp, emptyTensor, dynSizes);
1028
1029 // Generate a InsertSliceOp for copying the PadOp source.
1030 auto sourceType = padOp.getSourceType();
1031 // Compute size of source of tensor::PadOp.
1032 SmallVector<OpFoldResult> srcSizes =
1033 tensor::getMixedSizes(rewriter, padOp.getLoc(), padOp.getSource());
1034 // Strides of InsertSliceOp are all 1.
1035 SmallVector<OpFoldResult> strides(sourceType.getRank(),
1036 rewriter.getIndexAttr(1));
1037 rewriter.replaceOpWithNewOp<tensor::InsertSliceOp>(
1038 padOp, padOp.getSource(), fill, padOp.getMixedLowPad(), srcSizes,
1039 strides);
1040
1041 return success();
1042}
1043
1045 tensor::ExtractSliceOp sliceOp, PatternRewriter &rewriter) const {
1046 if (!sliceOp.hasUnitStride())
1047 return failure();
1048
1049 auto padOp = sliceOp.getSource().getDefiningOp<tensor::PadOp>();
1050 if (!padOp)
1051 return failure();
1052
1053 bool zeroSliceGuard = true;
1054 if (controlFn) {
1055 if (std::optional<bool> control = controlFn(sliceOp))
1056 zeroSliceGuard = *control;
1057 else
1058 return failure();
1059 }
1060
1061 FailureOr<TilingResult> tilingResult =
1062 tensor::bubbleUpPadSlice(rewriter, padOp, sliceOp.getMixedOffsets(),
1063 sliceOp.getMixedSizes(), zeroSliceGuard);
1064 if (failed(tilingResult))
1065 return failure();
1066
1067 RankedTensorType sourceType = sliceOp.getSourceType();
1068 RankedTensorType resultType = sliceOp.getResultType();
1069
1070 // If the extract_slice is not rank-reduced, all shapes are static and the
1071 // data source is actually used. Rewrite into pad(extract_slice(x)).
1072 if (sourceType.getRank() == resultType.getRank()) {
1073 rewriter.replaceOp(sliceOp, tilingResult->tiledValues);
1074 return success();
1075 }
1076
1077 // Handle rank-reduced slice by creating another extract_slice op.
1079 rewriter, sliceOp.getLoc(), tilingResult->tiledValues[0], resultType);
1080
1081 rewriter.replaceOp(sliceOp, rankReduced);
1082 return success();
1083}
1084
1085/// If padding value is set, returns a tensor.pad Op for the source tensor,
1086/// with the output shape matching the output of `packOp`. Otherwise, returns
1087/// the source directly.
1088///
1089/// This method assumes that all outer dims for this pack Op are 1.
1091 linalg::PackOp packOp) {
1092 Value input = packOp.getSource();
1093 // Pack/unpack memref transformations are unsupported. The memref forms
1094 // are mainly for bufferization and scalar lowering. Other uses are not
1095 // recommended, see #225650 for details.
1096 if (!packOp.hasPureTensorSemantics())
1097 return input;
1098
1099 if (!packOp.getPaddingValue()) {
1100 return input;
1101 }
1102
1103 assert(llvm::all_of(packOp.getAllOuterDims(),
1104 [](int64_t val) { return val == 1; }) &&
1105 "some outer dims are != 1");
1106
1107 Location loc = packOp.getLoc();
1108 ShapedType inputType = packOp.getSourceType();
1109 int64_t inputRank = inputType.getRank();
1110
1111 DenseMap<int64_t, OpFoldResult> tileAndPosMapping =
1112 packOp.getDimAndTileMapping();
1113
1114 // The sizes of dynamic tiles
1115 SmallVector<Value> dynamicTileSizes;
1116
1117 // Collect dims for the padded shape.
1118 SmallVector<int64_t> paddedShape;
1119 for (int64_t dimIdx = 0; dimIdx < inputRank; ++dimIdx) {
1120 // 1. Non-tiled outer dims.
1121 // These dims should be 1 and we simply preserve them.
1122 if (!tileAndPosMapping.count(dimIdx)) {
1123 int64_t inputDimSize = inputType.getDimSize(dimIdx);
1124 assert(inputDimSize == 1 &&
1125 "with all outer dims == 1, this non-tiled input dim should be 1!");
1126 paddedShape.push_back(inputDimSize);
1127 continue;
1128 }
1129
1130 // 2. Tiled outer dims
1131 // As all outer dims == 1, it is safe to use the tile size for the padded
1132 // shape.
1133 OpFoldResult tileSizeForDim = tileAndPosMapping.lookup(dimIdx);
1134
1135 // 2.1 Static tile sizes
1136 std::optional<int64_t> cstTileSize = getConstantIntValue(tileSizeForDim);
1137 if (cstTileSize.has_value()) {
1138 paddedShape.push_back(cstTileSize.value());
1139 continue;
1140 }
1141
1142 // 2.2 Dynamic tile sizes
1143 paddedShape.push_back(ShapedType::kDynamic);
1144
1145 // Get the value that holds the dynamic size.
1146 dynamicTileSizes.push_back(llvm::dyn_cast<Value>(tileSizeForDim));
1147 }
1148 auto resultType =
1149 RankedTensorType::get(paddedShape, inputType.getElementType());
1150 return tensor::createPadHighOp(resultType, input, packOp.getPaddingValue(),
1151 /*nofold=*/false, loc, builder,
1152 dynamicTileSizes);
1153}
1154
1155// Normalizes a permutation on a higher rank space to its actual size, e.g.
1156// perm = [1, 4, 2]
1157// becomes
1158// norm = [0, 2, 1]
1159static SmallVector<int64_t>
1161 constexpr int64_t kNonTiledMarker = -1;
1162 SmallVector<int64_t> vec(rank, kNonTiledMarker);
1163 for (auto [index, value] : llvm::enumerate(perm))
1164 vec[value] = index;
1165 SmallVector<int64_t> normalizedPerm = llvm::filter_to_vector(
1166 vec, [&](int64_t v) { return v != kNonTiledMarker; });
1167 // This inverts the permutation in addition to normalizing so invert back.
1168 return invertPermutationVector(normalizedPerm);
1169}
1170
1171// Gets the normalized permutation implied by innerDimsPos and outerDimsPerm
1172// assuming rank reduction of unit outer dims.
1173static SmallVector<int64_t>
1175 ArrayRef<int64_t> innerDimsPos,
1176 ArrayRef<int64_t> outerDimsPerm) {
1177 SmallVector<int64_t> rankReducedOuterDimsPerm;
1178 SmallVector<int64_t> outerDims;
1179 SmallVector<int64_t> innerDims;
1180 int64_t dim = 0;
1181 int64_t unpackedRank = shape.size();
1182 for (auto i : llvm::seq<unsigned>(0, unpackedRank)) {
1183 if (llvm::is_contained(innerDimsPos, i)) {
1184 innerDims.push_back(dim++);
1185 continue;
1186 }
1187 if (shape[i] == 1)
1188 continue;
1189 outerDims.push_back(dim++);
1190 if (!outerDimsPerm.empty())
1191 rankReducedOuterDimsPerm.push_back(outerDimsPerm[i]);
1192 }
1193
1194 // Get the position of the inner dims after permutation.
1195 SmallVector<int64_t> innerPerm =
1196 getPackUnpackNormalizedPerm(unpackedRank, innerDimsPos);
1197 applyPermutationToVector<int64_t>(innerDims, innerPerm);
1198
1199 // Ditto for the outer dims.
1200 SmallVector<int64_t> perm = outerDims;
1201
1202 rankReducedOuterDimsPerm =
1203 getPackUnpackNormalizedPerm(unpackedRank, rankReducedOuterDimsPerm);
1204 if (!rankReducedOuterDimsPerm.empty())
1205 applyPermutationToVector<int64_t>(perm, rankReducedOuterDimsPerm);
1206
1207 // The tile always ends up as the inner most dims after packing.
1208 perm.append(innerDims);
1209
1210 return perm;
1211}
1212
1214 linalg::PackOp packOp, PatternRewriter &rewriter) const {
1215 // Pack/unpack memref transformations are unsupported. The memref forms
1216 // are mainly for bufferization and scalar lowering. Other uses are not
1217 // recommended, see #225650 for details.
1218 if (!packOp.hasPureTensorSemantics())
1219 return failure();
1220
1221 if (llvm::any_of(packOp.getTiledOuterDims(),
1222 [](int64_t dim) { return dim != 1; })) {
1223 return rewriter.notifyMatchFailure(
1224 packOp, "not all outer dimensions of the result are 1s");
1225 }
1226
1227 // When a padding value is set, getPackOpSourceOrPaddedSource only supports
1228 // the case where every outer dim (including un-tiled ones) is 1. Bail out
1229 // instead of hitting an assertion on a non-unit un-tiled outer dim.
1230 // FIXME: Handle this case by decomposing the padded pack instead of bailing
1231 // out; a non-unit un-tiled outer dim should be supported here.
1232 if (packOp.getPaddingValue() &&
1233 llvm::any_of(packOp.getAllOuterDims(),
1234 [](int64_t dim) { return dim != 1; })) {
1235 return rewriter.notifyMatchFailure(
1236 packOp, "cannot decompose padded pack with a non-unit un-tiled outer "
1237 "dimension");
1238 }
1239
1240 ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
1241 auto outerDimsPerm = packOp.getOuterDimsPerm();
1242
1243 // Verify that there are no:
1244 // * non-unit + un-tiled-outer-dims,
1245 // that are permuted. Supporting such cases would require refining the logic
1246 // that generates the Transpose Op.
1247 if (!llvm::all_of(outerDimsPerm, [&innerDimsPos, &packOp](int64_t dim) {
1248 static int prev = 0;
1249 // Skip tiled dims - these can be permuted.
1250 if (llvm::is_contained(innerDimsPos, dim))
1251 return true;
1252
1253 // Check whether this dim has been permuted. Permuting unit dims is fine
1254 // as that's effectively a no-op.
1255 if (dim < prev && (packOp.getResult().getType().getShape()[prev] != 1 ||
1256 packOp.getResult().getType().getShape()[dim] != 1))
1257 return false;
1258
1259 prev = dim;
1260 return true;
1261 })) {
1262 return rewriter.notifyMatchFailure(
1263 packOp, "At least one non-unit and un-tiled outer dim is permuted, "
1264 "this is not supported ATM!");
1265 }
1266
1267 Location loc = packOp.getLoc();
1268
1269 int64_t srcRank = packOp.getSourceRank();
1270
1271 // 1. Get the input that is going to be packed. If the input requires padding,
1272 // add a padding operation and return that as the input.
1273 Value input = getPackOpSourceOrPaddedSource(rewriter, packOp);
1274
1275 // 2. Transpose the input to match the inner tile order:
1276 // %init = tensor.empty()
1277 // %transposed_tile = linalg.transpose ins(%source_or_padded_source),
1278 // outs(%init)
1279 // Assumptions made:
1280 // - All tiled outer dims are 1 - the corresponding transposition order
1281 // doesn't matter, but requires all dim indices to be present.
1282 // - Un-tiled outer dims remain un-permuted.
1283
1284 // 2.1 Get the permutation for linalg.transpose:
1285 // [ untiled-dims, inner-dims-pos ]
1286 // Note, this logic assumes that the untiled dims are not permuted.
1287 SmallVector<int64_t> srcPermForTranspose;
1288 for (int64_t i = 0; i < srcRank; i++) {
1289 // We assume the `k` dimensions of the inner dim position, where `k` is the
1290 // rank of the inner tiling, correspond to the last `k` indices of the
1291 // transpose permutation. This is done by adding the indices not contained
1292 // in the inner dimension position in order from 0 to `n`. Where n is the
1293 // rank of the source tensor. For example if we have a source tensor with
1294 // indices [0, 1, 2, 3] and inner dim position of [3, 0], the remaining
1295 // indices are [1, 2]. and the transpose will be [1, 2, 3, 0].
1296 if (llvm::is_contained(innerDimsPos, i))
1297 continue;
1298 srcPermForTranspose.push_back(i);
1299 }
1300 srcPermForTranspose.append(innerDimsPos.begin(), innerDimsPos.end());
1301
1302 // 2.2 Create the init tensor for linalg.transpose with the correct shape:
1303 // [ untiled-dims, tiled-dims ]
1304 ShapedType inputTy = cast<ShapedType>(input.getType());
1305 SmallVector<OpFoldResult> shapeForEmptyOp;
1306 for (int64_t i = 0; i < srcRank; i++) {
1307 if (llvm::is_contained(innerDimsPos, i)) {
1308 // The tiled dims are appended after this loop.
1309 continue;
1310 }
1311 if (inputTy.isStaticDim(i))
1312 shapeForEmptyOp.push_back(rewriter.getIndexAttr(inputTy.getShape()[i]));
1313 else
1314 shapeForEmptyOp.emplace_back(
1315 tensor::DimOp::create(rewriter, loc, input, i).getResult());
1316 }
1317 shapeForEmptyOp.append(packOp.getMixedTiles());
1318
1319 // getMixedTiles() may contain Values pointing to constant ops (as opposed to
1320 // constant attributes with the corresponding value). Replace those with
1321 // attributes. This is to match the behaviour in
1322 // `getPackOpSourceOrPaddedSource`, which replaces constant SSA values with
1323 // attributes.
1324 llvm::transform(shapeForEmptyOp, shapeForEmptyOp.begin(),
1325 [&](OpFoldResult ofr) {
1326 if (auto val = llvm::dyn_cast<Value>(ofr))
1327 return getAsOpFoldResult(val);
1328 return ofr;
1329 });
1330
1331 LDBG() << "Pack permutation: " << packOp;
1332 LDBG() << "perm: " << llvm::interleaved(srcPermForTranspose);
1333 LDBG() << "Shape of empty tensor: " << llvm::interleaved(shapeForEmptyOp);
1334
1335 Value empty = tensor::EmptyOp::create(
1336 rewriter, loc, shapeForEmptyOp, packOp.getSourceType().getElementType());
1337
1338 // 2.3 Create linalg.transpose
1339 auto transposedOp = linalg::TransposeOp::create(rewriter, loc, input, empty,
1340 srcPermForTranspose);
1341
1342 // 3. Insert the inner tile into the destination tensor:
1343 // %inserted_tile = tensor.insert_slice(%transposed_tile)
1344
1345 // Compute the sizes attribute:
1346 // [ outer-dims, tile-sizes ]
1347 // Note that the output from the transpose Op excludes the tiled outer dims.
1348 // However, given the assumption that:
1349 // * all tiled outer dims == 1,
1350 // we can just use a rank-expanding tensor.insert_slice.
1351 SmallVector<OpFoldResult> writeSizes;
1352 for (auto size : packOp.getAllOuterDims()) {
1353 writeSizes.push_back(rewriter.getIndexAttr(size));
1354 }
1355
1356 for (auto tileSize : packOp.getMixedTiles()) {
1357 auto [_, tileSizeOfr] =
1358 getSimplifiedOfrAndStaticSizePair(tileSize, rewriter);
1359 writeSizes.push_back(tileSizeOfr);
1360 }
1361
1362 auto insert = tensor::InsertSliceOp::create(
1363 rewriter, loc, transposedOp.getResult()[0], packOp.getDest(), writeSizes);
1364
1365 // 4. Replace tensor.packOp with tensor.insert_slice created above
1366 rewriter.replaceOp(packOp, insert.getResult());
1367
1368 return success();
1369}
1370
1372 linalg::UnPackOp unpackOp, PatternRewriter &rewriter) const {
1373 if (!unpackOp.hasPureTensorSemantics())
1374 return failure();
1375
1376 int64_t destRank = unpackOp.getDestRank();
1377 ArrayRef<int64_t> srcShape = unpackOp.getSourceType().getShape();
1378 ArrayRef<int64_t> innerDimsPos = unpackOp.getInnerDimsPos();
1379 if (llvm::any_of(unpackOp.getTiledOuterDims(),
1380 [](int64_t dim) { return dim != 1; })) {
1381 return rewriter.notifyMatchFailure(
1382 unpackOp,
1383 "require the tiled outer dimensions of the result are all 1s");
1384 }
1385
1386 // 1. Use rank-reduced tensor.extract_slice op to extract the tile:
1387 // %extracted_tile = tensor.extract_slice(%unpack_op_input)
1388 Location loc = unpackOp.getLoc();
1389 Value source = unpackOp.getSource();
1390 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1391 unpackOp.getDimAndTileMapping();
1392 Attribute oneIdxAttr = rewriter.getIndexAttr(1);
1393
1394 // The shape for ExtractSliceOp. Note that this will consist of 3 blocks of
1395 // dims:
1396 // [ outer-untiled-dims, outer-tiled-dims, tile-sizes ]
1397 SmallVector<int64_t> readShapeForExtractSlice;
1398 // The sizes attribute for ExtractSliceOp. Due to rank-reducing (and
1399 // outer-tiled-dims being all 1), this will be
1400 // [ outer-untiled-dims, tile-sizes ]
1401 SmallVector<OpFoldResult> extractSliceSizes;
1402
1403 // Shape for EmptyOp that's used as the init value for TransposeOp below.
1404 // This should be:
1405 // [ outer-untiled-dims, tile-sizes ]
1406 // However, skip unit dims - TransposeOp (below) applies rank-reduced
1407 // permutation.
1408 SmallVector<OpFoldResult> shapeForEmptyOp;
1409
1410 for (auto i : llvm::seq<unsigned>(0, destRank)) {
1411 // Compute sizes attribute for ExtractSliceOp - outer-tiled-dims.
1412 //
1413 // As all outer tiled dims are 1, so the corresponding
1414 // slice size to read will also 1. As this will be rank-reducing "extract
1415 // slice" (i.e. the unit dims will be "collapsed"), there's no need to
1416 // update:
1417 // * the output shape for ExtractSliceOp, nor
1418 // * the shape for EmptyOp.
1419 if (dimAndTileMapping.count(i)) {
1420 extractSliceSizes.push_back(oneIdxAttr);
1421 continue;
1422 }
1423
1424 // Compute sizes attribute for ExtractSliceOp + EmptyOp -
1425 // outer-untiled-dims
1426 if (ShapedType::isDynamic(srcShape[i])) {
1427 OpFoldResult dynamicDim =
1428 tensor::DimOp::create(rewriter, loc, source, i).getResult();
1429 extractSliceSizes.push_back(dynamicDim);
1430 shapeForEmptyOp.push_back(dynamicDim);
1431 } else {
1432 extractSliceSizes.push_back(rewriter.getIndexAttr(srcShape[i]));
1433 if (srcShape[i] != 1)
1434 shapeForEmptyOp.push_back(rewriter.getIndexAttr(srcShape[i]));
1435 }
1436 // Compute the output shape for ExtractSliceOp - outer-untiled-dims (take
1437 // into account rank-reducing)
1438 if (srcShape[i] != 1) {
1439 readShapeForExtractSlice.push_back(srcShape[i]);
1440 }
1441 }
1442 // Append the tile sizes to "sizes attribute" for ExtractSliceOp and the
1443 // shape for EmptyOp.
1444 auto mixedTiles = unpackOp.getMixedTiles();
1445 extractSliceSizes.append(mixedTiles.begin(), mixedTiles.end());
1446 shapeForEmptyOp.append(mixedTiles.begin(), mixedTiles.end());
1447
1448 // Explicitly create the type for extract_slice op because the inner tile
1449 // size could be 1. We want to represent the whole inner tile in this case.
1450 auto tileShape = srcShape.drop_front(destRank);
1451 // Append the inner tile shape to the permuted and rank-reduced outer shape.
1452 readShapeForExtractSlice.append(tileShape.begin(), tileShape.end());
1453 Type elemType = unpackOp.getSourceType().getElementType();
1454 auto readType = RankedTensorType::get(readShapeForExtractSlice, elemType);
1455 Value innerTile = tensor::ExtractSliceOp::create(
1456 rewriter, loc, readType, unpackOp.getSource(), extractSliceSizes);
1457
1458 // 2. Transpose the tile to match the outer corresponding tile order.
1460 srcShape.take_front(destRank), innerDimsPos, unpackOp.getOuterDimsPerm());
1461 // Unpack is a transition out of packed space so we invert the permutation.
1462 perm = invertPermutationVector(perm);
1463 applyPermutationToVector<OpFoldResult>(shapeForEmptyOp, perm);
1464
1465 Value empty =
1466 tensor::EmptyOp::create(rewriter, loc, shapeForEmptyOp, elemType);
1467 auto transposedOp =
1468 linalg::TransposeOp::create(rewriter, loc, innerTile, empty, perm);
1469
1470 // 3. Handle in-complete tiles if needed. It truncates trailing data from the
1471 // transposed tile.
1472 SmallVector<OpFoldResult> tileSizes;
1473 ArrayRef<int64_t> destShape = unpackOp.getDestType().getShape();
1474 for (auto i : llvm::seq<unsigned>(0, destRank)) {
1475 if (dimAndTileMapping.count(i) || destShape[i] != 1)
1476 tileSizes.push_back(
1477 tensor::getMixedSize(rewriter, loc, unpackOp.getDest(), i));
1478 }
1479
1480 auto partialTile =
1481 tensor::ExtractSliceOp::create(rewriter, loc, RankedTensorType(),
1482 transposedOp.getResult()[0], tileSizes);
1483
1484 // 4. Insert the result to the destination tensor.
1485 SmallVector<OpFoldResult> writeSizes;
1486 for (int i = 0, idx = 0; i < destRank; ++i) {
1487 if (dimAndTileMapping.count(i) || destShape[i] != 1)
1488 writeSizes.push_back(tileSizes[idx++]);
1489 else
1490 writeSizes.push_back(oneIdxAttr);
1491 }
1492 auto insert = tensor::InsertSliceOp::create(rewriter, loc, partialTile,
1493 unpackOp.getDest(), writeSizes);
1494 rewriter.replaceOp(unpackOp, insert.getResult());
1495
1496 return success();
1497}
1498
1499//===----------------------------------------------------------------------===//
1500// Generic DownscaleSizeOneWindowedConvolution
1501//===----------------------------------------------------------------------===//
1502//
1503/// Returns the indices of affine map results that reference any of the given
1504/// dimensions.
1507 SmallVector<unsigned> resultIndices;
1508 for (unsigned dim : dims) {
1509 for (unsigned i = 0, e = map.getNumResults(); i < e; ++i) {
1510 AffineExpr expr = map.getResult(i);
1511 if (expr.isFunctionOfDim(dim)) {
1512 resultIndices.push_back(i);
1513 break;
1514 }
1515 }
1516 }
1517 return resultIndices;
1518}
1519
1520/// Helper to create a rank-reducing extract_slice that removes specific
1521/// dimensions from a tensor.
1523 Location loc, Value tensor,
1524 ArrayRef<unsigned> dimsToRemove) {
1525 auto tensorType = cast<RankedTensorType>(tensor.getType());
1526 int64_t rank = tensorType.getRank();
1527
1528 // Compute new shape by removing the specified dimensions.
1529 SmallVector<int64_t> newShape;
1530 for (int64_t i = 0; i < rank; ++i) {
1531 if (!llvm::is_contained(dimsToRemove, i))
1532 newShape.push_back(tensorType.getDimSize(i));
1533 }
1534
1535 auto newType = RankedTensorType::get(newShape, tensorType.getElementType());
1537 tensor, newType);
1538}
1539
1540/// Drops specified dimensions from an AffineExpr and compresses remaining
1541/// dimension indices. Returns std::nullopt if the expression only references
1542/// the dropped dimensions.
1543static std::optional<AffineExpr>
1545 unsigned newNumDims, MLIRContext *ctx) {
1546 // Check if expr only references dimensions to be dropped.
1547 bool onlyReferencesDroppedDims = true;
1548 for (unsigned d = 0; d < newNumDims + dimsToDrop.size(); ++d) {
1549 if (expr.isFunctionOfDim(d) && !llvm::is_contained(dimsToDrop, d)) {
1550 onlyReferencesDroppedDims = false;
1551 break;
1552 }
1553 }
1554 if (onlyReferencesDroppedDims && llvm::any_of(dimsToDrop, [&](unsigned d) {
1555 return expr.isFunctionOfDim(d);
1556 }))
1557 return std::nullopt;
1558
1559 // Replace dimensions: compute new index for each old dimension.
1560 // Dropped dimensions get mapped to constant 0, others get compressed.
1561 SmallVector<AffineExpr> dimReplacements;
1562 unsigned newDimIdx = 0;
1563 for (unsigned d = 0; d < newNumDims + dimsToDrop.size(); ++d) {
1564 if (llvm::is_contained(dimsToDrop, d)) {
1565 dimReplacements.push_back(getAffineConstantExpr(0, ctx));
1566 } else {
1567 dimReplacements.push_back(getAffineDimExpr(newDimIdx++, ctx));
1568 }
1569 }
1570
1571 return expr.replaceDims(dimReplacements);
1572}
1573
1574FailureOr<LinalgOp>
1576 LinalgOp op) {
1577 auto maybeDims = inferConvolutionDims(op);
1578 if (failed(maybeDims))
1579 return failure();
1580
1581 // Currently supports only 2D convolutions.
1582 if (maybeDims->outputImage.size() != 2 || maybeDims->filterLoop.size() != 2)
1583 return failure();
1584
1585 if (op.hasPureBufferSemantics())
1586 return failure();
1587
1588 // Get loop domain indices for spatial dimensions.
1589 unsigned outSpatial0 = maybeDims->outputImage[0];
1590 unsigned outSpatial1 = maybeDims->outputImage[1];
1591 unsigned filterSpatial0 = maybeDims->filterLoop[0];
1592 unsigned filterSpatial1 = maybeDims->filterLoop[1];
1593
1594 // Get sizes from loop bounds.
1595 SmallVector<int64_t, 4> loopRanges = op.getStaticLoopRanges();
1596 int64_t outSize0 = loopRanges[outSpatial0];
1597 int64_t outSize1 = loopRanges[outSpatial1];
1598 int64_t filterSize0 = loopRanges[filterSpatial0];
1599 int64_t filterSize1 = loopRanges[filterSpatial1];
1600
1601 // Check if we can downscale by removing a spatial dimension.
1602 bool canRemoveSpatial0 = (filterSize0 == 1 && outSize0 == 1);
1603 bool canRemoveSpatial1 = (filterSize1 == 1 && outSize1 == 1);
1604 if (!canRemoveSpatial0 && !canRemoveSpatial1)
1605 return failure();
1606
1607 // Determine which loop dims to remove (output spatial + corresponding filter)
1608 // and sort for correct index compression when removing dimensions from affine
1609 // maps.
1610 SmallVector<unsigned> loopDimsToRemove;
1611 if (canRemoveSpatial0) {
1612 loopDimsToRemove.push_back(outSpatial0);
1613 loopDimsToRemove.push_back(filterSpatial0);
1614 } else {
1615 loopDimsToRemove.push_back(outSpatial1);
1616 loopDimsToRemove.push_back(filterSpatial1);
1617 }
1618 llvm::sort(loopDimsToRemove);
1619
1620 // Create new indexing maps with dimensions removed.
1621 SmallVector<AffineMap> newMaps;
1622 MLIRContext *ctx = op.getContext();
1623 unsigned numDims = op.getNumLoops();
1624 unsigned newNumDims = numDims - loopDimsToRemove.size();
1625 for (AffineMap map : op.getIndexingMapsArray()) {
1626 SmallVector<AffineExpr> newResults;
1627 for (AffineExpr expr : map.getResults()) {
1628 auto newExpr =
1629 dropDimsAndCompress(expr, loopDimsToRemove, newNumDims, ctx);
1630 if (newExpr)
1631 newResults.push_back(*newExpr);
1632 }
1633 newMaps.push_back(AffineMap::get(newNumDims, 0, newResults, ctx));
1634 }
1635
1636 // Create new iterator types.
1638 auto iterTypes = op.getIteratorTypesArray();
1639 for (unsigned idx = 0; idx < iterTypes.size(); ++idx) {
1640 if (!llvm::is_contained(loopDimsToRemove, idx))
1641 newIterTypes.push_back(iterTypes[idx]);
1642 }
1643
1644 // Rank-reduce operands using extract_slice.
1645 Location loc = op.getLoc();
1646 SmallVector<Value> newInputs;
1647 for (OpOperand *input : op.getDpsInputOperands()) {
1648 AffineMap map = op.getMatchingIndexingMap(input);
1649 SmallVector<unsigned> tensorDimsToRemove =
1650 getResultIndicesReferencingDims(map, loopDimsToRemove);
1651 Value reduced = createRankReducingExtractSlice(rewriter, loc, input->get(),
1652 tensorDimsToRemove);
1653 newInputs.push_back(reduced);
1654 }
1655
1656 OpOperand &output = *op.getDpsInitsMutable().begin();
1657 AffineMap outputMap = op.getMatchingIndexingMap(&output);
1658 SmallVector<unsigned> outputDimsToRemove =
1659 getResultIndicesReferencingDims(outputMap, loopDimsToRemove);
1660 Value newOutput = createRankReducingExtractSlice(rewriter, loc, output.get(),
1661 outputDimsToRemove);
1662
1663 // Create new linalg.generic with reduced dimensions.
1664 auto newOp =
1665 linalg::GenericOp::create(rewriter, loc, TypeRange{newOutput.getType()},
1666 newInputs, newOutput, newMaps, newIterTypes);
1667 rewriter.inlineRegionBefore(op->getRegion(0), newOp.getRegion(),
1668 newOp.getRegion().begin());
1669
1670 // Try to specialize the generic back to a named op only if the input was
1671 // already a specialized (named) op.
1672 LinalgOp resultOp = newOp;
1673 if (!isa<GenericOp>(op)) {
1674 FailureOr<LinalgOp> specializedOp = specializeGenericOp(rewriter, newOp);
1675 if (succeeded(specializedOp))
1676 resultOp = *specializedOp;
1677 }
1678
1679 // Insert result back into original shape.
1681 rewriter, loc, resultOp->getResult(0), output.get());
1682
1683 rewriter.replaceOp(op, result);
1684 return resultOp;
1685}
1686
1687namespace {
1688/// Pattern wrapper around `downscaleSizeOneWindowedConvolution`.
1689struct DownscaleSizeOneWindowedConvolution final
1690 : public OpInterfaceRewritePattern<LinalgOp> {
1691 DownscaleSizeOneWindowedConvolution(MLIRContext *context,
1692 PatternBenefit benefit = 1)
1693 : OpInterfaceRewritePattern<LinalgOp>(context, benefit) {}
1694
1695 LogicalResult matchAndRewrite(LinalgOp op,
1696 PatternRewriter &rewriter) const override {
1698 }
1699};
1700} // namespace
1701
1703 PatternBenefit benefit) {
1704 patterns.add<DownscaleSizeOneWindowedConvolution>(patterns.getContext(),
1705 benefit);
1706}
1707
1712
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static RankedTensorType permuteShape(RankedTensorType tensorType, ArrayRef< int64_t > permutationVector)
Return a copy of tensorType after permutation by permutationVector.
static std::optional< AffineExpr > dropDimsAndCompress(AffineExpr expr, ArrayRef< unsigned > dimsToDrop, unsigned newNumDims, MLIRContext *ctx)
Drops specified dimensions from an AffineExpr and compresses remaining dimension indices.
static SmallVector< int64_t > getPackUnpackRankReducedPerm(ArrayRef< int64_t > shape, ArrayRef< int64_t > innerDimsPos, ArrayRef< int64_t > outerDimsPerm)
static Value createRankReducingExtractSlice(RewriterBase &rewriter, Location loc, Value tensor, ArrayRef< unsigned > dimsToRemove)
Helper to create a rank-reducing extract_slice that removes specific dimensions from a tensor.
static FailureOr< SmallVector< std::optional< int64_t > > > packLinalgMetadataOnce(SmallVectorImpl< AffineMap > &indexingMaps, SmallVectorImpl< utils::IteratorType > &iteratorTypes, int64_t dim)
Perform one step of packing of a LinalgOp's metadata along dim into the newDim at iteratorTypes....
static void remapPackedIndexOps(RewriterBase &rewriter, LinalgOp packedLinalgOp, ArrayRef< OpFoldResult > packedSizes, ArrayRef< std::optional< int64_t > > packedLoopToInnerLoop)
Rewrite index operations in a packed Linalg op to preserve their values in the original iteration spa...
static LinalgOp transposeOneLinalgOperandAndReplace(RewriterBase &rewriter, LinalgOp linalgOp, OpOperand &opOperand, ArrayRef< int64_t > permutation, Value transposedValue)
Return a new GenericOp obtained by transposing opOperand by the permutation vector:
static SmallVector< int64_t > getPackUnpackNormalizedPerm(int rank, ArrayRef< int64_t > perm)
static bool hasAtMostOneResultFunctionOfDim(AffineMap map, int64_t dim)
Return true if map has 0 or 1 result function of AffineDimExpr(dim).
static Value getPackOpSourceOrPaddedSource(OpBuilder &builder, linalg::PackOp packOp)
If padding value is set, returns a tensor.pad Op for the source tensor, with the output shape matchin...
static SmallVector< unsigned > getResultIndicesReferencingDims(AffineMap map, ArrayRef< unsigned > dims)
Returns the indices of affine map results that reference any of the given dimensions.
static std::string stringifyReassocIndices(ReassociationIndicesRef ri)
static std::optional< int64_t > getFirstResultIndexFunctionOf(AffineMap map, int64_t dim)
Return the index of the first result of map that is a function of AffineDimExpr(dim),...
Base type for affine expression.
Definition AffineExpr.h:68
bool isFunctionOfDim(unsigned position) const
Return true if the affine expression involves AffineDimExpr position.
AffineExpr replaceDims(ArrayRef< AffineExpr > dimReplacements) const
Dim-only version of replaceDimsAndSymbols.
AffineExpr ceilDiv(uint64_t v) const
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
MLIRContext * getContext() const
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
AffineMap shiftDims(unsigned shift, unsigned offset=0) const
Replace dims[offset ... numDims) by dims[offset + shift ... shift + numDims).
Definition AffineMap.h:269
AffineMap insertResult(AffineExpr expr, unsigned pos) const
Returns a new AffineMap with the same number of dims and symbols and an extra result inserted at pos.
Definition AffineMap.h:317
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
AffineExpr getResult(unsigned idx) const
static AffineMap getPermutationMap(ArrayRef< unsigned > permutation, MLIRContext *context)
Returns an AffineMap representing a permutation.
AffineMap compose(AffineMap map) const
Returns the AffineMap resulting from composing this with map.
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
MLIRContext * getContext() const
Definition Builders.h:56
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
IRValueT get() const
Return the current value being used by this operand.
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:71
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:528
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:256
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:231
This is a value defined by a result of an operation.
Definition Value.h:456
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
result_range getResults()
Definition Operation.h:442
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This is a builder type that keeps local references to arguments.
Builder & setShape(ArrayRef< int64_t > newShape)
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void moveOpBefore(Operation *op, Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
void inlineRegionBefore(Region &region, Region &parent, Region::iterator before)
Move the blocks that belong to "region" before the given position in another region "parent".
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
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:392
type_range getTypes() const
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
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...
SmallVector< int64_t > getUnPackInverseSrcPerm(linalg::UnPackOp, PackingMetadata &metadata)
Compute inverse permutation for the source tensor (i.e.
FailureOr< PackTransposeResult > packTranspose(RewriterBase &rewriter, linalg::PackOp packOp, linalg::LinalgOp linalgOp, linalg::UnPackOp maybeUnPackOp, ArrayRef< int64_t > outerPerm, ArrayRef< int64_t > innerPerm)
Transpose a single PackOp -> LinalgOp -> UnPackOp chain and return the transposed PackOp -> LinalgOp ...
FailureOr< LowerUnPackOpResult > lowerUnPack(RewriterBase &rewriter, linalg::UnPackOp unPackOp, bool lowerUnpadLikeWithExtractSlice=true)
Rewrite pack as empty + transpose + reshape + extract_slice + copy.
void peelLoops(RewriterBase &rewriter, ArrayRef< scf::ForOp > loops)
Peel 'loops' and applies affine_min/max bounds simplification on the fly where relevant.
FailureOr< ConvolutionDimensions > inferConvolutionDims(LinalgOp linalgOp)
Find at least 1 parallel (output_image) and reduction (filter_loop) dimension candidates that form a ...
FailureOr< LinalgOp > generalizeNamedOp(RewriterBase &rewriter, LinalgOp linalgOp, bool emitCategoryOps=false)
Create a GenericOp or CategoryOp from the given named operation linalgOp and replace the given linalg...
FailureOr< LinalgOp > specializeGenericOp(RewriterBase &rewriter, GenericOp genericOp, bool emitCategoryOps=false)
Replace the given GenericOp with a namedOp or categoryOp.
void populateDecomposeConvolutionPatterns(RewritePatternSet &patterns, PatternBenefit benefit=1)
Linalg decompose convolutions patterns.
LogicalResult vectorizeCopy(RewriterBase &builder, memref::CopyOp copyOp)
Emit a suitable vector form for a Copy op with fully static shape.
FailureOr< GenericOp > interchangeGenericOp(RewriterBase &rewriter, GenericOp genericOp, ArrayRef< unsigned > interchangeVector)
Interchange the iterator_types and iterator_maps dimensions and adapts the index accesses of op.
SmallVector< int64_t > getPackInverseDestPerm(linalg::PackOp packOp, PackingMetadata &metadata)
Compute inverse permutation for the destination tensor (i.e.
void populateDecomposePackUnpackPatterns(RewritePatternSet &patterns)
Populates patterns to decompose linalg.pack and linalg.unpack Ops into e.g.
FailureOr< ContractionDimensions > inferContractionDims(LinalgOp linalgOp)
Find at least 2 parallel (m and n) and 1 reduction (k) dimension candidates that form a matmul subcom...
FailureOr< PackResult > packMatmulGreedily(RewriterBase &rewriter, LinalgOp linalgOp, ArrayRef< OpFoldResult > mnkPackedSizes, ArrayRef< int64_t > mnkPaddedSizesNextMultipleOf, ArrayRef< int64_t > mnkOrder)
Pack a LinalgOp by greedily inferring matmul dimensions (m, n, k) where m and n are proper parallel d...
FailureOr< PackResult > pack(RewriterBase &rewriter, linalg::LinalgOp linalgOp, ArrayRef< OpFoldResult > packedSizes)
Implement packing of a single LinalgOp by packedSizes.
SmallVector< Value > peelLoop(RewriterBase &rewriter, Operation *op)
Try to peel and canonicalize loop op and return the new result.
void populateDecomposePadPatterns(RewritePatternSet &patterns)
Populates patterns to decompose tensor.pad into e.g.
FailureOr< LinalgOp > downscaleSizeOneWindowedConvolution(RewriterBase &rewriter, LinalgOp op)
Rewrite convolution/pooling/depthwise ops with size-1 window dimensions into lower-dimensional ops.
FailureOr< LowerPackResult > lowerPack(RewriterBase &rewriter, linalg::PackOp packOp, bool lowerPadLikeWithInsertSlice=true)
Rewrite pack as pad + reshape + transpose.
LogicalResult peelForLoopAndSimplifyBounds(RewriterBase &rewriter, ForOp forOp, scf::ForOp &partialIteration)
Rewrite a for loop with bounds/step that potentially do not divide evenly into a for loop where the s...
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.
PadOp createPadHighOp(RankedTensorType resType, Value source, Value pad, bool nofold, Location loc, OpBuilder &builder, ValueRange dynOutDims={})
Definition Utils.cpp:23
Value createCanonicalRankReducingInsertSliceOp(OpBuilder &b, Location loc, Value tensor, Value dest)
Create a rank-reducing InsertSliceOp @[0 .
Value createCanonicalRankReducingExtractSliceOp(OpBuilder &b, Location loc, Value tensor, RankedTensorType targetType)
Create a rank-reducing ExtractSliceOp @[0 .
OpFoldResult getMixedSize(OpBuilder &builder, Location loc, Value value, int64_t dim)
Return the dimension of the given tensor value.
Definition TensorOps.cpp:82
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:91
Include the generated interface declarations.
SliceVerificationResult
Enum that captures information related to verifier error conditions on slice insert/extract type of o...
ArrayRef< int64_t > ReassociationIndicesRef
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:313
SmallVector< int64_t > computePermutationVector(int64_t permSize, ArrayRef< int64_t > positions, ArrayRef< int64_t > desiredPositions)
Return a permutation vector of size permSize that would result in moving positions into desiredPositi...
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:327
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
SmallVector< Loops, 8 > tile(ArrayRef< scf::ForOp > forOps, ArrayRef< Value > sizes, ArrayRef< scf::ForOp > targets)
Performs tiling fo imperfectly nested loops (with interchange) by strip-mining the forOps by sizes an...
Definition Utils.cpp:1389
AffineExpr getAffineConstantExpr(int64_t constant, MLIRContext *context)
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
std::pair< int64_t, OpFoldResult > getSimplifiedOfrAndStaticSizePair(OpFoldResult ofr, Builder &b)
Given OpFoldResult representing dim size value (*), generates a pair of sizes:
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
SliceVerificationResult isRankReducedType(ShapedType originalType, ShapedType candidateReducedType)
Check if originalType can be rank reduced to candidateReducedType type by dropping some dimensions wi...
bool isPermutationVector(ArrayRef< int64_t > interchange)
Method to check if an interchange vector is a permutation.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
OpInterfaceRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting a...
Represents a range (offset, size, and stride) where each element of the triple may be dynamic or stat...
OpFoldResult size
LogicalResult matchAndRewrite(memref::CopyOp copyOp, PatternRewriter &rewriter) const override
Rewrites a linalg::PackOp into a sequence of:
LogicalResult matchAndRewrite(linalg::PackOp packOp, PatternRewriter &rewriter) const override
Rewrites a linalg::UnPackOp into a sequence of:
LogicalResult matchAndRewrite(linalg::UnPackOp unpackOp, PatternRewriter &rewriter) const override
Rewrite a tensor::PadOp into a sequence of EmptyOp, FillOp and InsertSliceOp.
LogicalResult matchAndRewrite(tensor::PadOp padOp, PatternRewriter &rewriter) const override
Value createFillOrGenerateOp(RewriterBase &rewriter, tensor::PadOp padOp, Value dest, const SmallVector< Value > &dynSizes) const
Filling dest using FillOp constant padding value if possible.
LogicalResult matchAndRewrite(tensor::ExtractSliceOp sliceOp, PatternRewriter &rewriter) const override
LinalgTilingOptions & setTileSizes(const SmallVector< Value, 4 > &ts)
Set the tileSizeComputationFunction to return the values ts.
Definition Transforms.h:204
TileSizeComputationFunction tileSizeComputationFunction
Computation function that returns the tile sizes for each operation.
Definition Transforms.h:194
Struct to hold the result of a pack call.
Struct to hold the result of a packTranspose call.