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 OpBuilder::InsertionGuard guard(rewriter);
540 rewriter.setInsertionPoint(outerLoop);
541
542 SmallVector<Value> loopItrArgs;
543 auto zeroTileType = amx::TileType::get({16, 16}, opType);
544
545 for (int i = 0; i < size; i++) {
546 auto zeroTile = amx::TileZeroOp::create(rewriter, loc, zeroTileType);
547 loopItrArgs.push_back(zeroTile);
548 }
549 return loopItrArgs;
550}
551
552// Find memref source and indices of the accumulator tile for an AMX
553// contraction. Returns nullopt if the access is not suitable for AMX tile
554// load/store.
555static std::optional<std::pair<Value, SmallVector<Value>>>
556getAccTileAccess(Operation *op) {
557 if (!op)
558 return std::nullopt;
559
560 Value base;
561 VectorType vecTy;
563 if (auto transfer = dyn_cast<VectorTransferOpInterface>(op)) {
564 if (transfer.getMask() || transfer.hasOutOfBoundsDim() ||
565 !transfer.getPermutationMap().isMinorIdentity())
566 return std::nullopt;
567 base = transfer.getBase();
568 vecTy = transfer.getVectorType();
569 indices = transfer.getIndices();
570 } else if (auto load = dyn_cast<vector::LoadOp>(op)) {
571 base = load.getBase();
572 vecTy = load.getVectorType();
573 indices = load.getIndices();
574 } else if (auto store = dyn_cast<vector::StoreOp>(op)) {
575 base = store.getBase();
576 vecTy = store.getVectorType();
577 indices = store.getIndices();
578 } else {
579 return std::nullopt;
580 }
581
582 auto memrefTy = dyn_cast<MemRefType>(base.getType());
583 ArrayRef<int64_t> shape = vecTy.getShape();
584 if (!memrefTy || memrefTy.getRank() < 2 || !memrefTy.isLastDimUnitStride() ||
585 shape.size() < 2 || shape.end()[-1] != 16 || shape.end()[-2] != 16 ||
586 llvm::any_of(shape.drop_back(2), [](int64_t d) { return d != 1; }))
587 return std::nullopt;
588
589 return std::make_pair(base, SmallVector<Value>(indices));
590}
591
592// Returns the accumulator read and result write for each contraction if the
593// accumulator can be updated in place through direct AMX tile accesses.
594// If writeOnly is true, only the result writes are considered, and the read
595// accesses are ignored.
596static std::pair<std::optional<SmallVector<Operation *>>,
597 std::optional<SmallVector<Operation *>>>
598getInPlaceAccAccesses(ArrayRef<vector::ContractionOp> ops, bool writeOnly) {
599 SmallVector<Operation *> reads, writes;
600 for (vector::ContractionOp op : ops) {
602 if (!result || !result.hasOneUse())
603 return {std::nullopt, std::nullopt};
604 Operation *write = *result.user_begin();
605 if (!isa<vector::TransferWriteOp, vector::StoreOp>(write) ||
606 write->getOperand(0) != result || !getAccTileAccess(write))
607 return {std::nullopt, std::nullopt};
608 writes.push_back(write);
609
610 if (writeOnly)
611 continue;
612
614 if (!read || !getAccTileAccess(read))
615 return {std::nullopt, std::nullopt};
616 reads.push_back(read);
617 }
618
619 if (writeOnly)
620 return {std::nullopt, writes};
621 return std::make_pair(reads, writes);
622}
623
624// Loads the initial accumulator tiles before the given loop.
625static SmallVector<Value> createAccTileLoads(OpBuilder &rewriter, Location loc,
626 Type opType, scf::ForOp loop,
627 ArrayRef<Operation *> reads) {
628 OpBuilder::InsertionGuard guard(rewriter);
629 rewriter.setInsertionPoint(loop);
630 auto tileType = amx::TileType::get({16, 16}, opType);
631 SmallVector<Value> tiles;
632 for (Operation *read : reads) {
633 auto [base, indices] = *getAccTileAccess(read);
634 tiles.push_back(
635 amx::TileLoadOp::create(rewriter, loc, tileType, base, indices));
636 }
637 return tiles;
638}
639
640static Value getIndxToLoadStoreFromPckBuffer(OpBuilder &rewriter, Location loc,
641 Value ivInnerLoop,
642 Value ivOuterLoop,
643 bool isInnerLoopUBHasOddQuot,
644 bool isInnerLoopUBLarger,
645 bool pack, Value blockStride) {
646
647 Value c2 = arith::ConstantIndexOp::create(rewriter, loc, 2);
648
649 // `blockStride` is the reduction (K) loop step, i.e. the amount by which the
650 // induction variable advances for one K-block. Dividing the induction value
651 // by it yields the K-block index regardless of whether the loop counts
652 // K-elements (step == 16*blockingFactor) or pre-blocked K-tiles (step == 1).
653 Value quotientInnerLoop =
654 arith::DivUIOp::create(rewriter, loc, ivInnerLoop, blockStride);
655 Value remInnerLoop = arith::RemUIOp::create(
656 rewriter, loc, rewriter.getIndexType(), quotientInnerLoop, c2);
657
658 if (!isInnerLoopUBLarger && !pack) {
659 remInnerLoop = arith::RemUIOp::create(
660 rewriter, loc, rewriter.getIndexType(), ivOuterLoop, c2);
661 }
662
663 if (isInnerLoopUBHasOddQuot) {
664 auto remOuterLoop = arith::RemUIOp::create(
665 rewriter, loc, rewriter.getIndexType(), ivOuterLoop, c2);
666 auto remAdd = arith::AddIOp::create(rewriter, loc, rewriter.getIndexType(),
667 remInnerLoop, remOuterLoop);
668 remInnerLoop = arith::RemUIOp::create(rewriter, loc,
669 rewriter.getIndexType(), remAdd, c2);
670 }
671
672 return remInnerLoop;
673}
674
675static scf::ForOp
676createLoops(OpBuilder &rewriter, Location loc, Value lowerBound,
677 Value upperBound, Value step, SmallVector<Value> loopItrArgs,
678 Type ipType, Type opType, unsigned int blockingFactor, bool isVnni,
679 Operation *vectorOpLhs, Operation *vectorOpRhs,
680 vector::ContractionOp contractOp, scf::ForOp outerLoop,
681 scf::ForOp innerLoop, SmallVector<vector::ContractionOp> ops,
682 Value ivOuterLoop, Value packedBuffer, bool pack,
683 arith::ConstantIndexOp innerLoopIndex, bool isInnerLoopUBLarger,
684 bool isInnerLoopUBHasOddQuot) {
685
686 Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
687 Value c1 = arith::ConstantIndexOp::create(rewriter, loc, 1);
688 Value c2 = arith::ConstantIndexOp::create(rewriter, loc, 2);
689
690 int64_t offset = 16 * blockingFactor;
691 if (auto cst = step.getDefiningOp<arith::ConstantIndexOp>())
692 offset = cst.value();
693
694 auto newLoop = scf::ForOp::create(
695 rewriter, loc, lowerBound, upperBound, step, loopItrArgs,
696 [&](OpBuilder &rewriterNewInnerLoop, Location locNewInnerLoop,
697 Value ivNewInnerLoop, ValueRange iterArgsNewInnerLoop) {
698 IRMapping mapping;
699 if (outerLoop)
700 mapping.map(vectorOpLhs->getOperand(
701 getIndexPosition(contractOp.getLhs(), outerLoop) + 1),
702 ivOuterLoop);
703
704 mapping.map(vectorOpLhs->getOperand(
705 getIndexPosition(contractOp.getLhs(), innerLoop) + 1),
706 ivNewInnerLoop);
707 auto lhsClone = rewriterNewInnerLoop.clone(*vectorOpLhs, mapping);
708
709 Value indxToStoreInBuffer = c0;
710 Value indxToLoadFromBuffer = c0;
711 if (!isVnni) {
712 if (outerLoop) {
713 if (innerLoopIndex.value() == 0) {
714 if (pack) {
715 ivNewInnerLoop = c0;
716 ivOuterLoop = arith::AddIOp::create(rewriter, locNewInnerLoop,
717 c1, ivOuterLoop);
718
719 if (!isInnerLoopUBLarger || isInnerLoopUBHasOddQuot) {
720 indxToStoreInBuffer = arith::RemUIOp::create(
721 rewriter, locNewInnerLoop, rewriter.getIndexType(),
722 ivOuterLoop, c2);
723 }
724
725 Value indxToLoadFromMatB = arith::AddIOp::create(
726 rewriter, loc, indxToStoreInBuffer, c1);
727 indxToLoadFromBuffer = arith::RemUIOp::create(
728 rewriter, loc, rewriter.getIndexType(), indxToLoadFromMatB,
729 c2);
730 }
731
732 } else {
734 rewriter, locNewInnerLoop, offset);
735 ivNewInnerLoop = arith::AddIOp::create(rewriter, locNewInnerLoop,
736 nLoadIndx, ivNewInnerLoop);
737 indxToStoreInBuffer = getIndxToLoadStoreFromPckBuffer(
738 rewriter, loc, ivNewInnerLoop, ivOuterLoop,
739 isInnerLoopUBHasOddQuot, isInnerLoopUBLarger, pack, step);
740 Value indxToLoadFromMatB =
741 arith::AddIOp::create(rewriter, loc, indxToStoreInBuffer, c1);
742 indxToLoadFromBuffer =
743 arith::RemUIOp::create(rewriter, loc, rewriter.getIndexType(),
744 indxToLoadFromMatB, c2);
745 }
746 } else {
747 if (pack) {
749 rewriter, locNewInnerLoop, offset);
750 ivNewInnerLoop = arith::AddIOp::create(rewriter, locNewInnerLoop,
751 nLoadIndx, ivNewInnerLoop);
752 Value quotient_K = arith::DivUIOp::create(
753 rewriter, loc, ivNewInnerLoop, nLoadIndx);
754 indxToStoreInBuffer = arith::RemUIOp::create(
755 rewriter, loc, rewriter.getIndexType(), quotient_K, c2);
756
757 Value indxToLoadFromMatB =
758 arith::AddIOp::create(rewriter, loc, indxToStoreInBuffer, c1);
759 indxToLoadFromBuffer =
760 arith::RemUIOp::create(rewriter, loc, rewriter.getIndexType(),
761 indxToLoadFromMatB, c2);
762 }
763 }
764 }
765 IRMapping rhsMapping;
766
767 Value matB;
768 Operation *rhsOp = vectorOpRhs;
769
770 // Clone for the subview type operations
771 if (rhsOp->getNumOperands() > 0) {
772
773 if (outerLoop) {
774 int64_t outerPos = getIndexPosition(contractOp.getRhs(), outerLoop);
775
776 if (outerPos >= 0) {
777 unsigned operandIdx = static_cast<unsigned>(outerPos + 1);
778
779 if (operandIdx < rhsOp->getNumOperands())
780 rhsMapping.map(rhsOp->getOperand(operandIdx), ivOuterLoop);
781 }
782 }
783
784 int64_t innerPos = getIndexPosition(contractOp.getRhs(), innerLoop);
785
786 if (innerPos >= 0) {
787 unsigned operandIdx = static_cast<unsigned>(innerPos + 1);
788
789 if (operandIdx < rhsOp->getNumOperands())
790 rhsMapping.map(rhsOp->getOperand(operandIdx), ivNewInnerLoop);
791 }
792
793 auto rhsClone = rewriterNewInnerLoop.clone(*rhsOp, rhsMapping);
794 matB = rhsClone->getResult(0);
795
796 } else {
797 // The mat B is of kind 'memref.get_global @__constant'
798 matB = rhsOp->getResult(0);
799 }
800
801 if (!isVnni) {
802 if (outerLoop) {
803 if (!pack) {
804 matB = Value();
805 indxToLoadFromBuffer = c0;
806 // Use the real spill-block induction value (== spillInnerLoop)
807 // together with the loop step so the computed ping-pong slot
808 // matches the prefetch store side for any number of register
809 // blocks, including odd counts (e.g. 96 = 3 blocks). Passing a
810 // constant here mis-parities the slot for odd block counts.
811 indxToLoadFromBuffer = getIndxToLoadStoreFromPckBuffer(
812 rewriter, loc, ivNewInnerLoop, ivOuterLoop,
813 isInnerLoopUBHasOddQuot, isInnerLoopUBLarger, pack, step);
814 }
815 } else {
816 if (!pack) {
818 rewriter, locNewInnerLoop, offset);
819 matB = Value();
820 Value quotient_K = arith::DivUIOp::create(
821 rewriter, loc, ivNewInnerLoop, nLoadIndx);
822 indxToLoadFromBuffer = arith::RemUIOp::create(
823 rewriter, loc, rewriter.getIndexType(), quotient_K, c2);
824 }
825 }
826 }
827 // compute tiled dot-product
828 SmallVector<Value> accumulators = createTiledDp(
829 rewriter, locNewInnerLoop, ops, lhsClone->getResult(0), matB,
830 ipType, opType, iterArgsNewInnerLoop, blockingFactor, isVnni,
831 packedBuffer, pack, indxToStoreInBuffer, indxToLoadFromBuffer);
832
833 scf::YieldOp::create(rewriterNewInnerLoop, locNewInnerLoop,
834 accumulators);
835 });
836
837 return newLoop;
838}
839
840// Implements tiled dot-product operation for a vector.contract operation or a
841// sequence of vector.contracts inside the reduction loops.
842//
843// For example:
844// Case 1: register blocked vector.contract with prepacked input
845// ```
846// vector.transfer_read %arg0 {{.}*} : memref<16x32x4xi8>, vector<16x16x4xi8>
847// vector.transfer_read %arg1 {{.}*} : memref<16x32x4xi8>, vector<16x16x4xi8>
848// vector.contract <16x16x4xi8>, <16x16x4xi8> into <16x16xi32>
849// vector.transfer_write arg2 {{.}*} : vector<16x16xi32>, memref<32x32xi32>
850// ```
851// to
852// ```
853// amx.tile_load %arg0 {{.}*} : memref<16x32x4xi8> into !amx.tile<16x64xi8>
854// amx.tile_load %arg1 {{.}*} : memref<16x32x4xi8> into !amx.tile<16x64xi8>
855// amx.tile_muli !amx.tile<16x64xi8> -> !amx.tile<16x16xi32>
856// amx.tile_store %arg2{{.}*} : memref<32x32xi32>, !amx.tile<16x16xi32>
857// ```
858//
859//
860// Case2: vector.contract with register blocked
861//
862// Output IR with online packing (with s/w pipeline advantage):
863// s/w pipeline: load, pack to VNNI, and store the B sub matrix
864// of the 0th batch-reduce and K iteration.
865// scf.for (0 to 31) {
866// - load 0th and 1st vector<32xbf16>, pack into VNNI, store the
867// first shuffle in 0th and 2nd shuffle in 16th index of the
868// buffer.
869// }
870// scf.for (0 to br-2) { batch-reduce loop
871// scf.for (0 to k-2) { K loop
872// - load A matrix
873// - scf.loop for s/w pipeline: load, pack to VNNI, and store the B sub
874// matrix for the next K loop iteration (c) load VNNI pack B matrix of K
875// iteration from the buffer (d) compute the tiled dot-product
876// }
877// Last iteration of the the K Loop (k-1) {
878// - load A matrix
879// - scf.loop for s/w pipeline: load, pack to VNNI, and store the B sub
880// matrix for the next batch-reduce + K loop iteration (c) load VNNI pack B
881// matrix of K iteration from the buffer (d) compute the tiled dot-product
882// }
883// }
884// Last iteration of the batch-reduce loop (br-1) {
885// scf.for (0 to k-2) { K loop
886// - load A matrix
887// - scf.loop for s/w pipeline: load, pack to VNNI, and store the B sub
888// matrix for the next K loop iteration (c) load VNNI pack B matrix of K
889// iteration from the buffer (d) compute the tiled dot-product
890// }
891// Last iteration of the the K Loop (k-1) {
892// - load A matrix
893// - load VNNI pack B matrix of K iteration from the buffer
894// - compute the tiled dot-product
895// }
896// }
897//
898// scf.for (0 to M)
899// scf.for (0 to N)
900// - Load the ith and i+1th acc
901// - Shuffle them as we packed using vpunpack
902// - Load C matrix and do arith.add with the shuffle
903// - Store back into C matrix
904struct VectorContractToAMXDotProduct
905 : public OpRewritePattern<vector::ContractionOp> {
906 using OpRewritePattern<vector::ContractionOp>::OpRewritePattern;
907
908 LogicalResult matchAndRewrite(vector::ContractionOp contractOp,
909 PatternRewriter &rewriter) const override {
910
911 if (contractOp.getKind() != vector::CombiningKind::ADD)
912 return rewriter.notifyMatchFailure(contractOp,
913 "Expects add combining kind.");
914
915 unsigned int blockingFactor =
916 contractOp.getLhsType().getElementType().isBF16() ? 2 : 4;
917 bool isVnni =
918 isInVnniLayout(contractOp.getOperation(),
919 contractOp.getIndexingMapsArray(), blockingFactor);
920
921 VectorType lhsTy = contractOp.getLhsType();
922 if (!lhsTy.getElementType().isBF16() &&
923 !lhsTy.getElementType().isSignlessInteger(8) &&
924 !lhsTy.getElementType().isF8E4M3FN() &&
925 !lhsTy.getElementType().isF8E5M2())
926 return rewriter.notifyMatchFailure(
927 contractOp, "Only BF16/Int8/F8 lowering is supported.");
928
929 if (lhsTy.getElementType() != contractOp.getRhsType().getElementType())
930 return rewriter.notifyMatchFailure(
931 contractOp, "Contraction should have same lhs and rhs type.");
932
933 VectorType accTy = dyn_cast<VectorType>(contractOp.getAccType());
934 if (!accTy)
935 return rewriter.notifyMatchFailure(contractOp, "Wrong accmulator type.");
936
937 if (((lhsTy.getElementType().isBF16() ||
938 lhsTy.getElementType().isF8E4M3FN() ||
939 lhsTy.getElementType().isF8E5M2()) &&
940 !accTy.getElementType().isF32()) ||
941 (lhsTy.getElementType().isSignlessInteger(8) &&
942 !accTy.getElementType().isSignlessInteger(32)))
943 return rewriter.notifyMatchFailure(contractOp,
944 "Only F32 for BF16 or Int32 for Int8 "
945 "accumulation type is supported.");
946
947 Operation *accReadOp =
948 traceToVectorReadLikeParentOperation(contractOp.getAcc());
949
950 // Only the contract result's first consumer is needed, not the final
951 // store. This keeps the lowering independent of the epilogue ops (truncf,
952 // bias add, ReLU, ...) that sit between the contraction and the write.
953 Value resultChainEnd = contractionUsersAfterYield(contractOp.getResult());
954
955 if (!accReadOp || !resultChainEnd)
956 return rewriter.notifyMatchFailure(
957 contractOp, "The ACC operand of the vector.contract should be a "
958 "transfer_read or a load. And, the result should have a "
959 "single-use chain to its consumer.");
960
961 bool isAccZeroVectorConstant = isZeroVectorConstant(accReadOp);
962
963 Block *resultBlock = resultChainEnd.user_begin()->getBlock();
964
965 Type ipType = rewriter.getBF16Type();
966 Type opType = rewriter.getF32Type();
967
968 if (lhsTy.getElementType().isSignlessInteger(8)) {
969 ipType = rewriter.getIntegerType(8);
970 opType = rewriter.getIntegerType(32);
971 }
972
973 if (lhsTy.getElementType().isF8E4M3FN())
974 ipType = rewriter.getF8E4M3FNType();
975
976 if (lhsTy.getElementType().isF8E5M2())
977 ipType = rewriter.getF8E5M2Type();
978
979 if (accReadOp->getBlock() == contractOp->getBlock() &&
980 resultBlock != contractOp->getBlock())
981 return rewriter.notifyMatchFailure(
982 contractOp, "The accumulator store is in different block.");
983
984 if (accReadOp->getBlock() != contractOp->getBlock() &&
985 resultBlock == contractOp->getBlock())
986 return rewriter.notifyMatchFailure(
987 contractOp, "The accumulator read is in different block.");
988
989 if (!(isReadSrcMemref(contractOp.getLhs()) &&
990 isReadSrcMemref(contractOp.getRhs())))
991 return rewriter.notifyMatchFailure(
992 contractOp, "The LHS or RHS src is not a MemRef type.");
993
994 if (isVnni && !(isVnniOperandRead(contractOp.getLhs(), blockingFactor) &&
995 isVnniOperandRead(contractOp.getRhs(), blockingFactor)))
996 return rewriter.notifyMatchFailure(
997 contractOp, "The LHS or RHS src does not read whole VNNI groups.");
998
999 unsigned int dimValue = blockingFactor;
1000 if (!isVnni)
1001 dimValue = 16 * blockingFactor;
1002
1003 // Case 1: For just one VC rewrite. Where all accumulator read/write
1004 // within the same block.
1005 if (accReadOp->getBlock() == contractOp->getBlock() &&
1006 resultBlock == contractOp->getBlock()) {
1007
1008 if (!isReadSrcMemref(contractOp.getAcc()))
1009 return rewriter.notifyMatchFailure(contractOp,
1010 "The ACC src is not a MemRef type.");
1011
1012 bool collapse = false;
1013 if (isVnni)
1014 collapse = true;
1015
1016 LogicalResult validate = validateContractOps(
1017 rewriter, contractOp, dimValue, Value(), Value(), false);
1018
1019 if (failed(validate))
1020 return rewriter.notifyMatchFailure(
1021 contractOp, "The contract operation doesn't satisfy the operands "
1022 "dimensions. M, N, and vnni dims are 16, 16, and 2/4. "
1023 "The rest dims should be 1. Op should have one user.");
1024
1025 Location loc = contractOp.getLoc();
1026
1027 auto srcIndxLhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
1028 contractOp.getLhs(), collapse);
1029 if (failed(srcIndxLhs))
1030 return rewriter.notifyMatchFailure(contractOp,
1031 "Failed to get the LHS src.");
1032 auto [srcBuffLhs, indicesLhs] = *srcIndxLhs;
1033
1034 auto srcIndxRhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
1035 contractOp.getRhs(), collapse);
1036 if (failed(srcIndxRhs))
1037 return rewriter.notifyMatchFailure(contractOp,
1038 "Failed to get the RHS src.");
1039 auto rhsSrc = *srcIndxRhs;
1040 auto srcBuffRhs = rhsSrc.first;
1041 auto indicesRhs = rhsSrc.second;
1042
1043 auto srcIndxAcc = getSrcIndxValue(rewriter, contractOp.getLoc(),
1044 contractOp.getAcc(), false);
1045 if (failed(srcIndxAcc))
1046 return rewriter.notifyMatchFailure(contractOp,
1047 "Failed to get the ACC src.");
1048 auto [srcBuffAcc, indicesAcc] = *srcIndxAcc;
1049
1050 Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
1051
1052 // amx.tile_loads
1053 auto tileType = amx::TileType::get({16, (16 * blockingFactor)}, ipType);
1054 auto loadLhs = amx::TileLoadOp::create(rewriter, loc, tileType,
1055 srcBuffLhs, indicesLhs);
1056
1057 // Create the subview and then load.
1058 amx::TileLoadOp loadRhs;
1059 if (!isVnni) {
1060 VectorType vecTy;
1061 SmallVector<OpFoldResult> indexVals;
1062 llvm::TypeSwitch<Operation *>(contractOp.getRhs().getDefiningOp())
1063 .Case<TransferReadOp, LoadOp>([&](auto readOp) {
1064 indexVals = SmallVector<OpFoldResult>(readOp.getIndices().begin(),
1065 readOp.getIndices().end());
1066 vecTy = readOp.getType();
1067 });
1068 auto one = rewriter.getIndexAttr(1);
1069 SmallVector<OpFoldResult> strides(indexVals.size(), one);
1070 // A rank-reducing read drops leading unit dims. Append them back for
1071 // the subview.
1072 SmallVector<int64_t> subviewShape(indexVals.size() - vecTy.getRank(),
1073 1);
1074 llvm::append_range(subviewShape, vecTy.getShape());
1075 SmallVector<OpFoldResult> sizes =
1076 getAsIndexOpFoldResult(contractOp->getContext(), subviewShape);
1077 auto subview = memref::SubViewOp::create(rewriter, loc, srcBuffRhs,
1078 indexVals, sizes, strides);
1079 auto bufferType = MemRefType::get({16, (16 * blockingFactor)}, ipType);
1080 auto packedBuffer = memref::AllocaOp::create(rewriter, loc, bufferType);
1081
1082 // Create a loop that does online packing.
1083 Value step =
1084 arith::ConstantIndexOp::create(rewriter, loc, blockingFactor);
1085 Value uBound = arith::ConstantIndexOp::create(rewriter, loc,
1086 (blockingFactor * 16));
1087 Value nextLoadIndx =
1088 arith::ConstantIndexOp::create(rewriter, loc, (blockingFactor / 2));
1089 Value nextStoreIndx = arith::ConstantIndexOp::create(
1090 rewriter, loc, 16 * (blockingFactor / 2));
1091
1092 scf::ForOp::create(
1093 rewriter, loc, c0, uBound, step, ValueRange{},
1094 [&](OpBuilder &nestedBuilder, Location loc, Value iv,
1095 ValueRange iterArgs) {
1096 Value i1_load =
1097 arith::AddIOp::create(rewriter, loc, nextLoadIndx, iv);
1098
1099 // The source subview already accounts for the leading unit
1100 // dimensions.
1101 size_t numOffsets = indicesRhs.size();
1102 SmallVector<Value> indices(numOffsets, c0);
1103 indices[numOffsets - 2] = iv;
1104 auto vec1 = vector::LoadOp::create(
1105 rewriter, loc,
1106 VectorType::get(16 * (blockingFactor / 2), ipType), subview,
1107 indices);
1108
1109 indices[numOffsets - 2] = i1_load;
1110 auto vec2 = vector::LoadOp::create(
1111 rewriter, loc,
1112 VectorType::get(16 * (blockingFactor / 2), ipType), subview,
1113 indices);
1114
1115 vector::ShuffleOp writeVal1;
1116 vector::ShuffleOp writeVal2;
1117
1118 if (blockingFactor == 2) {
1119
1120 writeVal1 = vector::ShuffleOp::create(
1121 rewriter, loc, VectorType::get({16}, ipType), vec1, vec2,
1122 ArrayRef<int64_t>{0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21,
1123 6, 22, 7, 23});
1124
1125 writeVal2 = vector::ShuffleOp::create(
1126 rewriter, loc, VectorType::get({16}, ipType), vec1, vec2,
1127 ArrayRef<int64_t>{8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13,
1128 29, 14, 30, 15, 31});
1129 }
1130
1131 if (blockingFactor == 4) {
1132 writeVal1 = vector::ShuffleOp::create(
1133 rewriter, loc, VectorType::get({32}, ipType), vec1, vec2,
1134 ArrayRef<int64_t>{0, 16, 32, 48, 1, 17, 33, 49,
1135 2, 18, 34, 50, 3, 19, 35, 51,
1136 4, 20, 36, 52, 5, 21, 37, 53,
1137 6, 22, 38, 54, 7, 23, 39, 55});
1138
1139 writeVal2 = vector::ShuffleOp::create(
1140 rewriter, loc, VectorType::get({32}, ipType), vec1, vec2,
1141 ArrayRef<int64_t>{8, 24, 40, 56, 9, 25, 41, 57,
1142 10, 26, 42, 58, 11, 27, 43, 59,
1143 12, 28, 44, 60, 13, 29, 45, 61,
1144 14, 30, 46, 62, 15, 31, 47, 63});
1145 }
1146
1147 auto rem = arith::DivUIOp::create(
1148 rewriter, loc, rewriter.getIndexType(), iv, step);
1149
1150 vector::StoreOp::create(rewriter, loc, writeVal1, packedBuffer,
1151 ValueRange{rem, c0});
1152 vector::StoreOp::create(rewriter, loc, writeVal2, packedBuffer,
1153 ValueRange{rem, nextStoreIndx});
1154
1155 scf::YieldOp::create(nestedBuilder, loc);
1156 });
1157 loadRhs = amx::TileLoadOp::create(rewriter, loc, tileType, packedBuffer,
1158 ValueRange{c0, c0});
1159 } else {
1160
1161 loadRhs = amx::TileLoadOp::create(rewriter, loc, tileType, srcBuffRhs,
1162 indicesRhs);
1163 }
1164
1165 auto tileTypeAcc = amx::TileType::get({16, 16}, opType);
1166 auto loadAcc = amx::TileLoadOp::create(rewriter, loc, tileTypeAcc,
1167 srcBuffAcc, indicesAcc);
1168
1169 // Tiled dot-product.
1170 Value dp;
1171 if (ipType.isBF16() || ipType.isF8E5M2() || ipType.isF8E4M3FN())
1172 dp = amx::TileMulFOp::create(rewriter, loc, tileTypeAcc, loadLhs,
1173 loadRhs, loadAcc);
1174
1175 if (ipType.isSignlessInteger(8))
1176 dp = amx::TileMulIOp::create(rewriter, loc, tileTypeAcc, loadLhs,
1177 loadRhs, loadAcc);
1178
1179 auto bufferType = MemRefType::get({16, 16}, opType);
1180 auto resultBuffer = memref::AllocaOp::create(rewriter, loc, bufferType);
1181
1182 amx::TileStoreOp::create(rewriter, loc, resultBuffer, ValueRange{c0, c0},
1183 dp);
1184
1185 auto vectorType = VectorType::get({16, 16}, opType);
1186 int64_t srcRank =
1187 (dyn_cast<ShapedType>(resultBuffer.getType())).getRank();
1188 Value padding = ub::PoisonOp::create(rewriter, loc, opType);
1189 auto map = AffineMap::getMinorIdentityMap(srcRank, vectorType.getRank(),
1190 rewriter.getContext());
1191 SmallVector<bool> inBounds(vectorType.getRank(), true);
1192
1193 Value vecRow = vector::TransferReadOp::create(
1194 rewriter, loc, vectorType, resultBuffer, ValueRange{c0, c0}, padding,
1195 map, inBounds);
1196
1197 Value resultOp = contractionUsersAfterYield(contractOp.getResult());
1198 if (auto vecType = dyn_cast<VectorType>(resultOp.getType()))
1199 vecRow = vector::ShapeCastOp::create(rewriter, loc, vecType, vecRow);
1200
1201 rewriter.replaceAllUsesWith(resultOp, vecRow);
1202 return success();
1203 }
1204
1205 // Case 2: The acc are passed as iter args through the reduction loop.
1206 // We support, reduction loop depth until 2. TODO: Support for n-depth
1207 // reduction loop.
1208 // TODOs: Re-factor 2a and 2b.
1209 SmallVector<scf::ForOp> loopLists;
1210 Operation *current = contractOp;
1211 while (true) {
1212 Operation *parent = current->getParentOfType<scf::ForOp>();
1213
1214 if (!parent) {
1215 // The accumulator initialization can be hoisted above an enclosing
1216 // parallel region (scf.parallel/scf.forall) when the register tile
1217 // matches the problem size and the M/N register loops fold away. In
1218 // that case the reduction loop(s) collected so far are still valid to
1219 // rewrite, so stop climbing instead of bailing out.
1220 if (!loopLists.empty())
1221 break;
1222 return rewriter.notifyMatchFailure(
1223 contractOp,
1224 "Accumulator read and contract op not within scf.for op");
1225 }
1226
1227 loopLists.push_back(dyn_cast<scf::ForOp>(parent));
1228
1229 if (resultBlock == parent->getBlock()) {
1230 break;
1231 }
1232
1233 current = parent;
1234 }
1235
1236 if (loopLists.size() > 2 || loopLists.size() == 0)
1237 return rewriter.notifyMatchFailure(
1238 contractOp, "Rewrite is supported until reduction loop depth of 2.");
1239
1240 auto srcIndxLhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
1241 contractOp.getLhs(), false);
1242 if (failed(srcIndxLhs))
1243 return rewriter.notifyMatchFailure(contractOp,
1244 "Failed to get the LHS src.");
1245 auto [srcBuffLhs, indicesLhs] = *srcIndxLhs;
1246
1247 auto srcIndxRhs = getSrcIndxValue(rewriter, contractOp.getLoc(),
1248 contractOp.getRhs(), false);
1249 if (failed(srcIndxRhs))
1250 return rewriter.notifyMatchFailure(contractOp,
1251 "Failed to get the RHS src.");
1252 auto [srcBuffRhs, indicesRhs] = *srcIndxRhs;
1253 Operation *vectorOpLhs;
1254 llvm::TypeSwitch<Operation *>(contractOp.getLhs().getDefiningOp())
1255 .Case<TransferReadOp, LoadOp>([&](auto readOp) {
1256 vectorOpLhs = readOp.getBase().getDefiningOp();
1257 });
1258
1259 Operation *vectorOpRhs;
1260 llvm::TypeSwitch<Operation *>(contractOp.getRhs().getDefiningOp())
1261 .Case<TransferReadOp, LoadOp>([&](auto readOp) {
1262 vectorOpRhs = readOp.getBase().getDefiningOp();
1263 });
1264
1265 if (!vectorOpLhs || !vectorOpRhs)
1266 return rewriter.notifyMatchFailure(
1267 contractOp, "Failed to find LHS or RHS read source operation");
1268
1269 // Retrive all the contaction operation within the loop.
1270 SmallVector<vector::ContractionOp> ops;
1271 // Accumulators whose initial value passed into the rewritten reduction
1272 // loops is neither read from memory nor zero, e.g. an iter_arg of an
1273 // enclosing loop. Paired with the index of their contraction in `ops`.
1274 SmallVector<std::pair<size_t, Value>> carriedAccs;
1275 for (Operation &op : loopLists[0].getBody()->getOperations()) {
1276
1277 if (auto contract = dyn_cast<vector::ContractionOp>(op)) {
1278 if (isVnni && !(isVnniOperandRead(contract.getLhs(), blockingFactor) &&
1279 isVnniOperandRead(contract.getRhs(), blockingFactor)))
1280 return rewriter.notifyMatchFailure(
1281 contractOp, "The LHS or RHS src of an associated contract "
1282 "operation does not read whole VNNI groups.");
1283
1284 LogicalResult validate = validateContractOps(
1285 rewriter, contract, dimValue, srcBuffLhs, srcBuffRhs, true);
1286
1287 if (failed(validate))
1288 return rewriter.notifyMatchFailure(
1289 contractOp,
1290 "The associated contract operations doesn't satisfy "
1291 "the re-write conditions either the dimensions are "
1292 "wrong or MemRef source are different or many users.");
1293
1294 Operation *contractReadOp =
1296
1297 if (isAccZeroVectorConstant != isZeroVectorConstant(contractReadOp))
1298 return rewriter.notifyMatchFailure(
1299 contractOp, "The input acc should be a zero constant or "
1300 "transfer_read for all contracts");
1301
1302 Value accInit = getLoopNestInit(contract.getAcc(), loopLists);
1303 Operation *accInitOp = accInit.getDefiningOp();
1304 bool carried = !isa_and_present<TransferReadOp, LoadOp>(accInitOp) &&
1305 !isZeroVectorConstant(accInitOp);
1306 if (carried) {
1307 if (loopLists.back().getRegion().isAncestor(
1308 accInit.getParentRegion()))
1309 return rewriter.notifyMatchFailure(
1310 contractOp, "The accumulator should enter the reduction loops "
1311 "through their init args.");
1312 carriedAccs.emplace_back(ops.size(), accInit);
1313 }
1314
1315 ops.push_back(contract);
1316 }
1317 }
1318
1319 // The accumulators read from memory are added back to all the results at
1320 // once, so they can't be mixed with carried ones.
1321 if (!isAccZeroVectorConstant && !carriedAccs.empty() &&
1322 carriedAccs.size() != ops.size())
1323 return rewriter.notifyMatchFailure(
1324 contractOp, "The accumulators read from memory can't be mixed with "
1325 "carried in accumulators.");
1326
1327 if (!isVnni) {
1328 unsigned int pairCount = 0;
1329 for (size_t j = 0; j < ops.size(); j++) {
1330 for (size_t i = j; i < ops.size(); i++) {
1331 if (i != j && validatePairVectorContract(ops[j], ops[i], true, 16))
1332 pairCount = pairCount + 2;
1333 }
1334 }
1335
1336 if (pairCount != ops.size())
1337 return rewriter.notifyMatchFailure(
1338 contractOp, "Coudn't find the pair vector contract ");
1339 }
1340
1341 scf::ForOp innerLoop;
1342 scf::ForOp outerLoop;
1343
1344 // For VNNI layout, accumulator tiles stay in their original layout.
1345 // Load and store them back in place when possible.
1346 // Otherwise, start from zero tile and accumulate only the final result.
1347 std::optional<SmallVector<Operation *>> inPlaceAccReads, inPlaceAccWrites;
1348 if (isVnni)
1349 std::tie(inPlaceAccReads, inPlaceAccWrites) =
1350 getInPlaceAccAccesses(ops, /*writeOnly=*/isAccZeroVectorConstant);
1351 auto createInitTiles = [&](scf::ForOp loop) {
1352 if (inPlaceAccReads)
1353 return createAccTileLoads(rewriter, loop.getLoc(), opType, loop,
1354 *inPlaceAccReads);
1355 return createTileZeros(rewriter, loop.getLoc(), opType, loop, ops.size());
1356 };
1357
1358 scf::ForOp newLoop;
1359 // Case 2a: Reduction loop depth is 2.
1360 if (loopLists.size() == 2) {
1361 outerLoop = loopLists[1];
1362 innerLoop = loopLists[0];
1363
1364 LogicalResult validateOuterLoopStep =
1365 validateLoopStep(rewriter, outerLoop.getStep(), 1);
1366 if (failed(validateOuterLoopStep))
1367 return rewriter.notifyMatchFailure(contractOp, "Invalid loop step.");
1368
1369 int64_t stepValue = 16;
1370 if (!isVnni)
1371 stepValue = stepValue * blockingFactor;
1372 LogicalResult validateInnerLoopStep =
1373 validateLoopStep(rewriter, innerLoop.getStep(), stepValue);
1374 if (failed(validateInnerLoopStep))
1375 return rewriter.notifyMatchFailure(
1376 contractOp, "Invalid loop step. The step should be 32 for BF16 and "
1377 "64 for Int8/F8.");
1378
1379 SmallVector<Value> loopItrArgs = createInitTiles(outerLoop);
1380 rewriter.setInsertionPoint(outerLoop);
1381
1382 if (isVnni) {
1383 newLoop = scf::ForOp::create(
1384 rewriter, outerLoop.getLoc(), outerLoop.getLowerBound(),
1385 outerLoop.getUpperBound(), outerLoop.getStep(), loopItrArgs,
1386 [&](OpBuilder &rewriterOuterLoop, Location locOuterLoop,
1387 Value ivOuterLoop, ValueRange iterArgsOuterLoop) {
1388 auto newInnerLoop = createLoops(
1389 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1390 innerLoop.getUpperBound(), innerLoop.getStep(),
1391 iterArgsOuterLoop, ipType, opType, blockingFactor, isVnni,
1392 vectorOpLhs, vectorOpRhs, contractOp, outerLoop, innerLoop,
1393 ops, ivOuterLoop, nullptr, true, nullptr, false, false);
1394
1395 scf::YieldOp::create(rewriterOuterLoop, locOuterLoop,
1396 newInnerLoop.getResults());
1397 });
1398
1399 } else {
1400
1401 bool isInnerLoopUBLarger = false;
1402 bool isInnerLoopUBHasOddQuot = false;
1403
1404 int64_t ubVal = 16 * blockingFactor;
1405 Value ub = innerLoop.getUpperBound();
1406 if (auto constOp = ub.getDefiningOp<arith::ConstantOp>()) {
1407 if (auto intAttr = dyn_cast<IntegerAttr>(constOp.getValue())) {
1408 ubVal = intAttr.getInt();
1409 }
1410 }
1411
1412 isInnerLoopUBLarger = ubVal > 16 * blockingFactor;
1413 isInnerLoopUBHasOddQuot =
1414 (((ubVal / (16 * blockingFactor)) % 2) == 1) && isInnerLoopUBLarger;
1415
1416 rewriter.setInsertionPoint(outerLoop);
1417
1418 auto c0 =
1419 arith::ConstantIndexOp::create(rewriter, outerLoop.getLoc(), 0);
1420 auto c1 =
1421 arith::ConstantIndexOp::create(rewriter, outerLoop.getLoc(), 1);
1422 auto spillLoopBound = arith::ConstantIndexOp::create(
1423 rewriter, outerLoop.getLoc(), 16 * blockingFactor);
1424
1425 Value spillOuterLoop = arith::SubIOp::create(
1426 rewriter, outerLoop.getLoc(), outerLoop.getUpperBound(), c1);
1427 Value spillInnerLoop =
1428 arith::SubIOp::create(rewriter, innerLoop.getLoc(),
1429 innerLoop.getUpperBound(), spillLoopBound);
1430 auto bufferType =
1431 MemRefType::get({2, 32, (blockingFactor * 16)}, ipType);
1432 auto packedBuffer =
1433 memref::AllocaOp::create(rewriter, outerLoop.getLoc(), bufferType);
1434
1435 // First Shuffling outside the reduction loops
1436 IRMapping rhsMapping;
1437 rhsMapping.map(
1438 vectorOpRhs->getOperand(
1439 getIndexPosition(contractOp.getRhs(), outerLoop) + 1),
1440 outerLoop.getLowerBound());
1441 rhsMapping.map(
1442 vectorOpRhs->getOperand(
1443 getIndexPosition(contractOp.getRhs(), innerLoop) + 1),
1444 innerLoop.getLowerBound());
1445 auto rhsClone = rewriter.clone(*vectorOpRhs, rhsMapping);
1446
1447 Value quotient_batch = arith::DivUIOp::create(
1448 rewriter, outerLoop.getLoc(), outerLoop.getLowerBound(),
1449 outerLoop.getStep());
1450 Value quotient_k = arith::DivUIOp::create(rewriter, outerLoop.getLoc(),
1451 innerLoop.getLowerBound(),
1452 innerLoop.getStep());
1453
1454 Value quotient_add = arith::AddIOp::create(rewriter, outerLoop.getLoc(),
1455 quotient_batch, quotient_k);
1456 Value c2 =
1457 arith::ConstantIndexOp::create(rewriter, outerLoop.getLoc(), 2);
1458 Value rem = arith::RemUIOp::create(rewriter, outerLoop.getLoc(),
1459 quotient_add, c2);
1460
1461 performShuffle(rewriter, outerLoop.getLoc(), rhsClone->getResult(0),
1462 ipType, blockingFactor, packedBuffer, rem);
1463
1464 // First Set of Loops
1465 auto newLoopNonSpill = scf::ForOp::create(
1466 rewriter, outerLoop.getLoc(), outerLoop.getLowerBound(),
1467 spillOuterLoop, outerLoop.getStep(), loopItrArgs,
1468 [&](OpBuilder &rewriterOuterLoop, Location locOuterLoop,
1469 Value ivOuterLoop, ValueRange iterArgsOuterLoop) {
1470 auto newInnerLoop1 = createLoops(
1471 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1472 spillInnerLoop, innerLoop.getStep(), iterArgsOuterLoop,
1473 ipType, opType, blockingFactor, isVnni, vectorOpLhs,
1474 vectorOpRhs, contractOp, outerLoop, innerLoop, ops,
1475 ivOuterLoop, packedBuffer, true, spillLoopBound,
1476 isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1477
1478 auto newInnerLoop = createLoops(
1479 rewriter, innerLoop.getLoc(), spillInnerLoop,
1480 innerLoop.getUpperBound(), innerLoop.getStep(),
1481 newInnerLoop1.getResults(), ipType, opType, blockingFactor,
1482 isVnni, vectorOpLhs, vectorOpRhs, contractOp, outerLoop,
1483 innerLoop, ops, ivOuterLoop, packedBuffer, true, c0,
1484 isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1485
1486 scf::YieldOp::create(rewriterOuterLoop, locOuterLoop,
1487 newInnerLoop.getResults());
1488 });
1489
1490 // Last set of Loops
1491 newLoop = scf::ForOp::create(
1492 rewriter, outerLoop.getLoc(), spillOuterLoop,
1493 outerLoop.getUpperBound(), outerLoop.getStep(),
1494 newLoopNonSpill.getResults(),
1495 [&](OpBuilder &rewriterOuterLoop, Location locOuterLoop,
1496 Value ivOuterLoop, ValueRange iterArgsOuterLoop) {
1497 auto newInnerLoop1 = createLoops(
1498 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1499 spillInnerLoop, innerLoop.getStep(), iterArgsOuterLoop,
1500 ipType, opType, blockingFactor, isVnni, vectorOpLhs,
1501 vectorOpRhs, contractOp, outerLoop, innerLoop, ops,
1502 ivOuterLoop, packedBuffer, true, spillLoopBound,
1503 isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1504
1505 auto newInnerLoop = createLoops(
1506 rewriter, innerLoop.getLoc(), spillInnerLoop,
1507 innerLoop.getUpperBound(), innerLoop.getStep(),
1508 newInnerLoop1.getResults(), ipType, opType, blockingFactor,
1509 isVnni, vectorOpLhs, vectorOpRhs, contractOp, outerLoop,
1510 innerLoop, ops, ivOuterLoop, packedBuffer, false, c0,
1511 isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1512
1513 scf::YieldOp::create(rewriterOuterLoop, locOuterLoop,
1514 newInnerLoop.getResults());
1515 });
1516 }
1517 }
1518
1519 // Case 2b: Reduction loop depth is 1.
1520 if (loopLists.size() == 1) {
1521
1522 innerLoop = loopLists[0];
1523 int64_t stepValue = 16;
1524 if (!isVnni)
1525 stepValue = stepValue * blockingFactor;
1526
1527 LogicalResult validateInnerLoopStep =
1528 validateLoopStep(rewriter, innerLoop.getStep(), stepValue);
1529 if (failed(validateInnerLoopStep))
1530 return rewriter.notifyMatchFailure(
1531 contractOp,
1532 "Invalid loop step. The step should be 32 for BF16 and "
1533 "64 for Int8/F8 or 1 if it is rduction loop other than K.");
1534
1535 SmallVector<Value> loopItrArgs = createInitTiles(innerLoop);
1536 rewriter.setInsertionPoint(innerLoop);
1537
1538 if (isVnni) {
1539 newLoop = createLoops(
1540 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1541 innerLoop.getUpperBound(), innerLoop.getStep(), loopItrArgs, ipType,
1542 opType, blockingFactor, isVnni, vectorOpLhs, vectorOpRhs,
1543 contractOp, nullptr, innerLoop, ops, nullptr, nullptr, true,
1544 nullptr, false, false);
1545
1546 } else {
1547
1548 bool isInnerLoopUBLarger = false;
1549 bool isInnerLoopUBHasOddQuot = false;
1550
1551 int64_t ubVal = 16 * blockingFactor;
1552 Value ub = innerLoop.getUpperBound();
1553 if (auto constOp = ub.getDefiningOp<arith::ConstantOp>()) {
1554 if (auto intAttr = dyn_cast<IntegerAttr>(constOp.getValue())) {
1555 ubVal = intAttr.getInt();
1556 }
1557 }
1558
1559 isInnerLoopUBLarger = ubVal > 16 * blockingFactor;
1560 isInnerLoopUBHasOddQuot =
1561 (((ubVal / (16 * blockingFactor)) % 2) == 1) && isInnerLoopUBLarger;
1562
1563 rewriter.setInsertionPoint(innerLoop);
1564
1565 auto c0 =
1566 arith::ConstantIndexOp::create(rewriter, innerLoop.getLoc(), 0);
1567 int64_t offset = 16 * blockingFactor;
1568 if (auto cst =
1569 innerLoop.getStep().getDefiningOp<arith::ConstantIndexOp>())
1570 offset = cst.value();
1571
1572 auto spillLoopBound = arith::ConstantIndexOp::create(
1573 rewriter, innerLoop.getLoc(), offset);
1574 Value spillInnerLoop =
1575 arith::SubIOp::create(rewriter, innerLoop.getLoc(),
1576 innerLoop.getUpperBound(), spillLoopBound);
1577
1578 auto bufferType =
1579 MemRefType::get({2, 32, (blockingFactor * 16)}, ipType);
1580 auto packedBuffer =
1581 memref::AllocaOp::create(rewriter, innerLoop.getLoc(), bufferType);
1582
1583 // First Shuffling outside the reduction loops
1584 IRMapping rhsMapping;
1585 rhsMapping.map(
1586 vectorOpRhs->getOperand(
1587 getIndexPosition(contractOp.getRhs(), innerLoop) + 1),
1588 innerLoop.getLowerBound());
1589 auto rhsClone = rewriter.clone(*vectorOpRhs, rhsMapping);
1590
1591 Value quotient_k = arith::DivUIOp::create(rewriter, innerLoop.getLoc(),
1592 innerLoop.getLowerBound(),
1593 innerLoop.getStep());
1594 Value c2 =
1595 arith::ConstantIndexOp::create(rewriter, innerLoop.getLoc(), 2);
1596 Value rem = arith::RemUIOp::create(rewriter, innerLoop.getLoc(),
1597 quotient_k, c2);
1598
1599 performShuffle(rewriter, innerLoop.getLoc(), rhsClone->getResult(0),
1600 ipType, blockingFactor, packedBuffer, rem);
1601
1602 auto newLoopNonSpill = createLoops(
1603 rewriter, innerLoop.getLoc(), innerLoop.getLowerBound(),
1604 spillInnerLoop, innerLoop.getStep(), loopItrArgs, ipType, opType,
1605 blockingFactor, isVnni, vectorOpLhs, vectorOpRhs, contractOp,
1606 nullptr, innerLoop, ops, nullptr, packedBuffer, true,
1607 spillLoopBound, isInnerLoopUBLarger, isInnerLoopUBHasOddQuot);
1608
1609 newLoop = createLoops(rewriter, innerLoop.getLoc(), spillInnerLoop,
1610 innerLoop.getUpperBound(), innerLoop.getStep(),
1611 newLoopNonSpill.getResults(), ipType, opType,
1612 blockingFactor, isVnni, vectorOpLhs, vectorOpRhs,
1613 contractOp, nullptr, innerLoop, ops, nullptr,
1614 packedBuffer, false, c0, isInnerLoopUBLarger,
1615 isInnerLoopUBHasOddQuot);
1616 }
1617
1618 // This helps the final store back to the acc uses the same code for
1619 // the both reduction loop depth 1 or 2.
1620 outerLoop = innerLoop;
1621 }
1622
1623 // Copy the amx tile accumulation results to a MemRef buffer, add the
1624 // initial accumulation value, and store back to the C-Matrix
1625 Location loc = outerLoop.getLoc();
1626
1627 if (inPlaceAccWrites) {
1628 // The tiles already include the initial accumulator or were
1629 // zero-initialized: store each one where its contraction result was
1630 // written.
1631 for (auto [i, write] : llvm::enumerate(*inPlaceAccWrites)) {
1632 auto [base, indices] = *getAccTileAccess(write);
1633 rewriter.setInsertionPoint(write);
1634 amx::TileStoreOp::create(rewriter, write->getLoc(), base, indices,
1635 newLoop.getResult(i));
1636 rewriter.eraseOp(write);
1637 }
1638 return success();
1639 }
1640
1641 // Carried-in accumulators are added to the results instead.
1642 bool addAccFromMemory = !isAccZeroVectorConstant && carriedAccs.empty();
1643
1644 // Direct tile stores weren't possible, so we need a temporary buffer to
1645 // move tile register contents through memory.
1646 unsigned int M, N;
1647 Value srcBuffAcc;
1648 SmallVector<Value> indicesAcc;
1649
1650 if (addAccFromMemory) {
1651 // We accumulate onto a register tile in memory, hence we let the
1652 // temporary buffer match its shape.
1653 llvm::TypeSwitch<Operation *>(accReadOp).Case<TransferReadOp, LoadOp>(
1654 [&](auto readOp) {
1655 srcBuffAcc = readOp.getOperand(0);
1656
1657 auto indices = readOp.getIndices();
1658 indicesAcc.reserve(indices.size());
1659
1660 llvm::transform(indices, std::back_inserter(indicesAcc),
1661 [&](OpFoldResult ofr) {
1663 rewriter, loc, ofr);
1664 });
1665 });
1666 auto outputShapes = cast<MemRefType>(srcBuffAcc.getType()).getShape();
1667 M = outputShapes[outputShapes.size() - 2];
1668 N = outputShapes[outputShapes.size() - 1];
1669 } else {
1670 // Otherwise we can just pick a suitable shape to hold all result tiles.
1671 // For the VNNI-packed case, we pass the result from each tile register to
1672 // memory individually. For the online packing case, we ensure two tiles
1673 // are neighboring in the N-dimension to facilitate the shuffling.
1674 M = isVnni ? ops.size() * 16 : ops.size() / 2 * 16;
1675 N = isVnni ? 16 : 2 * 16;
1676 }
1677
1678 SmallVector<Value> dps = newLoop.getResults();
1679 auto bufferType = MemRefType::get({M, N}, opType);
1680 auto resultBuffer = memref::AllocaOp::create(rewriter, loc, bufferType);
1681
1682 // Store the amx tiled-dot product output into an MxN memref.
1683 for (unsigned int i = 0, k = 0; i < M; i = i + 16) {
1684 for (unsigned int j = 0; j < N; j = j + 16) {
1685 Value indexOp_i = arith::ConstantIndexOp::create(rewriter, loc, i);
1686 Value indexOp_j = arith::ConstantIndexOp::create(rewriter, loc, j);
1687 amx::TileStoreOp::create(rewriter, loc, resultBuffer,
1688 ValueRange{indexOp_i, indexOp_j}, dps[k]);
1689 k++;
1690 }
1691 }
1692
1693 auto c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
1694 auto c16 = arith::ConstantIndexOp::create(rewriter, loc, 16);
1695 auto one = arith::ConstantIndexOp::create(rewriter, loc, 1);
1696 auto mBound = arith::ConstantIndexOp::create(rewriter, loc, M);
1697
1698 if (!isVnni || addAccFromMemory) {
1699 // Create a loop that iterates over the MxN memref, retrieves two rows +
1700 // shuffles them, adds up the C element values and stores them to the temp
1701 // buffer.
1702 scf::ForOp::create(
1703 rewriter, loc, c0, mBound, one, ValueRange{},
1704 [&](OpBuilder &nestedBuilder, Location loc, Value iv,
1705 ValueRange iterArgs) {
1706 auto row = vector::LoadOp::create(rewriter, loc,
1707 VectorType::get(16, opType),
1708 resultBuffer, ValueRange{iv, c0});
1709
1710 auto row2 = vector::LoadOp::create(
1711 rewriter, loc, VectorType::get(16, opType), resultBuffer,
1712 ValueRange{iv, c16});
1713
1714 Value writeVal1 = row;
1715 Value writeVal2 = row2;
1716
1717 if (!isVnni) {
1718 writeVal1 = vector::ShuffleOp::create(
1719 rewriter, loc, VectorType::get(16, opType), row, row2,
1720 ArrayRef<int64_t>{0, 1, 2, 3, 16, 17, 18, 19, 4, 5, 6, 7, 20,
1721 21, 22, 23});
1722
1723 writeVal2 = vector::ShuffleOp::create(
1724 rewriter, loc, VectorType::get(16, opType), row, row2,
1725 ArrayRef<int64_t>{8, 9, 10, 11, 24, 25, 26, 27, 12, 13, 14,
1726 15, 28, 29, 30, 31});
1727 }
1728
1729 if (addAccFromMemory) {
1730 indicesAcc[indicesAcc.size() - 2] = iv;
1731 indicesAcc[indicesAcc.size() - 1] = c0;
1732
1733 Value valueCRow1 = vector::LoadOp::create(
1734 rewriter, loc, VectorType::get(16, opType), srcBuffAcc,
1735 indicesAcc);
1736 indicesAcc[indicesAcc.size() - 1] = c16;
1737
1738 Value valueCRow2 = vector::LoadOp::create(
1739 rewriter, loc, VectorType::get(16, opType), srcBuffAcc,
1740 indicesAcc);
1741
1742 if (ipType.isBF16() || ipType.isF8E5M2() || ipType.isF8E4M3FN()) {
1743 writeVal1 =
1744 arith::AddFOp::create(rewriter, loc, writeVal1, valueCRow1);
1745
1746 writeVal2 =
1747 arith::AddFOp::create(rewriter, loc, writeVal2, valueCRow2);
1748 }
1749
1750 if (ipType.isSignlessInteger(8)) {
1751 writeVal1 =
1752 arith::AddIOp::create(rewriter, loc, writeVal1, valueCRow1);
1753
1754 writeVal2 =
1755 arith::AddIOp::create(rewriter, loc, writeVal2, valueCRow2);
1756 }
1757 }
1758
1759 vector::StoreOp::create(rewriter, loc, writeVal1, resultBuffer,
1760 ValueRange{iv, c0});
1761 vector::StoreOp::create(rewriter, loc, writeVal2, resultBuffer,
1762 ValueRange{iv, c16});
1763
1764 scf::YieldOp::create(nestedBuilder, loc);
1765 });
1766 }
1767
1768 SmallVector<Value> writeResults;
1769 for (unsigned int i = 0; i < M; i = i + 16) {
1770 for (unsigned int j = 0; j < N; j = j + 16) {
1771 Value indexOp_i = arith::ConstantIndexOp::create(rewriter, loc, i);
1772 Value indexOp_j = arith::ConstantIndexOp::create(rewriter, loc, j);
1773
1774 auto vectorType = VectorType::get({16, 16}, opType);
1775
1776 int64_t srcRank =
1777 (dyn_cast<ShapedType>(resultBuffer.getType())).getRank();
1778 Value padding = ub::PoisonOp::create(rewriter, loc, opType);
1779 auto map = AffineMap::getMinorIdentityMap(srcRank, vectorType.getRank(),
1780 rewriter.getContext());
1781 SmallVector<bool> inBounds(vectorType.getRank(), true);
1782
1783 auto vec1 = vector::TransferReadOp::create(
1784 rewriter, loc, vectorType, resultBuffer,
1785 ValueRange{indexOp_i, indexOp_j}, padding, map, inBounds);
1786 writeResults.push_back(vec1);
1787 }
1788 }
1789
1790 for (auto [idx, acc] : carriedAccs) {
1791 Type resultType = writeResults[idx].getType();
1792 if (acc.getType() != resultType)
1793 acc = vector::ShapeCastOp::create(rewriter, loc, resultType, acc);
1794 if (isa<FloatType>(opType))
1795 writeResults[idx] =
1796 arith::AddFOp::create(rewriter, loc, writeResults[idx], acc);
1797 else
1798 writeResults[idx] =
1799 arith::AddIOp::create(rewriter, loc, writeResults[idx], acc);
1800 }
1801
1802 // Replace use of vector.contract with dot-products.
1803 for (size_t i = 0; i < ops.size(); i++) {
1804 vector::ContractionOp contOp = ops[i];
1805 Value vecRow = writeResults[i];
1806
1807 Value resultWriteOp = contractionUsersAfterYield(contOp.getResult());
1808 if (auto vecType = dyn_cast<VectorType>(resultWriteOp.getType()))
1809 vecRow = vector::ShapeCastOp::create(rewriter, loc, vecType,
1810 writeResults[i]);
1811
1812 rewriter.replaceAllUsesWith(resultWriteOp, vecRow);
1813 }
1814
1815 return success();
1816 }
1817};
1818
1819} // namespace
1820
1822 RewritePatternSet &patterns) {
1823 patterns.add<VectorContractToAMXDotProduct>(patterns.getContext());
1824}
return success()
getNumOperands() - 1))) return failure()
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
auto load
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
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
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:256
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:231
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:377
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:434
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
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:373
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.
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
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:392
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:92
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:744
mlir::x86::AMXTileType TileType
Definition X86Dialect.h:36
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.