MLIR 24.0.0git
LowerVectorContract.cpp
Go to the documentation of this file.
1//===- LowerVectorContract.cpp - Lower 'vector.contract' operation --------===//
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 target-independent rewrites and utilities to lower the
10// 'vector.contract' operation.
11//
12//===----------------------------------------------------------------------===//
13
22#include "mlir/IR/Location.h"
25
26#define DEBUG_TYPE "vector-contract-lowering"
27
28using namespace mlir;
29using namespace mlir::vector;
30
31//===----------------------------------------------------------------------===//
32// Helper functions
33//===----------------------------------------------------------------------===//
34// Helper to find an index in an affine map.
35static std::optional<int64_t> getResultIndex(AffineMap map, int64_t index) {
36 for (int64_t i = 0, e = map.getNumResults(); i < e; ++i) {
37 int64_t idx = map.getDimPosition(i);
38 if (idx == index)
39 return i;
40 }
41 return std::nullopt;
42}
43
44// Helper to construct iterator types with one index removed.
46 int64_t index) {
48 for (const auto &it : llvm::enumerate(iteratorTypes)) {
49 int64_t idx = it.index();
50 if (idx == index)
51 continue;
52 results.push_back(it.value());
53 }
54 return results;
55}
56
57// Helper to construct an affine map with one index removed.
59 PatternRewriter &rewriter) {
60 auto *ctx = rewriter.getContext();
62 for (int64_t i = 0, e = map.getNumResults(); i < e; ++i) {
63 int64_t idx = map.getDimPosition(i);
64 if (idx == index)
65 continue;
66 // Re-insert remaining indices, but renamed when occurring
67 // after the removed index.
68 auto targetExpr = getAffineDimExpr(idx < index ? idx : idx - 1, ctx);
69 results.push_back(targetExpr);
70 }
71 return AffineMap::get(map.getNumDims() - 1, 0, results, ctx);
72}
73
74/// Returns `val` with the dimension at position `index` dropped by indexing
75/// that dimension with `pos`.
76///
77/// If `index == -1`, returns `val` unchanged. If `index == 0`, the result is
78/// a single `vector.extract %val[pos]`.
79///
80/// Example (`index == 0`): extract the sub-vector at `pos` along the leading
81/// dimension.
82/// // val : vector<4x8xf32>, pos = 2
83/// %res = vector.extract %val[2] : vector<8xf32> from vector<4x8xf32>
84///
85/// For `index > 0`, recursively applies the same drop to each sub-vector of
86/// the leading dimension and reassembles the result.
88 PatternRewriter &rewriter) {
89 if (index == -1)
90 return val;
91
92 // At extraction dimension?
93 if (index == 0)
94 return vector::ExtractOp::create(rewriter, loc, val, pos);
95
96 // Unroll leading dimensions.
97 VectorType type = cast<VectorType>(val.getType());
98 VectorType resType = VectorType::Builder(type).dropDim(index);
99 Value result = arith::ConstantOp::create(rewriter, loc, resType,
100 rewriter.getZeroAttr(resType));
101 for (int64_t d = 0, e = resType.getDimSize(0); d < e; d++) {
102 Value ext = vector::ExtractOp::create(rewriter, loc, val, d);
103 Value load = reshapeLoad(loc, ext, index - 1, pos, rewriter);
104 result = vector::InsertOp::create(rewriter, loc, load, result, d);
105 }
106 return result;
107}
108
109/// Inserts `val` into `result` at position `pos` along dimension `index`.
110///
111/// This is the inverse of `reshapeLoad`. If `index == -1`, returns `val`. If
112/// `index == 0`, the result is a single `vector.insert %val, %result [pos]`.
113///
114/// Example (`index == 0`): insert `val` at `pos` along the leading dimension.
115/// // val : vector<4xf32>, acc : vector<2x4xf32>, pos = 1
116/// %res = vector.insert %val, %acc [1] : vector<4xf32> into vector<2x4xf32>
117///
118/// For `index > 0`, recursively applies the same insertion to each sub-vector
119/// of the leading dimension and reassembles the result.
121 int64_t pos, PatternRewriter &rewriter) {
122 // Unmodified?
123 if (index == -1)
124 return val;
125 // At insertion dimension?
126 if (index == 0)
127 return vector::InsertOp::create(rewriter, loc, val, result, pos);
128
129 // Unroll leading dimensions.
130 VectorType type = cast<VectorType>(result.getType());
131 for (int64_t d = 0, e = type.getDimSize(0); d < e; d++) {
132 Value ext = vector::ExtractOp::create(rewriter, loc, result, d);
133 Value ins = vector::ExtractOp::create(rewriter, loc, val, d);
134 Value sto = reshapeStore(loc, ins, ext, index - 1, pos, rewriter);
135 result = vector::InsertOp::create(rewriter, loc, sto, result, d);
136 }
137 return result;
138}
139
140/// Helper to create arithmetic operation associated with a kind of contraction.
141static std::optional<Value>
143 vector::CombiningKind kind, PatternRewriter &rewriter,
144 bool isInt, Value mask = Value(),
145 arith::FastMathFlagsAttr fmf = {}) {
146 using vector::CombiningKind;
147 Value mul;
148
149 if (isInt) {
150 if (kind == CombiningKind::MINNUMF || kind == CombiningKind::MAXNUMF ||
151 kind == CombiningKind::MINIMUMF || kind == CombiningKind::MAXIMUMF ||
152 kind == CombiningKind::MINIMUMNUMF ||
153 kind == CombiningKind::MAXIMUMNUMF)
154 // Only valid for floating point types.
155 return std::nullopt;
156 mul = arith::MulIOp::create(rewriter, loc, x, y);
157 } else {
158 // Float case.
159 if (kind == CombiningKind::AND || kind == CombiningKind::MINUI ||
160 kind == CombiningKind::MINSI || kind == CombiningKind::MAXUI ||
161 kind == CombiningKind::MAXSI || kind == CombiningKind::OR ||
162 kind == CombiningKind::XOR)
163 // Only valid for integer types.
164 return std::nullopt;
165 // Special case for fused multiply-add.
166 if (acc && isa<VectorType>(acc.getType()) && kind == CombiningKind::ADD) {
167 Value fma = vector::FMAOp::create(rewriter, loc, x, y, acc);
168 if (mask)
169 // The fma op doesn't need explicit masking. However, fma ops used in
170 // reductions must preserve previous 'acc' values for masked-out lanes.
171 fma = selectPassthru(rewriter, mask, fma, acc);
172 return fma;
173 }
174 mul = arith::MulFOp::create(rewriter, loc, x, y, fmf);
175 }
176
177 if (!acc)
178 return std::optional<Value>(mul);
179
180 return makeArithReduction(rewriter, loc, kind, mul, acc, fmf, mask);
181}
182
183/// Return the positions of the reductions in the given map.
185 ArrayAttr iteratorTypes) {
186 SmallVector<int64_t> dimsIdx;
187 for (unsigned i = 0, e = map.getNumResults(); i < e; i++) {
188 if (isReductionIterator(iteratorTypes[map.getDimPosition(i)]))
189 dimsIdx.push_back(i);
190 }
191 return dimsIdx;
192}
193
194/// Look for a given dimension in an affine map and return its position. Return
195/// std::nullopt if the dimension is not in the map results.
196static std::optional<unsigned> getDimPosition(AffineMap map, unsigned dim) {
197 for (unsigned i = 0, e = map.getNumResults(); i < e; i++) {
198 if (map.getDimPosition(i) == dim)
199 return i;
200 }
201 return std::nullopt;
202}
203
204/// Creates an AddIOp if `isInt` is true otherwise create an arith::AddFOp using
205/// operands `x` and `y`.
206static Value createAdd(Location loc, Value x, Value y, bool isInt,
207 PatternRewriter &rewriter,
208 arith::FastMathFlagsAttr fmf = {}) {
209 if (isInt)
210 return arith::AddIOp::create(rewriter, loc, x, y);
211 return arith::AddFOp::create(rewriter, loc, x, y, fmf);
212}
213
214/// Creates a MulIOp if `isInt` is true otherwise create an MulFOp using
215/// operands `x and `y`.
216static Value createMul(Location loc, Value x, Value y, bool isInt,
217 PatternRewriter &rewriter,
218 arith::FastMathFlagsAttr fmf = {}) {
219 if (isInt)
220 return arith::MulIOp::create(rewriter, loc, x, y);
221 return arith::MulFOp::create(rewriter, loc, x, y, fmf);
222}
223
224namespace {
225
226/// Progressive lowering of a `vector.contract %a, %b, %c` with row-major matmul
227/// semantics to a reduction_size-unrolled sequence:
228/// ```
229/// %at = vector.transpose %a, [1, 0]
230/// %bRow0 = vector.extract %b[0]
231/// %atRow0 = vector.extract %at[0]
232/// %c0 = vector.outerproduct %atRow0, %bRow0, %c
233/// ...
234/// %bRowK = vector.extract %b[K]
235/// %atRowK = vector.extract %at[K]
236/// %cK = vector.outerproduct %atRowK, %bRowK, %cK-1
237/// ```
238///
239/// This only kicks in when vectorContractLowering is set to OuterProduct and
240/// the vector.contract op is a row-major matrix multiply.
241class ContractionOpToOuterProductOpLowering
242 : public MaskableOpRewritePattern<vector::ContractionOp> {
243public:
244 using MaskableOpRewritePattern::MaskableOpRewritePattern;
245
246 using FilterConstraintType =
247 std::function<LogicalResult(vector::ContractionOp op)>;
248
249 static LogicalResult defaultFilter(vector::ContractionOp op) {
250 return success();
251 }
252
253 ContractionOpToOuterProductOpLowering(
254 vector::VectorContractLowering vectorContractLowering,
255 MLIRContext *context, PatternBenefit benefit = 1,
256 FilterConstraintType constraint = defaultFilter)
257 : MaskableOpRewritePattern<vector::ContractionOp>(context, benefit),
258 vectorContractLowering(vectorContractLowering),
259 filter(std::move(constraint)) {}
260
261 FailureOr<Value>
262 matchAndRewriteMaskableOp(vector::ContractionOp op, MaskingOpInterface maskOp,
263 PatternRewriter &rewriter) const override;
264
265private:
266 /// Options to control the vector patterns.
267 vector::VectorContractLowering vectorContractLowering;
268 FilterConstraintType filter;
269};
270
271/// Progressive lowering of a `vector.contract %a, %b, %c` with row-major matmul
272/// semantics to an output-size-unrolled sequence:
273/// ```
274/// %out = arith.constant ... : vector<MxNxelt_type>
275/// %bt = vector.transpose %b, [1, 0]
276/// %aRow0 = vector.extract %a[0]
277/// %btRow0 = vector.extract %bt[0]
278/// %c00 = vector.reduction %atRow0, %bRow0
279/// %out00 = vector.insert %c00, %out[0, 0]
280/// ...
281/// %aRowLast = vector.extract %at[M-1]
282/// %btRowLast = vector.extract %b[N-1]
283/// %cLastLast = vector.reduction %atRowLast, %bRowLast
284/// %outcLastLast = vector.insert %cLastLast, %out[M-1, N-1]
285/// ```
286///
287/// This only kicks in when VectorTransformsOptions is set to Dot and
288/// the vector.contract op is a row-major matmul or matvec.
289class ContractionOpToDotLowering
290 : public MaskableOpRewritePattern<vector::ContractionOp> {
291public:
292 using MaskableOpRewritePattern::MaskableOpRewritePattern;
293
294 using FilterConstraintType =
295 std::function<LogicalResult(vector::ContractionOp op)>;
296
297 static LogicalResult defaultFilter(vector::ContractionOp op) {
298 return success();
299 }
300
301 ContractionOpToDotLowering(
302 vector::VectorContractLowering vectorContractLowering,
303 MLIRContext *context, PatternBenefit benefit = 1,
304 const FilterConstraintType &constraint = defaultFilter)
305 : MaskableOpRewritePattern<vector::ContractionOp>(context, benefit),
306 vectorContractLowering(vectorContractLowering), filter(defaultFilter) {}
307
308 FailureOr<Value>
309 matchAndRewriteMaskableOp(vector::ContractionOp op, MaskingOpInterface maskOp,
310 PatternRewriter &rewriter) const override;
311
312private:
313 /// Options to control the vector patterns.
314 vector::VectorContractLowering vectorContractLowering;
315 FilterConstraintType filter;
316};
317
318/// Progressive lowering of ContractionOp.
319///
320/// One:
321/// %x = vector.contract with at least one free/batch dimension
322/// is replaced by:
323/// %a = vector.contract with one less free/batch dimension
324/// %b = vector.contract with one less free/batch dimension
325/// ..
326/// %x = combine %a %b ..
327/// until a pure contraction is reached (no free/batch dimensions),
328/// which is replaced by a dot-product.
329///
330/// This only kicks in when either VectorTransformsOptions is set
331/// to Dot or when other contraction patterns fail.
332class ContractionOpLowering
333 : public MaskableOpRewritePattern<vector::ContractionOp> {
334public:
335 using MaskableOpRewritePattern::MaskableOpRewritePattern;
336 using FilterConstraintType =
337 std::function<LogicalResult(vector::ContractionOp op)>;
338
339 static LogicalResult defaultFilter(vector::ContractionOp op) {
340 return success();
341 }
342
343 ContractionOpLowering(
344 vector::VectorContractLowering vectorContractLoweringOption,
345 MLIRContext *context, PatternBenefit benefit = 1,
346 FilterConstraintType constraint = defaultFilter)
347 : MaskableOpRewritePattern<vector::ContractionOp>(context, benefit),
348 vectorContractLoweringOption(vectorContractLoweringOption),
349 filter(std::move(constraint)) {}
350
351 FailureOr<Value>
352 matchAndRewriteMaskableOp(vector::ContractionOp op, MaskingOpInterface maskOp,
353 PatternRewriter &rewriter) const override;
354
355private:
356 /// Options to control the vector patterns.
357 vector::VectorContractLowering vectorContractLoweringOption;
358 FilterConstraintType filter;
359 // Lower one parallel dimension.
360 FailureOr<Value> lowerParallel(PatternRewriter &rewriter,
361 vector::ContractionOp op, int64_t lhsIndex,
362 int64_t rhsIndex, Value mask) const;
363 // Lower one reduction dimension.
364 FailureOr<Value> lowerReduction(PatternRewriter &rewriter,
365 vector::ContractionOp op, Value mask) const;
366};
367
368/// Generate a vector implementation for matmat, matvec and tmatvec.
369/// This unrolls outer-products along the reduction dimension.
370struct UnrolledOuterProductGenerator
371 : public StructuredGenerator<vector::ContractionOp, vector::IteratorType> {
372 UnrolledOuterProductGenerator(RewriterBase &b, vector::ContractionOp op)
373 : StructuredGenerator<vector::ContractionOp, vector::IteratorType>(b, op),
374 kind(op.getKind()), lhs(op.getLhs()), rhs(op.getRhs()),
375 res(op.getAcc()), lhsType(op.getLhsType()) {
376 auto maskableOp = cast<MaskableOpInterface>(op.getOperation());
377 if (maskableOp.isMasked())
378 mask = maskableOp.getMaskingOp().getMask();
379 }
380
381 Value t(Value v, ArrayRef<int64_t> perm = {1, 0}) {
382 if (!v)
383 return v;
384 return vector::TransposeOp::create(rewriter, loc, v, perm);
385 }
386
387 Value promote(Value v, Type dstElementType) {
388 Type elementType = v.getType();
389 auto vecType = dyn_cast<VectorType>(elementType);
390 if (vecType)
391 elementType = vecType.getElementType();
392 if (elementType == dstElementType)
393 return v;
394 Type promotedType = dstElementType;
395 if (vecType)
396 promotedType = vecType.clone(promotedType);
397 if (isa<FloatType>(dstElementType))
398 return arith::ExtFOp::create(rewriter, loc, promotedType, v,
399 /*fastmath=*/{});
400 return arith::ExtSIOp::create(rewriter, loc, promotedType, v);
401 }
402
403 FailureOr<Value> outerProd(Value lhs, Value rhs, Value res,
404 VectorType lhsType, int reductionSize,
405 std::optional<Value> maybeMask = std::nullopt) {
406 // Incremental support for masking.
407 if (mask && !maybeMask.has_value())
408 return failure();
409
410 Type resElementType = cast<VectorType>(res.getType()).getElementType();
411 for (int64_t k = 0; k < reductionSize; ++k) {
412 Value extractA = vector::ExtractOp::create(rewriter, loc, lhs, k);
413 Value extractB = vector::ExtractOp::create(rewriter, loc, rhs, k);
414 extractA = promote(extractA, resElementType);
415 extractB = promote(extractB, resElementType);
416 Value extractMask;
417 if (maybeMask.has_value() && maybeMask.value())
418 extractMask =
419 vector::ExtractOp::create(rewriter, loc, maybeMask.value(), k);
420
421 Operation *outerProdOp = vector::OuterProductOp::create(
422 rewriter, loc, res.getType(), extractA, extractB, res, kind);
423 res = maskOperation(rewriter, outerProdOp, extractMask)->getResult(0);
424 }
425 return res;
426 }
427
428 /// Helper function for `matmat`, `matvec`, `tmatvec`. Returns the size of
429 /// dimension `reductionDim`. If the dimension is a scalable dimension,
430 /// returns "nullopt".
431 std::optional<int64_t> getReductionSize(VectorType vecType,
432 int64_t reductionDim) {
433 // Cannot unroll scalable dimension.
434 if (vecType.getScalableDims()[reductionDim])
435 return std::nullopt;
436 int64_t reductionSize = vecType.getDimSize(reductionDim);
437 assert(reductionSize > 0 &&
438 "Reduction dim must be a known static size to allow unrolling");
439 return reductionSize;
440 }
441
442 /// Two outer parallel, one inner reduction (matmat flavor).
443 FailureOr<Value> matmat() {
444 if (!iters({Par(), Par(), Red()}))
445 return failure();
446 // Set up the parallel/reduction structure in the right form.
447 AffineExpr m, n, k;
448 bindDims(rewriter.getContext(), m, n, k);
449
450 // Classical row-major matmul: Just permute the lhs.
451 if (layout({{m, k}, {k, n}, {m, n}})) {
452 if (auto reductionSize = getReductionSize(lhsType, 1)) {
453 // Note: `t` creates new IR. It must be nested within this `if` check
454 // so that no IR is created when then pattern returns "failure".
455 Value tLhs = t(lhs);
456 Value tMask = t(mask, {2, 0, 1});
457 return outerProd(tLhs, rhs, res, lhsType, *reductionSize, tMask);
458 }
459 }
460 // TODO: may be better to fail and use some vector<k> -> scalar reduction.
461 if (layout({{m, k}, {n, k}, {m, n}})) {
462 if (auto reductionSize = getReductionSize(lhsType, 1)) {
463 Value tLhs = t(lhs);
464 Value tRhs = t(rhs);
465 Value tMask = t(mask, {2, 0, 1});
466 return outerProd(tLhs, tRhs, res, lhsType, *reductionSize, tMask);
467 }
468 }
469 // No need to permute anything.
470 if (layout({{k, m}, {k, n}, {m, n}})) {
471 if (auto reductionSize = getReductionSize(lhsType, 0)) {
472 Value tMask = t(mask, {2, 0, 1});
473 return outerProd(lhs, rhs, res, lhsType, *reductionSize, tMask);
474 }
475 }
476 // Just permute the rhs.
477 if (layout({{k, m}, {n, k}, {m, n}})) {
478 if (auto reductionSize = getReductionSize(lhsType, 0)) {
479 Value tRhs = t(rhs);
480 Value tMask = t(mask, {2, 0, 1});
481 return outerProd(lhs, tRhs, res, lhsType, *reductionSize, tMask);
482 }
483 }
484 // Transposed output: swap RHS and LHS.
485 // Classical row-major matmul: permute the lhs.
486 if (layout({{m, k}, {k, n}, {n, m}})) {
487 if (auto reductionSize = getReductionSize(lhsType, 1)) {
488 Value tLhs = t(lhs);
489 Value tMask = t(mask, {2, 0, 1});
490 return outerProd(rhs, tLhs, res, lhsType, *reductionSize, tMask);
491 }
492 }
493 // TODO: may be better to fail and use some vector<k> -> scalar reduction.
494 if (layout({{m, k}, {n, k}, {n, m}})) {
495 if (auto reductionSize = getReductionSize(lhsType, 1)) {
496 Value tRhs = t(rhs);
497 Value tLhs = t(lhs);
498 Value tMask = t(mask, {2, 0, 1});
499 return outerProd(tRhs, tLhs, res, lhsType, *reductionSize, tMask);
500 }
501 }
502 if (layout({{k, m}, {k, n}, {n, m}})) {
503 if (auto reductionSize = getReductionSize(lhsType, 0)) {
504 Value tMask = t(mask, {2, 0, 1});
505 return outerProd(rhs, lhs, res, lhsType, *reductionSize, tMask);
506 }
507 }
508 if (layout({{k, m}, {n, k}, {n, m}})) {
509 if (auto reductionSize = getReductionSize(lhsType, 0)) {
510 Value tRhs = t(rhs);
511 Value tMask = t(mask, {2, 0, 1});
512 return outerProd(tRhs, lhs, res, lhsType, *reductionSize, tMask);
513 }
514 }
515 return failure();
516 }
517
518 //
519 // One outer parallel, one inner reduction (matvec flavor).
520 // Mask needs to be transposed everywhere to turn the reduction dimension
521 // outermost as required by outerproduct.
522 //
523 FailureOr<Value> matvec() {
524 if (!iters({Par(), Red()}))
525 return failure();
526 AffineExpr m, k;
527 bindDims(rewriter.getContext(), m, k);
528
529 // Case mat-vec: transpose.
530 if (layout({{m, k}, {k}, {m}})) {
531 if (auto reductionSize = getReductionSize(lhsType, 1)) {
532 Value tLhs = t(lhs);
533 Value tMask = t(mask);
534 return outerProd(tLhs, rhs, res, lhsType, *reductionSize, tMask);
535 }
536 }
537 // Case mat-trans-vec: ready to go.
538 if (layout({{k, m}, {k}, {m}})) {
539 if (auto reductionSize = getReductionSize(lhsType, 0)) {
540 Value tMask = t(mask);
541 return outerProd(lhs, rhs, res, lhsType, *reductionSize, tMask);
542 }
543 }
544 // Case vec-mat: swap and transpose.
545 if (layout({{k}, {m, k}, {m}})) {
546 if (auto reductionSize = getReductionSize(lhsType, 0)) {
547 Value tRhs = t(rhs);
548 Value tMask = t(mask);
549 return outerProd(tRhs, lhs, res, lhsType, *reductionSize, tMask);
550 }
551 }
552 // Case vec-mat-trans: swap and ready to go.
553 if (layout({{k}, {k, m}, {m}})) {
554 if (auto reductionSize = getReductionSize(lhsType, 0)) {
555 Value tMask = t(mask);
556 return outerProd(rhs, lhs, res, lhsType, *reductionSize, tMask);
557 }
558 }
559 return failure();
560 }
561
562 //
563 // One outer reduction, one inner parallel (tmatvec flavor).
564 // Mask already has the shape of the outer product.
565 //
566 FailureOr<Value> tmatvec() {
567 if (!iters({Red(), Par()}))
568 return failure();
569 AffineExpr k, m;
570 bindDims(rewriter.getContext(), k, m);
571
572 // Case mat-vec: transpose.
573 if (layout({{m, k}, {k}, {m}}))
574 if (auto reductionSize = getReductionSize(lhsType, 1))
575 return outerProd(t(lhs), rhs, res, lhsType, *reductionSize, mask);
576 // Case mat-trans-vec: ready to go.
577 if (layout({{k, m}, {k}, {m}}))
578 if (auto reductionSize = getReductionSize(lhsType, 0))
579 return outerProd(lhs, rhs, res, lhsType, *reductionSize, mask);
580 // Case vec-mat: swap and transpose.
581 if (layout({{k}, {m, k}, {m}}))
582 if (auto reductionSize = getReductionSize(lhsType, 0))
583 return outerProd(t(rhs), lhs, res, lhsType, *reductionSize, mask);
584 // Case vec-mat-trans: swap and ready to go.
585 if (layout({{k}, {k, m}, {m}}))
586 if (auto reductionSize = getReductionSize(lhsType, 0))
587 return outerProd(rhs, lhs, res, lhsType, *reductionSize, mask);
588 return failure();
589 }
590
591private:
592 vector::CombiningKind kind;
593 Value lhs, rhs, res, mask;
594 VectorType lhsType;
595};
596
597/// Progressively lower a `vector.contract %a, %b, %c` with row-major matmul
598/// semantics to a reduction_size-unrolled sequence:
599/// ```
600/// %at = vector.transpose %a, [1, 0]
601/// %bRow0 = vector.extract %b[0]
602/// %atRow0 = vector.extract %at[0]
603/// %c0 = vector.outerproduct %atRow0, %bRow0, %c
604/// ...
605/// %bRowK = vector.extract %b[K]
606/// %atRowK = vector.extract %at[K]
607/// %cK = vector.outerproduct %atRowK, %bRowK, %cK-1
608/// ```
609///
610/// This only kicks in when vectorContractLowering is set to OuterProduct but
611/// otherwise supports any layout permutation of the matrix-multiply.
612FailureOr<Value>
613ContractionOpToOuterProductOpLowering::matchAndRewriteMaskableOp(
614 vector::ContractionOp op, MaskingOpInterface maskOp,
615 PatternRewriter &rewriter) const {
616 if (vectorContractLowering != vector::VectorContractLowering::OuterProduct)
617 return failure();
618
619 if (failed(filter(op)))
620 return failure();
621
622 UnrolledOuterProductGenerator e(rewriter, op);
623 FailureOr<Value> matmatRes = e.matmat();
624 if (succeeded(matmatRes)) {
625 return matmatRes;
626 }
627 FailureOr<Value> matvecRes = e.matvec();
628 if (succeeded(matvecRes)) {
629 return matvecRes;
630 }
631
632 FailureOr<Value> tmatvecRes = e.tmatvec();
633 return tmatvecRes;
634}
635
636FailureOr<Value> ContractionOpToDotLowering::matchAndRewriteMaskableOp(
637 vector::ContractionOp op, MaskingOpInterface maskOp,
638 PatternRewriter &rewriter) const {
639 // TODO: Support vector.mask.
640 if (maskOp)
641 return failure();
642
643 if (failed(filter(op)))
644 return failure();
645
646 if (vectorContractLowering != vector::VectorContractLowering::Dot)
647 return failure();
648
649 // A scalable dst dim can't be unrolled with a static trip count; check
650 // before any IR is created below.
651 if (auto dstType = dyn_cast<VectorType>(op.getResultType());
652 dstType && dstType.isScalable())
653 return rewriter.notifyMatchFailure(
654 op, "cannot unroll a scalable dimension in dot-product lowering");
655
656 auto iteratorTypes = op.getIteratorTypes().getValue();
657 static constexpr std::array<int64_t, 2> perm = {1, 0};
658 Location loc = op.getLoc();
659 Value lhs = op.getLhs(), rhs = op.getRhs();
660
661 using MapList = ArrayRef<ArrayRef<AffineExpr>>;
662 auto infer = [&](MapList m) {
663 return AffineMap::inferFromExprList(m, op.getContext());
664 };
665 AffineExpr m, n, k;
666 bindDims(rewriter.getContext(), m, n, k);
667 SmallVector<AffineMap> maps = op.getIndexingMapsArray();
668 //
669 // In the following we wish to make the reduction dimension innermost so we
670 // can load vectors and just fmul + reduce into a scalar.
671 //
672 if (isParallelIterator(iteratorTypes[0]) &&
673 isParallelIterator(iteratorTypes[1]) &&
674 isReductionIterator(iteratorTypes[2])) {
675 //
676 // Two outer parallel, one inner reduction (matmat flavor).
677 //
678 if (maps == infer({{m, k}, {k, n}, {m, n}})) {
679 rhs = vector::TransposeOp::create(rewriter, loc, rhs, perm);
680 } else if (maps == infer({{m, k}, {n, k}, {m, n}})) {
681 // No need to permute anything.
682 } else if (maps == infer({{k, m}, {k, n}, {m, n}})) {
683 lhs = vector::TransposeOp::create(rewriter, loc, lhs, perm);
684 rhs = vector::TransposeOp::create(rewriter, loc, rhs, perm);
685 } else if (maps == infer({{k, m}, {n, k}, {m, n}})) {
686 lhs = vector::TransposeOp::create(rewriter, loc, lhs, perm);
687 } else if (maps == infer({{m, k}, {k, n}, {n, m}})) {
688 // This is the classical row-major matmul. Just permute the lhs.
689 Value tmp = lhs;
690 lhs = vector::TransposeOp::create(rewriter, loc, rhs, perm);
691 rhs = tmp;
692 } else if (maps == infer({{m, k}, {n, k}, {n, m}})) {
693 std::swap(lhs, rhs);
694 } else if (maps == infer({{k, m}, {k, n}, {n, m}})) {
695 Value tmp = lhs;
696 lhs = vector::TransposeOp::create(rewriter, loc, rhs, perm);
697 rhs = vector::TransposeOp::create(rewriter, loc, tmp, perm);
698 } else if (maps == infer({{k, m}, {n, k}, {n, m}})) {
699 Value tmp = rhs;
700 rhs = vector::TransposeOp::create(rewriter, loc, lhs, perm);
701 lhs = tmp;
702 } else {
703 return failure();
704 }
705 } else if (isParallelIterator(iteratorTypes[0]) &&
706 isReductionIterator(iteratorTypes[1])) {
707 //
708 // One outer parallel, one inner reduction (matvec flavor)
709 //
710 if (maps == infer({{m, n}, {n}, {m}})) {
711 // No need to permute anything.
712 } else if (maps == infer({{n, m}, {n}, {m}})) {
713 lhs = vector::TransposeOp::create(rewriter, loc, lhs, perm);
714 } else if (maps == infer({{n}, {m, n}, {m}})) {
715 std::swap(lhs, rhs);
716 } else if (maps == infer({{n}, {n, m}, {m}})) {
717 std::swap(lhs, rhs);
718 lhs = vector::TransposeOp::create(rewriter, loc, lhs, perm);
719 } else {
720 return failure();
721 }
722 } else {
723 return failure();
724 }
725
726 VectorType dstType = cast<VectorType>(op.getResultType());
727 assert(dstType.getRank() >= 1 && dstType.getRank() <= 2 &&
728 "Expected dst type of rank 1 or 2");
729
730 unsigned rank = dstType.getRank();
731 unsigned dstRows = dstType.getShape()[0];
732 unsigned dstColumns = rank == 1 ? 1 : dstType.getShape()[1];
733
734 // ExtractOp does not allow dynamic indexing, we must unroll explicitly.
735 Value res = arith::ConstantOp::create(rewriter, loc, dstType,
736 rewriter.getZeroAttr(dstType));
737 bool isInt = isa<IntegerType>(dstType.getElementType());
738 arith::FastMathFlagsAttr fmf = op.getFastmathAttr();
739 llvm::SmallVector<Value> extractedCols;
740 extractedCols.reserve(dstColumns);
741 for (unsigned r = 0; r < dstRows; ++r) {
742 Value rowLhs = vector::ExtractOp::create(rewriter, op.getLoc(), lhs, r);
743 for (unsigned c = 0; c < dstColumns; ++c) {
744 // Extract each respective row and column of the LHS and RHS once to
745 // avoid having duplicate SSA values pointing to the same rows/columns.
746 if (r == 0) {
747 Value colRhs =
748 rank == 1
749 ? rhs
750 : vector::ExtractOp::create(rewriter, op.getLoc(), rhs, c);
751 extractedCols.push_back(colRhs);
752 }
753 Value extractedColRhs = extractedCols[c];
754 Value product =
755 createMul(op.getLoc(), rowLhs, extractedColRhs, isInt, rewriter, fmf);
756 Value sum = vector::ReductionOp::create(rewriter, op.getLoc(),
757 vector::CombiningKind::ADD,
758 product, op.getFastmath());
759
762 res = vector::InsertOp::create(rewriter, op.getLoc(), sum, res, pos);
763 }
764 }
765 if (auto acc = op.getAcc())
766 res = createAdd(op.getLoc(), res, acc, isInt, rewriter, fmf);
767 return res;
768}
769
770/// Lower vector.contract with all size one reduction dimensions to
771/// elementwise ops when possible.
772struct ContractOpToElementwise
773 : public MaskableOpRewritePattern<vector::ContractionOp> {
774 using MaskableOpRewritePattern::MaskableOpRewritePattern;
775 using FilterConstraintType =
776 std::function<LogicalResult(vector::ContractionOp op)>;
777 static LogicalResult defaultFilter(vector::ContractionOp op) {
778 return success();
779 }
780 ContractOpToElementwise(
781 vector::VectorContractLowering vectorContractLowering,
782 MLIRContext *context, PatternBenefit benefit = 1,
783 const FilterConstraintType &constraint = defaultFilter)
784 : MaskableOpRewritePattern<vector::ContractionOp>(context, benefit),
785 vectorContractLowering(vectorContractLowering), filter(defaultFilter) {}
786
787 FailureOr<Value>
788 matchAndRewriteMaskableOp(vector::ContractionOp contractOp,
789 MaskingOpInterface maskOp,
790 PatternRewriter &rewriter) const override {
791 // TODO: Support vector.mask.
792 if (maskOp)
793 return failure();
794
795 if (failed(filter(contractOp)))
796 return failure();
797
798 if (vectorContractLowering != vector::VectorContractLowering::ParallelArith)
799 return failure();
800
801 ArrayRef<int64_t> lhsShape = contractOp.getLhsType().getShape();
802 ArrayRef<int64_t> rhsShape = contractOp.getRhsType().getShape();
803 AffineMap lhsMap = contractOp.getIndexingMapsArray()[0];
804 AffineMap rhsMap = contractOp.getIndexingMapsArray()[1];
805 SmallVector<int64_t> lhsReductionDims =
806 getReductionIndex(lhsMap, contractOp.getIteratorTypes());
807 SmallVector<int64_t> rhsReductionDims =
808 getReductionIndex(rhsMap, contractOp.getIteratorTypes());
809 // All the reduction dimensions must be a size 1.
810 for (int64_t dim : lhsReductionDims) {
811 if (lhsShape[dim] != 1)
812 return failure();
813 }
814 for (int64_t dim : rhsReductionDims) {
815 if (rhsShape[dim] != 1)
816 return failure();
817 }
818 AffineMap accMap = contractOp.getIndexingMapsArray()[2];
819 unsigned numParallelDims = accMap.getNumResults();
820 unsigned numLhsDimToBroadcast =
821 numParallelDims - (lhsMap.getNumResults() - lhsReductionDims.size());
822 unsigned numRhsDimToBroadcast =
823 numParallelDims - (rhsMap.getNumResults() - rhsReductionDims.size());
824 SmallVector<int64_t> lhsDims;
825 SmallVector<int64_t> lhsTranspose;
826 SmallVector<int64_t> rhsDims;
827 SmallVector<int64_t> rhsTranspose;
828 for (int64_t dim : lhsReductionDims)
829 lhsTranspose.push_back(numLhsDimToBroadcast + dim);
830 for (int64_t dim : rhsReductionDims)
831 rhsTranspose.push_back(numRhsDimToBroadcast + dim);
832 // Loop through the parallel dimensions to calculate the dimensions to
833 // broadcast and to permute in order to extract only parallel dimensions.
834 for (unsigned i = 0; i < numParallelDims; i++) {
835 std::optional<unsigned> lhsDim =
836 getDimPosition(lhsMap, accMap.getDimPosition(i));
837 if (lhsDim) {
838 lhsTranspose.push_back(numLhsDimToBroadcast + *lhsDim);
839 } else {
840 // If the parallel dimension doesn't exist we will have to broadcast it.
841 lhsDims.push_back(
842 cast<VectorType>(contractOp.getResultType()).getDimSize(i));
843 lhsTranspose.push_back(lhsDims.size() - 1);
844 }
845 std::optional<unsigned> rhsDim =
846 getDimPosition(rhsMap, accMap.getDimPosition(i));
847 if (rhsDim) {
848 rhsTranspose.push_back(numRhsDimToBroadcast + *rhsDim);
849 } else {
850 // If the parallel dimension doesn't exist we will have to broadcast it.
851 rhsDims.push_back(
852 cast<VectorType>(contractOp.getResultType()).getDimSize(i));
853 rhsTranspose.push_back(rhsDims.size() - 1);
854 }
855 }
856 Value newLhs = contractOp.getLhs();
857 Value newRhs = contractOp.getRhs();
858 Location loc = contractOp.getLoc();
859 if (!lhsDims.empty()) {
860 lhsDims.append(lhsShape.begin(), lhsShape.end());
861 auto expandedType =
862 VectorType::get(lhsDims, contractOp.getLhsType().getElementType());
863 newLhs = vector::BroadcastOp::create(rewriter, loc, expandedType, newLhs);
864 }
865 if (!rhsDims.empty()) {
866 rhsDims.append(rhsShape.begin(), rhsShape.end());
867 auto expandedType =
868 VectorType::get(rhsDims, contractOp.getRhsType().getElementType());
869 newRhs = vector::BroadcastOp::create(rewriter, loc, expandedType, newRhs);
870 }
871 bool isInt = contractOp.getLhsType().getElementType().isIntOrIndex();
872 newLhs = vector::TransposeOp::create(rewriter, loc, newLhs, lhsTranspose);
873 newRhs = vector::TransposeOp::create(rewriter, loc, newRhs, rhsTranspose);
874 SmallVector<int64_t> lhsOffsets(lhsReductionDims.size(), 0);
875 SmallVector<int64_t> rhsOffsets(rhsReductionDims.size(), 0);
876 newLhs = vector::ExtractOp::create(rewriter, loc, newLhs, lhsOffsets);
877 newRhs = vector::ExtractOp::create(rewriter, loc, newRhs, rhsOffsets);
878 std::optional<Value> result =
879 createContractArithOp(loc, newLhs, newRhs, contractOp.getAcc(),
880 contractOp.getKind(), rewriter, isInt,
881 /*mask=*/Value(), contractOp.getFastmathAttr());
882 if (result)
883 return *result;
884
885 return failure();
886 }
887
888private:
889 /// Options to control the vector patterns.
890 vector::VectorContractLowering vectorContractLowering;
891 FilterConstraintType filter;
892};
893
894/// Progressive lowering of ContractionOp.
895/// One:
896/// %x = vector.contract with at least one free/batch dimension
897/// is replaced by:
898/// %a = vector.contract with one less free/batch dimension
899/// %b = vector.contract with one less free/batch dimension
900/// ..
901/// %x = combine %a %b ..
902/// until a pure contraction is reached (no free/batch dimensions),
903/// which is replaced by a dot-product.
904///
905/// This only kicks in when either vectorContractLoweringOption is set
906/// to DOT or when other contraction patterns fail.
907//
908// TODO: break down into transpose/reshape/cast ops
909// when they become available to avoid code dup
910// TODO: investigate lowering order impact on performance
911FailureOr<Value> ContractionOpLowering::matchAndRewriteMaskableOp(
912 vector::ContractionOp op, MaskingOpInterface maskOp,
913 PatternRewriter &rewriter) const {
914 if (failed(filter(op)))
915 return failure();
916
917 // TODO: support mixed mode contract lowering.
918 if (op.getLhsType().getElementType() !=
919 getElementTypeOrSelf(op.getAccType()) ||
920 op.getRhsType().getElementType() != getElementTypeOrSelf(op.getAccType()))
921 return failure();
922
923 // TODO: the code below assumes the default contraction, make sure it supports
924 // other kinds before enabling this lowering.
925 if (!op.isAddKind()) {
926 return rewriter.notifyMatchFailure(
927 op, "contractions other than 'add' not supported");
928 }
929
930 // TODO: implement benefits, cost models.
931 MLIRContext *ctx = op.getContext();
932
933 ContractionOpToOuterProductOpLowering pat1(vectorContractLoweringOption, ctx);
934 FailureOr<Value> newVal1 =
935 pat1.matchAndRewriteMaskableOp(op, maskOp, rewriter);
936 if (!failed(newVal1))
937 return newVal1;
938
939 ContractionOpToDotLowering pat2(vectorContractLoweringOption, ctx);
940 FailureOr<Value> newVal2 =
941 pat2.matchAndRewriteMaskableOp(op, maskOp, rewriter);
942 if (!failed(newVal2))
943 return newVal2;
944
945 ContractOpToElementwise pat4(vectorContractLoweringOption, ctx);
946 FailureOr<Value> newVal4 =
947 pat4.matchAndRewriteMaskableOp(op, maskOp, rewriter);
948 if (!failed(newVal4))
949 return newVal4;
950
951 // Vector mask setup.
952
953 Value mask;
954 if (maskOp)
955 mask = maskOp.getMask();
956 // Find first batch dimension in LHS/RHS, and lower when found.
957 std::vector<std::pair<int64_t, int64_t>> batchDimMap = op.getBatchDimMap();
958 if (!batchDimMap.empty()) {
959 int64_t lhsIndex = batchDimMap[0].first;
960 int64_t rhsIndex = batchDimMap[0].second;
961 auto newOp = lowerParallel(rewriter, op, lhsIndex, rhsIndex, mask);
962 if (failed(newOp))
963 return failure();
964 return newOp;
965 }
966
967 // Collect contracting dimensions.
968 std::vector<std::pair<int64_t, int64_t>> contractingDimMap =
969 op.getContractingDimMap();
970 DenseSet<int64_t> lhsContractingDimSet;
971 DenseSet<int64_t> rhsContractingDimSet;
972 for (auto &dimPair : contractingDimMap) {
973 lhsContractingDimSet.insert(dimPair.first);
974 rhsContractingDimSet.insert(dimPair.second);
975 }
976
977 // Find first free dimension in LHS, and lower when found.
978 VectorType lhsType = op.getLhsType();
979 for (int64_t lhsIndex = 0, e = lhsType.getRank(); lhsIndex < e; ++lhsIndex) {
980 if (lhsContractingDimSet.count(lhsIndex) == 0) {
981 auto newOp = lowerParallel(rewriter, op, lhsIndex, /*rhsIndex=*/-1, mask);
982 if (failed(newOp))
983 return failure();
984 return newOp;
985 }
986 }
987
988 // Find first free dimension in RHS, and lower when found.
989 VectorType rhsType = op.getRhsType();
990 for (int64_t rhsIndex = 0, e = rhsType.getRank(); rhsIndex < e; ++rhsIndex) {
991 if (rhsContractingDimSet.count(rhsIndex) == 0) {
992 auto newOp = lowerParallel(rewriter, op, /*lhsIndex=*/-1, rhsIndex, mask);
993 if (failed(newOp))
994 return failure();
995 return newOp;
996 }
997 }
998
999 // Lower the first remaining reduction dimension.
1000 if (!contractingDimMap.empty()) {
1001 auto newOp = lowerReduction(rewriter, op, mask);
1002 if (failed(newOp))
1003 return failure();
1004 return newOp;
1005 }
1006
1007 return failure();
1008}
1009
1010// Lower one parallel dimension.
1011// TODO: consider reusing existing contract unrolling
1012FailureOr<Value> ContractionOpLowering::lowerParallel(PatternRewriter &rewriter,
1013 vector::ContractionOp op,
1014 int64_t lhsIndex,
1015 int64_t rhsIndex,
1016 Value mask) const {
1017 VectorType lhsType = op.getLhsType();
1018 VectorType rhsType = op.getRhsType();
1019 VectorType resType = cast<VectorType>(op.getResultType());
1020 // Find the iterator type index and result index.
1021 SmallVector<AffineMap> iMap = op.getIndexingMapsArray();
1022 int64_t iterIndex = -1;
1023 int64_t dimSize = -1;
1024 if (lhsIndex >= 0) {
1025 iterIndex = iMap[0].getDimPosition(lhsIndex);
1026 if (rhsIndex >= 0 && iterIndex != iMap[1].getDimPosition(rhsIndex))
1027 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1028 diag << "expected lhsIndex=" << lhsIndex << " and rhsIndex=" << rhsIndex
1029 << " to map to the same dimension";
1030 });
1031 if (lhsType.getScalableDims()[lhsIndex])
1032 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1033 diag << "Unrolling scalable dimension (lhsIndex=" << lhsIndex
1034 << ") is not supported yet";
1035 });
1036 dimSize = lhsType.getDimSize(lhsIndex);
1037 } else if (rhsIndex >= 0) {
1038 iterIndex = iMap[1].getDimPosition(rhsIndex);
1039 if (rhsType.getScalableDims()[rhsIndex])
1040 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1041 diag << "Unrolling scalable dimension (rhsIndex=" << rhsIndex
1042 << ") is not supported yet";
1043 });
1044 dimSize = rhsType.getDimSize(rhsIndex);
1045 }
1046 if (iterIndex < 0)
1047 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1048 diag << "expected either lhsIndex=" << lhsIndex
1049 << " or rhsIndex=" << rhsIndex << " to be nonnegative";
1050 });
1051
1052 int64_t resIndex = getResultIndex(iMap[2], iterIndex).value_or(-1);
1053 assert(resIndex != -1 && "A dimension missing from the accumulator indexing "
1054 "map is not a parallel dim!");
1055
1056 // Construct new iterator types and affine map array attribute.
1057 std::array<AffineMap, 3> lowIndexingMaps = {
1058 adjustMap(iMap[0], iterIndex, rewriter),
1059 adjustMap(iMap[1], iterIndex, rewriter),
1060 adjustMap(iMap[2], iterIndex, rewriter)};
1061 auto lowAffine = rewriter.getAffineMapArrayAttr(lowIndexingMaps);
1062 auto lowIter =
1063 rewriter.getArrayAttr(adjustIter(op.getIteratorTypes(), iterIndex));
1064 // Unroll into a series of lower dimensional vector.contract ops.
1065 Location loc = op.getLoc();
1066 Value result = arith::ConstantOp::create(rewriter, loc, resType,
1067 rewriter.getZeroAttr(resType));
1068
1069 for (int64_t d = 0; d < dimSize; ++d) {
1070 auto lhs = reshapeLoad(loc, op.getLhs(), lhsIndex, d, rewriter);
1071 auto rhs = reshapeLoad(loc, op.getRhs(), rhsIndex, d, rewriter);
1072 auto acc = reshapeLoad(loc, op.getAcc(), resIndex, d, rewriter);
1073
1074 Value lowMask;
1075 if (mask)
1076 lowMask = reshapeLoad(loc, mask, iterIndex, d, rewriter);
1077
1078 Operation *lowContract =
1079 vector::ContractionOp::create(rewriter, loc, lhs, rhs, acc, lowAffine,
1080 lowIter, op.getKind(), op.getFastmath());
1081 lowContract = maskOperation(rewriter, lowContract, lowMask);
1082 result = reshapeStore(loc, lowContract->getResult(0), result, resIndex, d,
1083 rewriter);
1084 }
1085 return result;
1086}
1087
1088// Lower one reduction dimension.
1089FailureOr<Value> ContractionOpLowering::lowerReduction(
1090 PatternRewriter &rewriter, vector::ContractionOp op, Value mask) const {
1091 auto loc = op.getLoc();
1092 VectorType lhsType = op.getLhsType();
1093 VectorType rhsType = op.getRhsType();
1094 Type resType = op.getResultType();
1095 auto resVectorType = dyn_cast<VectorType>(resType);
1096 if (resVectorType && resVectorType.getRank() != 0)
1097 return rewriter.notifyMatchFailure(op,
1098 "did not expect a VectorType result");
1099 bool isInt = isa<IntegerType>(getElementTypeOrSelf(resType));
1100 // Use iterator index 0.
1101 int64_t iterIndex = 0;
1102 SmallVector<AffineMap> iMap = op.getIndexingMapsArray();
1103 std::optional<int64_t> lookupLhs = getResultIndex(iMap[0], iterIndex);
1104 std::optional<int64_t> lookupRhs = getResultIndex(iMap[1], iterIndex);
1105 if (!lookupLhs.has_value())
1106 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1107 diag << "expected iterIndex=" << iterIndex << "to map to a LHS dimension";
1108 });
1109 if (!lookupRhs.has_value())
1110 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1111 diag << "expected iterIndex=" << iterIndex << "to map to a RHS dimension";
1112 });
1113 int64_t lhsIndex = *lookupLhs;
1114 int64_t rhsIndex = *lookupRhs;
1115 int64_t dimSize = lhsType.getDimSize(lhsIndex);
1116 if (dimSize != rhsType.getDimSize(rhsIndex))
1117 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1118 diag << "expect LHS dimension " << lhsIndex
1119 << " to have the same size as RHS dimension " << rhsIndex;
1120 });
1121 // Base case.
1122 if (lhsType.getRank() == 1) {
1123 if (rhsType.getRank() != 1)
1124 return rewriter.notifyMatchFailure(
1125 op, "When LHS has rank 1, expected also RHS to have rank 1");
1126 arith::FastMathFlagsAttr fmf = op.getFastmathAttr();
1127 Value m = createMul(loc, op.getLhs(), op.getRhs(), isInt, rewriter, fmf);
1128 auto kind = vector::CombiningKind::ADD;
1129
1130 Value acc = op.getAcc();
1131 // vector.reduction takes a scalar accumulator.
1132 if (resVectorType)
1133 acc = vector::ExtractOp::create(rewriter, loc, acc, ArrayRef<int64_t>{});
1134 Operation *reductionOp =
1135 acc ? vector::ReductionOp::create(rewriter, loc, kind, m, acc,
1136 op.getFastmath())
1137 : vector::ReductionOp::create(rewriter, loc, kind, m,
1138 op.getFastmath());
1139 Value reduced = maskOperation(rewriter, reductionOp, mask)->getResult(0);
1140 if (resVectorType)
1141 reduced =
1142 vector::BroadcastOp::create(rewriter, loc, resVectorType, reduced);
1143 return reduced;
1144 }
1145 // Construct new iterator types and affine map array attribute.
1146 std::array<AffineMap, 3> lowIndexingMaps = {
1147 adjustMap(iMap[0], iterIndex, rewriter),
1148 adjustMap(iMap[1], iterIndex, rewriter),
1149 adjustMap(iMap[2], iterIndex, rewriter)};
1150 auto lowAffine = rewriter.getAffineMapArrayAttr(lowIndexingMaps);
1151 auto lowIter =
1152 rewriter.getArrayAttr(adjustIter(op.getIteratorTypes(), iterIndex));
1153 // Unroll into a series of lower dimensional vector.contract ops.
1154 // By feeding the initial accumulator into the first contraction,
1155 // and the result of each contraction into the next, eventually
1156 // the sum of all reductions is computed.
1157 Value result = op.getAcc();
1158 for (int64_t d = 0; d < dimSize; ++d) {
1159 auto lhs = reshapeLoad(loc, op.getLhs(), lhsIndex, d, rewriter);
1160 auto rhs = reshapeLoad(loc, op.getRhs(), rhsIndex, d, rewriter);
1161 Value newMask;
1162 if (mask)
1163 newMask = reshapeLoad(loc, mask, iterIndex, d, rewriter);
1164
1165 Operation *newContract = vector::ContractionOp::create(
1166 rewriter, loc, lhs, rhs, result, lowAffine, lowIter, op.getKind(),
1167 op.getFastmath());
1168 result = maskOperation(rewriter, newContract, newMask)->getResult(0);
1169 }
1170 return result;
1171}
1172
1173/// Progressive lowering of OuterProductOp.
1174/// One:
1175/// %x = vector.outerproduct %lhs, %rhs, %acc
1176/// is replaced by:
1177/// %z = zero-result
1178/// %0 = vector.extract %lhs[0]
1179/// %1 = vector.broadcast %0
1180/// %2 = vector.extract %acc[0]
1181/// %3 = vector.fma %1, %rhs, %2
1182/// %4 = vector.insert %3, %z[0]
1183/// ..
1184/// %x = vector.insert %.., %..[N-1]
1185///
1186class OuterProductOpLowering : public OpRewritePattern<vector::OuterProductOp> {
1187public:
1188 using Base::Base;
1189
1190 LogicalResult matchAndRewrite(vector::OuterProductOp op,
1191 PatternRewriter &rewriter) const override {
1192 VectorType resType = op.getResultVectorType();
1193 if ((resType.getShape().size() >= 2) && resType.allDimsScalable())
1194 return failure();
1195
1196 auto loc = op.getLoc();
1197
1198 VectorType lhsType = op.getOperandVectorTypeLHS();
1199 VectorType rhsType = dyn_cast<VectorType>(op.getOperandTypeRHS());
1200 Type eltType = resType.getElementType();
1201 bool isInt = isa<IntegerType, IndexType>(eltType);
1202 Value acc = op.getAcc();
1203 vector::CombiningKind kind = op.getKind();
1204
1205 // Vector mask setup.
1206 OpBuilder::InsertionGuard guard(rewriter);
1207 auto maskableOp = cast<vector::MaskableOpInterface>(op.getOperation());
1208 Operation *rootOp;
1209 Value mask;
1210 if (maskableOp.isMasked()) {
1211 rewriter.setInsertionPoint(maskableOp.getMaskingOp());
1212 rootOp = maskableOp.getMaskingOp();
1213 mask = maskableOp.getMaskingOp().getMask();
1214 } else {
1215 rootOp = op;
1216 }
1217
1218 if (!rhsType) {
1219 // Special case: AXPY operation.
1220 Value b =
1221 vector::BroadcastOp::create(rewriter, loc, lhsType, op.getRhs());
1222 std::optional<Value> mult = createContractArithOp(
1223 loc, op.getLhs(), b, acc, kind, rewriter, isInt, mask);
1224 if (!mult.has_value())
1225 return failure();
1226 rewriter.replaceOp(rootOp, *mult);
1227 return success();
1228 }
1229
1230 Value result = arith::ConstantOp::create(rewriter, loc, resType,
1231 rewriter.getZeroAttr(resType));
1232 for (int64_t d = 0, e = resType.getDimSize(0); d < e; ++d) {
1233 Value x = vector::ExtractOp::create(rewriter, loc, op.getLhs(), d);
1234 Value a = vector::BroadcastOp::create(rewriter, loc, rhsType, x);
1235 Value r = nullptr;
1236 if (acc)
1237 r = vector::ExtractOp::create(rewriter, loc, acc, d);
1238 Value extrMask;
1239 if (mask)
1240 extrMask = vector::ExtractOp::create(rewriter, loc, mask, d);
1241
1242 std::optional<Value> m = createContractArithOp(
1243 loc, a, op.getRhs(), r, kind, rewriter, isInt, extrMask);
1244 if (!m.has_value())
1245 return failure();
1246 result = vector::InsertOp::create(rewriter, loc, *m, result, d);
1247 }
1248
1249 rewriter.replaceOp(rootOp, result);
1250 return success();
1251 }
1252};
1253
1254} // namespace
1255
1257 RewritePatternSet &patterns,
1258 VectorContractLowering vectorContractLoweringOption, PatternBenefit benefit,
1259 bool disableOuterProductLowering) {
1260 if (!disableOuterProductLowering)
1261 patterns.add<OuterProductOpLowering>(patterns.getContext(), benefit);
1262 patterns.add<ContractionOpLowering, ContractionOpToOuterProductOpLowering>(
1263 vectorContractLoweringOption, patterns.getContext(), benefit);
1264}
1265
1267 RewritePatternSet &patterns, PatternBenefit benefit) {
1268 patterns.add<OuterProductOpLowering>(patterns.getContext(), benefit);
1269}
return success()
static int64_t product(ArrayRef< int64_t > vals)
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
auto load
static std::optional< int64_t > getResultIndex(AffineMap map, int64_t index)
static SmallVector< int64_t > getReductionIndex(AffineMap map, ArrayAttr iteratorTypes)
Return the positions of the reductions in the given map.
static std::optional< unsigned > getDimPosition(AffineMap map, unsigned dim)
Look for a given dimension in an affine map and return its position.
static Value reshapeStore(Location loc, Value val, Value result, int64_t index, int64_t pos, PatternRewriter &rewriter)
Inserts val into result at position pos along dimension index.
static SmallVector< Attribute > adjustIter(ArrayAttr iteratorTypes, int64_t index)
FailureOr< Value > tmatvec()
static Value createAdd(Location loc, Value x, Value y, bool isInt, PatternRewriter &rewriter, arith::FastMathFlagsAttr fmf={})
Creates an AddIOp if isInt is true otherwise create an arith::AddFOp using operands x and y.
FailureOr< Value > outerProd(Value lhs, Value rhs, Value res, VectorType lhsType, int reductionSize, std::optional< Value > maybeMask=std::nullopt)
FailureOr< Value > matvec()
static AffineMap adjustMap(AffineMap map, int64_t index, PatternRewriter &rewriter)
static Value reshapeLoad(Location loc, Value val, int64_t index, int64_t pos, PatternRewriter &rewriter)
Returns val with the dimension at position index dropped by indexing that dimension with pos.
FailureOr< Value > matmat()
Two outer parallel, one inner reduction (matmat flavor).
static std::optional< Value > createContractArithOp(Location loc, Value x, Value y, Value acc, vector::CombiningKind kind, PatternRewriter &rewriter, bool isInt, Value mask=Value(), arith::FastMathFlagsAttr fmf={})
Helper to create arithmetic operation associated with a kind of contraction.
std::optional< int64_t > getReductionSize(VectorType vecType, int64_t reductionDim)
Helper function for matmat, matvec, tmatvec. Returns the size of dimension reductionDim....
#define mul(a, b)
Base type for affine expression.
Definition AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
unsigned getDimPosition(unsigned idx) const
Extracts the position of the dimensional expression at the given result, when the caller knows it is ...
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
unsigned getNumDims() const
unsigned getNumResults() const
static SmallVector< AffineMap, 4 > inferFromExprList(ArrayRef< ArrayRef< AffineExpr > > exprsList, MLIRContext *context)
Returns a vector of AffineMaps; each with as many results as exprs.size(), as many dims as the larges...
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:275
MLIRContext * getContext() const
Definition Builders.h:56
ArrayAttr getAffineMapArrayAttr(ArrayRef< AffineMap > values)
Definition Builders.cpp:327
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
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
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:434
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...
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.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
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,...
Helper StructuredGenerator class to manipulate and rewrite ops with StructuredOpInterface.
bool iters(ArrayRef< IteratorType > its)
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
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
This is a builder type that keeps local references to arguments.
Builder & dropDim(unsigned pos)
Erase a dim from shape @pos.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:853
LogicalResult promote(RewriterBase &rewriter, scf::ForallOp forallOp)
Promotes the loop body of a scf::ForallOp to its containing block.
Definition SCF.cpp:753
Value makeArithReduction(OpBuilder &b, Location loc, CombiningKind kind, Value v1, Value acc, arith::FastMathFlagsAttr fastmath=nullptr, Value mask=nullptr)
Returns the result value of reducing two scalar/vector values with the corresponding arith operation.
Operation * maskOperation(OpBuilder &builder, Operation *maskableOp, Value mask, Value passthru=Value())
Creates a vector.mask operation around a maskable operation.
bool isReductionIterator(Attribute attr)
Returns true if attr has "reduction" iterator type semantics.
Definition VectorOps.h:146
Value selectPassthru(OpBuilder &builder, Value mask, Value newValue, Value passthru)
Creates a vector select operation that picks values from newValue or passthru for each result vector ...
bool isParallelIterator(Attribute attr)
Returns true if attr has "parallel" iterator type semantics.
Definition VectorOps.h:141
void populateVectorOuterProductLoweringPatterns(RewritePatternSet &patterns, PatternBenefit benefit=1)
Populate the pattern set with the following patterns:
void populateVectorContractLoweringPatterns(RewritePatternSet &patterns, VectorContractLowering vectorContractLoweringOption, PatternBenefit benefit=1, bool disableOuterProductLowering=false)
Populate the pattern set with the following patterns:
Include the generated interface declarations.
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:313
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:125
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern Base
Type alias to allow derived classes to inherit constructors with using Base::Base;.
A pattern for ops that implement MaskableOpInterface and that might be masked (i.e.
virtual FailureOr< Value > matchAndRewriteMaskableOp(SourceOp sourceOp, MaskingOpInterface maskingOp, PatternRewriter &rewriter) const =0