MLIR 24.0.0git
WmmaOpsToSPIRV.cpp
Go to the documentation of this file.
1//===------ WmmaOpsToSPIRV.cpp - WMMA LD/ST/Compute to SPIRV lowering -----===//
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 contains definitions of patterns to lower GPU Subgroup MMA ops to
10// SPIRV Cooperative Matrix ops.
11//
12//===----------------------------------------------------------------------===//
13
24#include "mlir/IR/ValueRange.h"
25#include "llvm/ADT/STLExtras.h"
26#include "llvm/ADT/StringSwitch.h"
27
28#include <cassert>
29#include <limits>
30
31namespace mlir {
32//===----------------------------------------------------------------------===//
33// Patterns and helpers.
34//===----------------------------------------------------------------------===//
35
39 auto use =
41 .Case("AOp", spirv::CooperativeMatrixUseKHR::MatrixA)
42 .Case("BOp", spirv::CooperativeMatrixUseKHR::MatrixB)
43 .Default(spirv::CooperativeMatrixUseKHR::MatrixAcc);
45 type.getElementType(), shape[0], shape[1], spirv::Scope::Subgroup, use);
46}
47
48// Convert a memref of `gpu.mma_matrix` into a SPIR-V pointer to an array
49// of spirv::CooperativeMatrix.
50static std::optional<Type> convertMemrefOfMMAMatrixType(MemRefType type) {
51 auto matrixType = dyn_cast<gpu::MMAMatrixType>(type.getElementType());
52 if (!matrixType)
53 return std::nullopt;
54 // SPV Cooperative matrix types, and composites containing them, may only be
55 // allocated in Function or Private storage classes. For now, support only
56 // function-local arrays.
57 auto storageClass =
58 dyn_cast_or_null<spirv::StorageClassAttr>(type.getMemorySpace());
59 if (!storageClass ||
60 storageClass.getValue() != spirv::StorageClass::Function ||
61 !type.hasStaticShape() || !type.getLayout().isIdentity() ||
62 type.getNumElements() <= 0 ||
63 type.getNumElements() > std::numeric_limits<unsigned>::max())
64 return Type();
65 auto arrayType =
67 static_cast<unsigned>(type.getNumElements()));
68 return spirv::PointerType::get(arrayType, spirv::StorageClass::Function);
69}
70
71/// Creates a SPIR-V op to replace the given GPU subgroup mma elementwise op
72/// when the elementwise op directly supports with cooperative matrix type.
73/// Returns false if cannot.
74///
75/// See SPV_KHR_cooperative_matrix for supported elementwise ops.
76static bool createElementwiseOp(ConversionPatternRewriter &builder,
77 gpu::SubgroupMmaElementwiseOp op, Type coopType,
78 ValueRange operands) {
79 assert((isa<spirv::CooperativeMatrixType>(coopType)));
80
81 switch (op.getOpType()) {
82 case gpu::MMAElementwiseOp::ADDF:
83 builder.replaceOpWithNewOp<spirv::FAddOp>(op, coopType, operands);
84 return true;
85 case gpu::MMAElementwiseOp::ADDI:
86 builder.replaceOpWithNewOp<spirv::IAddOp>(op, coopType, operands);
87 return true;
88 case gpu::MMAElementwiseOp::SUBF:
89 builder.replaceOpWithNewOp<spirv::FSubOp>(op, coopType, operands);
90 return true;
91 case gpu::MMAElementwiseOp::SUBI:
92 builder.replaceOpWithNewOp<spirv::ISubOp>(op, coopType, operands);
93 return true;
94 case gpu::MMAElementwiseOp::MULF:
95 builder.replaceOpWithNewOp<spirv::FMulOp>(op, coopType, operands);
96 return true;
97 case gpu::MMAElementwiseOp::DIVF:
98 builder.replaceOpWithNewOp<spirv::FDivOp>(op, coopType, operands);
99 return true;
100 case gpu::MMAElementwiseOp::DIVS:
101 builder.replaceOpWithNewOp<spirv::SDivOp>(op, coopType, operands);
102 return true;
103 case gpu::MMAElementwiseOp::DIVU:
104 builder.replaceOpWithNewOp<spirv::UDivOp>(op, coopType, operands);
105 return true;
106 case gpu::MMAElementwiseOp::NEGATEF:
107 builder.replaceOpWithNewOp<spirv::FNegateOp>(op, coopType, operands);
108 return true;
109 case gpu::MMAElementwiseOp::NEGATES:
110 builder.replaceOpWithNewOp<spirv::SNegateOp>(op, coopType, operands);
111 return true;
112 case gpu::MMAElementwiseOp::EXTF:
113 case gpu::MMAElementwiseOp::TRUNCF:
114 builder.replaceOpWithNewOp<spirv::FConvertOp>(op, coopType, operands);
115 return true;
116 default:
117 break;
118 }
119 return false;
120}
121
123 assert(!operands.empty());
124 if (!llvm::all_equal(
125 llvm::map_range(operands, [](Value v) { return v.getType(); })))
126 return false;
127
128 return isa<spirv::CooperativeMatrixType>(operands.front().getType());
129}
130
132 auto elementType = dyn_cast<IntegerType>(type.getElementType());
133 return elementType && elementType.isSigned();
134}
135
136static spirv::CooperativeMatrixOperandsKHR
140 spirv::CooperativeMatrixType resultType) {
141 using Operands = spirv::CooperativeMatrixOperandsKHR;
142
143 Operands operands = Operands::None;
145 operands |= Operands::ASigned;
147 operands |= Operands::BSigned;
149 operands |= Operands::CSigned;
150 if (hasSignedIntegerElementType(resultType))
151 operands |= Operands::ResultSigned;
152 return operands;
153}
154
155namespace {
156/// Converts GPU MMA ConstantMatrixOp to constant SPIR-V KHR/NV cooperative
157/// matrix ops.
158struct WmmaConstantOpToSPIRVLowering final
159 : OpConversionPattern<gpu::SubgroupMmaConstantMatrixOp> {
160 using Base::Base;
161
162 LogicalResult
163 matchAndRewrite(gpu::SubgroupMmaConstantMatrixOp op, OpAdaptor adaptor,
164 ConversionPatternRewriter &rewriter) const override {
165 Value cst = llvm::getSingleElement(adaptor.getOperands());
166 auto coopType = getTypeConverter()->convertType(op.getType());
167 if (!coopType)
168 return rewriter.notifyMatchFailure(op, "type conversion failed");
169
170 rewriter.replaceOpWithNewOp<spirv::CompositeConstructOp>(op, coopType, cst);
171 return success();
172 }
173};
174
175/// Converts GPU MMA ExtractOp to CompositeExtract SPIR-V KHR/NV cooperative
176/// matrix ops.
177struct WmmaExtractOpToSPIRVLowering final
178 : OpConversionPattern<gpu::SubgroupMmaExtractThreadLocalOp> {
179 using Base::Base;
180
181 LogicalResult
182 matchAndRewrite(gpu::SubgroupMmaExtractThreadLocalOp op, OpAdaptor adaptor,
183 ConversionPatternRewriter &rewriter) const override {
184 Value matrix = adaptor.getMatrix();
185 auto coopType =
186 getTypeConverter()->convertType<spirv::CooperativeMatrixType>(
187 matrix.getType());
188 if (!coopType)
189 return rewriter.notifyMatchFailure(op, "type conversion failed");
190
191 SmallVector<int32_t> intValues;
192 for (Value val : op.getIndices()) {
193 if (auto constOp = val.getDefiningOp<arith::ConstantIndexOp>()) {
194 intValues.push_back(static_cast<int32_t>(constOp.value()));
195 } else {
196 return rewriter.notifyMatchFailure(op, "indices must be constants");
197 }
198 }
199
200 Type elementType = coopType.getElementType();
201 rewriter.replaceOpWithNewOp<spirv::CompositeExtractOp>(
202 op, elementType, matrix, rewriter.getI32ArrayAttr(intValues));
203 return success();
204 }
205};
206
207/// Converts GPU MMA InsertOp to CompositeInsert SPIR-V KHR/NV cooperative
208/// matrix ops.
209struct WmmaInsertOpToSPIRVLowering final
210 : OpConversionPattern<gpu::SubgroupMmaInsertThreadLocalOp> {
211 using Base::Base;
212
213 LogicalResult
214 matchAndRewrite(gpu::SubgroupMmaInsertThreadLocalOp op, OpAdaptor adaptor,
215 ConversionPatternRewriter &rewriter) const override {
216 Value value = adaptor.getValue();
217 Value matrix = adaptor.getMatrix();
218 auto coopType = getTypeConverter()->convertType(matrix.getType());
219 if (!coopType)
220 return rewriter.notifyMatchFailure(op, "type conversion failed");
221
222 SmallVector<int32_t> intValues;
223 for (Value val : op.getIndices()) {
224 if (auto constOp = val.getDefiningOp<arith::ConstantIndexOp>()) {
225 intValues.push_back(static_cast<int32_t>(constOp.value()));
226 } else {
227 return rewriter.notifyMatchFailure(op, "indices must be constants");
228 }
229 }
230
231 rewriter.replaceOpWithNewOp<spirv::CompositeInsertOp>(
232 op, coopType, value, matrix, rewriter.getI32ArrayAttr(intValues));
233 return success();
234 }
235};
236
237/// Converts elementwise ops to SPIR-V cooperative matrix elementwise ops for
238/// the default case.
239struct WmmaElementwiseOpToSPIRVDefaultLowering final
240 : OpConversionPattern<gpu::SubgroupMmaElementwiseOp> {
241 using Base::Base;
242
243 LogicalResult
244 matchAndRewrite(gpu::SubgroupMmaElementwiseOp op, OpAdaptor adaptor,
245 ConversionPatternRewriter &rewriter) const override {
246 // All operands should be of cooperative matrix types.
247 if (!allOperandsHaveSameCoopMatrixType(adaptor.getOperands())) {
248 return rewriter.notifyMatchFailure(op,
249 "not all operands are coop matrices");
250 }
251
252 auto coopType = getTypeConverter()->convertType(op.getType());
253 if (!coopType)
254 return rewriter.notifyMatchFailure(op, "type conversion failed");
255
256 return success(
257 createElementwiseOp(rewriter, op, coopType, adaptor.getOperands()));
258 }
259};
260
261/// Converts elementwise ops to SPIR-V cooperative matrix elementwise ops for
262/// matrix times scalar case.
263struct WmmaElementwiseOpToSPIRVScalarMulLowering final
264 : OpConversionPattern<gpu::SubgroupMmaElementwiseOp> {
265 using Base::Base;
266
267 LogicalResult
268 matchAndRewrite(gpu::SubgroupMmaElementwiseOp op, OpAdaptor adaptor,
269 ConversionPatternRewriter &rewriter) const override {
270 if (adaptor.getOperands().size() != 2)
271 return failure();
272
273 // All operands should be of cooperative matrix types.
274 if (!allOperandsHaveSameCoopMatrixType(adaptor.getOperands())) {
275 return rewriter.notifyMatchFailure(op,
276 "not all operands are coop matrices");
277 }
278
279 if (op.getOpType() != gpu::MMAElementwiseOp::MULF)
280 return failure();
281
282 // Use the original operands to check whether one of the operands is a splat
283 // scalar value.
284 Value lhs = op.getOperands().front();
285 Value rhs = op.getOperands().back();
286 Value splat = nullptr;
287 Value matrix = nullptr;
288 if (lhs.getDefiningOp<gpu::SubgroupMmaConstantMatrixOp>()) {
289 splat = adaptor.getOperands().front();
290 matrix = adaptor.getOperands().back();
291 } else if (rhs.getDefiningOp<gpu::SubgroupMmaConstantMatrixOp>()) {
292 matrix = adaptor.getOperands().front();
293 splat = adaptor.getOperands().back();
294 }
295 if (!splat || !matrix)
296 return rewriter.notifyMatchFailure(op, "no splat operand");
297
298 // Constant MMA matrix ops are converted to `spirv.CompositeConstruct` ops.
299 Value scalar;
300 auto cc = splat.getDefiningOp<spirv::CompositeConstructOp>();
301 if (!cc) {
302 return rewriter.notifyMatchFailure(op,
303 "splat is not a composite construct");
304 }
305
306 scalar = llvm::getSingleElement(cc.getConstituents());
307
308 auto coopType = getTypeConverter()->convertType(op.getType());
309 if (!coopType)
310 return rewriter.notifyMatchFailure(op, "type conversion failed");
311 rewriter.replaceOpWithNewOp<spirv::MatrixTimesScalarOp>(
312 op, coopType, ValueRange{matrix, scalar});
313 return success();
314 }
315};
316} // namespace
317
318//===----------------------------------------------------------------------===//
319// SPV_KHR_cooperative_matrix
320//===----------------------------------------------------------------------===//
321
322namespace khr {
323namespace {
324
325/// Converts the GPU MMA loadOp to KHRCooperativeMatrixLoad op in the SPIRV
326/// dialect.
327struct WmmaLoadOpToSPIRVLowering final
328 : OpConversionPattern<gpu::SubgroupMmaLoadMatrixOp> {
329 using Base::Base;
330
331 LogicalResult
332 matchAndRewrite(gpu::SubgroupMmaLoadMatrixOp op, OpAdaptor adaptor,
333 ConversionPatternRewriter &rewriter) const override {
334 const auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
335 Location loc = op->getLoc();
336
337 auto retType = cast<gpu::MMAMatrixType>(op.getRes().getType());
338 MemRefType memrefType = op.getSrcMemref().getType();
339 Value bufferPtr =
340 spirv::getElementPtr(typeConverter, memrefType, adaptor.getSrcMemref(),
341 adaptor.getIndices(), loc, rewriter);
342
343 auto coopType =
344 typeConverter.convertType<spirv::CooperativeMatrixType>(retType);
345 if (!coopType)
346 return rewriter.notifyMatchFailure(op, "type conversion failed");
347
348 int64_t stride = op.getLeadDimension().getSExtValue();
349 IntegerType i32Type = rewriter.getI32Type();
350 auto strideValue = spirv::ConstantOp::create(
351 rewriter, loc, i32Type, IntegerAttr::get(i32Type, stride));
352
353 bool isColMajor = op.getTranspose().value_or(false);
354 auto layout = isColMajor ? spirv::CooperativeMatrixLayoutKHR::ColumnMajor
355 : spirv::CooperativeMatrixLayoutKHR::RowMajor;
356
357 rewriter.replaceOpWithNewOp<spirv::KHRCooperativeMatrixLoadOp>(
358 op, coopType, bufferPtr, strideValue, layout);
359 return success();
360 }
361};
362
363/// Converts the GPU MMA StoreOp to KHRCooperativeMatrixStore op in the SPIRV
364/// dialect.
365struct WmmaStoreOpToSPIRVLowering final
366 : OpConversionPattern<gpu::SubgroupMmaStoreMatrixOp> {
367 using Base::Base;
368
369 LogicalResult
370 matchAndRewrite(gpu::SubgroupMmaStoreMatrixOp op, OpAdaptor adaptor,
371 ConversionPatternRewriter &rewriter) const override {
372 const auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
373 Location loc = op->getLoc();
374
375 auto memrefType = cast<MemRefType>(op.getDstMemref().getType());
376 Value bufferPtr =
377 spirv::getElementPtr(typeConverter, memrefType, adaptor.getDstMemref(),
378 adaptor.getIndices(), loc, rewriter);
379
380 int64_t stride = op.getLeadDimension().getSExtValue();
381 IntegerType i32Type = rewriter.getI32Type();
382 auto strideValue = spirv::ConstantOp::create(
383 rewriter, loc, i32Type, IntegerAttr::get(i32Type, stride));
384
385 bool isColMajor = op.getTranspose().value_or(false);
386 auto layout = isColMajor ? spirv::CooperativeMatrixLayoutKHR::ColumnMajor
387 : spirv::CooperativeMatrixLayoutKHR::RowMajor;
388
389 rewriter.replaceOpWithNewOp<spirv::KHRCooperativeMatrixStoreOp>(
390 op, bufferPtr, adaptor.getSrc(), strideValue, layout);
391 return success();
392 }
393};
394
395/// Converts GPU MMA Compute to KHRCooperativeMatrixMulAdd op in the SPIRV
396/// dialect.
397struct WmmaMmaOpToSPIRVLowering final
398 : OpConversionPattern<gpu::SubgroupMmaComputeOp> {
399 using Base::Base;
400
401 LogicalResult
402 matchAndRewrite(gpu::SubgroupMmaComputeOp subgroupMmaComputeOp,
403 OpAdaptor adaptor,
404 ConversionPatternRewriter &rewriter) const override {
405 auto aType =
406 dyn_cast<spirv::CooperativeMatrixType>(adaptor.getOpA().getType());
407 auto bType =
408 dyn_cast<spirv::CooperativeMatrixType>(adaptor.getOpB().getType());
409 auto cType =
410 dyn_cast<spirv::CooperativeMatrixType>(adaptor.getOpC().getType());
411 auto resultType =
412 getTypeConverter()->convertType<spirv::CooperativeMatrixType>(
413 subgroupMmaComputeOp.getResult().getType());
414 if (!aType || !bType || !cType || !resultType)
415 return rewriter.notifyMatchFailure(subgroupMmaComputeOp,
416 "type conversion failed");
417
418 using Operands = spirv::CooperativeMatrixOperandsKHR;
419 Operands operands =
420 getSignedCoopMatrixOperands(aType, bType, cType, resultType);
421 spirv::CooperativeMatrixOperandsKHRAttr operandsAttr;
422 if (operands != Operands::None)
423 operandsAttr = spirv::CooperativeMatrixOperandsKHRAttr::get(
424 rewriter.getContext(), operands);
425
426 rewriter.replaceOpWithNewOp<spirv::KHRCooperativeMatrixMulAddOp>(
427 subgroupMmaComputeOp, adaptor.getOpA(), adaptor.getOpB(),
428 adaptor.getOpC(), operandsAttr);
429 return success();
430 }
431};
432
433} // namespace
434} // namespace khr
435} // namespace mlir
436
438 const SPIRVTypeConverter &converter, RewritePatternSet &patterns) {
439 using namespace mlir;
440 MLIRContext *context = patterns.getContext();
441 patterns.add<khr::WmmaLoadOpToSPIRVLowering, khr::WmmaMmaOpToSPIRVLowering,
442 khr::WmmaStoreOpToSPIRVLowering, WmmaConstantOpToSPIRVLowering,
443 WmmaExtractOpToSPIRVLowering, WmmaInsertOpToSPIRVLowering,
444 WmmaElementwiseOpToSPIRVDefaultLowering>(converter, context);
445 // Give the following patterns higher benefit to prevail over the default one.
446 patterns.add<WmmaElementwiseOpToSPIRVScalarMulLowering>(converter, context,
447 /*benefit=*/2);
448}
449
451 mlir::SPIRVTypeConverter &typeConverter) {
452 typeConverter.addConversion(convertMMAMatrixType);
453 typeConverter.addConversion(convertMemrefOfMMAMatrixType);
454}
return success()
lhs
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
Type conversion from builtin types to SPIR-V types for shader interface.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
type_range getType() const
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
MMAMatrix represents a matrix held by a subgroup for matrix-matrix multiply accumulate operations.
Definition GPUDialect.h:143
ArrayRef< int64_t > getShape() const
Get shape of the matrix.
Type getElementType() const
Get elementType of a single element.
StringRef getOperand() const
The general form of operation this type supports is given by the equation C += A*B.
static ArrayType get(Type elementType, unsigned elementCount)
static CooperativeMatrixType get(Type elementType, uint32_t rows, uint32_t columns, Scope scope, CooperativeMatrixUseKHR use)
static PointerType get(Type pointeeType, StorageClass storageClass)
Value getElementPtr(const SPIRVTypeConverter &typeConverter, MemRefType baseType, Value basePtr, ValueRange indices, Location loc, OpBuilder &builder)
Performs the index computation to get to the element at indices of the memory pointed to by basePtr,...
Include the generated interface declarations.
static std::optional< Type > convertMemrefOfMMAMatrixType(MemRefType type)
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
static bool createElementwiseOp(ConversionPatternRewriter &builder, gpu::SubgroupMmaElementwiseOp op, Type coopType, ValueRange operands)
Creates a SPIR-V op to replace the given GPU subgroup mma elementwise op when the elementwise op dire...
void populateGpuWMMAToSPIRVCoopMatrixKHRConversionPatterns(const SPIRVTypeConverter &typeConverter, RewritePatternSet &patterns)
Collect a set of patterns to convert WMMA ops from GPU dialect to SPIRV, using the KHR Cooperative Ma...
static spirv::CooperativeMatrixType convertMMAMatrixType(gpu::MMAMatrixType type)
static spirv::CooperativeMatrixOperandsKHR getSignedCoopMatrixOperands(spirv::CooperativeMatrixType aType, spirv::CooperativeMatrixType bType, spirv::CooperativeMatrixType cType, spirv::CooperativeMatrixType resultType)
bool allOperandsHaveSameCoopMatrixType(ValueRange operands)
void populateMMAToSPIRVCoopMatrixTypeConversion(SPIRVTypeConverter &typeConverter)
Adds MMAMatrixType conversions to SPIR-V cooperative matrix KHR type conversion to the type converter...
static bool hasSignedIntegerElementType(spirv::CooperativeMatrixType type)