MLIR 24.0.0git
VectorContractToAMXDotProduct.cpp
Go to the documentation of this file.
1//===- VectorContractToAMXDotProduct.cpp ----------------------------------===//
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
15
17#include "mlir/IR/Dominance.h"
19#include "llvm/Support/Casting.h"
20
21#include "mlir/Pass/Pass.h"
23
24using namespace mlir;
25using namespace mlir::vector;
26using namespace mlir::x86;
27
28namespace {
29
30// Return true if the operation is a constant dense element
31// attribute.
32static bool isZeroVectorConstant(Operation *op) {
33 auto constantOp = dyn_cast_if_present<arith::ConstantOp>(op);
34 if (!constantOp)
35 return false;
36
37 if (!isa<VectorType>(constantOp.getType()))
38 return false;
39
40 auto denseAttr = dyn_cast<DenseElementsAttr>(constantOp.getValue());
41 if (!denseAttr || !denseAttr.isSplat())
42 return false;
43
44 return isZeroIntegerOrFloat(denseAttr.getSplatValue<Attribute>());
45}
46
47// Recursively follows single-use values through scf.yield operations
48// and returns the first non-yield user result in the contraction chain.
50 if (v.getNumUses() != 1)
51 return nullptr;
52
53 OpOperand &use = *v.use_begin();
54 Operation *user = use.getOwner();
55
56 if (!isa<scf::YieldOp>(user))
57 return v;
58
59 auto yield = cast<scf::YieldOp>(user);
60 Operation *parent = yield->getParentOp();
61 unsigned idx = use.getOperandNumber();
62
63 return contractionUsersAfterYield(parent->getResult(idx));
64}
65
66// Function to collapse the last two dimension (k or n, and vnni) to help the
67// amx.tile_load to correctly load the packed element type.
68static Value collapseInnerDims(OpBuilder &builder, Location loc, Value input) {
69 ShapedType inputType = cast<ShapedType>(input.getType());
70 int64_t firstDimToCollapse = inputType.getRank() - 2;
71
72 if (inputType.getRank() == 1)
73 return input;
74
76 for (int64_t i = 0; i < firstDimToCollapse; ++i)
77 reassociation.push_back(ReassociationIndices{i});
78
79 ReassociationIndices collapsedIndices;
80 for (int64_t i = firstDimToCollapse; i < inputType.getRank(); ++i)
81 collapsedIndices.push_back(i);
82
83 reassociation.push_back(collapsedIndices);
84 return memref::CollapseShapeOp::create(builder, loc, input, reassociation);
85}
86
87// Check if a vector.contract operand has a memref read source.
88static bool isReadSrcMemref(Value operand) {
89 Operation *defOp = operand.getDefiningOp();
90 if (!defOp)
91 return false;
92
93 Value srcBuff;
95 .Case<TransferReadOp, LoadOp>(
96 [&](auto readOp) { srcBuff = readOp.getOperand(0); });
97
98 return srcBuff && isa<MemRefType>(srcBuff.getType());
99}
100
101// Check if a vector.contract operand reads whole VNNI groups: the memref's
102// static innermost dim is the VNNI factor and the read starts at its origin.
103static bool isVnniOperandRead(Value operand, int64_t vnni) {
104 Operation *defOp = operand.getDefiningOp();
105 if (!defOp)
106 return false;
107
108 Value srcBuff;
109 OpFoldResult vnniIndex;
110 llvm::TypeSwitch<Operation *>(defOp).Case<TransferReadOp, LoadOp>(
111 [&](auto readOp) {
112 srcBuff = readOp.getBase();
113 vnniIndex = readOp.getIndices().back();
114 });
115
116 if (!srcBuff)
117 return false;
118 auto srcType = dyn_cast<MemRefType>(srcBuff.getType());
119 return srcType && srcType.getRank() > 0 &&
120 srcType.getShape().back() == vnni && isZeroInteger(vnniIndex);
121}
122
123// Replaces the indices of the two innermost dims of a VNNI operand by the
124// index of the dim they collapse into.
125static void collapseVnniIndices(OpBuilder &rewriter, Location loc,
127 Value inner = indices.pop_back_val();
128 Value cVnni = arith::ConstantIndexOp::create(rewriter, loc, vnni);
129 Value scaled = arith::MulIOp::create(rewriter, loc, indices.back(), cVnni);
130 indices.back() = arith::AddIOp::create(rewriter, loc, scaled, inner);
131}
132
133// Get the MemRef source and offset index for the operands of
134// vector.contract.
135static FailureOr<std::pair<Value, SmallVector<Value>>>
136getSrcIndxValue(OpBuilder &rewriter, Location loc, Value operand,
137 bool isNotAcc) {
138 Operation *defOp = operand.getDefiningOp();
139 if (!defOp)
140 return failure();
141
142 Value srcBuff;
145 .Case<TransferReadOp, LoadOp>([&](auto readOp) {
146 indexVals = SmallVector<OpFoldResult>(readOp.getIndices().begin(),
147 readOp.getIndices().end());
148 srcBuff = readOp.getOperand(0);
149 });
150
151 if (!srcBuff || !isa<MemRefType>(srcBuff.getType()))
152 return failure();
153
155 indices.reserve(indexVals.size());
156
157 for (OpFoldResult ofr : indexVals) {
158 indices.push_back(
159 mlir::getValueOrCreateConstantIndexOp(rewriter, loc, ofr));
160 }
161
162 if (isNotAcc) {
163 collapseVnniIndices(rewriter, loc, indices,
164 cast<MemRefType>(srcBuff.getType()).getShape().back());
165 srcBuff = collapseInnerDims(rewriter, loc, srcBuff);
166 }
167
168 return std::make_pair(srcBuff, indices);
169}
170
171// Function to validate the loop step value.
172static LogicalResult validateLoopStep(OpBuilder &rewriter, Value step,
173 int64_t value) {
174
175 auto cst = step.getDefiningOp<arith::ConstantIndexOp>();
176 if (!cst)
177 return failure();
178
179 if (cst.value() != value && cst.value() != 1)
180 return failure();
181
182 return success();
183}
184
185// Function to validate the vector.contract operation.
186static LogicalResult validateContractOps(OpBuilder &rewriter,
187 vector::ContractionOp contractOp,
188 unsigned int blockingFactor,
189 Value srcBuffLhs, Value srcBuffRhs,
190 bool srcValidate) {
191
192 if (srcValidate) {
193 // Get the MemRef buffer of LHS operand.
194 auto srcIndxLhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
195 contractOp.getLhs(), false);
196 if (failed(srcIndxLhs))
197 return failure();
198 auto [buffLhs, indicesLhs] = *srcIndxLhs;
199
200 // Get the MemRef buffer of RHS operand.
201 auto srcIndxRhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
202 contractOp.getRhs(), false);
203 if (failed(srcIndxRhs))
204 return failure();
205 auto [buffRhs, indicesRhs] = *srcIndxRhs;
206
207 // Return failure if the Memref buff didn't match.
208 if (buffLhs != srcBuffLhs)
209 return failure();
210
211 if (buffRhs != srcBuffRhs)
212 return failure();
213 }
214
215 if (!contractionUsersAfterYield(contractOp.getResult()))
216 return failure();
217
218 VectorType accTy = dyn_cast<VectorType>(contractOp.getAccType());
219 if (!accTy)
220 return failure();
221
222 // The Accumulator dims should be 16 or 1. Like <1x16x16> or <16x16>.
223 ArrayRef<int64_t> accShape = accTy.getShape();
224 SmallVector<int64_t> nonUnitDimAcc;
225 llvm::copy_if(accShape, std::back_inserter(nonUnitDimAcc),
226 [](int64_t dim) { return (dim != 16 && dim != 1); });
227
228 if (nonUnitDimAcc.size() != 0)
229 return failure();
230
231 // The LHS dims should be 16 or vnni or 1. Like <1x16x16x2> or
232 // <16x16x4>. The vnni dims should be 2 or 4.
233 VectorType lhsTy = contractOp.getLhsType();
234 ArrayRef<int64_t> lhsShape = lhsTy.getShape();
235 SmallVector<int64_t> nonUnitDimLhs;
236 llvm::copy_if(lhsShape, std::back_inserter(nonUnitDimLhs),
237 [](int64_t dim) { return (dim != 16 && dim != 1); });
238
239 if (nonUnitDimLhs.size() != 1)
240 return failure();
241
242 if (nonUnitDimLhs[0] != blockingFactor)
243 return failure();
244
245 // The RHS dims should be 16 or vnni or 1. Like <1x16x16x2> or
246 // <16x16x4>. The vnni dims should be 2 or 4.
247 VectorType rhsTy = contractOp.getRhsType();
248 ArrayRef<int64_t> rhsShape = rhsTy.getShape();
249 llvm::SmallVector<int64_t> nonUnitDimRhs;
250 llvm::copy_if(rhsShape, std::back_inserter(nonUnitDimRhs),
251 [](int64_t dim) { return (dim != 16 && dim != 1); });
252
253 if (nonUnitDimRhs.size() != 1)
254 return failure();
255
256 if (nonUnitDimRhs[0] != blockingFactor)
257 return failure();
258
259 return success();
260}
261
262// Traces an accumulator back through the iter_args of the given loops and
263// returns the first value that is not one of them, e.g., the init value of the
264// outermost loop or a value computed within the loops.
265static Value getLoopNestInit(Value acc, ArrayRef<scf::ForOp> loops) {
266 while (auto arg = dyn_cast<BlockArgument>(acc)) {
267 auto loop = dyn_cast_if_present<scf::ForOp>(arg.getOwner()->getParentOp());
268 if (!loop || !llvm::is_contained(loops, loop))
269 break;
270 OpOperand *init = loop.getTiedLoopInit(arg);
271 if (!init)
272 break;
273 acc = init->get();
274 }
275 return acc;
276}
277
278// Returns the loop index position to get mapped during the
279// MemRef type clone.
280static unsigned getIndexPosition(Value operand, scf::ForOp loop) {
281 Value iv = loop.getInductionVar();
282
283 Value srcBuff;
285 .Case<TransferReadOp, LoadOp>(
286 [&](auto readOp) { srcBuff = readOp.getOperand(0); });
287
288 auto subview = srcBuff.getDefiningOp<memref::SubViewOp>();
289 if (!subview)
290 return 0;
291
292 auto offsets = subview.getOffsets();
293
294 for (auto it : llvm::enumerate(offsets)) {
295 if (it.value() == iv)
296 return it.index();
297 }
298
299 return 0;
300}
301
302// Creates amx.tile_loads.
303static amx::TileLoadOp createTileLoads(OpBuilder &rewriter, Location loc,
304 Value operand, Value mat, Type ipType,
305 unsigned int offset, bool isVnni) {
306
307 auto srcIndx = getSrcIndxValue(rewriter, loc, operand, false);
308 auto [srcBuff, indices] = *srcIndx;
309 if (isVnni)
310 collapseVnniIndices(rewriter, loc, indices,
311 cast<MemRefType>(srcBuff.getType()).getShape().back());
312
313 amx::TileType tileType = amx::TileType::get({16, (16 * offset)}, ipType);
314 return amx::TileLoadOp::create(rewriter, loc, tileType, mat, indices);
315}
316
317static void performShuffle(OpBuilder &rewriter, Location loc, Value matB,
318 Type ipType, unsigned int offset, Value packedBuffer,
319 Value indxToStoreInBuffer) {
320
321 Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
322 Value c16 = arith::ConstantIndexOp::create(rewriter, loc, 16);
323 SmallVector<Value> subviewOffset(
324 llvm::cast<MemRefType>(matB.getType()).getRank(), c0);
325
326 Value cStep = arith::ConstantIndexOp::create(rewriter, loc, offset);
327 Value cBound = arith::ConstantIndexOp::create(rewriter, loc, (16 * offset));
328 Value offsetIndx =
329 arith::ConstantIndexOp::create(rewriter, loc, (offset / 2));
330
331 scf::ForOp::create(
332 rewriter, loc, c0, cBound, cStep, ValueRange{},
333 [&](OpBuilder &nestedBuilder, Location loc, Value iv,
334 ValueRange iterArgs) {
335 subviewOffset[subviewOffset.size() - 2] = iv;
336
337 // Retrieve two rows of vector (32) for int8 and f8 type. For bf16,
338 // retrieve one row of vector (32).
339 auto vectorType = VectorType::get({2, (16 * (offset / 2))}, ipType);
340 if (ipType.isBF16())
341 vectorType = VectorType::get((16 * offset), ipType);
342
343 int64_t srcRank = (dyn_cast<ShapedType>(matB.getType())).getRank();
344 Value padding = ub::PoisonOp::create(rewriter, loc, ipType);
345 auto map = AffineMap::getMinorIdentityMap(srcRank, vectorType.getRank(),
346 rewriter.getContext());
347 SmallVector<bool> inBounds(vectorType.getRank(), true);
348 Value vec1 = vector::TransferReadOp::create(
349 rewriter, loc, vectorType, matB, ValueRange(subviewOffset), padding,
350 map, inBounds);
351
352 if (!ipType.isBF16())
353 vec1 = vector::ShapeCastOp::create(
354 rewriter, loc, VectorType::get((16 * offset), ipType), vec1);
355
356 // Increment the iv by 1 or 2 based on the type to load the next 32/64
357 // elements
358 Value incIV = arith::AddIOp::create(rewriter, loc, offsetIndx, iv);
359 subviewOffset[subviewOffset.size() - 2] = incIV;
360
361 Value vec2 = vector::TransferReadOp::create(
362 rewriter, loc, vectorType, matB, ValueRange(subviewOffset), padding,
363 map, inBounds);
364 if (!ipType.isBF16())
365 vec2 = vector::ShapeCastOp::create(
366 rewriter, loc, VectorType::get((16 * offset), ipType), vec2);
367
368 vector::ShuffleOp writeVal1;
369 vector::ShuffleOp writeVal2;
370
371 if (ipType.isBF16()) {
372
373 writeVal1 = vector::ShuffleOp::create(
374 rewriter, loc, VectorType::get({(16 * offset)}, ipType), vec1,
375 vec2,
376 ArrayRef<int64_t>{0, 32, 1, 33, 2, 34, 3, 35, 8, 40, 9,
377 41, 10, 42, 11, 43, 16, 48, 17, 49, 18, 50,
378 19, 51, 24, 56, 25, 57, 26, 58, 27, 59});
379
380 writeVal2 = vector::ShuffleOp::create(
381 rewriter, loc, VectorType::get({(16 * offset)}, ipType), vec1,
382 vec2,
383 ArrayRef<int64_t>{4, 36, 5, 37, 6, 38, 7, 39, 12, 44, 13,
384 45, 14, 46, 15, 47, 20, 52, 21, 53, 22, 54,
385 23, 55, 28, 60, 29, 61, 30, 62, 31, 63});
386 }
387
388 if (ipType.isSignlessInteger(8) || ipType.isF8E5M2() ||
389 ipType.isF8E4M3FN()) {
390
391 writeVal1 = vector::ShuffleOp::create(
392 rewriter, loc, VectorType::get({(16 * offset)}, ipType), vec1,
393 vec2,
395 0, 32, 64, 96, 1, 33, 65, 97, 2, 34, 66, 98, 3,
396 35, 67, 99, 8, 40, 72, 104, 9, 41, 73, 105, 10, 42,
397 74, 106, 11, 43, 75, 107, 16, 48, 80, 112, 17, 49, 81,
398 113, 18, 50, 82, 114, 19, 51, 83, 115, 24, 56, 88, 120,
399 25, 57, 89, 121, 26, 58, 90, 122, 27, 59, 91, 123});
400
401 writeVal2 = vector::ShuffleOp::create(
402 rewriter, loc, VectorType::get({(16 * offset)}, ipType), vec1,
403 vec2,
405 4, 36, 68, 100, 5, 37, 69, 101, 6, 38, 70, 102, 7, 39,
406 71, 103, 12, 44, 76, 108, 13, 45, 77, 109, 14, 46, 78, 110,
407 15, 47, 79, 111, 20, 52, 84, 116, 21, 53, 85, 117, 22, 54,
408 86, 118, 23, 55, 87, 119, 28, 60, 92, 124, 29, 61, 93, 125,
409 30, 62, 94, 126, 31, 63, 95, 127});
410 }
411
412 // iv to store the shuffled elements
413 Value ivShuff1 = arith::DivUIOp::create(rewriter, loc, iv, cStep);
414 Value ivShuff2 = arith::AddIOp::create(rewriter, loc, ivShuff1, c16);
415
416 vector::StoreOp::create(rewriter, loc, writeVal1, packedBuffer,
417 ValueRange{indxToStoreInBuffer, ivShuff1, c0});
418 vector::StoreOp::create(rewriter, loc, writeVal2, packedBuffer,
419 ValueRange{indxToStoreInBuffer, ivShuff2, c0});
420
421 scf::YieldOp::create(nestedBuilder, loc);
422 });
423}
424
426packInputs(OpBuilder &rewriter, Location loc,
428 unsigned int offset, Value packedBuffer, bool pack,
429 Value indxToStoreInBuffer, Value indxToLoadFromMatB) {
430
432 Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
433 Value c16 = arith::ConstantIndexOp::create(rewriter, loc, 16);
434
435 for (size_t j = 0; j < ops.size(); j++) {
436 for (size_t i = 0; i < ops.size(); i++) {
437
438 if (i != j && validatePairVectorContract(ops[j], ops[i], true, 16)) {
439
440 Operation *readOpRhs = ops[j].getRhs().getDefiningOp();
441 auto itRhs = readsToTileLoads.find(readOpRhs);
442 if (itRhs != readsToTileLoads.end()) {
443 continue;
444 }
445
446 if (pack) {
447 performShuffle(rewriter, loc, matB, ipType, offset, packedBuffer,
448 indxToStoreInBuffer);
449 }
450
451 amx::TileType tileType =
452 amx::TileType::get({16, (16 * offset)}, ipType);
453 auto loadRow1 =
454 amx::TileLoadOp::create(rewriter, loc, tileType, packedBuffer,
455 ValueRange{indxToLoadFromMatB, c0, c0});
456
457 auto loadRow2 =
458 amx::TileLoadOp::create(rewriter, loc, tileType, packedBuffer,
459 ValueRange{indxToLoadFromMatB, c16, c0});
460
461 readsToTileLoads.try_emplace(readOpRhs, loadRow1);
462 readsToTileLoads.try_emplace(ops[i].getRhs().getDefiningOp(), loadRow2);
463 }
464 }
465 }
466
467 return readsToTileLoads;
468}
469
470// Creates tiled amx dot-products.
472createTiledDp(OpBuilder &rewriter, Location loc,
474 Type ipType, Type opType, ValueRange accIterArgs,
475 unsigned int offset, bool isVnni, Value packedBuffer, bool pack,
476 Value indxToStoreInBuffer, Value indxToLoadFromMatB) {
477
478 if (isVnni) {
479 matA = collapseInnerDims(rewriter, loc, matA);
480 matB = collapseInnerDims(rewriter, loc, matB);
481 }
482
483 SmallVector<Value> accumulators;
484 // Stores the amx.tile_load operation vs it's equivalent vector tranfer_read
485 // or load operations.
487
488 // function call to online pack the input B matrix
489 if (!isVnni) {
490 readsToTileLoads =
491 packInputs(rewriter, loc, ops, matB, ipType, offset, packedBuffer, pack,
492 indxToStoreInBuffer, indxToLoadFromMatB);
493 }
494
495 // Iterate over the contraction operations and compute the tiled dot-product.
496 for (size_t i = 0; i < ops.size(); i++) {
497
498 Operation *readOpLhs = ops[i].getLhs().getDefiningOp();
499 amx::TileLoadOp tilesLhs;
500 auto itLhs = readsToTileLoads.find(readOpLhs);
501 if (itLhs != readsToTileLoads.end()) {
502 tilesLhs = itLhs->second;
503 } else {
504 tilesLhs = createTileLoads(rewriter, loc, ops[i].getLhs(), matA, ipType,
505 offset, isVnni);
506 readsToTileLoads.try_emplace(readOpLhs, tilesLhs);
507 }
508
509 Operation *readOpRhs = ops[i].getRhs().getDefiningOp();
510 amx::TileLoadOp tilesRhs;
511 auto itRhs = readsToTileLoads.find(readOpRhs);
512 if (itRhs != readsToTileLoads.end()) {
513 tilesRhs = itRhs->second;
514 } else {
515 tilesRhs = createTileLoads(rewriter, loc, ops[i].getRhs(), matB, ipType,
516 offset, isVnni);
517 readsToTileLoads.try_emplace(readOpRhs, tilesRhs);
518 }
519
520 auto accTileType = amx::TileType::get({16, 16}, opType);
521
522 Value dp;
523 if (ipType.isBF16() || ipType.isF8E5M2() || ipType.isF8E4M3FN())
524 dp = amx::TileMulFOp::create(rewriter, loc, accTileType, tilesLhs,
525 tilesRhs, accIterArgs[i]);
526
527 if (ipType.isSignlessInteger(8))
528 dp = amx::TileMulIOp::create(rewriter, loc, accTileType, tilesLhs,
529 tilesRhs, accIterArgs[i]);
530
531 accumulators.push_back(dp);
532 }
533 return accumulators;
534}
535
536static SmallVector<Value> createTileZeros(OpBuilder &rewriter, Location loc,
537 Type opType, scf::ForOp outerLoop,
538 int64_t size) {
539 rewriter.setInsertionPoint(outerLoop);
540
541 SmallVector<Value> loopItrArgs;
542 auto zeroTileType = amx::TileType::get({16, 16}, opType);
543
544 for (int i = 0; i < size; i++) {
545 auto zeroTile = amx::TileZeroOp::create(rewriter, loc, zeroTileType);
546 loopItrArgs.push_back(zeroTile);
547 }
548 return loopItrArgs;
549}
550
551static Value getIndxToLoadStoreFromPckBuffer(OpBuilder &rewriter, Location loc,
552 Value ivInnerLoop,
553 Value ivOuterLoop,
554 bool isInnerLoopUBHasOddQuot,
555 bool isInnerLoopUBLarger,
556 bool pack, Value blockStride) {
557
558 Value c2 = arith::ConstantIndexOp::create(rewriter, loc, 2);
559
560 // `blockStride` is the reduction (K) loop step, i.e. the amount by which the
561 // induction variable advances for one K-block. Dividing the induction value
562 // by it yields the K-block index regardless of whether the loop counts
563 // K-elements (step == 16*blockingFactor) or pre-blocked K-tiles (step == 1).
564 Value quotientInnerLoop =
565 arith::DivUIOp::create(rewriter, loc, ivInnerLoop, blockStride);
566 Value remInnerLoop = arith::RemUIOp::create(
567 rewriter, loc, rewriter.getIndexType(), quotientInnerLoop, c2);
568
569 if (!isInnerLoopUBLarger && !pack) {
570 remInnerLoop = arith::RemUIOp::create(
571 rewriter, loc, rewriter.getIndexType(), ivOuterLoop, c2);
572 }
573
574 if (isInnerLoopUBHasOddQuot) {
575 auto remOuterLoop = arith::RemUIOp::create(
576 rewriter, loc, rewriter.getIndexType(), ivOuterLoop, c2);
577 auto remAdd = arith::AddIOp::create(rewriter, loc, rewriter.getIndexType(),
578 remInnerLoop, remOuterLoop);
579 remInnerLoop = arith::RemUIOp::create(rewriter, loc,
580 rewriter.getIndexType(), remAdd, c2);
581 }
582
583 return remInnerLoop;
584}
585
586static scf::ForOp
587createLoops(OpBuilder &rewriter, Location loc, Value lowerBound,
588 Value upperBound, Value step, SmallVector<Value> loopItrArgs,
589 Type ipType, Type opType, unsigned int blockingFactor, bool isVnni,
590 Operation *vectorOpLhs, Operation *vectorOpRhs,
591 vector::ContractionOp contractOp, scf::ForOp outerLoop,
592 scf::ForOp innerLoop, SmallVector<vector::ContractionOp> ops,
593 Value ivOuterLoop, Value packedBuffer, bool pack,
594 arith::ConstantIndexOp innerLoopIndex, bool isInnerLoopUBLarger,
595 bool isInnerLoopUBHasOddQuot) {
596
597 Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
598 Value c1 = arith::ConstantIndexOp::create(rewriter, loc, 1);
599 Value c2 = arith::ConstantIndexOp::create(rewriter, loc, 2);
600
601 int64_t offset = 16 * blockingFactor;
602 if (auto cst = step.getDefiningOp<arith::ConstantIndexOp>())
603 offset = cst.value();
604
605 auto newLoop = scf::ForOp::create(
606 rewriter, loc, lowerBound, upperBound, step, loopItrArgs,
607 [&](OpBuilder &rewriterNewInnerLoop, Location locNewInnerLoop,
608 Value ivNewInnerLoop, ValueRange iterArgsNewInnerLoop) {
609 IRMapping mapping;
610 if (outerLoop)
611 mapping.map(vectorOpLhs->getOperand(
612 getIndexPosition(contractOp.getLhs(), outerLoop) + 1),
613 ivOuterLoop);
614
615 mapping.map(vectorOpLhs->getOperand(
616 getIndexPosition(contractOp.getLhs(), innerLoop) + 1),
617 ivNewInnerLoop);
618 auto lhsClone = rewriterNewInnerLoop.clone(*vectorOpLhs, mapping);
619
620 Value indxToStoreInBuffer = c0;
621 Value indxToLoadFromBuffer = c0;
622 if (!isVnni) {
623 if (outerLoop) {
624 if (innerLoopIndex.value() == 0) {
625 if (pack) {
626 ivNewInnerLoop = c0;
627 ivOuterLoop = arith::AddIOp::create(rewriter, locNewInnerLoop,
628 c1, ivOuterLoop);
629
630 if (!isInnerLoopUBLarger || isInnerLoopUBHasOddQuot) {
631 indxToStoreInBuffer = arith::RemUIOp::create(
632 rewriter, locNewInnerLoop, rewriter.getIndexType(),
633 ivOuterLoop, c2);
634 }
635
636 Value indxToLoadFromMatB = arith::AddIOp::create(
637 rewriter, loc, indxToStoreInBuffer, c1);
638 indxToLoadFromBuffer = arith::RemUIOp::create(
639 rewriter, loc, rewriter.getIndexType(), indxToLoadFromMatB,
640 c2);
641 }
642
643 } else {
645 rewriter, locNewInnerLoop, offset);
646 ivNewInnerLoop = arith::AddIOp::create(rewriter, locNewInnerLoop,
647 nLoadIndx, ivNewInnerLoop);
648 indxToStoreInBuffer = getIndxToLoadStoreFromPckBuffer(
649 rewriter, loc, ivNewInnerLoop, ivOuterLoop,
650 isInnerLoopUBHasOddQuot, isInnerLoopUBLarger, pack, step);
651 Value indxToLoadFromMatB =
652 arith::AddIOp::create(rewriter, loc, indxToStoreInBuffer, c1);
653 indxToLoadFromBuffer =
654 arith::RemUIOp::create(rewriter, loc, rewriter.getIndexType(),
655 indxToLoadFromMatB, c2);
656 }
657 } else {
658 if (pack) {
660 rewriter, locNewInnerLoop, offset);
661 ivNewInnerLoop = arith::AddIOp::create(rewriter, locNewInnerLoop,
662 nLoadIndx, ivNewInnerLoop);
663 Value quotient_K = arith::DivUIOp::create(
664 rewriter, loc, ivNewInnerLoop, nLoadIndx);
665 indxToStoreInBuffer = arith::RemUIOp::create(
666 rewriter, loc, rewriter.getIndexType(), quotient_K, c2);
667
668 Value indxToLoadFromMatB =
669 arith::AddIOp::create(rewriter, loc, indxToStoreInBuffer, c1);
670 indxToLoadFromBuffer =
671 arith::RemUIOp::create(rewriter, loc, rewriter.getIndexType(),
672 indxToLoadFromMatB, c2);
673 }
674 }
675 }
676 IRMapping rhsMapping;
677
678 Value matB;
679 Operation *rhsOp = vectorOpRhs;
680
681 // Clone for the subview type operations
682 if (rhsOp->getNumOperands() > 0) {
683
684 if (outerLoop) {
685 int64_t outerPos = getIndexPosition(contractOp.getRhs(), outerLoop);
686
687 if (outerPos >= 0) {
688 unsigned operandIdx = static_cast<unsigned>(outerPos + 1);
689
690 if (operandIdx < rhsOp->getNumOperands())
691 rhsMapping.map(rhsOp->getOperand(operandIdx), ivOuterLoop);
692 }
693 }
694
695 int64_t innerPos = getIndexPosition(contractOp.getRhs(), innerLoop);
696
697 if (innerPos >= 0) {
698 unsigned operandIdx = static_cast<unsigned>(innerPos + 1);
699
700 if (operandIdx < rhsOp->getNumOperands())
701 rhsMapping.map(rhsOp->getOperand(operandIdx), ivNewInnerLoop);
702 }
703
704 auto rhsClone = rewriterNewInnerLoop.clone(*rhsOp, rhsMapping);
705 matB = rhsClone->getResult(0);
706
707 } else {
708 // The mat B is of kind 'memref.get_global @__constant'
709 matB = rhsOp->getResult(0);
710 }
711
712 if (!isVnni) {
713 if (outerLoop) {
714 if (!pack) {
715 matB = Value();
716 indxToLoadFromBuffer = c0;
717 // Use the real spill-block induction value (== spillInnerLoop)
718 // together with the loop step so the computed ping-pong slot
719 // matches the prefetch store side for any number of register
720 // blocks, including odd counts (e.g. 96 = 3 blocks). Passing a
721 // constant here mis-parities the slot for odd block counts.
722 indxToLoadFromBuffer = getIndxToLoadStoreFromPckBuffer(
723 rewriter, loc, ivNewInnerLoop, ivOuterLoop,
724 isInnerLoopUBHasOddQuot, isInnerLoopUBLarger, pack, step);
725 }
726 } else {
727 if (!pack) {
729 rewriter, locNewInnerLoop, offset);
730 matB = Value();
731 Value quotient_K = arith::DivUIOp::create(
732 rewriter, loc, ivNewInnerLoop, nLoadIndx);
733 indxToLoadFromBuffer = arith::RemUIOp::create(
734 rewriter, loc, rewriter.getIndexType(), quotient_K, c2);
735 }
736 }
737 }
738 // compute tiled dot-product
739 SmallVector<Value> accumulators = createTiledDp(
740 rewriter, locNewInnerLoop, ops, lhsClone->getResult(0), matB,
741 ipType, opType, iterArgsNewInnerLoop, blockingFactor, isVnni,
742 packedBuffer, pack, indxToStoreInBuffer, indxToLoadFromBuffer);
743
744 scf::YieldOp::create(rewriterNewInnerLoop, locNewInnerLoop,
745 accumulators);
746 });
747
748 return newLoop;
749}
750
751// Implements tiled dot-product operation for a vector.contract operation or a
752// sequence of vector.contracts inside the reduction loops.
753//
754// For example:
755// Case 1: register blocked vector.contract with prepacked input
756// ```
757// vector.transfer_read %arg0 {{.}*} : memref<16x32x4xi8>, vector<16x16x4xi8>
758// vector.transfer_read %arg1 {{.}*} : memref<16x32x4xi8>, vector<16x16x4xi8>
759// vector.contract <16x16x4xi8>, <16x16x4xi8> into <16x16xi32>
760// vector.transfer_write arg2 {{.}*} : vector<16x16xi32>, memref<32x32xi32>
761// ```
762// to
763// ```
764// amx.tile_load %arg0 {{.}*} : memref<16x32x4xi8> into !amx.tile<16x64xi8>
765// amx.tile_load %arg1 {{.}*} : memref<16x32x4xi8> into !amx.tile<16x64xi8>
766// amx.tile_muli !amx.tile<16x64xi8> -> !amx.tile<16x16xi32>
767// amx.tile_store %arg2{{.}*} : memref<32x32xi32>, !amx.tile<16x16xi32>
768// ```
769//
770//
771// Case2: vector.contract with register blocked
772//
773// Output IR with online packing (with s/w pipeline advantage):
774// s/w pipeline: load, pack to VNNI, and store the B sub matrix
775// of the 0th batch-reduce and K iteration.
776// scf.for (0 to 31) {
777// - load 0th and 1st vector<32xbf16>, pack into VNNI, store the
778// first shuffle in 0th and 2nd shuffle in 16th index of the
779// buffer.
780// }
781// scf.for (0 to br-2) { batch-reduce loop
782// scf.for (0 to k-2) { K loop
783// - load A matrix
784// - scf.loop for s/w pipeline: load, pack to VNNI, and store the B sub
785// matrix for the next K loop iteration (c) load VNNI pack B matrix of K
786// iteration from the buffer (d) compute the tiled dot-product
787// }
788// Last iteration of the the K Loop (k-1) {
789// - load A matrix
790// - scf.loop for s/w pipeline: load, pack to VNNI, and store the B sub
791// matrix for the next batch-reduce + K loop iteration (c) load VNNI pack B
792// matrix of K iteration from the buffer (d) compute the tiled dot-product
793// }
794// }
795// Last iteration of the batch-reduce loop (br-1) {
796// scf.for (0 to k-2) { K loop
797// - load A matrix
798// - scf.loop for s/w pipeline: load, pack to VNNI, and store the B sub
799// matrix for the next K loop iteration (c) load VNNI pack B matrix of K
800// iteration from the buffer (d) compute the tiled dot-product
801// }
802// Last iteration of the the K Loop (k-1) {
803// - load A matrix
804// - load VNNI pack B matrix of K iteration from the buffer
805// - compute the tiled dot-product
806// }
807// }
808//
809// scf.for (0 to M)
810// scf.for (0 to N)
811// - Load the ith and i+1th acc
812// - Shuffle them as we packed using vpunpack
813// - Load C matrix and do arith.add with the shuffle
814// - Store back into C matrix
815struct VectorContractToAMXDotProduct
816 : public OpRewritePattern<vector::ContractionOp> {
817 using OpRewritePattern<vector::ContractionOp>::OpRewritePattern;
818
819 LogicalResult matchAndRewrite(vector::ContractionOp contractOp,
820 PatternRewriter &rewriter) const override {
821
822 if (contractOp.getKind() != vector::CombiningKind::ADD)
823 return rewriter.notifyMatchFailure(contractOp,
824 "Expects add combining kind.");
825
826 unsigned int blockingFactor =
827 contractOp.getLhsType().getElementType().isBF16() ? 2 : 4;
828 bool isVnni =
829 isInVnniLayout(contractOp.getOperation(),
830 contractOp.getIndexingMapsArray(), blockingFactor);
831
832 VectorType lhsTy = contractOp.getLhsType();
833 if (!lhsTy.getElementType().isBF16() &&
834 !lhsTy.getElementType().isSignlessInteger(8) &&
835 !lhsTy.getElementType().isF8E4M3FN() &&
836 !lhsTy.getElementType().isF8E5M2())
837 return rewriter.notifyMatchFailure(
838 contractOp, "Only BF16/Int8/F8 lowering is supported.");
839
840 if (lhsTy.getElementType() != contractOp.getRhsType().getElementType())
841 return rewriter.notifyMatchFailure(
842 contractOp, "Contraction should have same lhs and rhs type.");
843
844 VectorType accTy = dyn_cast<VectorType>(contractOp.getAccType());
845 if (!accTy)
846 return rewriter.notifyMatchFailure(contractOp, "Wrong accmulator type.");
847
848 if (((lhsTy.getElementType().isBF16() ||
849 lhsTy.getElementType().isF8E4M3FN() ||
850 lhsTy.getElementType().isF8E5M2()) &&
851 !accTy.getElementType().isF32()) ||
852 (lhsTy.getElementType().isSignlessInteger(8) &&
853 !accTy.getElementType().isSignlessInteger(32)))
854 return rewriter.notifyMatchFailure(contractOp,
855 "Only F32 for BF16 or Int32 for Int8 "
856 "accumulation type is supported.");
857
858 Operation *accReadOp =
859 traceToVectorReadLikeParentOperation(contractOp.getAcc());
860
861 Operation *accWrite =
862 traceToVectorWriteLikeUserOperation(contractOp.getResult());
863
864 // Only the contract result's first consumer is needed, not the final
865 // store. This keeps the lowering independent of the epilogue ops (truncf,
866 // bias add, ReLU, ...) that sit between the contraction and the write.
867 Value resultChainEnd = contractionUsersAfterYield(contractOp.getResult());
868
869 if (!accReadOp || !resultChainEnd)
870 return rewriter.notifyMatchFailure(
871 contractOp, "The ACC operand of the vector.contract should be a "
872 "transfer_read or a load. And, the result should have a "
873 "single-use chain to its consumer.");
874
875 bool isAccZeroVectorConstant = isZeroVectorConstant(accReadOp);
876
877 Block *resultBlock = resultChainEnd.user_begin()->getBlock();
878
879 Type ipType = rewriter.getBF16Type();
880 Type opType = rewriter.getF32Type();
881
882 if (lhsTy.getElementType().isSignlessInteger(8)) {
883 ipType = rewriter.getIntegerType(8);
884 opType = rewriter.getIntegerType(32);
885 }
886
887 if (lhsTy.getElementType().isF8E4M3FN())
888 ipType = rewriter.getF8E4M3FNType();
889
890 if (lhsTy.getElementType().isF8E5M2())
891 ipType = rewriter.getF8E5M2Type();
892
893 if (accReadOp->getBlock() == contractOp->getBlock() &&
894 resultBlock != contractOp->getBlock())
895 return rewriter.notifyMatchFailure(
896 contractOp, "The accumulator store is in different block.");
897
898 if (accReadOp->getBlock() != contractOp->getBlock() &&
899 resultBlock == contractOp->getBlock())
900 return rewriter.notifyMatchFailure(
901 contractOp, "The accumulator read is in different block.");
902
903 if (!(isReadSrcMemref(contractOp.getLhs()) &&
904 isReadSrcMemref(contractOp.getRhs())))
905 return rewriter.notifyMatchFailure(
906 contractOp, "The LHS or RHS src is not a MemRef type.");
907
908 if (isVnni && !(isVnniOperandRead(contractOp.getLhs(), blockingFactor) &&
909 isVnniOperandRead(contractOp.getRhs(), blockingFactor)))
910 return rewriter.notifyMatchFailure(
911 contractOp, "The LHS or RHS src does not read whole VNNI groups.");
912
913 unsigned int dimValue = blockingFactor;
914 if (!isVnni)
915 dimValue = 16 * blockingFactor;
916
917 // Case 1: For just one VC rewrite. Where all accumulator read/write
918 // within the same block.
919 if (accReadOp->getBlock() == contractOp->getBlock() &&
920 resultBlock == contractOp->getBlock()) {
921
922 if (!isReadSrcMemref(contractOp.getAcc()))
923 return rewriter.notifyMatchFailure(contractOp,
924 "The ACC src is not a MemRef type.");
925
926 bool collapse = false;
927 if (isVnni)
928 collapse = true;
929
930 LogicalResult validate = validateContractOps(
931 rewriter, contractOp, dimValue, Value(), Value(), false);
932
933 if (failed(validate))
934 return rewriter.notifyMatchFailure(
935 contractOp, "The contract operation doesn't satisfy the operands "
936 "dimensions. M, N, and vnni dims are 16, 16, and 2/4. "
937 "The rest dims should be 1. Op should have one user.");
938
939 Location loc = contractOp.getLoc();
940
941 auto srcIndxLhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
942 contractOp.getLhs(), collapse);
943 if (failed(srcIndxLhs))
944 return rewriter.notifyMatchFailure(contractOp,
945 "Failed to get the LHS src.");
946 auto [srcBuffLhs, indicesLhs] = *srcIndxLhs;
947
948 auto srcIndxRhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
949 contractOp.getRhs(), collapse);
950 if (failed(srcIndxRhs))
951 return rewriter.notifyMatchFailure(contractOp,
952 "Failed to get the RHS src.");
953 auto rhsSrc = *srcIndxRhs;
954 auto srcBuffRhs = rhsSrc.first;
955 auto indicesRhs = rhsSrc.second;
956
957 auto srcIndxAcc = getSrcIndxValue(rewriter, contractOp.getLoc(),
958 contractOp.getAcc(), false);
959 if (failed(srcIndxAcc))
960 return rewriter.notifyMatchFailure(contractOp,
961 "Failed to get the ACC src.");
962 auto [srcBuffAcc, indicesAcc] = *srcIndxAcc;
963
964 Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
965
966 // amx.tile_loads
967 auto tileType = amx::TileType::get({16, (16 * blockingFactor)}, ipType);
968 auto loadLhs = amx::TileLoadOp::create(rewriter, loc, tileType,
969 srcBuffLhs, indicesLhs);
970
971 // Create the subview and then load.
972 amx::TileLoadOp loadRhs;
973 if (!isVnni) {
974 VectorType vecTy;
975 SmallVector<OpFoldResult> indexVals;
976 llvm::TypeSwitch<Operation *>(contractOp.getRhs().getDefiningOp())
977 .Case<TransferReadOp, LoadOp>([&](auto readOp) {
978 indexVals = SmallVector<OpFoldResult>(readOp.getIndices().begin(),
979 readOp.getIndices().end());
980 vecTy = readOp.getType();
981 });
982 auto one = rewriter.getIndexAttr(1);
983 SmallVector<OpFoldResult> strides(indexVals.size(), one);
984 // A rank-reducing read drops leading unit dims. Append them back for
985 // the subview.
986 SmallVector<int64_t> subviewShape(indexVals.size() - vecTy.getRank(),
987 1);
988 llvm::append_range(subviewShape, vecTy.getShape());
989 SmallVector<OpFoldResult> sizes =
990 getAsIndexOpFoldResult(contractOp->getContext(), subviewShape);
991 auto subview = memref::SubViewOp::create(rewriter, loc, srcBuffRhs,
992 indexVals, sizes, strides);
993 auto bufferType = MemRefType::get({16, (16 * blockingFactor)}, ipType);
994 auto packedBuffer = memref::AllocaOp::create(rewriter, loc, bufferType);
995
996 // Create a loop that does online packing.
997 Value step =
998 arith::ConstantIndexOp::create(rewriter, loc, blockingFactor);
999 Value uBound = arith::ConstantIndexOp::create(rewriter, loc,
1000 (blockingFactor * 16));
1001 Value nextLoadIndx =
1002 arith::ConstantIndexOp::create(rewriter, loc, (blockingFactor / 2));
1003 Value nextStoreIndx = arith::ConstantIndexOp::create(
1004 rewriter, loc, 16 * (blockingFactor / 2));
1005
1006 scf::ForOp::create(
1007 rewriter, loc, c0, uBound, step, ValueRange{},
1008 [&](OpBuilder &nestedBuilder, Location loc, Value iv,
1009 ValueRange iterArgs) {
1010 Value i1_load =
1011 arith::AddIOp::create(rewriter, loc, nextLoadIndx, iv);
1012
1013 // The source subview already accounts for the leading unit
1014 // dimensions.
1015 size_t numOffsets = indicesRhs.size();
1016 SmallVector<Value> indices(numOffsets, c0);
1017 indices[numOffsets - 2] = iv;
1018 auto vec1 = vector::LoadOp::create(
1019 rewriter, loc,
1020 VectorType::get(16 * (blockingFactor / 2), ipType), subview,
1021 indices);
1022
1023 indices[numOffsets - 2] = i1_load;
1024 auto vec2 = vector::LoadOp::create(
1025 rewriter, loc,
1026 VectorType::get(16 * (blockingFactor / 2), ipType), subview,
1027 indices);
1028
1029 vector::ShuffleOp writeVal1;
1030 vector::ShuffleOp writeVal2;
1031
1032 if (blockingFactor == 2) {
1033
1034 writeVal1 = vector::ShuffleOp::create(
1035 rewriter, loc, VectorType::get({16}, ipType), vec1, vec2,
1036 ArrayRef<int64_t>{0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21,
1037 6, 22, 7, 23});
1038
1039 writeVal2 = vector::ShuffleOp::create(
1040 rewriter, loc, VectorType::get({16}, ipType), vec1, vec2,
1041 ArrayRef<int64_t>{8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13,
1042 29, 14, 30, 15, 31});
1043 }
1044
1045 if (blockingFactor == 4) {
1046 writeVal1 = vector::ShuffleOp::create(
1047 rewriter, loc, VectorType::get({32}, ipType), vec1, vec2,
1048 ArrayRef<int64_t>{0, 16, 32, 48, 1, 17, 33, 49,
1049 2, 18, 34, 50, 3, 19, 35, 51,
1050 4, 20, 36, 52, 5, 21, 37, 53,
1051 6, 22, 38, 54, 7, 23, 39, 55});
1052
1053 writeVal2 = vector::ShuffleOp::create(
1054 rewriter, loc, VectorType::get({32}, ipType), vec1, vec2,
1055 ArrayRef<int64_t>{8, 24, 40, 56, 9, 25, 41, 57,
1056 10, 26, 42, 58, 11, 27, 43, 59,
1057 12, 28, 44, 60, 13, 29, 45, 61,
1058 14, 30, 46, 62, 15, 31, 47, 63});
1059 }
1060
1061 auto rem = arith::DivUIOp::create(
1062 rewriter, loc, rewriter.getIndexType(), iv, step);
1063
1064 vector::StoreOp::create(rewriter, loc, writeVal1, packedBuffer,
1065 ValueRange{rem, c0});
1066 vector::StoreOp::create(rewriter, loc, writeVal2, packedBuffer,
1067 ValueRange{rem, nextStoreIndx});
1068
1069 scf::YieldOp::create(nestedBuilder, loc);
1070 });
1071 loadRhs = amx::TileLoadOp::create(rewriter, loc, tileType, packedBuffer,
1072 ValueRange{c0, c0});
1073 } else {
1074
1075 loadRhs = amx::TileLoadOp::create(rewriter, loc, tileType, srcBuffRhs,
1076 indicesRhs);
1077 }
1078
1079 auto tileTypeAcc = amx::TileType::get({16, 16}, opType);
1080 auto loadAcc = amx::TileLoadOp::create(rewriter, loc, tileTypeAcc,
1081 srcBuffAcc, indicesAcc);
1082
1083 // Tiled dot-product.
1084 Value dp;
1085 if (ipType.isBF16() || ipType.isF8E5M2() || ipType.isF8E4M3FN())
1086 dp = amx::TileMulFOp::create(rewriter, loc, tileTypeAcc, loadLhs,
1087 loadRhs, loadAcc);
1088
1089 if (ipType.isSignlessInteger(8))
1090 dp = amx::TileMulIOp::create(rewriter, loc, tileTypeAcc, loadLhs,
1091 loadRhs, loadAcc);
1092
1093 auto bufferType = MemRefType::get({16, 16}, opType);
1094 auto resultBuffer = memref::AllocaOp::create(rewriter, loc, bufferType);
1095
1096 amx::TileStoreOp::create(rewriter, loc, resultBuffer, ValueRange{c0, c0},
1097 dp);
1098
1099 auto vectorType = VectorType::get({16, 16}, opType);
1100 int64_t srcRank =
1101 (dyn_cast<ShapedType>(resultBuffer.getType())).getRank();
1102 Value padding = ub::PoisonOp::create(rewriter, loc, opType);
1103 auto map = AffineMap::getMinorIdentityMap(srcRank, vectorType.getRank(),
1104 rewriter.getContext());
1105 SmallVector<bool> inBounds(vectorType.getRank(), true);
1106
1107 Value vecRow = vector::TransferReadOp::create(
1108 rewriter, loc, vectorType, resultBuffer, ValueRange{c0, c0}, padding,
1109 map, inBounds);
1110
1111 Value resultOp = contractionUsersAfterYield(contractOp.getResult());
1112 if (auto vecType = dyn_cast<VectorType>(resultOp.getType()))
1113 vecRow = vector::ShapeCastOp::create(rewriter, loc, vecType, vecRow);
1114
1115 rewriter.replaceAllUsesWith(resultOp, vecRow);
1116 return success();
1117 }
1118
1119 // Case 2: The acc are passed as iter args through the reduction loop.
1120 // We support, reduction loop depth until 2. TODO: Support for n-depth
1121 // reduction loop.
1122 // TODOs: Re-factor 2a and 2b.
1123 SmallVector<scf::ForOp> loopLists;
1124 Operation *current = contractOp;
1125 while (true) {
1126 Operation *parent = current->getParentOfType<scf::ForOp>();
1127
1128 if (!parent) {
1129 // The accumulator initialization can be hoisted above an enclosing
1130 // parallel region (scf.parallel/scf.forall) when the register tile
1131 // matches the problem size and the M/N register loops fold away. In
1132 // that case the reduction loop(s) collected so far are still valid to
1133 // rewrite, so stop climbing instead of bailing out.
1134 if (!loopLists.empty())
1135 break;
1136 return rewriter.notifyMatchFailure(
1137 contractOp,
1138 "Accumulator read and contract op not within scf.for op");
1139 }
1140
1141 loopLists.push_back(dyn_cast<scf::ForOp>(parent));
1142
1143 if (resultBlock == parent->getBlock()) {
1144 break;
1145 }
1146
1147 current = parent;
1148 }
1149
1150 if (loopLists.size() > 2 || loopLists.size() == 0)
1151 return rewriter.notifyMatchFailure(
1152 contractOp, "Rewrite is supported until reduction loop depth of 2.");
1153
1154 if (isAccZeroVectorConstant && !accWrite)
1155 return rewriter.notifyMatchFailure(
1156 contractOp, "The result of the contract is not written to memory.");
1157
1158 auto srcIndxLhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
1159 contractOp.getLhs(), false);
1160 if (failed(srcIndxLhs))
1161 return rewriter.notifyMatchFailure(contractOp,
1162 "Failed to get the LHS src.");
1163 auto [srcBuffLhs, indicesLhs] = *srcIndxLhs;
1164
1165 auto srcIndxRhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
1166 contractOp.getRhs(), false);
1167 if (failed(srcIndxRhs))
1168 return rewriter.notifyMatchFailure(contractOp,
1169 "Failed to get the RHS src.");
1170 auto [srcBuffRhs, indicesRhs] = *srcIndxRhs;
1171 Operation *vectorOpLhs;
1172 llvm::TypeSwitch<Operation *>(contractOp.getLhs().getDefiningOp())
1173 .Case<TransferReadOp, LoadOp>([&](auto readOp) {
1174 vectorOpLhs = readOp.getBase().getDefiningOp();
1175 });
1176
1177 Operation *vectorOpRhs;
1178 llvm::TypeSwitch<Operation *>(contractOp.getRhs().getDefiningOp())
1179 .Case<TransferReadOp, LoadOp>([&](auto readOp) {
1180 vectorOpRhs = readOp.getBase().getDefiningOp();
1181 });
1182
1183 if (!vectorOpLhs || !vectorOpRhs)
1184 return rewriter.notifyMatchFailure(
1185 contractOp, "Failed to find LHS or RHS read source operation");
1186
1187 // Retrive all the contaction operation within the loop.
1188 SmallVector<vector::ContractionOp> ops;
1189 // Accumulators whose initial value passed into the rewritten reduction
1190 // loops is neither read from memory nor zero, e.g. an iter_arg of an
1191 // enclosing loop. Paired with the index of their contraction in `ops`.
1192 SmallVector<std::pair<size_t, Value>> carriedAccs;
1193 for (Operation &op : loopLists[0].getBody()->getOperations()) {
1194
1195 if (auto contract = dyn_cast<vector::ContractionOp>(op)) {
1196 if (isVnni && !(isVnniOperandRead(contract.getLhs(), blockingFactor) &&
1197 isVnniOperandRead(contract.getRhs(), blockingFactor)))
1198 return rewriter.notifyMatchFailure(
1199 contractOp, "The LHS or RHS src of an associated contract "
1200 "operation does not read whole VNNI groups.");
1201
1202 LogicalResult validate = validateContractOps(
1203 rewriter, contract, dimValue, srcBuffLhs, srcBuffRhs, true);
1204
1205 if (failed(validate))
1206 return rewriter.notifyMatchFailure(
1207 contractOp,
1208 "The associated contract operations doesn't satisfy "
1209 "the re-write conditions either the dimensions are "
1210 "wrong or MemRef source are different or many users.");
1211
1212 Operation *contractReadOp =
1214
1215 if (isAccZeroVectorConstant != isZeroVectorConstant(contractReadOp))
1216 return rewriter.notifyMatchFailure(
1217 contractOp, "The input acc should be a zero constant or "
1218 "transfer_read for all contracts");
1219
1220 Value accInit = getLoopNestInit(contract.getAcc(), loopLists);
1221 Operation *accInitOp = accInit.getDefiningOp();
1222 bool carried = !isa_and_present<TransferReadOp, LoadOp>(accInitOp) &&
1223 !isZeroVectorConstant(accInitOp);
1224 if (carried) {
1225 if (loopLists.back().getRegion().isAncestor(
1226 accInit.getParentRegion()))
1227 return rewriter.notifyMatchFailure(
1228 contractOp, "The accumulator should enter the reduction loops "
1229 "through their init args.");
1230 carriedAccs.emplace_back(ops.size(), accInit);
1231 }
1232
1233 ops.push_back(contract);
1234 }
1235 }
1236
1237 // The accumulators read from memory are added back to all the results at
1238 // once, so they can't be mixed with carried ones.
1239 if (!isAccZeroVectorConstant && !carriedAccs.empty() &&
1240 carriedAccs.size() != ops.size())
1241 return rewriter.notifyMatchFailure(
1242 contractOp, "The accumulators read from memory can't be mixed with "
1243 "carried in accumulators.");
1244
1245 if (!isVnni) {
1246 unsigned int pairCount = 0;
1247 for (size_t j = 0; j < ops.size(); j++) {
1248 for (size_t i = j; i < ops.size(); i++) {
1249 if (i != j && validatePairVectorContract(ops[j], ops[i], true, 16))
1250 pairCount = pairCount + 2;
1251 }
1252 }
1253
1254 if (pairCount != ops.size())
1255 return rewriter.notifyMatchFailure(
1256 contractOp, "Coudn't find the pair vector contract ");
1257 }
1258
1259 scf::ForOp innerLoop;
1260 scf::ForOp outerLoop;
1261
1262 scf::ForOp newLoop;
1263 // Case 2a: Reduction loop depth is 2.
1264 if (loopLists.size() == 2) {
1265 outerLoop = loopLists[1];
1266 innerLoop = loopLists[0];
1267
1268 LogicalResult validateOuterLoopStep =
1269 validateLoopStep(rewriter, outerLoop.getStep(), 1);
1270 if (failed(validateOuterLoopStep))
1271 return rewriter.notifyMatchFailure(contractOp, "Invalid loop step.");
1272
1273 int64_t stepValue = 16;
1274 if (!isVnni)
1275 stepValue = stepValue * blockingFactor;
1276 LogicalResult validateInnerLoopStep =
1277 validateLoopStep(rewriter, innerLoop.getStep(), stepValue);
1278 if (failed(validateInnerLoopStep))
1279 return rewriter.notifyMatchFailure(
1280 contractOp, "Invalid loop step. The step should be 32 for BF16 and "
1281 "64 for Int8/F8.");
1282
1283 SmallVector<Value> loopItrArgs = createTileZeros(
1284 rewriter, outerLoop.getLoc(), opType, outerLoop, ops.size());
1285
1286 if (isVnni) {
1287 newLoop = scf::ForOp::create(
1288 rewriter, outerLoop.getLoc(), outerLoop.getLowerBound(),
1289 outerLoop.getUpperBound(), outerLoop.getStep(), loopItrArgs,
1290 [&](OpBuilder &rewriterOuterLoop, Location locOuterLoop,
1291 Value ivOuterLoop, ValueRange iterArgsOuterLoop) {
1292 auto newInnerLoop = createLoops(
1293 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1294 innerLoop.getUpperBound(), innerLoop.getStep(),
1295 iterArgsOuterLoop, ipType, opType, blockingFactor, isVnni,
1296 vectorOpLhs, vectorOpRhs, contractOp, outerLoop, innerLoop,
1297 ops, ivOuterLoop, nullptr, true, nullptr, false, false);
1298
1299 scf::YieldOp::create(rewriterOuterLoop, locOuterLoop,
1300 newInnerLoop.getResults());
1301 });
1302
1303 } else {
1304
1305 bool isInnerLoopUBLarger = false;
1306 bool isInnerLoopUBHasOddQuot = false;
1307
1308 int64_t ubVal = 16 * blockingFactor;
1309 Value ub = innerLoop.getUpperBound();
1310 if (auto constOp = ub.getDefiningOp<arith::ConstantOp>()) {
1311 if (auto intAttr = dyn_cast<IntegerAttr>(constOp.getValue())) {
1312 ubVal = intAttr.getInt();
1313 }
1314 }
1315
1316 isInnerLoopUBLarger = ubVal > 16 * blockingFactor;
1317 isInnerLoopUBHasOddQuot =
1318 (((ubVal / (16 * blockingFactor)) % 2) == 1) && isInnerLoopUBLarger;
1319
1320 rewriter.setInsertionPoint(outerLoop);
1321
1322 auto c0 =
1323 arith::ConstantIndexOp::create(rewriter, outerLoop.getLoc(), 0);
1324 auto c1 =
1325 arith::ConstantIndexOp::create(rewriter, outerLoop.getLoc(), 1);
1326 auto spillLoopBound = arith::ConstantIndexOp::create(
1327 rewriter, outerLoop.getLoc(), 16 * blockingFactor);
1328
1329 Value spillOuterLoop = arith::SubIOp::create(
1330 rewriter, outerLoop.getLoc(), outerLoop.getUpperBound(), c1);
1331 Value spillInnerLoop =
1332 arith::SubIOp::create(rewriter, innerLoop.getLoc(),
1333 innerLoop.getUpperBound(), spillLoopBound);
1334 auto bufferType =
1335 MemRefType::get({2, 32, (blockingFactor * 16)}, ipType);
1336 auto packedBuffer =
1337 memref::AllocaOp::create(rewriter, outerLoop.getLoc(), bufferType);
1338
1339 // First Shuffling outside the reduction loops
1340 IRMapping rhsMapping;
1341 rhsMapping.map(
1342 vectorOpRhs->getOperand(
1343 getIndexPosition(contractOp.getRhs(), outerLoop) + 1),
1344 outerLoop.getLowerBound());
1345 rhsMapping.map(
1346 vectorOpRhs->getOperand(
1347 getIndexPosition(contractOp.getRhs(), innerLoop) + 1),
1348 innerLoop.getLowerBound());
1349 auto rhsClone = rewriter.clone(*vectorOpRhs, rhsMapping);
1350
1351 Value quotient_batch = arith::DivUIOp::create(
1352 rewriter, outerLoop.getLoc(), outerLoop.getLowerBound(),
1353 outerLoop.getStep());
1354 Value quotient_k = arith::DivUIOp::create(rewriter, outerLoop.getLoc(),
1355 innerLoop.getLowerBound(),
1356 innerLoop.getStep());
1357
1358 Value quotient_add = arith::AddIOp::create(rewriter, outerLoop.getLoc(),
1359 quotient_batch, quotient_k);
1360 Value c2 =
1361 arith::ConstantIndexOp::create(rewriter, outerLoop.getLoc(), 2);
1362 Value rem = arith::RemUIOp::create(rewriter, outerLoop.getLoc(),
1363 quotient_add, c2);
1364
1365 performShuffle(rewriter, outerLoop.getLoc(), rhsClone->getResult(0),
1366 ipType, blockingFactor, packedBuffer, rem);
1367
1368 // First Set of Loops
1369 auto newLoopNonSpill = scf::ForOp::create(
1370 rewriter, outerLoop.getLoc(), outerLoop.getLowerBound(),
1371 spillOuterLoop, outerLoop.getStep(), loopItrArgs,
1372 [&](OpBuilder &rewriterOuterLoop, Location locOuterLoop,
1373 Value ivOuterLoop, ValueRange iterArgsOuterLoop) {
1374 auto newInnerLoop1 = createLoops(
1375 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1376 spillInnerLoop, innerLoop.getStep(), iterArgsOuterLoop,
1377 ipType, opType, blockingFactor, isVnni, vectorOpLhs,
1378 vectorOpRhs, contractOp, outerLoop, innerLoop, ops,
1379 ivOuterLoop, packedBuffer, true, spillLoopBound,
1380 isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1381
1382 auto newInnerLoop = createLoops(
1383 rewriter, innerLoop.getLoc(), spillInnerLoop,
1384 innerLoop.getUpperBound(), innerLoop.getStep(),
1385 newInnerLoop1.getResults(), ipType, opType, blockingFactor,
1386 isVnni, vectorOpLhs, vectorOpRhs, contractOp, outerLoop,
1387 innerLoop, ops, ivOuterLoop, packedBuffer, true, c0,
1388 isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1389
1390 scf::YieldOp::create(rewriterOuterLoop, locOuterLoop,
1391 newInnerLoop.getResults());
1392 });
1393
1394 // Last set of Loops
1395 newLoop = scf::ForOp::create(
1396 rewriter, outerLoop.getLoc(), spillOuterLoop,
1397 outerLoop.getUpperBound(), outerLoop.getStep(),
1398 newLoopNonSpill.getResults(),
1399 [&](OpBuilder &rewriterOuterLoop, Location locOuterLoop,
1400 Value ivOuterLoop, ValueRange iterArgsOuterLoop) {
1401 auto newInnerLoop1 = createLoops(
1402 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1403 spillInnerLoop, innerLoop.getStep(), iterArgsOuterLoop,
1404 ipType, opType, blockingFactor, isVnni, vectorOpLhs,
1405 vectorOpRhs, contractOp, outerLoop, innerLoop, ops,
1406 ivOuterLoop, packedBuffer, true, spillLoopBound,
1407 isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1408
1409 auto newInnerLoop = createLoops(
1410 rewriter, innerLoop.getLoc(), spillInnerLoop,
1411 innerLoop.getUpperBound(), innerLoop.getStep(),
1412 newInnerLoop1.getResults(), ipType, opType, blockingFactor,
1413 isVnni, vectorOpLhs, vectorOpRhs, contractOp, outerLoop,
1414 innerLoop, ops, ivOuterLoop, packedBuffer, false, c0,
1415 isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1416
1417 scf::YieldOp::create(rewriterOuterLoop, locOuterLoop,
1418 newInnerLoop.getResults());
1419 });
1420 }
1421 }
1422
1423 // Case 2b: Reduction loop depth is 1.
1424 if (loopLists.size() == 1) {
1425
1426 innerLoop = loopLists[0];
1427 int64_t stepValue = 16;
1428 if (!isVnni)
1429 stepValue = stepValue * blockingFactor;
1430
1431 LogicalResult validateInnerLoopStep =
1432 validateLoopStep(rewriter, innerLoop.getStep(), stepValue);
1433 if (failed(validateInnerLoopStep))
1434 return rewriter.notifyMatchFailure(
1435 contractOp,
1436 "Invalid loop step. The step should be 32 for BF16 and "
1437 "64 for Int8/F8 or 1 if it is rduction loop other than K.");
1438
1439 SmallVector<Value> loopItrArgs = createTileZeros(
1440 rewriter, innerLoop.getLoc(), opType, innerLoop, ops.size());
1441
1442 if (isVnni) {
1443 newLoop = createLoops(
1444 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1445 innerLoop.getUpperBound(), innerLoop.getStep(), loopItrArgs, ipType,
1446 opType, blockingFactor, isVnni, vectorOpLhs, vectorOpRhs,
1447 contractOp, nullptr, innerLoop, ops, nullptr, nullptr, true,
1448 nullptr, false, false);
1449
1450 } else {
1451
1452 bool isInnerLoopUBLarger = false;
1453 bool isInnerLoopUBHasOddQuot = false;
1454
1455 int64_t ubVal = 16 * blockingFactor;
1456 Value ub = innerLoop.getUpperBound();
1457 if (auto constOp = ub.getDefiningOp<arith::ConstantOp>()) {
1458 if (auto intAttr = dyn_cast<IntegerAttr>(constOp.getValue())) {
1459 ubVal = intAttr.getInt();
1460 }
1461 }
1462
1463 isInnerLoopUBLarger = ubVal > 16 * blockingFactor;
1464 isInnerLoopUBHasOddQuot =
1465 (((ubVal / (16 * blockingFactor)) % 2) == 1) && isInnerLoopUBLarger;
1466
1467 rewriter.setInsertionPoint(innerLoop);
1468
1469 auto c0 =
1470 arith::ConstantIndexOp::create(rewriter, innerLoop.getLoc(), 0);
1471 int64_t offset = 16 * blockingFactor;
1472 if (auto cst =
1473 innerLoop.getStep().getDefiningOp<arith::ConstantIndexOp>())
1474 offset = cst.value();
1475
1476 auto spillLoopBound = arith::ConstantIndexOp::create(
1477 rewriter, innerLoop.getLoc(), offset);
1478 Value spillInnerLoop =
1479 arith::SubIOp::create(rewriter, innerLoop.getLoc(),
1480 innerLoop.getUpperBound(), spillLoopBound);
1481
1482 auto bufferType =
1483 MemRefType::get({2, 32, (blockingFactor * 16)}, ipType);
1484 auto packedBuffer =
1485 memref::AllocaOp::create(rewriter, innerLoop.getLoc(), bufferType);
1486
1487 // First Shuffling outside the reduction loops
1488 IRMapping rhsMapping;
1489 rhsMapping.map(
1490 vectorOpRhs->getOperand(
1491 getIndexPosition(contractOp.getRhs(), innerLoop) + 1),
1492 innerLoop.getLowerBound());
1493 auto rhsClone = rewriter.clone(*vectorOpRhs, rhsMapping);
1494
1495 Value quotient_k = arith::DivUIOp::create(rewriter, innerLoop.getLoc(),
1496 innerLoop.getLowerBound(),
1497 innerLoop.getStep());
1498 Value c2 =
1499 arith::ConstantIndexOp::create(rewriter, innerLoop.getLoc(), 2);
1500 Value rem = arith::RemUIOp::create(rewriter, innerLoop.getLoc(),
1501 quotient_k, c2);
1502
1503 performShuffle(rewriter, innerLoop.getLoc(), rhsClone->getResult(0),
1504 ipType, blockingFactor, packedBuffer, rem);
1505
1506 auto newLoopNonSpill = createLoops(
1507 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1508 spillInnerLoop, innerLoop.getStep(), loopItrArgs, ipType, opType,
1509 blockingFactor, isVnni, vectorOpLhs, vectorOpRhs, contractOp,
1510 nullptr, innerLoop, ops, nullptr, packedBuffer, true,
1511 spillLoopBound, isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1512
1513 newLoop = createLoops(rewriter, innerLoop.getLoc(), spillInnerLoop,
1514 innerLoop.getUpperBound(), innerLoop.getStep(),
1515 newLoopNonSpill.getResults(), ipType, opType,
1516 blockingFactor, isVnni, vectorOpLhs, vectorOpRhs,
1517 contractOp, nullptr, innerLoop, ops, nullptr,
1518 packedBuffer, false, c0, isInnerLoopUBLarger,
1519 isInnerLoopUBHasOddQuot);
1520 }
1521
1522 // This helps the final store back to the acc uses the same code for
1523 // the both reduction loop depth 1 or 2.
1524 outerLoop = innerLoop;
1525 }
1526
1527 // Copy the amx tile accumulation results to a MemRef buffer, add the
1528 // initial accumulation value, and store back to the C-Matrix
1529 Location loc = outerLoop.getLoc();
1530 Value srcBuffAcc;
1531 SmallVector<Value> indicesAcc;
1532
1533 if (isAccZeroVectorConstant) {
1534 llvm::TypeSwitch<Operation *>(accWrite)
1535 .Case<vector::TransferWriteOp, vector::StoreOp>([&](auto writeOp) {
1536 srcBuffAcc = writeOp->getOperand(1);
1537
1538 auto indices = writeOp.getIndices();
1539 indicesAcc.reserve(indices.size());
1540
1541 llvm::transform(
1542 indices, std::back_inserter(indicesAcc), [&](OpFoldResult ofr) {
1543 return getValueOrCreateConstantIndexOp(rewriter, loc, ofr);
1544 });
1545 });
1546 } else {
1547 llvm::TypeSwitch<Operation *>(accReadOp).Case<TransferReadOp, LoadOp>(
1548 [&](auto readOp) {
1549 srcBuffAcc = readOp.getOperand(0);
1550
1551 auto indices = readOp.getIndices();
1552 indicesAcc.reserve(indices.size());
1553
1554 llvm::transform(indices, std::back_inserter(indicesAcc),
1555 [&](OpFoldResult ofr) {
1557 rewriter, loc, ofr);
1558 });
1559 });
1560 }
1561
1562 auto outputShapes = cast<MemRefType>(srcBuffAcc.getType()).getShape();
1563 unsigned int M = outputShapes[outputShapes.size() - 2];
1564 unsigned int N = outputShapes[outputShapes.size() - 1];
1565
1566 SmallVector<Value> dps = newLoop.getResults();
1567 auto bufferType = MemRefType::get({M, N}, opType);
1568 auto resultBuffer = memref::AllocaOp::create(rewriter, loc, bufferType);
1569
1570 // Store the amx tiled-dot product output into an MxN memref.
1571 for (unsigned int i = 0, k = 0; i < M; i = i + 16) {
1572 for (unsigned int j = 0; j < N; j = j + 16) {
1573 Value indexOp_i = arith::ConstantIndexOp::create(rewriter, loc, i);
1574 Value indexOp_j = arith::ConstantIndexOp::create(rewriter, loc, j);
1575 amx::TileStoreOp::create(rewriter, loc, resultBuffer,
1576 ValueRange{indexOp_i, indexOp_j}, dps[k]);
1577 k++;
1578 }
1579 }
1580
1581 auto c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
1582 auto c16 = arith::ConstantIndexOp::create(rewriter, loc, 16);
1583 auto one = arith::ConstantIndexOp::create(rewriter, loc, 1);
1584 auto nBound = arith::ConstantIndexOp::create(rewriter, loc, N);
1585
1586 // Carried-in accumulators are added to the results instead.
1587 bool addAccFromMemory = !isAccZeroVectorConstant && carriedAccs.empty();
1588
1589 if (!isVnni || addAccFromMemory) {
1590 // Create a loop that iterates over the MxN memerf, retrives two rows +
1591 // shuffle them, add up the C element values and stores them to temp
1592 // buffer.
1593 scf::ForOp::create(
1594 rewriter, loc, c0, nBound, one, ValueRange{},
1595 [&](OpBuilder &nestedBuilder, Location loc, Value iv,
1596 ValueRange iterArgs) {
1597 auto row = vector::LoadOp::create(rewriter, loc,
1598 VectorType::get(16, opType),
1599 resultBuffer, ValueRange{iv, c0});
1600
1601 auto row2 = vector::LoadOp::create(
1602 rewriter, loc, VectorType::get(16, opType), resultBuffer,
1603 ValueRange{iv, c16});
1604
1605 Value writeVal1 = row;
1606 Value writeVal2 = row2;
1607
1608 if (!isVnni) {
1609 writeVal1 = vector::ShuffleOp::create(
1610 rewriter, loc, VectorType::get(16, opType), row, row2,
1611 ArrayRef<int64_t>{0, 1, 2, 3, 16, 17, 18, 19, 4, 5, 6, 7, 20,
1612 21, 22, 23});
1613
1614 writeVal2 = vector::ShuffleOp::create(
1615 rewriter, loc, VectorType::get(16, opType), row, row2,
1616 ArrayRef<int64_t>{8, 9, 10, 11, 24, 25, 26, 27, 12, 13, 14,
1617 15, 28, 29, 30, 31});
1618 }
1619
1620 if (addAccFromMemory) {
1621 indicesAcc[indicesAcc.size() - 2] = iv;
1622 indicesAcc[indicesAcc.size() - 1] = c0;
1623
1624 Value valueCRow1 = vector::LoadOp::create(
1625 rewriter, loc, VectorType::get(16, opType), srcBuffAcc,
1626 indicesAcc);
1627 indicesAcc[indicesAcc.size() - 1] = c16;
1628
1629 Value valueCRow2 = vector::LoadOp::create(
1630 rewriter, loc, VectorType::get(16, opType), srcBuffAcc,
1631 indicesAcc);
1632
1633 if (ipType.isBF16() || ipType.isF8E5M2() || ipType.isF8E4M3FN()) {
1634 writeVal1 =
1635 arith::AddFOp::create(rewriter, loc, writeVal1, valueCRow1);
1636
1637 writeVal2 =
1638 arith::AddFOp::create(rewriter, loc, writeVal2, valueCRow2);
1639 }
1640
1641 if (ipType.isSignlessInteger(8)) {
1642 writeVal1 =
1643 arith::AddIOp::create(rewriter, loc, writeVal1, valueCRow1);
1644
1645 writeVal2 =
1646 arith::AddIOp::create(rewriter, loc, writeVal2, valueCRow2);
1647 }
1648 }
1649
1650 vector::StoreOp::create(rewriter, loc, writeVal1, resultBuffer,
1651 ValueRange{iv, c0});
1652 vector::StoreOp::create(rewriter, loc, writeVal2, resultBuffer,
1653 ValueRange{iv, c16});
1654
1655 scf::YieldOp::create(nestedBuilder, loc);
1656 });
1657 }
1658
1659 SmallVector<Value> writeResults;
1660 for (unsigned int i = 0; i < M; i = i + 16) {
1661 for (unsigned int j = 0; j < N; j = j + 16) {
1662 Value indexOp_i = arith::ConstantIndexOp::create(rewriter, loc, i);
1663 Value indexOp_j = arith::ConstantIndexOp::create(rewriter, loc, j);
1664
1665 auto vectorType = VectorType::get({16, 16}, opType);
1666
1667 int64_t srcRank =
1668 (dyn_cast<ShapedType>(resultBuffer.getType())).getRank();
1669 Value padding = ub::PoisonOp::create(rewriter, loc, opType);
1670 auto map = AffineMap::getMinorIdentityMap(srcRank, vectorType.getRank(),
1671 rewriter.getContext());
1672 SmallVector<bool> inBounds(vectorType.getRank(), true);
1673
1674 auto vec1 = vector::TransferReadOp::create(
1675 rewriter, loc, vectorType, resultBuffer,
1676 ValueRange{indexOp_i, indexOp_j}, padding, map, inBounds);
1677 writeResults.push_back(vec1);
1678 }
1679 }
1680
1681 for (auto [idx, acc] : carriedAccs) {
1682 Type resultType = writeResults[idx].getType();
1683 if (acc.getType() != resultType)
1684 acc = vector::ShapeCastOp::create(rewriter, loc, resultType, acc);
1685 if (isa<FloatType>(opType))
1686 writeResults[idx] =
1687 arith::AddFOp::create(rewriter, loc, writeResults[idx], acc);
1688 else
1689 writeResults[idx] =
1690 arith::AddIOp::create(rewriter, loc, writeResults[idx], acc);
1691 }
1692
1693 // Replace use of vector.contract with dot-products.
1694 for (size_t i = 0; i < ops.size(); i++) {
1695 vector::ContractionOp contOp = ops[i];
1696 Value vecRow = writeResults[i];
1697
1698 Value resultWriteOp = contractionUsersAfterYield(contOp.getResult());
1699 if (auto vecType = dyn_cast<VectorType>(resultWriteOp.getType()))
1700 vecRow = vector::ShapeCastOp::create(rewriter, loc, vecType,
1701 writeResults[i]);
1702
1703 rewriter.replaceAllUsesWith(resultWriteOp, vecRow);
1704 }
1705
1706 return success();
1707 }
1708};
1709
1710} // namespace
1711
1713 RewritePatternSet &patterns) {
1714 patterns.add<VectorContractToAMXDotProduct>(patterns.getContext());
1715}
return success()
getNumOperands() - 1))) return failure()
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
static void contract(RootOrderingGraph &graph, ArrayRef< Value > cycle, const DenseMap< Value, unsigned > &parentDepths, DenseMap< Value, Value > &actualSource, DenseMap< Value, Value > &actualTarget)
Contracts the specified cycle in the given graph in-place.
static Value collapseInnerDims(PatternRewriter &rewriter, mlir::Location loc, Value input, int64_t firstDimToCollapse)
Creates a memref.collapse_shape collapsing all inner dimensions of the input starting at firstDimToCo...
#define rem(a, b)
static AffineMap getMinorIdentityMap(unsigned dims, unsigned results, MLIRContext *context)
Returns an identity affine map (d0, ..., dn) -> (dp, ..., dn) on the most minor dimensions.
Attributes are known-constant values of operations.
Definition Attributes.h:25
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
FloatType getF32Type()
Definition Builders.cpp:51
FloatType getF8E5M2Type()
Definition Builders.cpp:43
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
FloatType getBF16Type()
Definition Builders.cpp:45
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
FloatType getF8E4M3FNType()
Definition Builders.cpp:41
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
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
This class helps build Operations.
Definition Builders.h:210
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:581
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
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
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:375
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
unsigned getNumOperands()
Definition Operation.h:371
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
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.
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,...
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isF8E5M2() const
Definition Types.cpp:45
bool isSignlessInteger() const
Return true if this is a signless integer type (with the specified width).
Definition Types.cpp:66
bool isF8E4M3FN() const
Definition Types.cpp:44
bool isBF16() const
Definition Types.cpp:37
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
user_iterator user_begin() const
Definition Value.h:216
unsigned getNumUses() const
This method computes the number of uses of this Value.
Definition Value.cpp:52
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
use_iterator use_begin() const
Definition Value.h:184
Specialization of arith.constant op that returns an integer of index type.
Definition Arith.h:93
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
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
mlir::x86::AMXTileType TileType
Definition X86Dialect.h:40
Operation * traceToVectorWriteLikeUserOperation(Value v)
Definition X86Utils.cpp:233
bool isInVnniLayout(Operation *op, llvm::ArrayRef< AffineMap > indexingMaps, std::optional< unsigned > blockingFactor=std::nullopt)
Definition X86Utils.cpp:42
Value contractionUsersAfterYield(Value v)
Definition X86Utils.cpp:149
Operation * traceToVectorReadLikeParentOperation(Value v)
Definition X86Utils.cpp:173
bool validatePairVectorContract(vector::ContractionOp contractOp, vector::ContractionOp pairContOp, bool rhsHasMultipleNonUnitDims, int64_t nonUnitDimValue)
Definition X86Utils.cpp:391
void populateVectorContractToAMXDotProductPatterns(RewritePatternSet &patterns)
Include the generated interface declarations.
OpFoldResult getAsIndexOpFoldResult(MLIRContext *ctx, int64_t val)
Convert int64_t to integer attributes of index type and return them as OpFoldResult.
bool isZeroIntegerOrFloat(OpFoldResult v)
Return "true" if v is an integer/float value/attribute with constant value zero.
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
SmallVector< int64_t, 2 > ReassociationIndices
Definition Utils.h:27
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.