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 getTf32Enabled().value_or(false));
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 getTf32Enabled().value_or(false), true);
300}
301
302//===----------------------------------------------------------------------===//
303// NVGPU_LdMatrixOp
304//===----------------------------------------------------------------------===//
305LogicalResult LdMatrixOp::verify() {
306 // ldmatrix reads data from source in shared memory
307 auto srcMemref = llvm::cast<MemRefType>(getSrcMemref().getType());
308
309 // ldmatrix writes data to result/destination in vector registers
310 auto resVector = llvm::cast<VectorType>(getRes().getType());
311
312 // vector register shape, element type, and bitwidth
313 ArrayRef<int64_t> resShape = resVector.getShape();
314 Type resType = resVector.getElementType();
315 int64_t elementBitWidth = resType.getIntOrFloatBitWidth();
316
317 // ldmatrix loads 32 bits into vector registers per 8-by-8 tile per thread
318 int64_t numElementsPer32b = 32 / elementBitWidth;
319
320 // number of 8-by-8 tiles
321 int64_t numTiles = getNumTiles();
322
323 // transpose elements in vector registers at 16b granularity when true
324 bool isTranspose = getTranspose();
325
326 //
327 // verification
328 //
329
330 if (!NVGPUDialect::hasSharedMemoryAddressSpace(srcMemref))
331 return emitError()
332 << "expected nvgpu.ldmatrix srcMemref must have a memory space "
333 "attribute of IntegerAttr("
334 << NVGPUDialect::kSharedMemoryAddressSpace
335 << ") or gpu::AddressSpaceAttr(Workgroup)";
336 if (elementBitWidth > 32)
337 return emitError() << "nvgpu.ldmatrix works for 32b or lower";
338 if (isTranspose && !(elementBitWidth == 16))
339 return emitError()
340 << "nvgpu.ldmatrix transpose works only at 16b granularity";
341 if (resShape.size() != 2) {
342 return emitError() << "results must be 2 dimensional vector";
343 }
344 if (!(resShape[1] == numElementsPer32b))
345 return emitError() << "expected vector register shape[1] = "
346 << numElementsPer32b;
347 if (!(resShape[0] == numTiles))
348 return emitError()
349 << "expected vector register shape[0] and numTiles to match";
350
351 return success();
352}
353
354//===----------------------------------------------------------------------===//
355// NVGPU_TmaAsyncLoadOp
356//===----------------------------------------------------------------------===//
357
358static unsigned getSwizzleBytes(TensorMapSwizzleKind kind) {
359 switch (kind) {
360 case TensorMapSwizzleKind::SWIZZLE_32B:
361 return 32;
362 case TensorMapSwizzleKind::SWIZZLE_64B:
363 return 64;
364 case TensorMapSwizzleKind::SWIZZLE_128B:
365 return 128;
366 default:
367 return 0;
368 }
369}
370
371std::optional<InFlightDiagnostic> verifyTmaDescriptorWithMemref(
372 Operation *op, TensorMapDescriptorType descType,
373 std::optional<MemRefType> memrefType = std::nullopt) {
374 MemRefType descMemref = descType.getTensor();
375 // Limitation
376 if (descType.getInterleave() != TensorMapInterleaveKind::INTERLEAVE_NONE)
377 return op->emitError() << "Interleave options are not supported yet.";
378
379 // Address space check for shared memory check
380 if (!NVGPUDialect::hasSharedMemoryAddressSpace(descMemref)) {
381 return op->emitError() << "the tensor map descriptor has incorrect address "
382 "space, it must be shared memory address space.";
383 }
384 // Support only static shape for the time being
385 if (!descMemref.hasStaticShape())
386 return op->emitError() << "the tensor map descriptor must be static shaped";
387
388 for (auto dim : descMemref.getShape()) {
389 if (dim <= 0 || dim > kMaxTMADimension) {
390 return op->emitError() << "the tensor map descriptor must have "
391 "dimensions between 1 and "
392 << kMaxTMADimension << " but it is " << dim;
393 }
394 }
395 if (descMemref.getRank() > 1 &&
396 descType.getSwizzle() != TensorMapSwizzleKind::SWIZZLE_NONE) {
397 unsigned lastDimensionByte =
398 descMemref.getElementTypeBitWidth() * descMemref.getShape().back() / 8;
399 unsigned expectByte = getSwizzleBytes(descType.getSwizzle());
400 if (lastDimensionByte != expectByte)
401 return op->emitError() << "the tensormap descriptor must have last "
402 "dimension of "
403 << expectByte << " bytes but it is "
404 << lastDimensionByte << " bytes";
405 }
406
407 // No verification if memref type is not provided
408 if (!memrefType.has_value())
409 return std::nullopt;
410
411 MemRefType dstMemref = memrefType.value();
412
413 // Check element type
414 if (descMemref.getElementType() != dstMemref.getElementType()) {
415 return op->emitError() << "the element type of tensor map descriptor and "
416 "memref must be same";
417 }
418
419 if (!NVGPUDialect::hasSharedMemoryAddressSpace(dstMemref)) {
420 return op->emitError() << "the destination memref has incorrect address "
421 "space, it must be shared memory address space.";
422 }
423 if (!dstMemref.hasStaticShape())
424 return op->emitError() << "the destination memref must be static shaped";
425
426 if (dstMemref.getRank() != descMemref.getRank()) {
427 return op->emitError() << "the shape of tensor map descriptor and "
428 "memref must have same rank";
429 }
430 if (!descMemref.getShape().equals(dstMemref.getShape())) {
431 return op->emitError() << "memref and tensor map shapes mismatch "
432 << descMemref << " != " << dstMemref;
433 }
434
435 int lastDimBytes =
436 descMemref.getShape().back() * descMemref.getElementTypeBitWidth() / 8;
437 if (lastDimBytes % kTMALastdimByte != 0) {
438 return op->emitError() << "the bytes in the last dimension of the tensor "
439 "map must be a multiple of 16";
440 }
441 return std::nullopt;
442}
443
444LogicalResult TmaAsyncLoadOp::verify() {
445 std::optional<InFlightDiagnostic> error = verifyTmaDescriptorWithMemref(
446 *this, getTensorMapDescriptor().getType(), getDst().getType());
447 if (error.has_value())
448 return error.value();
449
450 if (getCoordinates().size() > kMaxTMATensorDimension) {
451 return emitError() << "Maximum " << kMaxTMATensorDimension
452 << " coordinates are supported.";
453 }
454 if (getCoordinates().size() !=
455 size_t(getTensorMapDescriptor().getType().getTensor().getRank())) {
456 return emitError() << "number of coordinates do not match with the rank of "
457 "tensor descriptor map.";
458 }
459
460 return success();
461}
462
463//===----------------------------------------------------------------------===//
464// NVGPU_TmaAsyncStoreOp
465//===----------------------------------------------------------------------===//
466
467LogicalResult TmaAsyncStoreOp::verify() {
468 std::optional<InFlightDiagnostic> error = verifyTmaDescriptorWithMemref(
469 *this, getTensorMapDescriptor().getType(), getSrc().getType());
470 if (error.has_value())
471 return error.value();
472
473 if (getCoordinates().size() > kMaxTMATensorDimension) {
474 return emitError() << "Maximum " << kMaxTMATensorDimension
475 << " coordinates are supported.";
476 }
477 if (getCoordinates().size() !=
478 size_t(getTensorMapDescriptor().getType().getTensor().getRank())) {
479 return emitError() << "number of coordinates do not match with the rank of "
480 "tensor descriptor map.";
481 }
482
483 return success();
484}
485
486LogicalResult TmaCreateDescriptorOp::verify() {
487 if (getBoxDimensions().size() > kMaxTMATensorDimension) {
488 return emitError() << "Maximum " << kMaxTMATensorDimension
489 << " coordinates are supported.";
490 }
491
492 std::optional<InFlightDiagnostic> error =
493 verifyTmaDescriptorWithMemref(*this, getTensorMap().getType());
494 if (error.has_value())
495 return error.value();
496
497 return success();
498}
499
500//===----------------------------------------------------------------------===//
501// NVGPU_WarpgroupGenerateDescriptorOp
502//===----------------------------------------------------------------------===//
503
504LogicalResult WarpgroupGenerateDescriptorOp::verify() {
505 std::optional<InFlightDiagnostic> error =
506 verifyTmaDescriptorWithMemref(*this, getTensorMap().getType());
507 if (error.has_value())
508 return error.value();
509
510 if (getTensorMap().getType().getSwizzle() !=
511 TensorMapSwizzleKind::SWIZZLE_128B) {
512 return emitError() << "supports only "
513 << stringifyTensorMapSwizzleKind(
514 TensorMapSwizzleKind::SWIZZLE_128B)
515 << " is supported for the time being";
516 }
517
518 if (getTensorMap().getType().getInterleave() !=
519 TensorMapInterleaveKind::INTERLEAVE_NONE) {
520 return emitError() << "supports only "
521 << stringifyTensorMapInterleaveKind(
522 TensorMapInterleaveKind::INTERLEAVE_NONE)
523 << " is supported for the time being";
524 }
525
526 return success();
527}
528
529//===----------------------------------------------------------------------===//
530// WarpgroupMmaOp
531//===----------------------------------------------------------------------===//
532
533LogicalResult isAllowedWGMMADataType(Type typeD, Type typeA, Type typeB) {
534 // F32 += F16 + F16
535 // F16 += F16 + F16
536 if (typeA.isF16() && typeB.isF16() && (typeD.isF32() || typeD.isF16()))
537 return success();
538 // F32 += TF32 + TF32
539 if (typeA.isTF32() && typeD.isF32() && typeB.isTF32())
540 return success();
541 // s32 += i8 + i8
542 if (typeA.isInteger(16) && typeB.isInteger(16) && typeD.isInteger(32))
543 return success();
544 // s32 += i1 + i1
545 if (typeA.isInteger(1) && typeB.isInteger(1) && typeD.isInteger(32))
546 return success();
547 // F32 += BF16 + BF16
548 // F16 += BF16 + BF16
549 if (typeA.isBF16() && typeB.isBF16() && (typeD.isF32() || typeD.isF16()))
550 return success();
551 // F16 += f8 + f8
552 // F32 += f8 + f8
553 if (isa<Float8E5M2Type, Float8E4M3FNType>(typeA) &&
554 isa<Float8E5M2Type, Float8E4M3FNType>(typeB) &&
555 (typeD.isF32() || typeD.isF16()))
556 return success();
557
558 return failure();
559}
560
561LogicalResult isAllowedSizeM(int sizeM) {
562 if (sizeM % kWgmmaSizeM)
563 return failure();
564 return success();
565}
566
567LogicalResult isAllowedSizeN(int sizeN, Type typeA) {
568 SmallVector<int> allowedN = {8, 16, 24, 32, 40, 48, 56, 64,
569 72, 80, 88, 96, 104, 112, 120, 128,
570 136, 144, 152, 160, 168, 176, 184, 192,
571 200, 208, 216, 224, 232, 240, 248, 256};
572 SmallVector<int> allowedNshort = {8, 16, 24, 32, 48, 64,
573 80, 96, 112, 128, 144, 160,
574 176, 192, 208, 224, 240, 256};
575 if (typeA.isBF16() || typeA.isF16() || typeA.isF32() || typeA.isTF32() ||
576 isa<Float8E5M2Type, Float8E4M3FNType>(typeA))
577 if (llvm::is_contained(allowedN, sizeN))
578 return success();
579
580 if (typeA.isInteger(8) || typeA.isInteger(1))
581 if (llvm::is_contained(allowedNshort, sizeN))
582 return success();
583 return failure();
584}
585
586LogicalResult WarpgroupMmaOp::verify() {
587 if (getTransposeA() && !getTransposeB())
588 return emitOpError()
589 << "supports non-transpose A (Row Major) "
590 "and transpose B (Column Major) for the time being ";
591 MemRefType matrixA = getDescriptorA().getType().getTensor();
592 MemRefType matrixB = getDescriptorB().getType().getTensor();
593 VectorType matrixC = getMatrixC().getType().getFragmented();
594 VectorType matrixD = getMatrixD().getType().getFragmented();
595
596 if (matrixC != matrixD)
597 return emitOpError() << "type of matrix C and matrix D must be the same";
598
599 if (matrixA.getRank() != 2 || matrixB.getRank() != 2 ||
600 matrixC.getRank() != 2 || matrixD.getRank() != 2) {
601 return emitOpError()
602 << "has matrices A, B, C and D, they must be 2 dimensional";
603 }
604
605 if (matrixA.getShape()[1] != matrixB.getShape()[0])
606 return emitOpError() << "2nd dim matrix-A (" << matrixA.getShape()[1]
607 << ")!= 1st dim matrix-B (" << matrixB.getShape()[0]
608 << " )";
609 if (matrixA.getShape()[0] != matrixC.getShape()[0])
610 return emitOpError() << "1st dim matrix-A ( " << matrixA.getShape()[0]
611 << " )!= 1st dim matrix-C ( " << matrixC.getShape()[0]
612 << " )";
613 if (matrixB.getShape()[1] != matrixC.getShape()[1])
614 return emitOpError() << "2nd dim matrix-B ( " << matrixB.getShape()[1]
615 << " ) != 2nd dim matrix-C ( " << matrixC.getShape()[1]
616 << " )";
617
618 if (failed(isAllowedWGMMADataType(matrixC.getElementType(),
619 matrixA.getElementType(),
620 matrixB.getElementType())))
621 return emitOpError() << matrixC.getElementType()
622 << " += " << matrixA.getElementType() << " * "
623 << matrixB.getElementType()
624 << ", it is not supported.";
625 // Check N
626 if (failed(isAllowedSizeN(matrixB.getDimSize(1), matrixA.getElementType()))) {
627 return emitOpError() << "has input type " << matrixB << " n is set to "
628 << matrixB.getDimSize(1) << ", it is not supported";
629 }
630
631 // Currently, f16/bf16 supported
632 if (!matrixC.getElementType().isF32() && !matrixA.getElementType().isF16() &&
633 !matrixA.getElementType().isBF16()) {
634 return emitOpError() << "hit a limitation: " << matrixC.getElementType()
635 << " += " << matrixA.getElementType() << " * "
636 << matrixB.getElementType()
637 << ", it is not supported yet";
638 }
639
640 return success();
641}
642
643LogicalResult WarpgroupMmaStoreOp::verify() {
644 MemRefType dstMemrefType = getDstMemref().getType();
645 VectorType vtype = getMatrixD().getType().getFragmented();
646
647 // Limitation
648 if (!vtype.getElementType().isF32()) {
649 return emitOpError()
650 << "hit a limitation: only f32 results for the time being";
651 }
652 if (vtype.getDimSize(0) != dstMemrefType.getDimSize(0) ||
653 vtype.getDimSize(1) != dstMemrefType.getDimSize(1)) {
654 return emitOpError() << "results [" << vtype << "][" << vtype.getDimSize(1)
655 << "] values. However, destination memref["
656 << dstMemrefType.getDimSize(0) << "]["
657 << dstMemrefType.getDimSize(1)
658 << "] does not have same size as results";
659 }
660 return success();
661}
662
663//===----------------------------------------------------------------------===//
664// WarpgroupMmaInitAccumulatorOp
665//===----------------------------------------------------------------------===//
666
667LogicalResult WarpgroupMmaInitAccumulatorOp::verify() {
668 WarpgroupAccumulatorType accType = getMatrixC().getType();
669 int64_t sizeM = accType.getFragmented().getDimSize(0);
670 int64_t sizeN = accType.getFragmented().getDimSize(1);
671 Type elemType = accType.getFragmented().getElementType();
672
673 if (failed(isAllowedSizeM(sizeM)) ||
674 failed(isAllowedSizeN(sizeN, elemType))) {
675 return emitOpError() << "has type " << accType.getFragmented()
676 << ". It does not fit into warp-group "
677 "level (wgmma) matrix multiplication instruction "
678 "(or not supported yet)";
679 }
680 return success();
681}
682
683//===----------------------------------------------------------------------===//
684// RcpOp
685//===----------------------------------------------------------------------===//
686
687LogicalResult RcpOp::verify() {
688 bool ftz = getFtz();
689 bool approx = getApprox();
690 mlir::NVVM::FPRoundingModeAttr rnd = getRoundingAttr();
691 // Currently, only `rcp_approx` and `ftz` is supported.
692 if (!approx || !ftz) {
693 return emitOpError()
694 << "has a limitation. non-approx or non-ftz is not supported yet.";
695 }
696 if (rnd.getValue() != mlir::NVVM::FPRoundingMode::NONE) {
697 return emitOpError() << "has a limitation. " << rnd
698 << " is not supported yet.";
699 }
700 return success();
701}
702
703//===----------------------------------------------------------------------===//
704// NVGPU_TruncfOp
705//===----------------------------------------------------------------------===//
706
707static LogicalResult verifyConversionShapes(Operation *op, Type inType,
708 Type outType) {
709 bool srcIsVector = llvm::isa<VectorType>(inType);
710 bool dstIsVector = llvm::isa<VectorType>(outType);
711 if (srcIsVector != dstIsVector)
712 return op->emitOpError("input and output must both be scalars or both be "
713 "vectors, got ")
714 << inType << " and " << outType;
715 if (srcIsVector) {
716 auto srcVector = llvm::cast<VectorType>(inType);
717 auto dstVector = llvm::cast<VectorType>(outType);
718 if (srcVector.getShape() != dstVector.getShape())
719 return op->emitOpError("input and output shapes must match, got ")
720 << inType << " and " << outType;
721 }
722 return success();
723}
724
725LogicalResult TruncfOp::verify() {
726 Type inType = getIn().getType();
727 Type outType = getType();
728 Type srcType = getElementTypeOrSelf(inType);
729 Type dstType = getElementTypeOrSelf(outType);
730 int srcBitWidth = srcType.getIntOrFloatBitWidth();
731 int dstBitWidth = dstType.getIntOrFloatBitWidth();
732 auto rnd = getRnd();
733
734 if (auto result = verifyConversionShapes(getOperation(), inType, outType);
735 failed(result))
736 return result;
737
738 if (srcBitWidth <= dstBitWidth)
739 return emitOpError("result type ")
740 << dstType << " must be narrower than operand type " << srcType;
741
742 if (!(srcBitWidth == 64 || srcBitWidth == 32 || srcBitWidth == 16))
743 return emitOpError("input type must be 64/32/16 bitwidth, but got ")
744 << srcBitWidth;
745
746 if (llvm::isa<Float8E8M0FNUType>(dstType)) {
747 if (rnd != mlir::NVVM::FPRoundingMode::RZ &&
748 rnd != mlir::NVVM::FPRoundingMode::RP)
749 return emitOpError("expects RZ or RP rounding mode when result type is "
750 "e8m0, but got ")
751 << getRndAttr();
752 } else if (rnd == mlir::NVVM::FPRoundingMode::RS) {
753 // TODO: Currently, we only support conversions which fit into a single i32
754 // register. Support f32->f8/f6/f4 conversions with RS rounding.
755 if (!(srcBitWidth == 32 && dstBitWidth == 16))
756 return emitOpError("RS (stochastic) rounding is only supported for "
757 "f32->f16/bf16, got ")
758 << srcType << " -> " << dstType;
759 if (!getRandomBits())
760 return emitOpError("random_bits operand is required with RS rounding");
761 } else if (srcType.isF64() && dstBitWidth >= 16) {
762 if (rnd != mlir::NVVM::FPRoundingMode::RN)
763 return emitOpError("expects RN rounding mode for f64 input, but got ")
764 << getRndAttr();
765 } else if (srcBitWidth == 32 && dstBitWidth == 16) {
766 if (rnd != mlir::NVVM::FPRoundingMode::RN &&
767 rnd != mlir::NVVM::FPRoundingMode::RZ)
768 return emitOpError("expects RN or RZ rounding mode for f32 to f16/bf16, "
769 "but got ")
770 << getRndAttr();
771 } else if (rnd != mlir::NVVM::FPRoundingMode::RN) {
772 return emitOpError("expects RN rounding mode, but got ") << getRndAttr();
773 }
774
775 if (getRandomBits() && rnd != mlir::NVVM::FPRoundingMode::RS)
776 return emitOpError("random_bits can only be used with RS rounding mode");
777
778 return success();
779}
780
781//===----------------------------------------------------------------------===//
782// NVGPU_ExtfOp
783//===----------------------------------------------------------------------===//
784
785LogicalResult ExtfOp::verify() {
786 Type inType = getIn().getType();
787 Type outType = getType();
788 Type srcType = getElementTypeOrSelf(inType);
789 Type dstType = getElementTypeOrSelf(outType);
790 int srcBitWidth = srcType.getIntOrFloatBitWidth();
791 int dstBitWidth = dstType.getIntOrFloatBitWidth();
792 auto rnd = getRnd();
793
794 if (failed(verifyConversionShapes(getOperation(), inType, outType)))
795 return failure();
796
797 if (srcBitWidth >= dstBitWidth)
798 return emitOpError("result type ")
799 << dstType << " must be wider than operand type " << srcType;
800
801 if (dstBitWidth != 16 && dstBitWidth != 32 && dstBitWidth != 64)
802 return emitOpError("result type must be 16, 32, or 64 bitwidth, but got ")
803 << dstBitWidth;
804
805 if (llvm::isa<Float8E8M0FNUType>(srcType) &&
806 !llvm::isa<BFloat16Type>(dstType) && !dstType.isF32())
807 return emitOpError("expects bf16 or f32 output type when input type is "
808 "e8m0.");
809
810 if (rnd != mlir::NVVM::FPRoundingMode::RN)
811 return emitOpError("expects RN rounding mode, but got ") << getRndAttr();
812
813 if (getRelu() && llvm::isa<BFloat16Type>(dstType))
814 return emitOpError("relu is not supported for bf16 destination");
815
816 return success();
817}
818
819//===----------------------------------------------------------------------===//
820// TableGen'd dialect, type, and op definitions
821//===----------------------------------------------------------------------===//
822
823#define GET_ATTRDEF_CLASSES
824#include "mlir/Dialect/NVGPU/IR/NVGPUAttrDefs.cpp.inc"
825
826#include "mlir/Dialect/NVGPU/IR/NVGPUEnums.cpp.inc"
827
828#define GET_OP_CLASSES
829#include "mlir/Dialect/NVGPU/IR/NVGPUOps.cpp.inc"
830
831#define GET_TYPEDEF_CLASSES
832#include "mlir/Dialect/NVGPU/IR/NVGPUTypeDefs.cpp.inc"
return success()
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:732
Include the generated interface declarations.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:310
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.