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
221FailureOr<LowerPackResult> linalg::lowerPack(RewriterBase &rewriter,
222 linalg::PackOp packOp,
223 bool lowerPadLikeWithInsertSlice) {
224 // TODO: Support Memref PackOp. Temporarily return failure.
225 if (!packOp.hasPureTensorSemantics())
226 return failure();
227
228 auto packedTensorType =
229 cast<RankedTensorType>(packOp->getResultTypes().front());
230
231 Location loc = packOp->getLoc();
232 OpBuilder::InsertionGuard g(rewriter);
233 rewriter.setInsertionPoint(packOp);
234
235 // 2. Compute the permutation vector to shuffle packed shape into the shape
236 // before any outer or inner permutations have been applied.
237 PackingMetadata packingMetadata;
238 SmallVector<int64_t> packedToStripMinedShapePerm =
239 getPackInverseDestPerm(packOp, packingMetadata);
240
241 // 3. Compute the stripMinedShape: this is the packed shape before any outer
242 // or inner permutations have been applied.
243 SmallVector<int64_t> stripMinedShape(packedTensorType.getShape());
244 applyPermutationToVector(stripMinedShape, packedToStripMinedShapePerm);
245
246 // Also compute the mixed (static+dynamic) strip-mined sizes for the
247 // expand_shape output. This is needed to support dynamic inner tile sizes,
248 // since the shapes cannot be inferred automatically when multiple dynamic
249 // dims appear in a single reassociation group during ExpandShapeOp
250 // construction.
251 SmallVector<OpFoldResult> stripMinedMixedSizes =
252 tensor::getMixedSizes(rewriter, loc, packOp.getDest());
253 applyPermutationToVector(stripMinedMixedSizes, packedToStripMinedShapePerm);
254
255 // 4. Pad the source of packOp to a shape we can expand into stripMinedShape.
256 SmallVector<OpFoldResult> lows(packOp.getSourceRank(),
257 rewriter.getIndexAttr(0));
258 SmallVector<OpFoldResult> highs(packOp.getSourceRank(),
259 rewriter.getIndexAttr(0));
260 for (auto [pos, innerSize] :
261 llvm::zip_equal(packOp.getInnerDimsPos(), packOp.getMixedTiles())) {
262 int outerPos =
263 packedToStripMinedShapePerm[packingMetadata.outerPositions[pos]];
264 OpFoldResult origSize =
265 tensor::getMixedSize(rewriter, loc, packOp.getSource(), pos);
266 OpFoldResult outerSize =
267 tensor::getMixedSize(rewriter, loc, packOp.getDest(), outerPos);
268 AffineExpr s0, d0, d1;
269 bindDims(rewriter.getContext(), d0, d1);
270 bindSymbols(rewriter.getContext(), s0);
271 auto map = AffineMap::get(/*dimCount=*/2, /*symbolCount=*/1, d0 * s0 - d1);
273 rewriter, loc, map, {outerSize, origSize, innerSize});
274 }
275 RankedTensorType collapsed = tensor::CollapseShapeOp::inferCollapsedType(
276 RankedTensorType::Builder(packedTensorType).setShape(stripMinedShape),
277 packingMetadata.reassociations);
278 Value paddingValue = packOp.getPaddingValue();
279 if (!paddingValue) {
280 paddingValue = arith::ConstantOp::create(
281 rewriter, loc, rewriter.getZeroAttr(getElementTypeOrSelf(collapsed)));
282 }
283 auto padOp =
284 tensor::PadOp::create(rewriter, loc, collapsed, packOp.getSource(), lows,
285 highs, paddingValue, /*nofold=*/false);
286
287 LDBG() << "insertPositions: "
288 << llvm::interleaved(packingMetadata.insertPositions);
289 LDBG() << "outerPositions: "
290 << llvm::interleaved(packingMetadata.outerPositions);
291 LDBG() << "packedShape: " << llvm::interleaved(packedTensorType.getShape());
292 LDBG() << "packedToStripMinedShapePerm: "
293 << llvm::interleaved(packedToStripMinedShapePerm);
294 LDBG() << "reassociations: "
295 << llvm::interleaved(llvm::map_range(packingMetadata.reassociations,
297 LDBG() << "stripMinedShape: " << llvm::interleaved(stripMinedShape);
298 LDBG() << "collapsed type: " << collapsed;
299
300 if (lowerPadLikeWithInsertSlice && packOp.isLikePad()) {
301 // Pack ops which operate as simple pads may not produce legal
302 // tensor.insert_slice operations when the packed type does not rank reduce
303 // to the padded type.
304 SliceVerificationResult rankReduces =
305 isRankReducedType(packedTensorType, padOp.getResultType());
306
307 if (rankReduces == SliceVerificationResult::Success) {
308 // This pack is just a plain pad.
309 // Just insert the pad in the higher ranked tensor.
310 // Offsets.
311 SmallVector<OpFoldResult> zeros(packOp.getDestRank(),
312 rewriter.getIndexAttr(0));
313 // Strides.
314 SmallVector<OpFoldResult> ones(packOp.getDestRank(),
315 rewriter.getIndexAttr(1));
317 tensor::getMixedSizes(rewriter, loc, packOp.getDest());
318
319 auto insertSliceOp = tensor::InsertSliceOp::create(
320 rewriter, loc, /*source=*/padOp, /*dest=*/packOp.getDest(),
321 /*offsets=*/zeros, sizes, /*strides=*/ones);
322
323 LDBG() << "insert_slice op: " << insertSliceOp;
324
325 rewriter.replaceOp(packOp, insertSliceOp->getResults());
326
327 return LowerPackResult{padOp, /*reshapeOp=*/nullptr,
328 /*transposeOp=*/nullptr};
329 }
330 }
331
332 // 5. Expand from the padded result to the stripMinedShape.
333 auto expandShapeResultType =
334 RankedTensorType::Builder(packedTensorType).setShape(stripMinedShape);
335 auto reshapeOp = tensor::ExpandShapeOp::create(
336 rewriter, loc, expandShapeResultType, padOp.getResult(),
337 packingMetadata.reassociations, stripMinedMixedSizes);
338
339 // 6. Transpose stripMinedShape to packedShape.
340 SmallVector<int64_t> transpPerm =
341 invertPermutationVector(packedToStripMinedShapePerm);
342 auto transposeOp = linalg::TransposeOp::create(
343 rewriter, loc, reshapeOp.getResult(), packOp.getDest(), transpPerm);
344
345 LDBG() << "reshape op: " << reshapeOp;
346 LDBG() << "transpPerm: " << llvm::interleaved(transpPerm);
347 LDBG() << "transpose op: " << transposeOp;
348
349 // 7. Replace packOp by transposeOp.
350 rewriter.replaceOp(packOp, transposeOp->getResults());
351
352 return LowerPackResult{padOp, reshapeOp, transposeOp};
353}
354
355FailureOr<LowerUnPackOpResult>
356linalg::lowerUnPack(RewriterBase &rewriter, linalg::UnPackOp unPackOp,
357 bool lowerUnpadLikeWithExtractSlice) {
358 // TODO: Support Memref UnPackOp. Temporarily return failure.
359 if (!unPackOp.hasPureTensorSemantics())
360 return failure();
361
362 Location loc = unPackOp->getLoc();
363 OpBuilder::InsertionGuard g(rewriter);
364 rewriter.setInsertionPoint(unPackOp);
365
366 auto packedTensorType = cast<RankedTensorType>(unPackOp.getSourceType());
367 int64_t packedRank = packedTensorType.getRank();
368
369 OpFoldResult zero = rewriter.getIndexAttr(0), one = rewriter.getIndexAttr(1);
370 auto destTensorType = cast<RankedTensorType>(unPackOp.getDest().getType());
371 if (lowerUnpadLikeWithExtractSlice && unPackOp.isLikeUnPad()) {
372 // This unpack is just a plain unpad.
373 // Just extract the slice from the higher ranked tensor.
374 ArrayRef<int64_t> destShape = destTensorType.getShape();
375 // The inner dimensions stay the same as the destination tensor, but the
376 // outer ones are additional 1s.
377 SmallVector<OpFoldResult> sizes(packedRank - destShape.size(), one);
378 sizes.append(tensor::getMixedSizes(rewriter, loc, unPackOp.getDest()));
379
380 auto extractSliceOp = tensor::ExtractSliceOp::create(
381 rewriter, loc, destTensorType, unPackOp.getSource(),
382 SmallVector<OpFoldResult>(packedRank, zero), sizes,
383 SmallVector<OpFoldResult>(packedRank, one));
384
385 rewriter.replaceOp(unPackOp, extractSliceOp->getResults());
386
387 return LowerUnPackOpResult{/*emptyOp=*/nullptr, /*transposeOp=*/nullptr,
388 /*reshapeOp=*/nullptr, extractSliceOp,
389 /*copyOp=*/nullptr};
390 }
391
392 // 1. Compute the permutation vector to shuffle packed shape into the shape
393 // before any outer or inner permutations have been applied.
394 PackingMetadata packingMetadata;
395 SmallVector<int64_t> packedToStripMinedShapePerm =
396 getUnPackInverseSrcPerm(unPackOp, packingMetadata);
397
398 // 2. Compute the stripMinedShape: this is the packed shape without outer and
399 // inner permutations.
400 SmallVector<int64_t> stripMinedShape(packedTensorType.getShape());
401 applyPermutationToVector(stripMinedShape, packedToStripMinedShapePerm);
402
403 // 3. Transpose packedShape to stripMinedShape.
404 RankedTensorType stripMinedTensorType =
405 RankedTensorType::Builder(packedTensorType).setShape(stripMinedShape);
406 RankedTensorType collapsedType = tensor::CollapseShapeOp::inferCollapsedType(
407 stripMinedTensorType, packingMetadata.reassociations);
408
409 // Get dynamic dims from input tensor based on packedToStripMinedShapePerm
410 // permutation.
412 tensor::getMixedSizes(rewriter, loc, unPackOp.getSource());
413 applyPermutationToVector(dims, packedToStripMinedShapePerm);
414 auto emptyOp = tensor::EmptyOp::create(rewriter, loc, dims,
415 stripMinedTensorType.getElementType());
416 auto transposeOp =
417 linalg::TransposeOp::create(rewriter, loc, unPackOp.getSource(), emptyOp,
418 packedToStripMinedShapePerm);
419
420 LDBG() << "insertPositions: "
421 << llvm::interleaved(packingMetadata.insertPositions);
422 LDBG() << "packedShape: " << llvm::interleaved(packedTensorType.getShape());
423 LDBG() << "packedToStripMinedShapePerm: "
424 << llvm::interleaved(packedToStripMinedShapePerm);
425 LDBG() << "reassociations: "
426 << llvm::interleaved(llvm::map_range(packingMetadata.reassociations,
428 LDBG() << "stripMinedShape: " << llvm::interleaved(stripMinedShape);
429 LDBG() << "collapsed type: " << collapsedType;
430
431 // 4. Collapse from the stripMinedShape to the padded result.
432 auto reshapeOp = tensor::CollapseShapeOp::create(
433 rewriter, loc, collapsedType, transposeOp->getResult(0),
434 packingMetadata.reassociations);
435
436 // 5. ExtractSlice.
437 int64_t destRank = destTensorType.getRank();
438 auto extractSliceOp = tensor::ExtractSliceOp::create(
439 rewriter, loc, destTensorType, reshapeOp->getResult(0),
440 SmallVector<OpFoldResult>(destRank, zero),
441 tensor::getMixedSizes(rewriter, loc, unPackOp.getDest()),
442 SmallVector<OpFoldResult>(destRank, one));
443
444 // 6. Inject a copy to preserve DPS.
445 auto copyOp = linalg::CopyOp::create(
446 rewriter, loc, extractSliceOp->getResult(0), unPackOp.getDest());
447
448 // 7. Replace unPackOp by copyOp.
449 rewriter.replaceOp(unPackOp, copyOp->getResults());
450
451 return LowerUnPackOpResult{emptyOp, transposeOp, reshapeOp, extractSliceOp,
452 copyOp};
453}
454
456PackedOperandsDimList::extractPackedDimsForOperand(int64_t operandPos) {
458 for (auto &i : spec) {
459 if (!i.packedDimForEachOperand[operandPos].has_value())
460 continue;
461 res.push_back(i.packedDimForEachOperand[operandPos].value());
462 }
463 return res;
464}
465
466SmallVector<OpFoldResult>
467PackedOperandsDimList::extractPackSizesForOperand(int64_t operandPos) {
468 SmallVector<OpFoldResult> res;
469 for (auto &i : spec) {
470 if (!i.packedDimForEachOperand[operandPos].has_value())
471 continue;
472 res.push_back(i.packedSize);
473 }
474 return res;
475}
476
477/// Implement packing of a single LinalgOp by performing packing by
478/// `packedSizes`. There must be one packedSizes entry per `linalgOp` iterator.
479/// Return the packed Linalg op on success, failure otherwise.
480FailureOr<PackResult> linalg::pack(RewriterBase &rewriter,
481 linalg::LinalgOp linalgOp,
482 ArrayRef<OpFoldResult> packedSizes) {
483 if (packedSizes.size() != linalgOp.getNumLoops()) {
484 return rewriter.notifyMatchFailure(linalgOp,
485 "incorrect number of pack sizes");
486 }
487 if (!linalgOp.hasPureTensorSemantics()) {
488 return rewriter.notifyMatchFailure(
489 linalgOp, "expects LinalgOp with pure tensor semantics");
490 }
491
492 Location loc = linalgOp->getLoc();
493 SmallVector<AffineMap> indexingMaps = linalgOp.getIndexingMapsArray();
495 linalgOp.getIteratorTypesArray();
496 LDBG() << "Start packing: " << linalgOp;
497 LDBG() << "maps: " << llvm::interleaved(indexingMaps);
498 LDBG() << "iterators: " << llvm::interleaved(iteratorTypes);
499
502 // Step 1. Pack each dim of the LinalgOp metadata by packedSizes[i].
503 PackedOperandsDimList listOfPackedOperandsDim;
504 for (int64_t i = 0, e = packedSizes.size(); i < e; ++i) {
505 std::optional<int64_t> maybeConstant = getConstantIntValue(packedSizes[i]);
506 // Skip tile sizes explicitly set to 0.
507 if (maybeConstant.has_value() && maybeConstant.value() == 0)
508 continue;
509
510 PackedOperandsDim packedOperandsDims;
511 packedOperandsDims.packedSize = packedSizes[i];
512 FailureOr<SmallVector<std::optional<int64_t>>>
513 maybePackedDimForEachOperand =
514 packLinalgMetadataOnce(indexingMaps, iteratorTypes, i);
515 if (failed(maybePackedDimForEachOperand))
516 return failure();
517 packedOperandsDims.packedDimForEachOperand = *maybePackedDimForEachOperand;
518
519 LDBG() << "++++ After pack size #" << i << ": " << packedSizes[i];
520 LDBG() << "maps: " << llvm::interleaved(indexingMaps);
521 LDBG() << "iterators: " << llvm::interleaved(iteratorTypes);
522 LDBG() << "packedDimForEachOperand: "
523 << llvm::interleaved(packedOperandsDims.packedDimForEachOperand);
524
525 listOfPackedOperandsDim.pushBack(std::move(packedOperandsDims));
526 }
527
528 // Step 2. Propagate packing to all LinalgOp operands.
529 SmallVector<Value> inputsAndInits, results;
530 SmallVector<OpOperand *> initOperands =
531 llvm::to_vector(llvm::make_pointer_range(linalgOp.getDpsInitsMutable()));
532 SmallVector<OpOperand *> inputOperands = linalgOp.getDpsInputOperands();
533 for (const auto &operandsList : {inputOperands, initOperands}) {
534 for (OpOperand *opOperand : operandsList) {
535 int64_t pos = opOperand->getOperandNumber();
536 Value operand = opOperand->get();
537 SmallVector<int64_t> innerPos =
538 listOfPackedOperandsDim.extractPackedDimsForOperand(pos);
539 SmallVector<OpFoldResult> innerPackSizes =
540 listOfPackedOperandsDim.extractPackSizesForOperand(pos);
541 LDBG() << "operand: " << operand;
542 LDBG() << "innerPos: " << llvm::interleaved(innerPos);
543 LDBG() << "innerPackSizes: " << llvm::interleaved(innerPackSizes);
544 if (innerPackSizes.empty()) {
545 inputsAndInits.push_back(operand);
546 continue;
547 }
548 Value dest = linalg::PackOp::createDestinationTensor(
549 rewriter, loc, operand, innerPackSizes, innerPos,
550 /*outerDimsPerm=*/{});
551 ShapedType operandType = cast<ShapedType>(operand.getType());
552 bool areConstantTiles =
553 llvm::all_of(innerPackSizes, [](OpFoldResult tile) {
554 return getConstantIntValue(tile).has_value();
555 });
556 if (areConstantTiles && operandType.hasStaticShape() &&
557 !linalg::PackOp::requirePaddingValue(
558 operandType.getShape(), innerPos,
559 cast<ShapedType>(dest.getType()).getShape(), {},
560 innerPackSizes)) {
561 packOps.push_back(linalg::PackOp::create(rewriter, loc, operand, dest,
562 innerPos, innerPackSizes));
563 } else {
564 // TODO: value of the padding attribute should be determined by
565 // consumers.
566 auto zeroAttr =
567 rewriter.getZeroAttr(getElementTypeOrSelf(dest.getType()));
568 Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
569 packOps.push_back(linalg::PackOp::create(
570 rewriter, loc, operand, dest, innerPos, innerPackSizes, zero));
571 }
572 inputsAndInits.push_back(packOps.back().getResult());
573 }
574 }
575
576 // Step 3. Build the packed op, use the type of `inits` as result types.
577 ValueRange inputs =
578 ValueRange{inputsAndInits}.take_front(linalgOp.getNumDpsInputs());
579 ValueRange inits =
580 ValueRange{inputsAndInits}.take_back(linalgOp.getNumDpsInits());
581 auto packedLinalgOp =
582 linalg::GenericOp::create(rewriter, linalgOp.getLoc(), inits.getTypes(),
583 inputs, inits, indexingMaps, iteratorTypes);
584 packedLinalgOp.getRegion().takeBody(linalgOp->getRegion(0));
585
586 // Step 4. Propagate packing to all the op results.
587 for (OpResult result : packedLinalgOp->getResults()) {
588 int64_t resultNum = result.getResultNumber();
589 linalg::PackOp maybePackedInit =
590 inits[resultNum].getDefiningOp<linalg::PackOp>();
591 if (!maybePackedInit) {
592 results.push_back(result);
593 continue;
594 }
595 // Build the symmetrical UnPackOp to the existing PackOp.
596 unPackOps.push_back(linalg::UnPackOp::create(
597 rewriter, packedLinalgOp->getLoc(), result, maybePackedInit.getSource(),
598 maybePackedInit.getInnerDimsPos(), maybePackedInit.getMixedTiles()));
599 results.push_back(unPackOps.back().getResult());
600 }
601
602 // Step 5. Replace `linalgOp`.
603 rewriter.replaceOp(linalgOp, results);
604
605 // Return packedLinalgOp.
606 return PackResult{packOps,
607 cast<linalg::LinalgOp>(packedLinalgOp.getOperation()),
608 unPackOps};
609}
610
611//===----------------------------------------------------------------------===//
612// packTranspose transformation.
613//===----------------------------------------------------------------------===//
614
615/// Return a copy of `tensorType` after permutation by `permutationVector`.
616// Note: Should be a new method in of MemRef/RankedTensor/VectorType::Builder
617// but this would introduce a dependence on Dialect in IR.
618// TODO: Restructure.
619static RankedTensorType permuteShape(RankedTensorType tensorType,
620 ArrayRef<int64_t> permutationVector) {
621 SmallVector<int64_t> shape(tensorType.getShape());
622 applyPermutationToVector(shape, permutationVector);
623 return RankedTensorType::Builder(tensorType).setShape(shape);
624}
625
626/// Return a new GenericOp obtained by transposing opOperand by the permutation
627/// vector:
628/// - the corresponding indexing map is transposed by `permutation`
629/// - the corresponding operand value is replaced by `transposedValue`
630/// `linalgOp` is replaced by the return op in the process.
631/// Asserts that `transposedValue` is of the proper transposed ShapedType.
633 RewriterBase &rewriter, LinalgOp linalgOp, OpOperand &opOperand,
634 ArrayRef<int64_t> permutation, Value transposedValue) {
635 // Sanity check the operand.
636 assert(linalgOp == opOperand.getOwner() && "linalg op must own the operand");
637
638 // Sanity check of the expected transposed tensor type.
639 auto tensorType = permuteShape(
640 cast<RankedTensorType>(opOperand.get().getType()), permutation);
641 (void)tensorType;
642 assert(tensorType == transposedValue.getType() &&
643 "expected tensor type mismatch");
644
645 // Compute the transposed indexing map.
646 // Sigh unsigned pollution.
647 SmallVector<unsigned> tmpTransposition =
648 llvm::map_to_vector(permutation, [](int64_t i) -> unsigned { return i; });
649 AffineMap permutationMap =
650 AffineMap::getPermutationMap(tmpTransposition, rewriter.getContext());
651 AffineMap transposedMap =
652 permutationMap.compose(linalgOp.getMatchingIndexingMap(&opOperand));
653
654 // Set the transposed indexing map in the proper position.
655 SmallVector<AffineMap> indexingMaps = linalgOp.getIndexingMapsArray();
656 indexingMaps[linalgOp.getIndexingMapIndex(&opOperand)] = transposedMap;
657 // Set the transposedValue in the proper operand position.
658 SmallVector<Value> operands = linalgOp->getOperands();
659 operands[opOperand.getOperandNumber()] = transposedValue;
660
661 ValueRange operandsRef(operands);
662 auto transposedGenericOp = linalg::GenericOp::create(
663 rewriter,
664 /*location=*/linalgOp->getLoc(),
665 /*resultTensorTypes=*/
666 operandsRef.drop_front(linalgOp.getNumDpsInputs()).getTypes(),
667 /*inputs=*/operandsRef.take_front(linalgOp.getNumDpsInputs()),
668 /*outputs=*/operandsRef.drop_front(linalgOp.getNumDpsInputs()),
669 /*indexingMaps=*/indexingMaps,
670 /*iteratorTypes=*/linalgOp.getIteratorTypesArray());
671 transposedGenericOp.getRegion().takeBody(linalgOp->getRegion(0));
672 rewriter.replaceOp(linalgOp, transposedGenericOp->getResults());
673
674 return cast<linalg::LinalgOp>(transposedGenericOp.getOperation());
675}
676
677FailureOr<PackTransposeResult>
678linalg::packTranspose(RewriterBase &rewriter, linalg::PackOp packOp,
679 linalg::LinalgOp linalgOp, linalg::UnPackOp maybeUnPackOp,
680 ArrayRef<int64_t> outerPerm,
681 ArrayRef<int64_t> innerPerm) {
682 Location loc = linalgOp.getLoc();
683
684 // Step 1. Transpose packOp.
685 rewriter.setInsertionPoint(packOp);
686 linalg::PackOp transposedPackOp =
687 packOp.createTransposedClone(rewriter, loc, innerPerm, outerPerm);
688
689 if (packOp.hasPureBufferSemantics() || !packOp.getResult().hasOneUse())
690 return rewriter.notifyMatchFailure(linalgOp, "expect single pack use");
691
692 OpOperand &packUse = *packOp->getUses().begin();
693 if (packUse.getOwner() != linalgOp) {
694 return rewriter.notifyMatchFailure(
695 linalgOp, "not a single use by the LinalgOp target");
696 }
697 if (maybeUnPackOp &&
698 (!linalgOp.isDpsInit(&packUse) ||
699 maybeUnPackOp.getSource() != linalgOp.getTiedOpResult(&packUse))) {
700 return rewriter.notifyMatchFailure(linalgOp,
701 "not produced by the LinalgOp target");
702 }
703
704 // Step 2. Transpose linalgOp.
705 // transposedPackOp.getOuterDimsPerm() may be empty, in which case it is the
706 // identity. Don't rely on it.
707 int64_t numLeadingDims = packOp.getSourceRank();
708 int64_t numTrailingDims = packOp.getInnerDimsPos().size();
709 // Step 2.a. Compute the permutation on the whole operand.
710 // Leading part just reuse the outerPerm.
711 SmallVector<int64_t> permutation(outerPerm);
712 if (permutation.empty())
713 llvm::append_range(permutation, llvm::seq<int64_t>(0, numLeadingDims));
714 // Trailing part needs to reindex positions by `numLeadingDims`.
715 if (innerPerm.empty()) {
716 llvm::append_range(
717 permutation,
718 llvm::seq<int64_t>(numLeadingDims, numLeadingDims + numTrailingDims));
719 } else {
720 llvm::append_range(permutation,
721 llvm::map_range(innerPerm, [&](int64_t pos) {
722 return numLeadingDims + pos;
723 }));
724 }
725 if (!isPermutationVector(permutation))
726 return rewriter.notifyMatchFailure(linalgOp, "invalid permutation");
727
728 // Step 2.b. Save the transposedPackUse operand number in case we need to
729 // get the tied OpResult after `linalgOp` has been replaced.
730 int64_t packUseOperandNumber = packUse.getOperandNumber();
731 // Step 2.c. Actually perform the transposition.
732 rewriter.setInsertionPoint(linalgOp);
733 linalg::LinalgOp transposedLinalgOp = transposeOneLinalgOperandAndReplace(
734 rewriter, linalgOp, packUse, permutation, transposedPackOp.getResult());
735
736 // Step 3. Maybe transpose unPackOp.
737 linalg::UnPackOp transposedUnPackOp;
738 if (maybeUnPackOp) {
739 OpOperand &opOperand =
740 transposedLinalgOp->getOpOperand(packUseOperandNumber);
741 OpResult transposedResult = transposedLinalgOp.getTiedOpResult(&opOperand);
742 rewriter.setInsertionPoint(maybeUnPackOp);
743 transposedUnPackOp = maybeUnPackOp.createTransposedClone(
744 rewriter, loc, transposedResult, innerPerm, outerPerm);
745
746 rewriter.replaceOp(maybeUnPackOp, transposedUnPackOp->getResults());
747 }
748
749 // Step 4. Finally, replace packOp now that we don't need it anymore.
750 if (packOp.hasPureTensorSemantics())
751 rewriter.replaceOp(packOp, transposedPackOp->getResults());
752 else
753 rewriter.eraseOp(packOp);
754
755 return PackTransposeResult{transposedPackOp, transposedLinalgOp,
756 transposedUnPackOp};
757}
758
759//===----------------------------------------------------------------------===//
760// packMatmulGreedily transformation.
761//===----------------------------------------------------------------------===//
762
763/// Pack a LinalgOp by greedily inferring matmul dimensions (m, n, k) where m
764/// and n are proper parallel dimensions and k is a proper reduction
765/// dimension. Packing occurs by rewriting the op as a linalg.generic and
766/// calling linalg::pack by `mnkPackedSizes`. The order of the packed
767/// dimensions is customizable: the `mnkOrder` is a permutation of {0, 1, 2}
768/// to reorder {m, n, k} into one of the 8 possible forms. The outer
769/// dimensions of the operands are not permuted at this time, this is left for
770/// future work.
771FailureOr<PackResult>
772linalg::packMatmulGreedily(RewriterBase &rewriter, LinalgOp linalgOp,
773 ArrayRef<OpFoldResult> mnkPackedSizes,
774 ArrayRef<int64_t> mnkPaddedSizesNextMultipleOf,
775 ArrayRef<int64_t> mnkOrder) {
776 assert(mnkPackedSizes.size() == 3 && "unexpected num of packing sizes");
777 assert((mnkPaddedSizesNextMultipleOf.empty() ||
778 mnkPaddedSizesNextMultipleOf.size() == 3) &&
779 "num of packing sizes next multiple should be empty or of size 3");
780 assert(mnkOrder.size() == 3 && "unexpected mnkOrder size");
781 assert(isPermutationVector(mnkOrder) && "expected a permutation");
782
783 int64_t numLoops = linalgOp.getNumLoops();
784 if (numLoops <= 2) {
785 LDBG() << "need 3+ loops to find a matmul to pack, got " << numLoops
786 << " in: " << linalgOp;
787 return rewriter.notifyMatchFailure(
788 linalgOp, "need 3+ loops to find a matmul to pack");
789 }
790
791 // Locally adjust the desired iterator position of mnk and packing sizes.
792 int64_t numPackedDims = mnkPackedSizes.size();
793 SmallVector<int64_t> mmnnkkPos(numPackedDims);
794 for (int64_t i = 0, e = numPackedDims; i < e; ++i)
795 mmnnkkPos[i] = numLoops - numPackedDims + mnkOrder[i];
796 SmallVector<OpFoldResult> packedSizes(numPackedDims);
797 for (int64_t i = 0, e = numPackedDims; i < e; ++i)
798 packedSizes[mnkOrder[i]] = mnkPackedSizes[i];
799 SmallVector<int64_t> paddedSizesNextMultipleOf(numPackedDims);
800 for (int64_t i = 0, e = numPackedDims; i < e; ++i) {
801 paddedSizesNextMultipleOf[mnkOrder[i]] =
802 mnkPaddedSizesNextMultipleOf.empty() ? 0
803 : mnkPaddedSizesNextMultipleOf[i];
804 }
805
806 // 1. Infer dims that are important for matmul.
807 FailureOr<ContractionDimensions> maybeDimensions =
808 inferContractionDims(linalgOp);
809 if (failed(maybeDimensions)) {
810 LDBG() << "couldn't infer matmul iterators in: " << linalgOp;
811 return rewriter.notifyMatchFailure(linalgOp,
812 "couldn't infer matmul iterators");
813 }
814
815 // 2. Normalize linalgOp to an kmn-matmul-like with [red, par, par] most
816 // minor iterators. In cases with multiple options for m, n, k bias towards
817 // the most minor embedding.
818 // If we wanted a different normalization order, this is where it would have
819 // to plug a heuristic.
820 int64_t mPos = maybeDimensions->m.back(), nPos = maybeDimensions->n.back(),
821 kPos = maybeDimensions->k.back();
822 LDBG() << "Start packing generic op greedily with (m@" << mPos << ", n@"
823 << nPos << ", k@" << kPos << "): " << linalgOp;
824
825 // 2.a. Rewrite as a generic.
826 auto genericOp = dyn_cast<GenericOp>(linalgOp.getOperation());
827 if (!genericOp) {
828 FailureOr<GenericOp> generalizeResult =
829 generalizeNamedOp(rewriter, linalgOp);
830 assert(succeeded(generalizeResult) && "unexpected failure generalizing op");
831 genericOp = *generalizeResult;
832 }
833
834 // 2.b. Interchange to move the dimensions (k, m, n) as most-minor
835 // iterators. Note that this only normalized the iteration order and does
836 // not change the indexings of any operand.
837 SmallVector<int64_t> permutation =
838 computePermutationVector(numLoops, {mPos, nPos, kPos}, mmnnkkPos);
839 LDBG() << "perm: " << llvm::interleaved(permutation);
840 // Sign .. unsigned pollution.
841 SmallVector<unsigned> unsignedPerm(permutation.begin(), permutation.end());
842 FailureOr<GenericOp> interchangeResult =
843 interchangeGenericOp(rewriter, genericOp, unsignedPerm);
844 assert(succeeded(interchangeResult) && "unexpected failure interchanging op");
845 genericOp = *interchangeResult;
846 LDBG() << "Generalized Op to pack: " << genericOp;
847
848 // At this point, the op iterators are normalized to {leading, k, m, n}.
849 // The layouts induced by packing will always be:
850 // - LHS{leading_lhs, kk, mm}
851 // - RHS{leading_rhs, kk, nn}
852 // - RES{leading_res, mm, nn}
853 // If we wanted to change the packed order, we would reorder (k, m, n) to
854 // something else above.
855 //
856 // Additional permutations of the outer dims of the operands (i.e.
857 // leading_lhs, leading_rhs and leading_res) could follow by computing the
858 // desired outerPerm for each operand.
859 // This is left for future work.
860
861 // TODO: this creates too much IR, go use reifyResultShapes.
862 SmallVector<Range, 4> loopRanges =
863 cast<LinalgOp>(genericOp.getOperation())
864 .createLoopRanges(rewriter, genericOp.getLoc());
865
866 // Add leading zeros to match numLoops, we only pack the last 3 dimensions
867 // post interchange.
868 LDBG() << "paddedSizesNextMultipleOf: "
869 << llvm::interleaved(paddedSizesNextMultipleOf);
870 LDBG() << "loopRanges: "
871 << llvm::interleaved(
872 llvm::map_range(loopRanges, [](Range r) { return r.size; }));
873 SmallVector<OpFoldResult> adjustedPackedSizes(numLoops - packedSizes.size(),
874 rewriter.getIndexAttr(0));
875 for (int64_t i = 0, e = numPackedDims; i < e; ++i) {
876 if (paddedSizesNextMultipleOf[i] == 0) {
877 adjustedPackedSizes.push_back(packedSizes[i]);
878 continue;
879 }
880 AffineExpr d0, s0;
881 bindDims(rewriter.getContext(), d0);
882 bindSymbols(rewriter.getContext(), s0);
883 adjustedPackedSizes.push_back(affine::makeComposedFoldedAffineApply(
884 rewriter, genericOp->getLoc(), d0.ceilDiv(s0) * s0,
885 {loopRanges[adjustedPackedSizes.size()].size,
886 rewriter.getIndexAttr(paddedSizesNextMultipleOf[i])}));
887 }
888 LDBG() << "adjustedPackedSizes: " << llvm::interleaved(adjustedPackedSizes);
889
890 // TODO: If we wanted to give the genericOp a name after packing, after
891 // calling `pack` would be a good time. One would still need to check that
892 // `containsMostMinorMatmul(packingRes->packedLinalgOp)` is true, since we
893 // also allow degenerate matmul cases (i.e. matvec, dot).
894 return pack(rewriter, genericOp, adjustedPackedSizes);
895}
896
897//===----------------------------------------------------------------------===//
898// Transformations exposed as rewrite patterns.
899//===----------------------------------------------------------------------===//
900
903 assert(!tileSizeComputationFunction && "tile sizes already set");
904 SmallVector<int64_t, 4> tileSizes(ts);
905 tileSizeComputationFunction = [tileSizes](OpBuilder &b, Operation *op) {
907 b.setInsertionPointToStart(
908 &op->getParentOfType<func::FuncOp>().getBody().front());
909 return llvm::map_to_vector<4>(tileSizes, [&](int64_t s) {
910 Value v = arith::ConstantIndexOp::create(b, op->getLoc(), s);
911 return v;
912 });
913 };
914 return *this;
915}
916
918 memref::CopyOp copyOp, PatternRewriter &rewriter) const {
919 return vectorizeCopy(rewriter, copyOp);
920}
921
922/// Filling `dest` using FillOp constant padding value if possible.
923/// Otherwise, generate a tensor::GenerateOp.
925 RewriterBase &rewriter, tensor::PadOp padOp, Value dest,
926 const SmallVector<Value> &dynSizes) const {
927 auto padValue = padOp.getConstantPaddingValue();
928 if (padValue) {
929 // Move the padding value defined inside the PadOp block to outside.
930 if (padValue.getParentBlock() == &padOp.getRegion().front())
931 rewriter.moveOpBefore(padValue.getDefiningOp(), padOp);
932 return FillOp::create(rewriter, padOp.getLoc(), padValue, dest).result();
933 }
934
935 // Fill could not be optimized: Lower to tensor::GenerateOp with region.
936 auto generateOp = tensor::GenerateOp::create(rewriter, padOp.getLoc(),
937 padOp.getResultType(), dynSizes);
938 // Copy region to new op.
939 IRMapping bvm;
940 padOp.getRegion().cloneInto(&generateOp.getRegion(), bvm);
941 return generateOp;
942}
943
944LogicalResult
946 PatternRewriter &rewriter) const {
947 // Given an OpFoldResult, return an index-typed value.
948 auto getIdxValue = [&](OpFoldResult ofr) {
949 if (auto val = llvm::dyn_cast_if_present<Value>(ofr))
950 return val;
952 rewriter, padOp.getLoc(),
953 cast<IntegerAttr>(cast<Attribute>(ofr)).getInt())
954 .getResult();
955 };
956
957 auto resultType = padOp.getResultType();
958 // Compute size of EmptyOp. Any combination of static/dynamic is supported.
959 SmallVector<Value> dynSizes;
960 SmallVector<int64_t> staticSizes;
961 for (unsigned dim = 0; dim < resultType.getRank(); ++dim) {
962 if (resultType.isDynamicDim(dim)) {
963 auto srcSize = getIdxValue(tensor::getMixedSize(rewriter, padOp.getLoc(),
964 padOp.getSource(), dim));
965 // Add low and high padding value.
966 auto plusLow = rewriter.createOrFold<arith::AddIOp>(
967 padOp.getLoc(), srcSize, getIdxValue(padOp.getMixedLowPad()[dim]));
968 auto plusHigh = rewriter.createOrFold<arith::AddIOp>(
969 padOp.getLoc(), plusLow, getIdxValue(padOp.getMixedHighPad()[dim]));
970 dynSizes.push_back(plusHigh);
971 }
972 staticSizes.push_back(resultType.getDimSize(dim));
973 }
974
975 // Init tensor and fill it with padding.
976 Value emptyTensor =
977 tensor::EmptyOp::create(rewriter, padOp.getLoc(), staticSizes,
978 resultType.getElementType(), dynSizes);
979 Value fill = createFillOrGenerateOp(rewriter, padOp, emptyTensor, dynSizes);
980
981 // Generate a InsertSliceOp for copying the PadOp source.
982 auto sourceType = padOp.getSourceType();
983 // Compute size of source of tensor::PadOp.
985 tensor::getMixedSizes(rewriter, padOp.getLoc(), padOp.getSource());
986 // Strides of InsertSliceOp are all 1.
987 SmallVector<OpFoldResult> strides(sourceType.getRank(),
988 rewriter.getIndexAttr(1));
989 rewriter.replaceOpWithNewOp<tensor::InsertSliceOp>(
990 padOp, padOp.getSource(), fill, padOp.getMixedLowPad(), srcSizes,
991 strides);
992
993 return success();
994}
995
997 tensor::ExtractSliceOp sliceOp, PatternRewriter &rewriter) const {
998 if (!sliceOp.hasUnitStride())
999 return failure();
1000
1001 auto padOp = sliceOp.getSource().getDefiningOp<tensor::PadOp>();
1002 if (!padOp)
1003 return failure();
1004
1005 bool zeroSliceGuard = true;
1006 if (controlFn) {
1007 if (std::optional<bool> control = controlFn(sliceOp))
1008 zeroSliceGuard = *control;
1009 else
1010 return failure();
1011 }
1012
1013 FailureOr<TilingResult> tilingResult =
1014 tensor::bubbleUpPadSlice(rewriter, padOp, sliceOp.getMixedOffsets(),
1015 sliceOp.getMixedSizes(), zeroSliceGuard);
1016 if (failed(tilingResult))
1017 return failure();
1018
1019 RankedTensorType sourceType = sliceOp.getSourceType();
1020 RankedTensorType resultType = sliceOp.getResultType();
1021
1022 // If the extract_slice is not rank-reduced, all shapes are static and the
1023 // data source is actually used. Rewrite into pad(extract_slice(x)).
1024 if (sourceType.getRank() == resultType.getRank()) {
1025 rewriter.replaceOp(sliceOp, tilingResult->tiledValues);
1026 return success();
1027 }
1028
1029 // Handle rank-reduced slice by creating another extract_slice op.
1031 rewriter, sliceOp.getLoc(), tilingResult->tiledValues[0], resultType);
1032
1033 rewriter.replaceOp(sliceOp, rankReduced);
1034 return success();
1035}
1036
1037/// If padding value is set, returns a tensor.pad Op for the source tensor,
1038/// with the output shape matching the output of `packOp`. Otherwise, returns
1039/// the source directly.
1040///
1041/// This method assumes that all outer dims for this pack Op are 1.
1043 linalg::PackOp packOp) {
1044 Value input = packOp.getSource();
1045 // TODO: Support Memref PackOp. Temporarily return just Op Source.
1046 if (!packOp.hasPureTensorSemantics())
1047 return input;
1048
1049 if (!packOp.getPaddingValue()) {
1050 return input;
1051 }
1052
1053 assert(llvm::all_of(packOp.getAllOuterDims(),
1054 [](int64_t val) { return val == 1; }) &&
1055 "some outer dims are != 1");
1056
1057 Location loc = packOp.getLoc();
1058 ShapedType inputType = packOp.getSourceType();
1059 int64_t inputRank = inputType.getRank();
1060
1061 DenseMap<int64_t, OpFoldResult> tileAndPosMapping =
1062 packOp.getDimAndTileMapping();
1063
1064 // The sizes of dynamic tiles
1065 SmallVector<Value> dynamicTileSizes;
1066
1067 // Collect dims for the padded shape.
1068 SmallVector<int64_t> paddedShape;
1069 for (int64_t dimIdx = 0; dimIdx < inputRank; ++dimIdx) {
1070 // 1. Non-tiled outer dims.
1071 // These dims should be 1 and we simply preserve them.
1072 if (!tileAndPosMapping.count(dimIdx)) {
1073 int64_t inputDimSize = inputType.getDimSize(dimIdx);
1074 assert(inputDimSize == 1 &&
1075 "with all outer dims == 1, this non-tiled input dim should be 1!");
1076 paddedShape.push_back(inputDimSize);
1077 continue;
1078 }
1079
1080 // 2. Tiled outer dims
1081 // As all outer dims == 1, it is safe to use the tile size for the padded
1082 // shape.
1083 OpFoldResult tileSizeForDim = tileAndPosMapping.lookup(dimIdx);
1084
1085 // 2.1 Static tile sizes
1086 std::optional<int64_t> cstTileSize = getConstantIntValue(tileSizeForDim);
1087 if (cstTileSize.has_value()) {
1088 paddedShape.push_back(cstTileSize.value());
1089 continue;
1090 }
1091
1092 // 2.2 Dynamic tile sizes
1093 paddedShape.push_back(ShapedType::kDynamic);
1094
1095 // Get the value that holds the dynamic size.
1096 dynamicTileSizes.push_back(llvm::dyn_cast<Value>(tileSizeForDim));
1097 }
1098 auto resultType =
1099 RankedTensorType::get(paddedShape, inputType.getElementType());
1100 return tensor::createPadHighOp(resultType, input, packOp.getPaddingValue(),
1101 /*nofold=*/false, loc, builder,
1102 dynamicTileSizes);
1103}
1104
1105// Normalizes a permutation on a higher rank space to its actual size, e.g.
1106// perm = [1, 4, 2]
1107// becomes
1108// norm = [0, 2, 1]
1109static SmallVector<int64_t>
1111 constexpr int64_t kNonTiledMarker = -1;
1112 SmallVector<int64_t> vec(rank, kNonTiledMarker);
1113 for (auto [index, value] : llvm::enumerate(perm))
1114 vec[value] = index;
1115 SmallVector<int64_t> normalizedPerm = llvm::filter_to_vector(
1116 vec, [&](int64_t v) { return v != kNonTiledMarker; });
1117 // This inverts the permutation in addition to normalizing so invert back.
1118 return invertPermutationVector(normalizedPerm);
1119}
1120
1121// Gets the normalized permutation implied by innerDimsPos and outerDimsPerm
1122// assuming rank reduction of unit outer dims.
1123static SmallVector<int64_t>
1125 ArrayRef<int64_t> innerDimsPos,
1126 ArrayRef<int64_t> outerDimsPerm) {
1127 SmallVector<int64_t> rankReducedOuterDimsPerm;
1128 SmallVector<int64_t> outerDims;
1129 SmallVector<int64_t> innerDims;
1130 int64_t dim = 0;
1131 int64_t unpackedRank = shape.size();
1132 for (auto i : llvm::seq<unsigned>(0, unpackedRank)) {
1133 if (llvm::is_contained(innerDimsPos, i)) {
1134 innerDims.push_back(dim++);
1135 continue;
1136 }
1137 if (shape[i] == 1)
1138 continue;
1139 outerDims.push_back(dim++);
1140 if (!outerDimsPerm.empty())
1141 rankReducedOuterDimsPerm.push_back(outerDimsPerm[i]);
1142 }
1143
1144 // Get the position of the inner dims after permutation.
1145 SmallVector<int64_t> innerPerm =
1146 getPackUnpackNormalizedPerm(unpackedRank, innerDimsPos);
1147 applyPermutationToVector<int64_t>(innerDims, innerPerm);
1148
1149 // Ditto for the outer dims.
1150 SmallVector<int64_t> perm = outerDims;
1151
1152 rankReducedOuterDimsPerm =
1153 getPackUnpackNormalizedPerm(unpackedRank, rankReducedOuterDimsPerm);
1154 if (!rankReducedOuterDimsPerm.empty())
1155 applyPermutationToVector<int64_t>(perm, rankReducedOuterDimsPerm);
1156
1157 // The tile always ends up as the inner most dims after packing.
1158 perm.append(innerDims);
1159
1160 return perm;
1161}
1162
1164 linalg::PackOp packOp, PatternRewriter &rewriter) const {
1165 // TODO: Support Memref PackOp. Temporarily return failure.
1166 if (!packOp.hasPureTensorSemantics())
1167 return failure();
1168
1169 if (llvm::any_of(packOp.getTiledOuterDims(),
1170 [](int64_t dim) { return dim != 1; })) {
1171 return rewriter.notifyMatchFailure(
1172 packOp, "not all outer dimensions of the result are 1s");
1173 }
1174
1175 // When a padding value is set, getPackOpSourceOrPaddedSource only supports
1176 // the case where every outer dim (including un-tiled ones) is 1. Bail out
1177 // instead of hitting an assertion on a non-unit un-tiled outer dim.
1178 // FIXME: Handle this case by decomposing the padded pack instead of bailing
1179 // out; a non-unit un-tiled outer dim should be supported here.
1180 if (packOp.getPaddingValue() &&
1181 llvm::any_of(packOp.getAllOuterDims(),
1182 [](int64_t dim) { return dim != 1; })) {
1183 return rewriter.notifyMatchFailure(
1184 packOp, "cannot decompose padded pack with a non-unit un-tiled outer "
1185 "dimension");
1186 }
1187
1188 ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
1189 auto outerDimsPerm = packOp.getOuterDimsPerm();
1190
1191 // Verify that there are no:
1192 // * non-unit + un-tiled-outer-dims,
1193 // that are permuted. Supporting such cases would require refining the logic
1194 // that generates the Transpose Op.
1195 if (!llvm::all_of(outerDimsPerm, [&innerDimsPos, &packOp](int64_t dim) {
1196 static int prev = 0;
1197 // Skip tiled dims - these can be permuted.
1198 if (llvm::is_contained(innerDimsPos, dim))
1199 return true;
1200
1201 // Check whether this dim has been permuted. Permuting unit dims is fine
1202 // as that's effectively a no-op.
1203 if (dim < prev && (packOp.getResult().getType().getShape()[prev] != 1 ||
1204 packOp.getResult().getType().getShape()[dim] != 1))
1205 return false;
1206
1207 prev = dim;
1208 return true;
1209 })) {
1210 return rewriter.notifyMatchFailure(
1211 packOp, "At least one non-unit and un-tiled outer dim is permuted, "
1212 "this is not supported ATM!");
1213 }
1214
1215 Location loc = packOp.getLoc();
1216
1217 int64_t srcRank = packOp.getSourceRank();
1218
1219 // 1. Get the input that is going to be packed. If the input requires padding,
1220 // add a padding operation and return that as the input.
1221 Value input = getPackOpSourceOrPaddedSource(rewriter, packOp);
1222
1223 // 2. Transpose the input to match the inner tile order:
1224 // %init = tensor.empty()
1225 // %transposed_tile = linalg.transpose ins(%source_or_padded_source),
1226 // outs(%init)
1227 // Assumptions made:
1228 // - All tiled outer dims are 1 - the corresponding transposition order
1229 // doesn't matter, but requires all dim indices to be present.
1230 // - Un-tiled outer dims remain un-permuted.
1231
1232 // 2.1 Get the permutation for linalg.transpose:
1233 // [ untiled-dims, inner-dims-pos ]
1234 // Note, this logic assumes that the untiled dims are not permuted.
1235 SmallVector<int64_t> srcPermForTranspose;
1236 for (int64_t i = 0; i < srcRank; i++) {
1237 // We assume the `k` dimensions of the inner dim position, where `k` is the
1238 // rank of the inner tiling, correspond to the last `k` indices of the
1239 // transpose permutation. This is done by adding the indices not contained
1240 // in the inner dimension position in order from 0 to `n`. Where n is the
1241 // rank of the source tensor. For example if we have a source tensor with
1242 // indices [0, 1, 2, 3] and inner dim position of [3, 0], the remaining
1243 // indices are [1, 2]. and the transpose will be [1, 2, 3, 0].
1244 if (llvm::is_contained(innerDimsPos, i))
1245 continue;
1246 srcPermForTranspose.push_back(i);
1247 }
1248 srcPermForTranspose.append(innerDimsPos.begin(), innerDimsPos.end());
1249
1250 // 2.2 Create the init tensor for linalg.transpose with the correct shape:
1251 // [ untiled-dims, tiled-dims ]
1252 ShapedType inputTy = cast<ShapedType>(input.getType());
1253 SmallVector<OpFoldResult> shapeForEmptyOp;
1254 for (int64_t i = 0; i < srcRank; i++) {
1255 if (llvm::is_contained(innerDimsPos, i)) {
1256 // The tiled dims are appended after this loop.
1257 continue;
1258 }
1259 if (inputTy.isStaticDim(i))
1260 shapeForEmptyOp.push_back(rewriter.getIndexAttr(inputTy.getShape()[i]));
1261 else
1262 shapeForEmptyOp.emplace_back(
1263 tensor::DimOp::create(rewriter, loc, input, i).getResult());
1264 }
1265 shapeForEmptyOp.append(packOp.getMixedTiles());
1266
1267 // getMixedTiles() may contain Values pointing to constant ops (as opposed to
1268 // constant attributes with the corresponding value). Replace those with
1269 // attributes. This is to match the behaviour in
1270 // `getPackOpSourceOrPaddedSource`, which replaces constant SSA values with
1271 // attributes.
1272 llvm::transform(shapeForEmptyOp, shapeForEmptyOp.begin(),
1273 [&](OpFoldResult ofr) {
1274 if (auto val = llvm::dyn_cast<Value>(ofr))
1275 return getAsOpFoldResult(val);
1276 return ofr;
1277 });
1278
1279 LDBG() << "Pack permutation: " << packOp;
1280 LDBG() << "perm: " << llvm::interleaved(srcPermForTranspose);
1281 LDBG() << "Shape of empty tensor: " << llvm::interleaved(shapeForEmptyOp);
1282
1283 Value empty = tensor::EmptyOp::create(
1284 rewriter, loc, shapeForEmptyOp, packOp.getSourceType().getElementType());
1285
1286 // 2.3 Create linalg.transpose
1287 auto transposedOp = linalg::TransposeOp::create(rewriter, loc, input, empty,
1288 srcPermForTranspose);
1289
1290 // 3. Insert the inner tile into the destination tensor:
1291 // %inserted_tile = tensor.insert_slice(%transposed_tile)
1292
1293 // Compute the sizes attribute:
1294 // [ outer-dims, tile-sizes ]
1295 // Note that the output from the transpose Op excludes the tiled outer dims.
1296 // However, given the assumption that:
1297 // * all tiled outer dims == 1,
1298 // we can just use a rank-expanding tensor.insert_slice.
1299 SmallVector<OpFoldResult> writeSizes;
1300 for (auto size : packOp.getAllOuterDims()) {
1301 writeSizes.push_back(rewriter.getIndexAttr(size));
1302 }
1303
1304 for (auto tileSize : packOp.getMixedTiles()) {
1305 auto [_, tileSizeOfr] =
1306 getSimplifiedOfrAndStaticSizePair(tileSize, rewriter);
1307 writeSizes.push_back(tileSizeOfr);
1308 }
1309
1310 auto insert = tensor::InsertSliceOp::create(
1311 rewriter, loc, transposedOp.getResult()[0], packOp.getDest(), writeSizes);
1312
1313 // 4. Replace tensor.packOp with tensor.insert_slice created above
1314 rewriter.replaceOp(packOp, insert.getResult());
1315
1316 return success();
1317}
1318
1320 linalg::UnPackOp unpackOp, PatternRewriter &rewriter) const {
1321 if (!unpackOp.hasPureTensorSemantics())
1322 return failure();
1323
1324 int64_t destRank = unpackOp.getDestRank();
1325 ArrayRef<int64_t> srcShape = unpackOp.getSourceType().getShape();
1326 ArrayRef<int64_t> innerDimsPos = unpackOp.getInnerDimsPos();
1327 if (llvm::any_of(unpackOp.getTiledOuterDims(),
1328 [](int64_t dim) { return dim != 1; })) {
1329 return rewriter.notifyMatchFailure(
1330 unpackOp,
1331 "require the tiled outer dimensions of the result are all 1s");
1332 }
1333
1334 // 1. Use rank-reduced tensor.extract_slice op to extract the tile:
1335 // %extracted_tile = tensor.extract_slice(%unpack_op_input)
1336 Location loc = unpackOp.getLoc();
1337 Value source = unpackOp.getSource();
1338 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1339 unpackOp.getDimAndTileMapping();
1340 Attribute oneIdxAttr = rewriter.getIndexAttr(1);
1341
1342 // The shape for ExtractSliceOp. Note that this will consist of 3 blocks of
1343 // dims:
1344 // [ outer-untiled-dims, outer-tiled-dims, tile-sizes ]
1345 SmallVector<int64_t> readShapeForExtractSlice;
1346 // The sizes attribute for ExtractSliceOp. Due to rank-reducing (and
1347 // outer-tiled-dims being all 1), this will be
1348 // [ outer-untiled-dims, tile-sizes ]
1349 SmallVector<OpFoldResult> extractSliceSizes;
1350
1351 // Shape for EmptyOp that's used as the init value for TransposeOp below.
1352 // This should be:
1353 // [ outer-untiled-dims, tile-sizes ]
1354 // However, skip unit dims - TransposeOp (below) applies rank-reduced
1355 // permutation.
1356 SmallVector<OpFoldResult> shapeForEmptyOp;
1357
1358 for (auto i : llvm::seq<unsigned>(0, destRank)) {
1359 // Compute sizes attribute for ExtractSliceOp - outer-tiled-dims.
1360 //
1361 // As all outer tiled dims are 1, so the corresponding
1362 // slice size to read will also 1. As this will be rank-reducing "extract
1363 // slice" (i.e. the unit dims will be "collapsed"), there's no need to
1364 // update:
1365 // * the output shape for ExtractSliceOp, nor
1366 // * the shape for EmptyOp.
1367 if (dimAndTileMapping.count(i)) {
1368 extractSliceSizes.push_back(oneIdxAttr);
1369 continue;
1370 }
1371
1372 // Compute sizes attribute for ExtractSliceOp + EmptyOp -
1373 // outer-untiled-dims
1374 if (ShapedType::isDynamic(srcShape[i])) {
1375 OpFoldResult dynamicDim =
1376 tensor::DimOp::create(rewriter, loc, source, i).getResult();
1377 extractSliceSizes.push_back(dynamicDim);
1378 shapeForEmptyOp.push_back(dynamicDim);
1379 } else {
1380 extractSliceSizes.push_back(rewriter.getIndexAttr(srcShape[i]));
1381 if (srcShape[i] != 1)
1382 shapeForEmptyOp.push_back(rewriter.getIndexAttr(srcShape[i]));
1383 }
1384 // Compute the output shape for ExtractSliceOp - outer-untiled-dims (take
1385 // into account rank-reducing)
1386 if (srcShape[i] != 1) {
1387 readShapeForExtractSlice.push_back(srcShape[i]);
1388 }
1389 }
1390 // Append the tile sizes to "sizes attribute" for ExtractSliceOp and the
1391 // shape for EmptyOp.
1392 auto mixedTiles = unpackOp.getMixedTiles();
1393 extractSliceSizes.append(mixedTiles.begin(), mixedTiles.end());
1394 shapeForEmptyOp.append(mixedTiles.begin(), mixedTiles.end());
1395
1396 // Explicitly create the type for extract_slice op because the inner tile
1397 // size could be 1. We want to represent the whole inner tile in this case.
1398 auto tileShape = srcShape.drop_front(destRank);
1399 // Append the inner tile shape to the permuted and rank-reduced outer shape.
1400 readShapeForExtractSlice.append(tileShape.begin(), tileShape.end());
1401 Type elemType = unpackOp.getSourceType().getElementType();
1402 auto readType = RankedTensorType::get(readShapeForExtractSlice, elemType);
1403 Value innerTile = tensor::ExtractSliceOp::create(
1404 rewriter, loc, readType, unpackOp.getSource(), extractSliceSizes);
1405
1406 // 2. Transpose the tile to match the outer corresponding tile order.
1408 srcShape.take_front(destRank), innerDimsPos, unpackOp.getOuterDimsPerm());
1409 // Unpack is a transition out of packed space so we invert the permutation.
1410 perm = invertPermutationVector(perm);
1411 applyPermutationToVector<OpFoldResult>(shapeForEmptyOp, perm);
1412
1413 Value empty =
1414 tensor::EmptyOp::create(rewriter, loc, shapeForEmptyOp, elemType);
1415 auto transposedOp =
1416 linalg::TransposeOp::create(rewriter, loc, innerTile, empty, perm);
1417
1418 // 3. Handle in-complete tiles if needed. It truncates trailing data from the
1419 // transposed tile.
1420 SmallVector<OpFoldResult> tileSizes;
1421 ArrayRef<int64_t> destShape = unpackOp.getDestType().getShape();
1422 for (auto i : llvm::seq<unsigned>(0, destRank)) {
1423 if (dimAndTileMapping.count(i) || destShape[i] != 1)
1424 tileSizes.push_back(
1425 tensor::getMixedSize(rewriter, loc, unpackOp.getDest(), i));
1426 }
1427
1428 auto partialTile =
1429 tensor::ExtractSliceOp::create(rewriter, loc, RankedTensorType(),
1430 transposedOp.getResult()[0], tileSizes);
1431
1432 // 4. Insert the result to the destination tensor.
1433 SmallVector<OpFoldResult> writeSizes;
1434 for (int i = 0, idx = 0; i < destRank; ++i) {
1435 if (dimAndTileMapping.count(i) || destShape[i] != 1)
1436 writeSizes.push_back(tileSizes[idx++]);
1437 else
1438 writeSizes.push_back(oneIdxAttr);
1439 }
1440 auto insert = tensor::InsertSliceOp::create(rewriter, loc, partialTile,
1441 unpackOp.getDest(), writeSizes);
1442 rewriter.replaceOp(unpackOp, insert.getResult());
1443
1444 return success();
1445}
1446
1447//===----------------------------------------------------------------------===//
1448// Generic DownscaleSizeOneWindowedConvolution
1449//===----------------------------------------------------------------------===//
1450//
1451/// Returns the indices of affine map results that reference any of the given
1452/// dimensions.
1455 SmallVector<unsigned> resultIndices;
1456 for (unsigned dim : dims) {
1457 for (unsigned i = 0, e = map.getNumResults(); i < e; ++i) {
1458 AffineExpr expr = map.getResult(i);
1459 if (expr.isFunctionOfDim(dim)) {
1460 resultIndices.push_back(i);
1461 break;
1462 }
1463 }
1464 }
1465 return resultIndices;
1466}
1467
1468/// Helper to create a rank-reducing extract_slice that removes specific
1469/// dimensions from a tensor.
1471 Location loc, Value tensor,
1472 ArrayRef<unsigned> dimsToRemove) {
1473 auto tensorType = cast<RankedTensorType>(tensor.getType());
1474 int64_t rank = tensorType.getRank();
1475
1476 // Compute new shape by removing the specified dimensions.
1477 SmallVector<int64_t> newShape;
1478 for (int64_t i = 0; i < rank; ++i) {
1479 if (!llvm::is_contained(dimsToRemove, i))
1480 newShape.push_back(tensorType.getDimSize(i));
1481 }
1482
1483 auto newType = RankedTensorType::get(newShape, tensorType.getElementType());
1485 tensor, newType);
1486}
1487
1488/// Drops specified dimensions from an AffineExpr and compresses remaining
1489/// dimension indices. Returns std::nullopt if the expression only references
1490/// the dropped dimensions.
1491static std::optional<AffineExpr>
1493 unsigned newNumDims, MLIRContext *ctx) {
1494 // Check if expr only references dimensions to be dropped.
1495 bool onlyReferencesDroppedDims = true;
1496 for (unsigned d = 0; d < newNumDims + dimsToDrop.size(); ++d) {
1497 if (expr.isFunctionOfDim(d) && !llvm::is_contained(dimsToDrop, d)) {
1498 onlyReferencesDroppedDims = false;
1499 break;
1500 }
1501 }
1502 if (onlyReferencesDroppedDims && llvm::any_of(dimsToDrop, [&](unsigned d) {
1503 return expr.isFunctionOfDim(d);
1504 }))
1505 return std::nullopt;
1506
1507 // Replace dimensions: compute new index for each old dimension.
1508 // Dropped dimensions get mapped to constant 0, others get compressed.
1509 SmallVector<AffineExpr> dimReplacements;
1510 unsigned newDimIdx = 0;
1511 for (unsigned d = 0; d < newNumDims + dimsToDrop.size(); ++d) {
1512 if (llvm::is_contained(dimsToDrop, d)) {
1513 dimReplacements.push_back(getAffineConstantExpr(0, ctx));
1514 } else {
1515 dimReplacements.push_back(getAffineDimExpr(newDimIdx++, ctx));
1516 }
1517 }
1518
1519 return expr.replaceDims(dimReplacements);
1520}
1521
1522FailureOr<LinalgOp>
1524 LinalgOp op) {
1525 auto maybeDims = inferConvolutionDims(op);
1526 if (failed(maybeDims))
1527 return failure();
1528
1529 // Currently supports only 2D convolutions.
1530 if (maybeDims->outputImage.size() != 2 || maybeDims->filterLoop.size() != 2)
1531 return failure();
1532
1533 if (op.hasPureBufferSemantics())
1534 return failure();
1535
1536 // Get loop domain indices for spatial dimensions.
1537 unsigned outSpatial0 = maybeDims->outputImage[0];
1538 unsigned outSpatial1 = maybeDims->outputImage[1];
1539 unsigned filterSpatial0 = maybeDims->filterLoop[0];
1540 unsigned filterSpatial1 = maybeDims->filterLoop[1];
1541
1542 // Get sizes from loop bounds.
1543 SmallVector<int64_t, 4> loopRanges = op.getStaticLoopRanges();
1544 int64_t outSize0 = loopRanges[outSpatial0];
1545 int64_t outSize1 = loopRanges[outSpatial1];
1546 int64_t filterSize0 = loopRanges[filterSpatial0];
1547 int64_t filterSize1 = loopRanges[filterSpatial1];
1548
1549 // Check if we can downscale by removing a spatial dimension.
1550 bool canRemoveSpatial0 = (filterSize0 == 1 && outSize0 == 1);
1551 bool canRemoveSpatial1 = (filterSize1 == 1 && outSize1 == 1);
1552 if (!canRemoveSpatial0 && !canRemoveSpatial1)
1553 return failure();
1554
1555 // Determine which loop dims to remove (output spatial + corresponding filter)
1556 // and sort for correct index compression when removing dimensions from affine
1557 // maps.
1558 SmallVector<unsigned> loopDimsToRemove;
1559 if (canRemoveSpatial0) {
1560 loopDimsToRemove.push_back(outSpatial0);
1561 loopDimsToRemove.push_back(filterSpatial0);
1562 } else {
1563 loopDimsToRemove.push_back(outSpatial1);
1564 loopDimsToRemove.push_back(filterSpatial1);
1565 }
1566 llvm::sort(loopDimsToRemove);
1567
1568 // Create new indexing maps with dimensions removed.
1569 SmallVector<AffineMap> newMaps;
1570 MLIRContext *ctx = op.getContext();
1571 unsigned numDims = op.getNumLoops();
1572 unsigned newNumDims = numDims - loopDimsToRemove.size();
1573 for (AffineMap map : op.getIndexingMapsArray()) {
1574 SmallVector<AffineExpr> newResults;
1575 for (AffineExpr expr : map.getResults()) {
1576 auto newExpr =
1577 dropDimsAndCompress(expr, loopDimsToRemove, newNumDims, ctx);
1578 if (newExpr)
1579 newResults.push_back(*newExpr);
1580 }
1581 newMaps.push_back(AffineMap::get(newNumDims, 0, newResults, ctx));
1582 }
1583
1584 // Create new iterator types.
1586 auto iterTypes = op.getIteratorTypesArray();
1587 for (unsigned idx = 0; idx < iterTypes.size(); ++idx) {
1588 if (!llvm::is_contained(loopDimsToRemove, idx))
1589 newIterTypes.push_back(iterTypes[idx]);
1590 }
1591
1592 // Rank-reduce operands using extract_slice.
1593 Location loc = op.getLoc();
1594 SmallVector<Value> newInputs;
1595 for (OpOperand *input : op.getDpsInputOperands()) {
1596 AffineMap map = op.getMatchingIndexingMap(input);
1597 SmallVector<unsigned> tensorDimsToRemove =
1598 getResultIndicesReferencingDims(map, loopDimsToRemove);
1599 Value reduced = createRankReducingExtractSlice(rewriter, loc, input->get(),
1600 tensorDimsToRemove);
1601 newInputs.push_back(reduced);
1602 }
1603
1604 OpOperand &output = *op.getDpsInitsMutable().begin();
1605 AffineMap outputMap = op.getMatchingIndexingMap(&output);
1606 SmallVector<unsigned> outputDimsToRemove =
1607 getResultIndicesReferencingDims(outputMap, loopDimsToRemove);
1608 Value newOutput = createRankReducingExtractSlice(rewriter, loc, output.get(),
1609 outputDimsToRemove);
1610
1611 // Create new linalg.generic with reduced dimensions.
1612 auto newOp =
1613 linalg::GenericOp::create(rewriter, loc, TypeRange{newOutput.getType()},
1614 newInputs, newOutput, newMaps, newIterTypes);
1615 rewriter.inlineRegionBefore(op->getRegion(0), newOp.getRegion(),
1616 newOp.getRegion().begin());
1617
1618 // Try to specialize the generic back to a named op only if the input was
1619 // already a specialized (named) op.
1620 LinalgOp resultOp = newOp;
1621 if (!isa<GenericOp>(op)) {
1622 FailureOr<LinalgOp> specializedOp = specializeGenericOp(rewriter, newOp);
1623 if (succeeded(specializedOp))
1624 resultOp = *specializedOp;
1625 }
1626
1627 // Insert result back into original shape.
1629 rewriter, loc, resultOp->getResult(0), output.get());
1630
1631 rewriter.replaceOp(op, result);
1632 return resultOp;
1633}
1634
1635namespace {
1636/// Pattern wrapper around `downscaleSizeOneWindowedConvolution`.
1637struct DownscaleSizeOneWindowedConvolution final
1638 : public OpInterfaceRewritePattern<LinalgOp> {
1639 DownscaleSizeOneWindowedConvolution(MLIRContext *context,
1640 PatternBenefit benefit = 1)
1641 : OpInterfaceRewritePattern<LinalgOp>(context, benefit) {}
1642
1643 LogicalResult matchAndRewrite(LinalgOp op,
1644 PatternRewriter &rewriter) const override {
1646 }
1647};
1648} // namespace
1649
1651 PatternBenefit benefit) {
1652 patterns.add<DownscaleSizeOneWindowedConvolution>(patterns.getContext(),
1653 benefit);
1654}
1655
1660
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 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:267
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:315
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:63
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:254
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
This is a value defined by a result of an operation.
Definition Value.h:454
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
result_range getResults()
Definition Operation.h:440
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:389
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:398
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< LinalgOp > specializeGenericOp(RewriterBase &rewriter, GenericOp genericOp, const GenericOpSpecializationOptions &options={})
Replace the given GenericOp with a namedOp or categoryOp.
FailureOr< ConvolutionDimensions > inferConvolutionDims(LinalgOp linalgOp)
Find at least 1 parallel (output_image) and reduction (filter_loop) dimension candidates that form a ...
FailureOr< GenericOp > generalizeNamedOp(RewriterBase &rewriter, LinalgOp linalgOp)
Create a GenericOp from the given named operation linalgOp and replace the given linalgOp.
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:311
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:325
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:1380
AffineExpr getAffineConstantExpr(int64_t constant, MLIRContext *context)
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
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.