MLIR 24.0.0git
VectorToNVGPU.cpp
Go to the documentation of this file.
1//===- VectorToNVGPU.cpp - Convert vector to NVGPU dialect ----------*- C++
2//-*-===//
3//
4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5// See https://llvm.org/LICENSE.txt for license information.
6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7//
8//===----------------------------------------------------------------------===//
9//
10// This file implements lowering of vector operations to NVGPU dialect ops.
11//
12//===----------------------------------------------------------------------===//
13
15
29#include "mlir/IR/Builders.h"
30#include "mlir/IR/Region.h"
31#include "mlir/Pass/Pass.h"
33#include "llvm/ADT/STLExtras.h"
34#include "llvm/ADT/TypeSwitch.h"
35#include "llvm/Support/DebugLog.h"
36
37#define DEBUG_TYPE "vector-to-nvgpu"
38
39namespace mlir {
40#define GEN_PASS_DEF_CONVERTVECTORTONVGPUPASS
41#include "mlir/Conversion/Passes.h.inc"
42} // namespace mlir
43
44using namespace mlir;
45
46/// For a vector TransferOpType `xferOp`, an empty `indices` vector, and an
47/// AffineMap representing offsets to apply to indices, the function fills
48/// `indices` with the original indices plus the offsets. The offsets are
49/// applied by taking into account the permutation map of the transfer op. If
50/// the `offsetMap` has dimension placeholders, those should be provided in
51/// `dimValues`.
52template <typename TransferOpType>
53static void getXferIndices(RewriterBase &rewriter, TransferOpType xferOp,
54 AffineMap offsetMap, ArrayRef<Value> dimValues,
56 indices.append(xferOp.getIndices().begin(), xferOp.getIndices().end());
57 Location loc = xferOp.getLoc();
58 unsigned offsetsIdx = 0;
59 for (auto expr : xferOp.getPermutationMap().getResults()) {
60 if (auto dim = dyn_cast<AffineDimExpr>(expr)) {
61 Value prevIdx = indices[dim.getPosition()];
62 SmallVector<OpFoldResult, 3> dims(dimValues);
63 dims.push_back(prevIdx);
64 AffineExpr d0 = rewriter.getAffineDimExpr(offsetMap.getNumDims());
65 indices[dim.getPosition()] = affine::makeComposedAffineApply(
66 rewriter, loc, d0 + offsetMap.getResult(offsetsIdx++), dims);
67 continue;
68 }
69 }
70}
71
72// Return true if the contract op can be convert to MMA matmul.
73static bool contractSupportsMMAMatrixType(vector::ContractionOp contract) {
74 using MapList = ArrayRef<ArrayRef<AffineExpr>>;
75 auto infer = [&](MapList m) {
76 return AffineMap::inferFromExprList(m, contract.getContext());
77 };
78 AffineExpr m, n, k;
79 bindDims(contract.getContext(), m, n, k);
80 auto iteratorTypes = contract.getIteratorTypes().getValue();
81 if (!(vector::isParallelIterator(iteratorTypes[0]) &&
82 vector::isParallelIterator(iteratorTypes[1]) &&
83 vector::isReductionIterator(iteratorTypes[2])))
84 return false;
85
86 // The contract needs to represent a matmul to be able to convert to
87 // MMAMatrix matmul.
88 if (contract.getIndexingMapsArray() != infer({{m, k}, {n, k}, {m, n}}))
89 return false;
90
91 return true;
92}
93
94/// Returns true if the extract strided slice op is supported with `mma.sync`
95/// path.
96static bool
97extractStridedSliceSupportsMMAMatrixType(vector::ExtractStridedSliceOp op) {
98
99 FailureOr<nvgpu::WarpMatrixInfo> warpMatrixInfo =
101 if (failed(warpMatrixInfo))
102 return false;
103
104 FailureOr<vector::ContractionOp> contractOp = nvgpu::getUserContract(op);
105 if (failed(contractOp))
106 return false;
107
108 // Handle vector.extract_strided_slice on registers containing
109 // matrixB and matrixC operands. vector.extract_strided_slice op
110 // is not supported on registers containing matrixA operands.
111 if (warpMatrixInfo->operandRole == nvgpu::MatMulOperandRole::B)
112 return (cast<VectorType>(op->getResult(0).getType()) ==
113 cast<VectorType>((*contractOp).getRhs().getType()));
114 if (warpMatrixInfo->operandRole == nvgpu::MatMulOperandRole::C)
115 return (cast<VectorType>(op->getResult(0).getType()) ==
116 cast<VectorType>((*contractOp).getAcc().getType()));
117
118 return false;
119}
120
122 if (isa<scf::ForOp, scf::YieldOp>(op))
123 return true;
124 // MMA ops cannot honor a mask. An op masked by an enclosing vector.mask has
125 // no mask operand, so the checks below would miss it; rewriting it in place
126 // would leave non-maskable ops inside the vector.mask region.
127 auto maskableOp = dyn_cast<vector::MaskableOpInterface>(op);
128 if (maskableOp && maskableOp.isMasked())
129 return false;
130 if (auto transferRead = dyn_cast<vector::TransferReadOp>(op))
131 return nvgpu::canLowerToWarpMatrixOperation(transferRead);
132 if (auto transferWrite = dyn_cast<vector::TransferWriteOp>(op))
133 return nvgpu::canLowerToWarpMatrixOperation(transferWrite);
134 if (auto extractStridedSlice = dyn_cast<vector::ExtractStridedSliceOp>(op))
135 return extractStridedSliceSupportsMMAMatrixType(extractStridedSlice);
136 if (auto contract = dyn_cast<vector::ContractionOp>(op))
138 if (auto constant = dyn_cast<arith::ConstantOp>(op))
139 return constantSupportsMMAMatrixType(constant);
140 if (auto broadcast = dyn_cast<vector::BroadcastOp>(op))
142 if (auto signedExtend = dyn_cast<arith::ExtSIOp>(op))
144 if (auto unsignedExtend = dyn_cast<arith::ExtUIOp>(op))
147}
148
149// Analyze slice of operations based on convert op to figure out if the whole
150// slice can be converted to MMA operations.
152 auto hasVectorDest = [](Operation *op) {
153 return llvm::any_of(op->getResultTypes(), llvm::IsaPred<VectorType>);
154 };
155 BackwardSliceOptions backwardSliceOptions;
156 backwardSliceOptions.filter = hasVectorDest;
157
158 auto hasVectorSrc = [](Operation *op) {
159 return llvm::any_of(op->getOperandTypes(), llvm::IsaPred<VectorType>);
160 };
161 ForwardSliceOptions forwardSliceOptions;
162 forwardSliceOptions.filter = hasVectorSrc;
163
166 DenseMap<Operation *, bool> supportsMMAMatrixTypeCache;
167
168 auto getCachedBackwardSlice =
169 [&](Operation *currentOp) -> ArrayRef<Operation *> {
170 auto [it, inserted] = backwardSliceCache.try_emplace(currentOp);
171 if (!inserted)
172 return it->second;
173
174 SetVector<Operation *> backwardSlice;
175 LogicalResult result =
176 getBackwardSlice(currentOp, &backwardSlice, backwardSliceOptions);
177 assert(result.succeeded() && "expected a backward slice");
178 (void)result;
179 it->second = backwardSlice.takeVector();
180 return it->second;
181 };
182
183 auto getCachedForwardSlice =
184 [&](Operation *currentOp) -> ArrayRef<Operation *> {
185 auto [it, inserted] = forwardSliceCache.try_emplace(currentOp);
186 if (!inserted)
187 return it->second;
188
189 SetVector<Operation *> forwardSlice;
190 // Special case for ForOp, we don't want to include the whole region but
191 // only the value using the region arguments.
192 // TODO: We should refine this to only care about the region arguments being
193 // converted to matrix type.
194 if (auto forOp = dyn_cast<scf::ForOp>(currentOp)) {
195 for (Value forOpResult : forOp.getResults())
196 getForwardSlice(forOpResult, &forwardSlice, forwardSliceOptions);
197 for (BlockArgument &arg : forOp.getRegionIterArgs())
198 getForwardSlice(arg, &forwardSlice, forwardSliceOptions);
199 } else {
200 getForwardSlice(currentOp, &forwardSlice, forwardSliceOptions);
201 }
202 it->second = forwardSlice.takeVector();
203 return it->second;
204 };
205
206 auto cachedSupportsMMAMatrixType = [&](Operation *currentOp) {
207 auto [it, inserted] =
208 supportsMMAMatrixTypeCache.try_emplace(currentOp, false);
209 if (inserted)
210 it->second = supportsMMaMatrixType(currentOp);
211 return it->second;
212 };
213
214 SetVector<Operation *> opToConvert;
215 op->walk([&](Operation *nestedOp) {
216 if (!isa<vector::ContractionOp>(nestedOp) &&
218 return;
219 if (backwardSliceCache.contains(nestedOp))
220 return;
221
222 SetVector<Operation *> dependentOps;
223 dependentOps.insert(nestedOp);
224 unsigned currentIndex = 0;
225 while (currentIndex != dependentOps.size()) {
226 Operation *currentOp = dependentOps[currentIndex++];
227 dependentOps.insert_range(getCachedBackwardSlice(currentOp));
228 dependentOps.insert_range(getCachedForwardSlice(currentOp));
229 }
230 // If any instruction cannot use MMA matrix type drop the whole
231 // chain. MMA matrix are stored in an opaque type so they cannot be used
232 // by all operations.
233 if (llvm::any_of(dependentOps, [&](Operation *op) {
234 if (!cachedSupportsMMAMatrixType(op)) {
235 LDBG() << "cannot convert op: " << *op;
236 return true;
237 }
238 return false;
239 }))
240 return;
241
242 opToConvert.insert_range(dependentOps);
243 });
244 // Sort the operations so that we can convert them in topological order.
245 return topologicalSort(opToConvert);
246}
247
248/// Returns the vector type which represents a matrix fragment.
249static VectorType
250getMmaSyncVectorOperandType(const nvgpu::FragmentElementInfo &regInfo) {
251 SmallVector<int64_t> shape{regInfo.numRegistersPerFragment,
252 regInfo.elementsPerRegister};
253 Type elType = regInfo.registerLLVMType;
254 if (auto vecType = dyn_cast<VectorType>(elType))
255 elType = vecType.getElementType();
256 return VectorType::get(shape, elType);
257}
258
259/// Convert a 2D splat ConstantOp to a SubgroupMmaConstantMatrix op.
260static LogicalResult
261convertConstantOpMmaSync(RewriterBase &rewriter, arith::ConstantOp op,
262 llvm::DenseMap<Value, Value> &valueMapping) {
263 OpBuilder::InsertionGuard g(rewriter);
264 rewriter.setInsertionPoint(op);
265
266 FailureOr<nvgpu::WarpMatrixInfo> warpMatrixInfo =
268 if (failed(warpMatrixInfo)) {
269 LDBG() << "no warpMatrixInfo";
270 return rewriter.notifyMatchFailure(op, "no warpMatrixInfo");
271 }
272
273 FailureOr<nvgpu::FragmentElementInfo> regInfo =
274 nvgpu::getMmaSyncRegisterType(*warpMatrixInfo);
275 if (failed(regInfo)) {
276 LDBG() << "not mma sync reg info";
277 return rewriter.notifyMatchFailure(op, "not mma sync reg info");
278 }
279
280 VectorType vectorType = getMmaSyncVectorOperandType(*regInfo);
281 auto dense = dyn_cast<SplatElementsAttr>(op.getValue());
282 if (!dense) {
283 LDBG() << "not a splat";
284 return rewriter.notifyMatchFailure(op, "not a splat");
285 }
286
287 Value result = arith::ConstantOp::create(
288 rewriter, op.getLoc(), vectorType,
289 DenseElementsAttr::get(vectorType, dense.getSplatValue<Attribute>()));
290 valueMapping[op.getResult()] = result;
291 return success();
292}
293
294/// Check if the loaded matrix operand requires transposed.
295/// Transposed Map Example:
296/// Example 1 : (..., d0, d1) -> (d1 * 1, d0 * 2)
297/// Example 2 : (d0, d1, d2, d3) -> (d3, d2)
298/// The code below checks if the output 2D is transposed using a generalized
299/// version : (d0, d1, dn, ..., dm, ...) -> (dm, dn)
300/// Returns : true; if m > n, false o.w.
301static FailureOr<bool> isTransposed(vector::TransferReadOp op) {
303
304 if (map.getNumResults() != 2) {
305 LDBG() << "Failed because the result of `vector.transfer_read` "
306 "is not a 2d operand";
307 return failure();
308 }
309
310 // Output 2D matrix dimensions in the order of d0, d1.
311 mlir::AffineExpr dM = map.getResult(0);
312 mlir::AffineExpr dN = map.getResult(1);
313
314 // Find the position of these expressions in the input.
315 auto exprM = dyn_cast<AffineDimExpr>(dM);
316 auto exprN = dyn_cast<AffineDimExpr>(dN);
317
318 if (!exprM || !exprN) {
319 LDBG() << "Failed because expressions are not affine dim "
320 "expressions, then transpose cannot be determined.";
321 return failure();
322 }
323
324 return exprM.getPosition() > exprN.getPosition();
325}
326
327static LogicalResult
328creatLdMatrixCompatibleLoads(RewriterBase &rewriter, vector::TransferReadOp op,
329 llvm::DenseMap<Value, Value> &valueMapping) {
330 OpBuilder::InsertionGuard g(rewriter);
331 rewriter.setInsertionPoint(op);
332 Location loc = op->getLoc();
333
334 FailureOr<nvgpu::WarpMatrixInfo> warpMatrixInfo =
336 if (failed(warpMatrixInfo)) {
337 LDBG() << "no warpMatrixInfo";
338 return rewriter.notifyMatchFailure(op, "no warpMatrixInfo");
339 }
340
341 FailureOr<nvgpu::FragmentElementInfo> regInfo =
342 nvgpu::getMmaSyncRegisterType(*warpMatrixInfo);
343 if (failed(regInfo)) {
344 LDBG() << "not mma sync reg info";
345 return rewriter.notifyMatchFailure(op, "not mma sync reg info");
346 }
347
348 FailureOr<bool> transpose = isTransposed(op);
349 if (failed(transpose)) {
350 LDBG() << "failed to determine the transpose";
351 return rewriter.notifyMatchFailure(
352 op, "Op should likely not be converted to a nvgpu.ldmatrix call.");
353 }
354
355 FailureOr<nvgpu::LdMatrixParams> params =
356 nvgpu::getLdMatrixParams(*warpMatrixInfo, *transpose);
357
358 if (failed(params)) {
359 LDBG() << "failed to convert vector.transfer_read to ldmatrix. "
360 << "Op should likely not be converted to a nvgpu.ldmatrix call.";
361 return rewriter.notifyMatchFailure(
362 op, "failed to convert vector.transfer_read to ldmatrix; this op "
363 "likely should not be converted to a nvgpu.ldmatrix call.");
364 }
365
366 // Adjust the load offset.
367 auto laneId = gpu::LaneIdOp::create(rewriter, loc, /*upper_bound=*/nullptr);
368 FailureOr<AffineMap> offsets =
369 nvgpu::getLaneIdToLdMatrixMatrixCoord(rewriter, loc, *params);
370 if (failed(offsets)) {
371 LDBG() << "no offsets";
372 return rewriter.notifyMatchFailure(op, "no offsets");
373 }
374
375 VectorType vectorType = getMmaSyncVectorOperandType(*regInfo);
376
378 getXferIndices<vector::TransferReadOp>(rewriter, op, *offsets, {laneId},
379 indices);
380
381 nvgpu::LdMatrixOp newOp =
382 nvgpu::LdMatrixOp::create(rewriter, loc, vectorType, op.getBase(),
383 indices, *transpose, params->numTiles);
384 valueMapping[op] = newOp->getResult(0);
385 return success();
386}
387
388static LogicalResult
389createNonLdMatrixLoads(RewriterBase &rewriter, vector::TransferReadOp op,
390 llvm::DenseMap<Value, Value> &valueMapping) {
391 OpBuilder::InsertionGuard g(rewriter);
392 rewriter.setInsertionPoint(op);
393
394 Location loc = op.getLoc();
395 FailureOr<nvgpu::WarpMatrixInfo> warpMatrixInfo =
397 if (failed(warpMatrixInfo))
398 return rewriter.notifyMatchFailure(op, "no warpMatrixInfo");
399 FailureOr<nvgpu::FragmentElementInfo> regInfo =
400 nvgpu::getMmaSyncRegisterType(*warpMatrixInfo);
401 if (failed(regInfo)) {
402 return rewriter.notifyMatchFailure(
403 op, "Failed to deduce register fragment type during "
404 "conversion to distributed non-ldmatrix compatible load");
405 }
406
407 Value laneId = gpu::LaneIdOp::create(rewriter, loc, /*upper_bound=*/nullptr);
408
409 // This is the individual element type.
410 Type loadedElType = regInfo->registerLLVMType;
411 VectorType vectorType = getMmaSyncVectorOperandType(*regInfo);
412
413 Value fill = arith::ConstantOp::create(
414 rewriter, op.getLoc(), vectorType.getElementType(),
415 rewriter.getZeroAttr(vectorType.getElementType()));
416 Value result =
417 vector::BroadcastOp::create(rewriter, op.getLoc(), vectorType, fill);
418
419 bool isTransposeLoad = !op.getPermutationMap().isMinorIdentity();
420
421 // If we are not transposing, then we can use vectorized loads. Otherwise, we
422 // must load each element individually.
423 if (!isTransposeLoad) {
424 if (!isa<VectorType>(loadedElType)) {
425 loadedElType = VectorType::get({1}, loadedElType);
426 }
427
428 for (int i = 0; i < vectorType.getShape()[0]; i++) {
429 FailureOr<AffineMap> coords = nvgpu::getLaneIdAndValueIdToOperandCoord(
430 rewriter, op.getLoc(), *warpMatrixInfo);
431 if (failed(coords))
432 return rewriter.notifyMatchFailure(op, "no coords");
433
434 Value logicalValueId = arith::ConstantOp::create(
435 rewriter, loc, rewriter.getIndexType(),
436 rewriter.getIndexAttr(i * regInfo->elementsPerRegister));
437 SmallVector<Value, 4> newIndices;
439 rewriter, op, *coords, {laneId, logicalValueId}, newIndices);
440
441 Value el = vector::LoadOp::create(rewriter, loc, loadedElType,
442 op.getBase(), newIndices);
443 result = vector::InsertOp::create(rewriter, loc, el, result, i);
444 }
445 } else {
446 if (auto vecType = dyn_cast<VectorType>(loadedElType)) {
447 loadedElType = vecType.getElementType();
448 }
449 for (int i = 0; i < vectorType.getShape()[0]; i++) {
450 for (unsigned innerIdx = 0; innerIdx < vectorType.getShape()[1];
451 innerIdx++) {
452
453 Value logicalValueId = arith::ConstantOp::create(
454 rewriter, loc, rewriter.getIndexType(),
455 rewriter.getIndexAttr(i * regInfo->elementsPerRegister + innerIdx));
456 FailureOr<AffineMap> coords = nvgpu::getLaneIdAndValueIdToOperandCoord(
457 rewriter, op.getLoc(), *warpMatrixInfo);
458 if (failed(coords))
459 return rewriter.notifyMatchFailure(op, "no coords");
460
461 SmallVector<Value, 4> newIndices;
463 rewriter, op, *coords, {laneId, logicalValueId}, newIndices);
464 Value el = memref::LoadOp::create(rewriter, op.getLoc(), loadedElType,
465 op.getBase(), newIndices);
466 result = vector::InsertOp::create(rewriter, op.getLoc(), el, result,
467 ArrayRef<int64_t>{i, innerIdx});
468 }
469 }
470 }
471
472 valueMapping[op.getResult()] = result;
473 return success();
474}
475
476/// Return true if this is a shared memory memref type.
477static bool isSharedMemory(MemRefType type) {
478 auto addressSpace =
479 dyn_cast_or_null<gpu::AddressSpaceAttr>(type.getMemorySpace());
480 return addressSpace &&
481 addressSpace.getValue() == gpu::GPUDialect::getWorkgroupAddressSpace();
482}
483
484/// Converts a `vector.transfer_read` operation directly to either a
485/// `vector.load` or a `nvgpu.ldmatrix` operation. This function should only be
486/// used when converting to `nvgpu.mma.sync` operations.
487static LogicalResult
488convertTransferReadToLoads(RewriterBase &rewriter, vector::TransferReadOp op,
489 llvm::DenseMap<Value, Value> &valueMapping) {
490 OpBuilder::InsertionGuard g(rewriter);
491 rewriter.setInsertionPoint(op);
492
493 FailureOr<nvgpu::WarpMatrixInfo> warpMatrixInfo =
495 if (failed(warpMatrixInfo))
496 return rewriter.notifyMatchFailure(op, "no warpMatrixInfo");
497
498 bool isLdMatrixCompatible =
499 isSharedMemory(cast<MemRefType>(op.getBase().getType())) &&
500 nvgpu::inferTileWidthInBits(*warpMatrixInfo) == 128;
501
502 VectorType vecTy = op.getVectorType();
503 int64_t bitWidth = vecTy.getElementType().getIntOrFloatBitWidth();
504
505 // When we are transposing the B operand, ldmatrix will only work if we have
506 // at least 8 rows to read and the width to read for the transpose is 128
507 // bits.
508 if (!op.getPermutationMap().isMinorIdentity() &&
509 (bitWidth != 16 || vecTy.getDimSize(1) < 8 ||
510 vecTy.getDimSize(0) * bitWidth < 128))
511 isLdMatrixCompatible = false;
512
513 if (!isLdMatrixCompatible)
514 return createNonLdMatrixLoads(rewriter, op, valueMapping);
515
516 return creatLdMatrixCompatibleLoads(rewriter, op, valueMapping);
517}
518
519static LogicalResult
520convertTransferWriteToStores(RewriterBase &rewriter, vector::TransferWriteOp op,
521 llvm::DenseMap<Value, Value> &valueMapping) {
522 OpBuilder::InsertionGuard g(rewriter);
523 rewriter.setInsertionPoint(op);
524
525 Location loc = op->getLoc();
526 auto it = valueMapping.find(op.getVector());
527 if (it == valueMapping.end())
528 return rewriter.notifyMatchFailure(op, "no mapping");
529 Value matrix = it->second;
530
531 FailureOr<nvgpu::WarpMatrixInfo> warpMatrixInfo =
533 if (failed(warpMatrixInfo))
534 return rewriter.notifyMatchFailure(op, "no warpMatrixInfo");
535 FailureOr<nvgpu::FragmentElementInfo> regInfo =
536 nvgpu::getMmaSyncRegisterType(*warpMatrixInfo);
537 if (failed(regInfo))
538 return rewriter.notifyMatchFailure(op, "not mma sync reg info");
539
540 VectorType vectorType = getMmaSyncVectorOperandType(*regInfo);
541 Value laneId = gpu::LaneIdOp::create(rewriter, loc, /*upper_bound=*/nullptr);
542
543 for (unsigned i = 0; i < vectorType.getShape()[0]; i++) {
544 Value logicalValueId = arith::ConstantOp::create(
545 rewriter, loc, rewriter.getIndexType(),
546 rewriter.getIndexAttr(i * regInfo->elementsPerRegister));
547 FailureOr<AffineMap> coords = nvgpu::getLaneIdAndValueIdToOperandCoord(
548 rewriter, op.getLoc(), *warpMatrixInfo);
549 if (failed(coords))
550 return rewriter.notifyMatchFailure(op, "no coords");
551
552 Value el =
553 vector::ExtractOp::create(rewriter, loc, matrix, ArrayRef<int64_t>{i});
554 SmallVector<Value, 4> newIndices;
556 rewriter, op, *coords, {laneId, logicalValueId}, newIndices);
557 vector::StoreOp::create(rewriter, loc, el, op.getBase(), newIndices);
558 }
559
560 LDBG() << "erase: " << op;
561 rewriter.eraseOp(op);
562 return success();
563}
564
566 SmallVectorImpl<int64_t> &results) {
567 for (auto attr : arrayAttr)
568 results.push_back(cast<IntegerAttr>(attr).getInt());
569}
570
571static LogicalResult
573 vector::ExtractStridedSliceOp op,
574 llvm::DenseMap<Value, Value> &valueMapping) {
575 OpBuilder::InsertionGuard g(rewriter);
576 rewriter.setInsertionPoint(op);
577
578 Location loc = op->getLoc();
579
580 FailureOr<nvgpu::WarpMatrixInfo> warpMatrixInfo =
582 if (failed(warpMatrixInfo))
583 return rewriter.notifyMatchFailure(op, "no warpMatrixInfo");
584
585 FailureOr<nvgpu::FragmentElementInfo> mmaSyncFragmentInfo =
586 nvgpu::getMmaSyncRegisterType(*warpMatrixInfo);
587 if (failed(mmaSyncFragmentInfo))
588 return rewriter.notifyMatchFailure(op, "no mmaSyncFragmentInfo");
589
590 // Find the vector.transer_read whose result vector is being sliced.
591 auto transferReadOp = op.getSource().getDefiningOp<vector::TransferReadOp>();
592 if (!transferReadOp)
593 return rewriter.notifyMatchFailure(op, "no transfer read");
594
595 warpMatrixInfo = nvgpu::getWarpMatrixInfo(transferReadOp);
596 if (failed(warpMatrixInfo))
597 return rewriter.notifyMatchFailure(op, "no warpMatrixInfo");
598
599 FailureOr<nvgpu::FragmentElementInfo> ldFragmentInfo =
600 nvgpu::getMmaSyncRegisterType(*warpMatrixInfo);
601 if (failed(ldFragmentInfo))
602 return rewriter.notifyMatchFailure(op, "no ldFragmentInfo");
603
604 assert(
605 (mmaSyncFragmentInfo->elementsPerRegister ==
606 ldFragmentInfo->elementsPerRegister) &&
607 "Number of elements per register should be same for load and mma.sync");
608
609 // Create vector.extract_strided_slice op for thread-owned fragments.
610 std::array<int64_t, 2> strides = {1,
611 1}; // stride for extract slice is always 1.
612 std::array<int64_t, 2> sliceShape = {
613 mmaSyncFragmentInfo->numRegistersPerFragment,
614 mmaSyncFragmentInfo->elementsPerRegister};
615 auto it = valueMapping.find(transferReadOp);
616 if (it == valueMapping.end())
617 return rewriter.notifyMatchFailure(op, "no mapping");
618 auto sourceVector = it->second;
619
620 // offset and sizes at warp-level of onwership.
621 SmallVector<int64_t> offsets;
622 populateFromInt64AttrArray(op.getOffsets(), offsets);
623
625 populateFromInt64AttrArray(op.getSizes(), sizes);
626 ArrayRef<int64_t> warpVectorShape = op.getSourceVectorType().getShape();
627
628 // Compute offset in vector registers. Note that the mma.sync vector registers
629 // are shaped as numberOfFragments x numberOfRegistersPerfFragment. The vector
630 // registers can only be sliced along numberOfFragments, i.e., sliceOffset[0].
631 std::array<int64_t, 2> sliceOffset = {0, 0};
632
633 if (offsets[0] && offsets[1])
634 return op->emitError() << "Slicing fragments in 2D is not supported. ";
635 if (offsets[0])
636 sliceOffset[0] = (warpVectorShape[0] / offsets[0]);
637 else if (offsets[1])
638 sliceOffset[0] = (warpVectorShape[1] / offsets[1]);
639
640 Value newOp = vector::ExtractStridedSliceOp::create(
641 rewriter, loc, sourceVector, sliceOffset, sliceShape, strides);
642
643 valueMapping[op] = newOp;
644 return success();
645}
646
647static LogicalResult
648convertContractOpToMmaSync(RewriterBase &rewriter, vector::ContractionOp op,
649 llvm::DenseMap<Value, Value> &valueMapping) {
650 OpBuilder::InsertionGuard g(rewriter);
651 rewriter.setInsertionPoint(op);
652
653 auto itA = valueMapping.find(op.getLhs());
654 auto itB = valueMapping.find(op.getRhs());
655 auto itC = valueMapping.find(op.getAcc());
656 if (itA == valueMapping.end() || itB == valueMapping.end() ||
657 itC == valueMapping.end())
658 return rewriter.notifyMatchFailure(op, "no mapping");
659 Value opA = itA->second, opB = itB->second, opC = itC->second;
660 int64_t m = cast<VectorType>(op.getLhs().getType()).getShape()[0];
661 int64_t n = cast<VectorType>(op.getRhs().getType()).getShape()[0];
662 int64_t k = cast<VectorType>(op.getLhs().getType()).getShape()[1];
663 Value matmul = nvgpu::MmaSyncOp::create(rewriter, op.getLoc(), opA, opB, opC,
664 rewriter.getI64ArrayAttr({m, n, k}));
665 valueMapping[op.getResult()] = matmul;
666 return success();
667}
668
673
675 Operation *rootOp) {
677 llvm::DenseMap<Value, Value> valueMapping;
678 for (Operation *op : ops) {
680 .Case([&](vector::TransferReadOp transferReadOp) {
681 return convertTransferReadToLoads(rewriter, transferReadOp,
682 valueMapping);
683 })
684 .Case([&](vector::TransferWriteOp transferWriteOp) {
685 return convertTransferWriteToStores(rewriter, transferWriteOp,
686 valueMapping);
687 })
688 .Case([&](vector::ExtractStridedSliceOp extractStridedSliceOp) {
689 return convertExtractStridedSlice(rewriter, extractStridedSliceOp,
690 valueMapping);
691 })
692 .Case([&](vector::ContractionOp contractionOp) {
693 return convertContractOpToMmaSync(rewriter, contractionOp,
694 valueMapping);
695 })
696 .Case([&](scf::ForOp forOp) {
697 return convertForOpForMMA(rewriter, forOp, valueMapping);
698 })
699 .Case([&](scf::YieldOp yieldOp) {
700 return convertYieldOpForMMA(rewriter, yieldOp, valueMapping);
701 })
702 .Case([&](arith::ConstantOp constOp) {
703 return convertConstantOpMmaSync(rewriter, constOp, valueMapping);
704 })
705 .Default([&](Operation *op) {
706 return op->emitError() << "unhandled vector to mma type: " << *op;
707 })
708 .failed()) {
709 return op->emitOpError()
710 << "failed to convert op during vector-to-nvgpu conversion";
711 }
712 }
713 return success();
714}
715
716namespace {
717struct ConvertVectorToNVGPUPass
718 : public impl::ConvertVectorToNVGPUPassBase<ConvertVectorToNVGPUPass> {
719
720 void runOnOperation() override {
721 RewritePatternSet patterns(&getContext());
723 if (failed(applyPatternsGreedily(getOperation(), std::move(patterns))))
724 return signalPassFailure();
725
726 IRRewriter rewriter(&getContext());
727 if (failed(convertVectorToNVVMCompatibleMMASync(rewriter, getOperation())))
728 return signalPassFailure();
729 }
730};
731} // namespace
return success()
ArrayAttr()
b getContext())
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be inserted(the insertion happens right before the *insertion point). Since `begin` can itself be invalidated due to the memref *rewriting done from this method
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 broadcast(Location loc, Value toBroadcast, unsigned numElements, const TypeConverter &typeConverter, ConversionPatternRewriter &rewriter)
Broadcasts the value to vector with numElements number of elements.
static bool supportsMMaMatrixType(Operation *op)
static LogicalResult convertContractOpToMmaSync(RewriterBase &rewriter, vector::ContractionOp op, llvm::DenseMap< Value, Value > &valueMapping)
static bool isSharedMemory(MemRefType type)
Return true if this is a shared memory memref type.
static VectorType getMmaSyncVectorOperandType(const nvgpu::FragmentElementInfo &regInfo)
Returns the vector type which represents a matrix fragment.
static void populateFromInt64AttrArray(ArrayAttr arrayAttr, SmallVectorImpl< int64_t > &results)
static FailureOr< bool > isTransposed(vector::TransferReadOp op)
Check if the loaded matrix operand requires transposed.
static LogicalResult convertTransferReadToLoads(RewriterBase &rewriter, vector::TransferReadOp op, llvm::DenseMap< Value, Value > &valueMapping)
Converts a vector.transfer_read operation directly to either a vector.load or a nvgpu....
static LogicalResult creatLdMatrixCompatibleLoads(RewriterBase &rewriter, vector::TransferReadOp op, llvm::DenseMap< Value, Value > &valueMapping)
static bool contractSupportsMMAMatrixType(vector::ContractionOp contract)
static bool extractStridedSliceSupportsMMAMatrixType(vector::ExtractStridedSliceOp op)
Returns true if the extract strided slice op is supported with mma.sync path.
static LogicalResult convertConstantOpMmaSync(RewriterBase &rewriter, arith::ConstantOp op, llvm::DenseMap< Value, Value > &valueMapping)
Convert a 2D splat ConstantOp to a SubgroupMmaConstantMatrix op.
static SetVector< Operation * > getOpToConvert(mlir::Operation *op)
static LogicalResult convertTransferWriteToStores(RewriterBase &rewriter, vector::TransferWriteOp op, llvm::DenseMap< Value, Value > &valueMapping)
static void getXferIndices(RewriterBase &rewriter, TransferOpType xferOp, AffineMap offsetMap, ArrayRef< Value > dimValues, SmallVector< Value, 4 > &indices)
For a vector TransferOpType xferOp, an empty indices vector, and an AffineMap representing offsets to...
static LogicalResult convertExtractStridedSlice(RewriterBase &rewriter, vector::ExtractStridedSliceOp op, llvm::DenseMap< Value, Value > &valueMapping)
static LogicalResult createNonLdMatrixLoads(RewriterBase &rewriter, vector::TransferReadOp op, llvm::DenseMap< Value, Value > &valueMapping)
Base type for affine expression.
Definition AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
unsigned getNumDims() const
unsigned getNumResults() const
static SmallVector< AffineMap, 4 > inferFromExprList(ArrayRef< ArrayRef< AffineExpr > > exprsList, MLIRContext *context)
Returns a vector of AffineMaps; each with as many results as exprs.size(), as many dims as the larges...
AffineExpr getResult(unsigned idx) const
static AffineMap getPermutationMap(ArrayRef< unsigned > permutation, MLIRContext *context)
Returns an AffineMap representing a permutation.
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:308
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
ArrayAttr getI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:290
IndexType getIndexType()
Definition Builders.cpp:59
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
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
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
operand_type_range getOperandTypes()
Definition Operation.h:424
result_type_range getResultTypes()
Definition Operation.h:455
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:851
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
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,...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
AffineApplyOp makeComposedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Returns a composed AffineApplyOp by composing map and operands with other AffineApplyOps supplying th...
FailureOr< vector::ContractionOp > getUserContract(Operation *op)
Returns the first user of the op that is vector.contract.
Definition MMAUtils.cpp:48
FailureOr< WarpMatrixInfo > getWarpMatrixInfo(Operation *op)
If op is a vector.transfer_write, return the WarpMatrixInfo for the vector operand.
Definition MMAUtils.cpp:56
bool canLowerToWarpMatrixOperation(vector::TransferWriteOp op)
Returns the number of bits in a single tile row.
Definition MMAUtils.cpp:296
bool isReductionIterator(Attribute attr)
Returns true if attr has "reduction" iterator type semantics.
Definition VectorOps.h:146
bool isParallelIterator(Attribute attr)
Returns true if attr has "parallel" iterator type semantics.
Definition VectorOps.h:141
void populateVectorContractCanonicalizeMatmulToMMT(RewritePatternSet &patterns, std::function< LogicalResult(vector::ContractionOp)> constraint=[](vector::ContractionOp) { return success();}, PatternBenefit=1)
Canonicalization of a vector.contract a, b, c with row-major matmul semantics to a contraction with M...
Include the generated interface declarations.
LogicalResult convertYieldOpForMMA(RewriterBase &rewriter, scf::YieldOp op, llvm::DenseMap< Value, Value > &valueMapping)
Update a yield operation to yield the converted values.
LogicalResult getBackwardSlice(Operation *op, SetVector< Operation * > *backwardSlice, const BackwardSliceOptions &options={})
Fills backwardSlice with the computed backward slice (i.e.
bool broadcastSupportsMMAMatrixType(vector::BroadcastOp broadcastOp)
Return true if this is a broadcast from scalar to a 2D vector.
bool elementwiseSupportsMMAMatrixType(Operation *op)
Return true if the operation is supported as elementwise on an MMA matrix.
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:313
LogicalResult convertForOpForMMA(RewriterBase &rewriter, scf::ForOp op, llvm::DenseMap< Value, Value > &valueMapping)
Extend loop-carried values with their converted counterparts.
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
bool constantSupportsMMAMatrixType(arith::ConstantOp constantOp)
Return true if the constant is a splat to a 2D vector so that it can be converted to an MMA constant ...
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:128
SliceOptions ForwardSliceOptions
LogicalResult convertVectorToNVVMCompatibleMMASync(RewriterBase &rewriter, Operation *rootOp)
Convert vector ops nested under rootOp to vector and GPU operations compatible with the nvvm....
void populateVectorTransferReadTransposePatterns(RewritePatternSet &patterns)
Populate patterns that fold vector.transpose into vector.transfer_read, including through arithmetic ...
template bool integerExtendSupportsMMAMatrixType< arith::ExtSIOp >(arith::ExtSIOp)
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
template bool integerExtendSupportsMMAMatrixType< arith::ExtUIOp >(arith::ExtUIOp)
void populatePrepareVectorToNVMMAPatterns(RewritePatternSet &patterns)
Populate patterns that prepare vector operations for lowering to nvgpu.mma.sync operations.
SetVector< Operation * > topologicalSort(const SetVector< Operation * > &toSort)
Sorts all operations in toSort topologically while also considering region semantics.
void getForwardSlice(Operation *op, SetVector< Operation * > *forwardSlice, const ForwardSliceOptions &options={})
Fills forwardSlice with the computed forward slice (i.e.
TransitiveFilter filter