MLIR 24.0.0git
NVGPUDialect.cpp
Go to the documentation of this file.
1//===- NVGPUDialect.cpp - MLIR NVGPU ops implementation -------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the NVGPU dialect and its operations.
10//
11//===----------------------------------------------------------------------===//
12
16#include "mlir/IR/Builders.h"
19#include "mlir/IR/Diagnostics.h"
22#include "mlir/IR/Verifier.h"
23#include "llvm/ADT/STLExtras.h"
24#include "llvm/ADT/TypeSwitch.h"
25
26using namespace mlir;
27using namespace mlir::nvgpu;
28
29#include "mlir/Dialect/NVGPU/IR/NVGPUDialect.cpp.inc"
30
31void NVGPUDialect::initialize() {
32 addTypes<
33#define GET_TYPEDEF_LIST
34#include "mlir/Dialect/NVGPU/IR/NVGPUTypeDefs.cpp.inc"
35 >();
36 addAttributes<
37#define GET_ATTRDEF_LIST
38#include "mlir/Dialect/NVGPU/IR/NVGPUAttrDefs.cpp.inc"
39 >();
40 addOperations<
41#define GET_OP_LIST
42#include "mlir/Dialect/NVGPU/IR/NVGPUOps.cpp.inc"
43 >();
44 declarePromisedInterfaces<memref::IndexedAccessOpInterface, LdMatrixOp>();
45 declarePromisedInterfaces<memref::IndexedMemCopyOpInterface,
46 DeviceAsyncCopyOp>();
47}
48
49bool NVGPUDialect::isSharedMemoryAddressSpace(Attribute memorySpace) {
50 if (!memorySpace)
51 return false;
52 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(memorySpace))
53 return intAttr.getValue().getZExtValue() ==
54 NVGPUDialect::kSharedMemoryAddressSpace;
55 if (auto gpuAttr = llvm::dyn_cast<gpu::AddressSpaceAttr>(memorySpace))
56 return gpuAttr.getValue() == gpu::AddressSpace::Workgroup;
57 return false;
58}
59
60bool NVGPUDialect::hasSharedMemoryAddressSpace(MemRefType type) {
61 Attribute memorySpace = type.getMemorySpace();
62 return isSharedMemoryAddressSpace(memorySpace);
63}
64
65//===----------------------------------------------------------------------===//
66// NVGPU_DeviceAsyncCopyOp
67//===----------------------------------------------------------------------===//
68
69LogicalResult DeviceAsyncCopyOp::verify() {
70 auto srcMemref = llvm::cast<MemRefType>(getSrc().getType());
71 auto dstMemref = llvm::cast<MemRefType>(getDst().getType());
72
73 if (!srcMemref.isLastDimUnitStride())
74 return emitError("source memref most minor dim must have unit stride");
75 if (!dstMemref.isLastDimUnitStride())
76 return emitError("destination memref most minor dim must have unit stride");
77 if (!NVGPUDialect::hasSharedMemoryAddressSpace(dstMemref))
78 return emitError()
79 << "destination memref must have a memory space attribute of "
80 "IntegerAttr("
81 << NVGPUDialect::kSharedMemoryAddressSpace
82 << ") or gpu::AddressSpaceAttr(Workgroup)";
83 if (dstMemref.getElementType() != srcMemref.getElementType())
84 return emitError("source and destination must have the same element type");
85 if (size_t(srcMemref.getRank()) != getSrcIndices().size())
86 return emitOpError() << "expected " << srcMemref.getRank()
87 << " source indices, got " << getSrcIndices().size();
88 if (size_t(dstMemref.getRank()) != getDstIndices().size())
89 return emitOpError() << "expected " << dstMemref.getRank()
90 << " destination indices, got "
91 << getDstIndices().size();
92 int64_t dstElements = getDstElements().getZExtValue();
93 int64_t sizeInBytes = (dstMemref.getElementTypeBitWidth() * dstElements) / 8;
94 if (sizeInBytes != 4 && sizeInBytes != 8 && sizeInBytes != 16) {
95 unsigned dstWidth = dstMemref.getElementTypeBitWidth();
97 diag << "Requested copy elements is " << dstElements << " with width "
98 << dstMemref.getElementTypeBitWidth()
99 << ". But copy elements could be one of ";
100 if ((32 / dstWidth) > 0)
101 diag << (32 / dstWidth) << ", ";
102 if ((64 / dstWidth) > 0)
103 diag << (64 / dstWidth) << ", ";
104 if ((128 / dstWidth) > 0)
105 diag << (128 / dstWidth) << ".";
106 return diag;
107 }
108 if (getBypassL1().has_value()) {
109 int64_t req = 16 * 8 / dstMemref.getElementTypeBitWidth();
110 if (getBypassL1().value() && sizeInBytes != 16) {
111 return emitOpError() << "bypassL1 does not satify alignment for "
112 << dstMemref << " with destination element "
113 << dstElements
114 << ". Unset bypassL1, or set "
115 "destination element to "
116 << req;
117 }
118 }
119 return success();
120}
121
122//===----------------------------------------------------------------------===//
123// NVGPU_MmaSyncOp
124//===----------------------------------------------------------------------===//
125void MmaSyncOp::build(::mlir::OpBuilder &odsBuilder,
126 ::mlir::OperationState &odsState, Value matrixA,
127 Value matrixB, Value matrixC, ArrayAttr mmaShape) {
128 build(odsBuilder, odsState, matrixC.getType(), matrixA, matrixB, matrixC,
129 mmaShape, UnitAttr());
130}
131
132void MmaSyncOp::build(::mlir::OpBuilder &odsBuilder,
133 ::mlir::OperationState &odsState, Value matrixA,
134 Value matrixB, Value matrixC, ArrayRef<int64_t> mmaShape,
135 bool tf32Enabled) {
136 build(odsBuilder, odsState, matrixC.getType(), matrixA, matrixB, matrixC,
137 odsBuilder.getI64ArrayAttr(mmaShape),
138 tf32Enabled ? odsBuilder.getUnitAttr() : UnitAttr());
139}
140
141/// Performs verification for MmaSyncOp and MmaSparseSyncOp.
142static LogicalResult verifyMmaSyncOp(Operation *op,
146 const std::array<int64_t, 3> &mmaShape,
147 bool tf32Enabled, bool sparse = false) {
148 // The verification for mma.sync covering various shapes and data types is
149 // based on the fundamental tensor core shape.
150
151 // "Fundamental" tensor core shapes:
152 // - For F32 (TF32), F16, S8, and S4 data
153 // types the fundamental tensor core operation is of shape 8-by-8-by-128b.
154 // - F64 is an exception and is of shape 8-by-8-by-256b.
155 int64_t shapeM = 8;
156 int64_t shapeN = 8;
157 int64_t shapeK; // set based on data type (128b for all data types except F64)
158
159 // Number of elements A, B, and C per thread per fundamental tensor core tile
160 int64_t numElementA; // set based on data type (32b except F64)
161 int64_t numElementB; // set based on data type (32b except F64)
162 int64_t numElementC{2}; // two accumulator elements per fundamental tile
163
164 // nvgpu.mma.sync vector operands (per thread)
165 auto aVector = matrixA.getType();
166 auto bVector = matrixB.getType();
167 auto cVector = matrixC.getType();
168
169 // vector shapes
170 ArrayRef<int64_t> aShape = aVector.getShape();
171 ArrayRef<int64_t> bShape = bVector.getShape();
172 ArrayRef<int64_t> cShape = cVector.getShape();
173
174 // vector element type
175 Type aType = aVector.getElementType();
176
177 // Certain data types are not allowed in sparse mode.
178 if (sparse && aType.isF64())
179 return op->emitError() << "f64 is not supported for sparse mode";
180
181 if (aType.isF64()) {
182 // exception to 8-by-8-128b fundamental tensor core tile size
183 shapeK = 4;
184 numElementA = 1;
185 numElementB = 1;
186 } else if (aType.isF32() || aType.isBF16() || aType.isF16() ||
187 aType.isInteger(8) || aType.isInteger(4) || aType.isF8E4M3FN() ||
188 aType.isF8E5M2()) {
189 // 8-by-8-128b fundamental tensor core tile size
190 int operandBitwidth = aType.getIntOrFloatBitWidth();
191 shapeK = 128 / operandBitwidth; // 128b wide shapeK
192
193 numElementA = 32 / operandBitwidth; // 32b wide operand A
194 numElementB = 32 / operandBitwidth; // 32b wide operand B
195 } else {
196 return op->emitError()
197 << "expected input data type (i4,i8,f16,bf16,tf32,f64,"
198 "f8E4M3FN,f8E5M2) supported by "
199 << op->getName();
200 }
201
202 //
203 // Basic verification
204 //
205
206 if (aShape.size() != 2) {
207 return op->emitError() << "matrixA must be 2 dimensional vector";
208 }
209
210 if (bShape.size() != 2) {
211 return op->emitError() << "matrixB must be 2 dimensional vector";
212 }
213
214 if (cShape.size() != 2) {
215 return op->emitError() << "matrixC must be 2 dimensional vector";
216 }
217
218 auto [m, n, k] = mmaShape;
219
220 // verify warp-wide size for vector a
221 int64_t sparseFactor = sparse ? 2 : 1;
222 if (aShape[0] * aShape[1] * kWarpSize != m * k / sparseFactor)
223 return op->emitOpError()
224 << "expected " << m * k << " warp-wide matrix A elements";
225
226 // verify warp-wide size for vector b
227 if (bShape[0] * bShape[1] * kWarpSize != k * n)
228 return op->emitOpError()
229 << "expected " << k * n << " warp-wide matrix B elements";
230
231 // verify warp-wide size for vector c
232 if (cShape[0] * cShape[1] * kWarpSize != m * n)
233 return op->emitOpError()
234 << "expected " << m * n << " warp-wide matrix C elements";
235
236 // verify tf32 tensor cores are enabled for only F32 datatype
237 if (tf32Enabled && !(aType.isF32()))
238 return op->emitOpError()
239 << "expected tf32 tensor cores only for F32 operands";
240
241 //
242 // Extended verification
243 //
244
245 // tiles of fundamental tensor core operations
246 int64_t mTile = m / shapeM;
247 int64_t nTile = n / shapeN;
248 int64_t kTile = k / shapeK;
249
250 // verify shape of aVector
251 if ((aShape[0] != mTile * kTile / (sparse ? 2 : 1)) ||
252 (aShape[1] != numElementA))
253 return op->emitOpError() << "expected matrix A to be shaped ("
254 << mTile * kTile << " x " << numElementA << ")";
255
256 // verify shape of bVector
257 if ((bShape[0] != kTile * nTile) || (bShape[1] != numElementB))
258 return op->emitOpError() << "expected matrix B to be shaped ("
259 << kTile * nTile << " x " << numElementB << ")";
260
261 // verify shape of cVector
262 if ((cShape[0] != mTile * nTile) || (cShape[1] != numElementC))
263 return op->emitOpError() << "expected matrix C to be shaped ("
264 << mTile * nTile << " x " << numElementC << ")";
265
266 return success();
267}
268
269LogicalResult MmaSyncOp::verify() {
270 if (getMmaShape().size() != 3)
271 return emitOpError() << "mmaShape must have exactly 3 elements";
272
273 return verifyMmaSyncOp(this->getOperation(), getMatrixA(), getMatrixB(),
274 getMatrixC(), getMmaShapeAsArray(),
275 getOperation()->hasAttr(getTf32EnabledAttrName()));
276}
277
278//===----------------------------------------------------------------------===//
279// NVGPU_MmaSparseSyncOp
280//===----------------------------------------------------------------------===//
281void MmaSparseSyncOp::build(::mlir::OpBuilder &odsBuilder,
282 ::mlir::OperationState &odsState, Value matrixA,
283 Value matrixB, Value matrixC, Value sparseMetadata,
284 ArrayRef<int64_t> mmaShape) {
285 build(odsBuilder, odsState, matrixC.getType(), matrixA, matrixB, matrixC,
286 sparseMetadata, odsBuilder.getI64ArrayAttr(mmaShape), 0, UnitAttr());
287}
288
289LogicalResult MmaSparseSyncOp::verify() {
290 unsigned sparsitySelector = getSparsitySelector();
291 if (sparsitySelector > 1)
292 return emitOpError() << "sparsity selector should be 0 or 1";
293
294 if (getMmaShape().size() != 3)
295 return emitOpError() << "mmaShape must have exactly 3 elements";
296
297 return verifyMmaSyncOp(this->getOperation(), getMatrixA(), getMatrixB(),
298 getMatrixC(), getMmaShapeAsArray(),
299 getOperation()->hasAttr(getTf32EnabledAttrName()),
300 true);
301}
302
303//===----------------------------------------------------------------------===//
304// NVGPU_LdMatrixOp
305//===----------------------------------------------------------------------===//
306LogicalResult LdMatrixOp::verify() {
307 // ldmatrix reads data from source in shared memory
308 auto srcMemref = llvm::cast<MemRefType>(getSrcMemref().getType());
309
310 // ldmatrix writes data to result/destination in vector registers
311 auto resVector = llvm::cast<VectorType>(getRes().getType());
312
313 // vector register shape, element type, and bitwidth
314 ArrayRef<int64_t> resShape = resVector.getShape();
315 Type resType = resVector.getElementType();
316 int64_t elementBitWidth = resType.getIntOrFloatBitWidth();
317
318 // ldmatrix loads 32 bits into vector registers per 8-by-8 tile per thread
319 int64_t numElementsPer32b = 32 / elementBitWidth;
320
321 // number of 8-by-8 tiles
322 int64_t numTiles = getNumTiles();
323
324 // transpose elements in vector registers at 16b granularity when true
325 bool isTranspose = getTranspose();
326
327 //
328 // verification
329 //
330
331 if (!NVGPUDialect::hasSharedMemoryAddressSpace(srcMemref))
332 return emitError()
333 << "expected nvgpu.ldmatrix srcMemref must have a memory space "
334 "attribute of IntegerAttr("
335 << NVGPUDialect::kSharedMemoryAddressSpace
336 << ") or gpu::AddressSpaceAttr(Workgroup)";
337 if (elementBitWidth > 32)
338 return emitError() << "nvgpu.ldmatrix works for 32b or lower";
339 if (isTranspose && !(elementBitWidth == 16))
340 return emitError()
341 << "nvgpu.ldmatrix transpose works only at 16b granularity";
342 if (resShape.size() != 2) {
343 return emitError() << "results must be 2 dimensional vector";
344 }
345 if (!(resShape[1] == numElementsPer32b))
346 return emitError() << "expected vector register shape[1] = "
347 << numElementsPer32b;
348 if (!(resShape[0] == numTiles))
349 return emitError()
350 << "expected vector register shape[0] and numTiles to match";
351
352 return success();
353}
354
355//===----------------------------------------------------------------------===//
356// NVGPU_TmaAsyncLoadOp
357//===----------------------------------------------------------------------===//
358
359static unsigned getSwizzleBytes(TensorMapSwizzleKind kind) {
360 switch (kind) {
361 case TensorMapSwizzleKind::SWIZZLE_32B:
362 return 32;
363 case TensorMapSwizzleKind::SWIZZLE_64B:
364 return 64;
365 case TensorMapSwizzleKind::SWIZZLE_128B:
366 return 128;
367 default:
368 return 0;
369 }
370}
371
372std::optional<InFlightDiagnostic> verifyTmaDescriptorWithMemref(
373 Operation *op, TensorMapDescriptorType descType,
374 std::optional<MemRefType> memrefType = std::nullopt) {
375 MemRefType descMemref = descType.getTensor();
376 // Limitation
377 if (descType.getInterleave() != TensorMapInterleaveKind::INTERLEAVE_NONE)
378 return op->emitError() << "Interleave options are not supported yet.";
379
380 // Address space check for shared memory check
381 if (!NVGPUDialect::hasSharedMemoryAddressSpace(descMemref)) {
382 return op->emitError() << "the tensor map descriptor has incorrect address "
383 "space, it must be shared memory address space.";
384 }
385 // Support only static shape for the time being
386 if (!descMemref.hasStaticShape())
387 return op->emitError() << "the tensor map descriptor must be static shaped";
388
389 for (auto dim : descMemref.getShape()) {
390 if (dim <= 0 || dim > kMaxTMADimension) {
391 return op->emitError() << "the tensor map descriptor must have "
392 "dimensions between 1 and "
393 << kMaxTMADimension << " but it is " << dim;
394 }
395 }
396 if (descMemref.getRank() > 1 &&
397 descType.getSwizzle() != TensorMapSwizzleKind::SWIZZLE_NONE) {
398 unsigned lastDimensionByte =
399 descMemref.getElementTypeBitWidth() * descMemref.getShape().back() / 8;
400 unsigned expectByte = getSwizzleBytes(descType.getSwizzle());
401 if (lastDimensionByte != expectByte)
402 return op->emitError() << "the tensormap descriptor must have last "
403 "dimension of "
404 << expectByte << " bytes but it is "
405 << lastDimensionByte << " bytes";
406 }
407
408 // No verification if memref type is not provided
409 if (!memrefType.has_value())
410 return std::nullopt;
411
412 MemRefType dstMemref = memrefType.value();
413
414 // Check element type
415 if (descMemref.getElementType() != dstMemref.getElementType()) {
416 return op->emitError() << "the element type of tensor map descriptor and "
417 "memref must be same";
418 }
419
420 if (!NVGPUDialect::hasSharedMemoryAddressSpace(dstMemref)) {
421 return op->emitError() << "the destination memref has incorrect address "
422 "space, it must be shared memory address space.";
423 }
424 if (!dstMemref.hasStaticShape())
425 return op->emitError() << "the destination memref must be static shaped";
426
427 if (dstMemref.getRank() != descMemref.getRank()) {
428 return op->emitError() << "the shape of tensor map descriptor and "
429 "memref must have same rank";
430 }
431 if (!descMemref.getShape().equals(dstMemref.getShape())) {
432 return op->emitError() << "memref and tensor map shapes mismatch "
433 << descMemref << " != " << dstMemref;
434 }
435
436 int lastDimBytes =
437 descMemref.getShape().back() * descMemref.getElementTypeBitWidth() / 8;
438 if (lastDimBytes % kTMALastdimByte != 0) {
439 return op->emitError() << "the bytes in the last dimension of the tensor "
440 "map must be a multiple of 16";
441 }
442 return std::nullopt;
443}
444
445LogicalResult TmaAsyncLoadOp::verify() {
446 std::optional<InFlightDiagnostic> error = verifyTmaDescriptorWithMemref(
447 *this, getTensorMapDescriptor().getType(), getDst().getType());
448 if (error.has_value())
449 return error.value();
450
451 if (getCoordinates().size() > kMaxTMATensorDimension) {
452 return emitError() << "Maximum " << kMaxTMATensorDimension
453 << " coordinates are supported.";
454 }
455 if (getCoordinates().size() !=
456 size_t(getTensorMapDescriptor().getType().getTensor().getRank())) {
457 return emitError() << "number of coordinates do not match with the rank of "
458 "tensor descriptor map.";
459 }
460
461 return success();
462}
463
464//===----------------------------------------------------------------------===//
465// NVGPU_TmaAsyncStoreOp
466//===----------------------------------------------------------------------===//
467
468LogicalResult TmaAsyncStoreOp::verify() {
469 std::optional<InFlightDiagnostic> error = verifyTmaDescriptorWithMemref(
470 *this, getTensorMapDescriptor().getType(), getSrc().getType());
471 if (error.has_value())
472 return error.value();
473
474 if (getCoordinates().size() > kMaxTMATensorDimension) {
475 return emitError() << "Maximum " << kMaxTMATensorDimension
476 << " coordinates are supported.";
477 }
478 if (getCoordinates().size() !=
479 size_t(getTensorMapDescriptor().getType().getTensor().getRank())) {
480 return emitError() << "number of coordinates do not match with the rank of "
481 "tensor descriptor map.";
482 }
483
484 return success();
485}
486
487LogicalResult TmaCreateDescriptorOp::verify() {
488 if (getBoxDimensions().size() > kMaxTMATensorDimension) {
489 return emitError() << "Maximum " << kMaxTMATensorDimension
490 << " coordinates are supported.";
491 }
492
493 std::optional<InFlightDiagnostic> error =
494 verifyTmaDescriptorWithMemref(*this, getTensorMap().getType());
495 if (error.has_value())
496 return error.value();
497
498 return success();
499}
500
501//===----------------------------------------------------------------------===//
502// NVGPU_WarpgroupGenerateDescriptorOp
503//===----------------------------------------------------------------------===//
504
505LogicalResult WarpgroupGenerateDescriptorOp::verify() {
506 std::optional<InFlightDiagnostic> error =
507 verifyTmaDescriptorWithMemref(*this, getTensorMap().getType());
508 if (error.has_value())
509 return error.value();
510
511 if (getTensorMap().getType().getSwizzle() !=
512 TensorMapSwizzleKind::SWIZZLE_128B) {
513 return emitError() << "supports only "
514 << stringifyTensorMapSwizzleKind(
515 TensorMapSwizzleKind::SWIZZLE_128B)
516 << " is supported for the time being";
517 }
518
519 if (getTensorMap().getType().getInterleave() !=
520 TensorMapInterleaveKind::INTERLEAVE_NONE) {
521 return emitError() << "supports only "
522 << stringifyTensorMapInterleaveKind(
523 TensorMapInterleaveKind::INTERLEAVE_NONE)
524 << " is supported for the time being";
525 }
526
527 return success();
528}
529
530//===----------------------------------------------------------------------===//
531// WarpgroupMmaOp
532//===----------------------------------------------------------------------===//
533
534LogicalResult isAllowedWGMMADataType(Type typeD, Type typeA, Type typeB) {
535 // F32 += F16 + F16
536 // F16 += F16 + F16
537 if (typeA.isF16() && typeB.isF16() && (typeD.isF32() || typeD.isF16()))
538 return success();
539 // F32 += TF32 + TF32
540 if (typeA.isTF32() && typeD.isF32() && typeB.isTF32())
541 return success();
542 // s32 += i8 + i8
543 if (typeA.isInteger(16) && typeB.isInteger(16) && typeD.isInteger(32))
544 return success();
545 // s32 += i1 + i1
546 if (typeA.isInteger(1) && typeB.isInteger(1) && typeD.isInteger(32))
547 return success();
548 // F32 += BF16 + BF16
549 // F16 += BF16 + BF16
550 if (typeA.isBF16() && typeB.isBF16() && (typeD.isF32() || typeD.isF16()))
551 return success();
552 // F16 += f8 + f8
553 // F32 += f8 + f8
554 if (isa<Float8E5M2Type, Float8E4M3FNType>(typeA) &&
555 isa<Float8E5M2Type, Float8E4M3FNType>(typeB) &&
556 (typeD.isF32() || typeD.isF16()))
557 return success();
558
559 return failure();
560}
561
562LogicalResult isAllowedSizeM(int sizeM) {
563 if (sizeM % kWgmmaSizeM)
564 return failure();
565 return success();
566}
567
568LogicalResult isAllowedSizeN(int sizeN, Type typeA) {
569 SmallVector<int> allowedN = {8, 16, 24, 32, 40, 48, 56, 64,
570 72, 80, 88, 96, 104, 112, 120, 128,
571 136, 144, 152, 160, 168, 176, 184, 192,
572 200, 208, 216, 224, 232, 240, 248, 256};
573 SmallVector<int> allowedNshort = {8, 16, 24, 32, 48, 64,
574 80, 96, 112, 128, 144, 160,
575 176, 192, 208, 224, 240, 256};
576 if (typeA.isBF16() || typeA.isF16() || typeA.isF32() || typeA.isTF32() ||
577 isa<Float8E5M2Type, Float8E4M3FNType>(typeA))
578 if (llvm::is_contained(allowedN, sizeN))
579 return success();
580
581 if (typeA.isInteger(8) || typeA.isInteger(1))
582 if (llvm::is_contained(allowedNshort, sizeN))
583 return success();
584 return failure();
585}
586
587LogicalResult WarpgroupMmaOp::verify() {
588 if (getTransposeA() && !getTransposeB())
589 return emitOpError()
590 << "supports non-transpose A (Row Major) "
591 "and transpose B (Column Major) for the time being ";
592 MemRefType matrixA = getDescriptorA().getType().getTensor();
593 MemRefType matrixB = getDescriptorB().getType().getTensor();
594 VectorType matrixC = getMatrixC().getType().getFragmented();
595 VectorType matrixD = getMatrixD().getType().getFragmented();
596
597 if (matrixC != matrixD)
598 return emitOpError() << "type of matrix C and matrix D must be the same";
599
600 if (matrixA.getRank() != 2 || matrixB.getRank() != 2 ||
601 matrixC.getRank() != 2 || matrixD.getRank() != 2) {
602 return emitOpError()
603 << "has matrices A, B, C and D, they must be 2 dimensional";
604 }
605
606 if (matrixA.getShape()[1] != matrixB.getShape()[0])
607 return emitOpError() << "2nd dim matrix-A (" << matrixA.getShape()[1]
608 << ")!= 1st dim matrix-B (" << matrixB.getShape()[0]
609 << " )";
610 if (matrixA.getShape()[0] != matrixC.getShape()[0])
611 return emitOpError() << "1st dim matrix-A ( " << matrixA.getShape()[0]
612 << " )!= 1st dim matrix-C ( " << matrixC.getShape()[0]
613 << " )";
614 if (matrixB.getShape()[1] != matrixC.getShape()[1])
615 return emitOpError() << "2nd dim matrix-B ( " << matrixB.getShape()[1]
616 << " ) != 2nd dim matrix-C ( " << matrixC.getShape()[1]
617 << " )";
618
619 if (failed(isAllowedWGMMADataType(matrixC.getElementType(),
620 matrixA.getElementType(),
621 matrixB.getElementType())))
622 return emitOpError() << matrixC.getElementType()
623 << " += " << matrixA.getElementType() << " * "
624 << matrixB.getElementType()
625 << ", it is not supported.";
626 // Check N
627 if (failed(isAllowedSizeN(matrixB.getDimSize(1), matrixA.getElementType()))) {
628 return emitOpError() << "has input type " << matrixB << " n is set to "
629 << matrixB.getDimSize(1) << ", it is not supported";
630 }
631
632 // Currently, f16/bf16 supported
633 if (!matrixC.getElementType().isF32() && !matrixA.getElementType().isF16() &&
634 !matrixA.getElementType().isBF16()) {
635 return emitOpError() << "hit a limitation: " << matrixC.getElementType()
636 << " += " << matrixA.getElementType() << " * "
637 << matrixB.getElementType()
638 << ", it is not supported yet";
639 }
640
641 return success();
642}
643
644LogicalResult WarpgroupMmaStoreOp::verify() {
645 MemRefType dstMemrefType = getDstMemref().getType();
646 VectorType vtype = getMatrixD().getType().getFragmented();
647
648 // Limitation
649 if (!vtype.getElementType().isF32()) {
650 return emitOpError()
651 << "hit a limitation: only f32 results for the time being";
652 }
653 if (vtype.getDimSize(0) != dstMemrefType.getDimSize(0) ||
654 vtype.getDimSize(1) != dstMemrefType.getDimSize(1)) {
655 return emitOpError() << "results [" << vtype << "][" << vtype.getDimSize(1)
656 << "] values. However, destination memref["
657 << dstMemrefType.getDimSize(0) << "]["
658 << dstMemrefType.getDimSize(1)
659 << "] does not have same size as results";
660 }
661 return success();
662}
663
664//===----------------------------------------------------------------------===//
665// WarpgroupMmaInitAccumulatorOp
666//===----------------------------------------------------------------------===//
667
668LogicalResult WarpgroupMmaInitAccumulatorOp::verify() {
669 WarpgroupAccumulatorType accType = getMatrixC().getType();
670 int64_t sizeM = accType.getFragmented().getDimSize(0);
671 int64_t sizeN = accType.getFragmented().getDimSize(1);
672 Type elemType = accType.getFragmented().getElementType();
673
674 if (failed(isAllowedSizeM(sizeM)) ||
675 failed(isAllowedSizeN(sizeN, elemType))) {
676 return emitOpError() << "has type " << accType.getFragmented()
677 << ". It does not fit into warp-group "
678 "level (wgmma) matrix multiplication instruction "
679 "(or not supported yet)";
680 }
681 return success();
682}
683
684//===----------------------------------------------------------------------===//
685// RcpOp
686//===----------------------------------------------------------------------===//
687
688LogicalResult RcpOp::verify() {
689 bool ftz = getFtz();
690 bool approx = getApprox();
691 mlir::NVVM::FPRoundingModeAttr rnd = getRoundingAttr();
692 // Currently, only `rcp_approx` and `ftz` is supported.
693 if (!approx || !ftz) {
694 return emitOpError()
695 << "has a limitation. non-approx or non-ftz is not supported yet.";
696 }
697 if (rnd.getValue() != mlir::NVVM::FPRoundingMode::NONE) {
698 return emitOpError() << "has a limitation. " << rnd
699 << " is not supported yet.";
700 }
701 return success();
702}
703
704//===----------------------------------------------------------------------===//
705// NVGPU_TruncfOp
706//===----------------------------------------------------------------------===//
707
708static LogicalResult verifyConversionShapes(Operation *op, Type inType,
709 Type outType) {
710 bool srcIsVector = llvm::isa<VectorType>(inType);
711 bool dstIsVector = llvm::isa<VectorType>(outType);
712 if (srcIsVector != dstIsVector)
713 return op->emitOpError("input and output must both be scalars or both be "
714 "vectors, got ")
715 << inType << " and " << outType;
716 if (srcIsVector) {
717 auto srcVector = llvm::cast<VectorType>(inType);
718 auto dstVector = llvm::cast<VectorType>(outType);
719 if (srcVector.getShape() != dstVector.getShape())
720 return op->emitOpError("input and output shapes must match, got ")
721 << inType << " and " << outType;
722 }
723 return success();
724}
725
726LogicalResult TruncfOp::verify() {
727 Type inType = getIn().getType();
728 Type outType = getType();
729 Type srcType = getElementTypeOrSelf(inType);
730 Type dstType = getElementTypeOrSelf(outType);
731 int srcBitWidth = srcType.getIntOrFloatBitWidth();
732 int dstBitWidth = dstType.getIntOrFloatBitWidth();
733 auto rnd = getRnd();
734
735 if (auto result = verifyConversionShapes(getOperation(), inType, outType);
736 failed(result))
737 return result;
738
739 if (srcBitWidth <= dstBitWidth)
740 return emitOpError("result type ")
741 << dstType << " must be narrower than operand type " << srcType;
742
743 if (!(srcBitWidth == 64 || srcBitWidth == 32 || srcBitWidth == 16))
744 return emitOpError("input type must be 64/32/16 bitwidth, but got ")
745 << srcBitWidth;
746
747 if (llvm::isa<Float8E8M0FNUType>(dstType)) {
748 if (rnd != mlir::NVVM::FPRoundingMode::RZ &&
749 rnd != mlir::NVVM::FPRoundingMode::RP)
750 return emitOpError("expects RZ or RP rounding mode when result type is "
751 "e8m0, but got ")
752 << getRndAttr();
753 } else if (rnd == mlir::NVVM::FPRoundingMode::RS) {
754 // TODO: Currently, we only support conversions which fit into a single i32
755 // register. Support f32->f8/f6/f4 conversions with RS rounding.
756 if (!(srcBitWidth == 32 && dstBitWidth == 16))
757 return emitOpError("RS (stochastic) rounding is only supported for "
758 "f32->f16/bf16, got ")
759 << srcType << " -> " << dstType;
760 if (!getRandomBits())
761 return emitOpError("random_bits operand is required with RS rounding");
762 } else if (srcType.isF64() && dstBitWidth >= 16) {
763 if (rnd != mlir::NVVM::FPRoundingMode::RN)
764 return emitOpError("expects RN rounding mode for f64 input, but got ")
765 << getRndAttr();
766 } else if (srcBitWidth == 32 && dstBitWidth == 16) {
767 if (rnd != mlir::NVVM::FPRoundingMode::RN &&
768 rnd != mlir::NVVM::FPRoundingMode::RZ)
769 return emitOpError("expects RN or RZ rounding mode for f32 to f16/bf16, "
770 "but got ")
771 << getRndAttr();
772 } else if (rnd != mlir::NVVM::FPRoundingMode::RN) {
773 return emitOpError("expects RN rounding mode, but got ") << getRndAttr();
774 }
775
776 if (getRandomBits() && rnd != mlir::NVVM::FPRoundingMode::RS)
777 return emitOpError("random_bits can only be used with RS rounding mode");
778
779 return success();
780}
781
782//===----------------------------------------------------------------------===//
783// NVGPU_ExtfOp
784//===----------------------------------------------------------------------===//
785
786LogicalResult ExtfOp::verify() {
787 Type inType = getIn().getType();
788 Type outType = getType();
789 Type srcType = getElementTypeOrSelf(inType);
790 Type dstType = getElementTypeOrSelf(outType);
791 int srcBitWidth = srcType.getIntOrFloatBitWidth();
792 int dstBitWidth = dstType.getIntOrFloatBitWidth();
793 auto rnd = getRnd();
794
795 if (failed(verifyConversionShapes(getOperation(), inType, outType)))
796 return failure();
797
798 if (srcBitWidth >= dstBitWidth)
799 return emitOpError("result type ")
800 << dstType << " must be wider than operand type " << srcType;
801
802 if (dstBitWidth != 16 && dstBitWidth != 32 && dstBitWidth != 64)
803 return emitOpError("result type must be 16, 32, or 64 bitwidth, but got ")
804 << dstBitWidth;
805
806 if (llvm::isa<Float8E8M0FNUType>(srcType) &&
807 !llvm::isa<BFloat16Type>(dstType) && !dstType.isF32())
808 return emitOpError("expects bf16 or f32 output type when input type is "
809 "e8m0.");
810
811 if (rnd != mlir::NVVM::FPRoundingMode::RN)
812 return emitOpError("expects RN rounding mode, but got ") << getRndAttr();
813
814 if (getRelu() && llvm::isa<BFloat16Type>(dstType))
815 return emitOpError("relu is not supported for bf16 destination");
816
817 return success();
818}
819
820//===----------------------------------------------------------------------===//
821// TableGen'd dialect, type, and op definitions
822//===----------------------------------------------------------------------===//
823
824#define GET_ATTRDEF_CLASSES
825#include "mlir/Dialect/NVGPU/IR/NVGPUAttrDefs.cpp.inc"
826
827#include "mlir/Dialect/NVGPU/IR/NVGPUEnums.cpp.inc"
828
829#define GET_OP_CLASSES
830#include "mlir/Dialect/NVGPU/IR/NVGPUOps.cpp.inc"
831
832#define GET_TYPEDEF_CLASSES
833#include "mlir/Dialect/NVGPU/IR/NVGPUTypeDefs.cpp.inc"
return success()
p<< " : "<< getMemRefType()<< ", "<< getType();}static LogicalResult verifyVectorMemoryOp(Operation *op, MemRefType memrefType, VectorType vectorType) { if(memrefType.getElementType() !=vectorType.getElementType()) return op-> emitOpError("requires memref and vector types of the same elemental type")
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
ArrayAttr()
static std::string diag(const llvm::Value &value)
LogicalResult isAllowedSizeM(int sizeM)
static LogicalResult verifyMmaSyncOp(Operation *op, TypedValue< VectorType > matrixA, TypedValue< VectorType > matrixB, TypedValue< VectorType > matrixC, const std::array< int64_t, 3 > &mmaShape, bool tf32Enabled, bool sparse=false)
Performs verification for MmaSyncOp and MmaSparseSyncOp.
std::optional< InFlightDiagnostic > verifyTmaDescriptorWithMemref(Operation *op, TensorMapDescriptorType descType, std::optional< MemRefType > memrefType=std::nullopt)
LogicalResult isAllowedSizeN(int sizeN, Type typeA)
LogicalResult isAllowedWGMMADataType(Type typeD, Type typeA, Type typeB)
static LogicalResult verifyConversionShapes(Operation *op, Type inType, Type outType)
static unsigned getSwizzleBytes(TensorMapSwizzleKind kind)
constexpr unsigned kTMALastdimByte
The bytes in the last dimension of the tensor map must be a multiple of 16.
constexpr int kWgmmaSizeM
M size of wgmma.mma_async instruction.
constexpr int kWarpSize
constexpr unsigned kMaxTMATensorDimension
Maximum TMA tile dimension (tensorRank) must be non-zero and less than or equal to the maximum suppor...
constexpr unsigned kMaxTMADimension
Maximum TMA tile size (boxDim), which specifies number of elements to be traversed along each of the ...
Attributes are known-constant values of operations.
Definition Attributes.h:25
UnitAttr getUnitAttr()
Definition Builders.cpp:106
ArrayAttr getI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:290
This class represents a diagnostic that is inflight and set to be reported.
This class helps build Operations.
Definition Builders.h:210
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...
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isF64() const
Definition Types.cpp:41
bool isTF32() const
Definition Types.cpp:39
bool isF8E5M2() const
Definition Types.cpp:45
bool isF8E4M3FN() const
Definition Types.cpp:44
bool isF32() const
Definition Types.cpp:40
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
bool isF16() const
Definition Types.cpp:38
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
bool isBF16() const
Definition Types.cpp:37
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
SmallVector< int64_t, 4 > getCoordinates(ArrayRef< int64_t > basis, unsigned linearIndex)
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
std::conditional_t< std::is_same_v< Ty, mlir::Type >, mlir::Value, detail::TypedValue< Ty > > TypedValue
If Ty is mlir::Type this will select Value instead of having a wrapper around it.
Definition Value.h:494
This represents an operation in an abstracted form, suitable for use with the builder APIs.