MLIR 24.0.0git
ArithToAMDGPU.cpp
Go to the documentation of this file.
1//===- ArithToAMDGPU.cpp - Arith to AMDGPU dialect conversion ---------===//
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
10
23#include "mlir/Pass/Pass.h"
25
26namespace mlir {
27#define GEN_PASS_DEF_ARITHTOAMDGPUCONVERSIONPASS
28#include "mlir/Conversion/Passes.h.inc"
29} // namespace mlir
30
31using namespace mlir;
32using namespace mlir::amdgpu;
33
34namespace {
35// Define commonly used chipsets versions for convenience.
36constexpr Chipset kGfx942 = Chipset(9, 4, 2);
37constexpr Chipset kGfx950 = Chipset(9, 5, 0);
38
39struct ArithToAMDGPUConversionPass final
40 : impl::ArithToAMDGPUConversionPassBase<ArithToAMDGPUConversionPass> {
41 using impl::ArithToAMDGPUConversionPassBase<
42 ArithToAMDGPUConversionPass>::ArithToAMDGPUConversionPassBase;
43
44 void runOnOperation() override;
45};
46
47struct ExtFOnFloat8RewritePattern final : OpRewritePattern<arith::ExtFOp> {
48 using Base::Base;
49
50 Chipset chipset;
51 ExtFOnFloat8RewritePattern(MLIRContext *ctx, Chipset chipset,
52 PatternBenefit benefit)
53 : OpRewritePattern::OpRewritePattern(ctx, benefit), chipset(chipset) {}
54
55 LogicalResult matchAndRewrite(arith::ExtFOp op,
56 PatternRewriter &rewriter) const override;
57};
58
59struct TruncFToFloat8RewritePattern final : OpRewritePattern<arith::TruncFOp> {
60 bool saturateFP8 = false;
61 TruncFToFloat8RewritePattern(MLIRContext *ctx, bool saturateFP8,
62 Chipset chipset, PatternBenefit benefit)
63 : OpRewritePattern::OpRewritePattern(ctx, benefit),
64 saturateFP8(saturateFP8), chipset(chipset) {}
65 Chipset chipset;
66
67 LogicalResult matchAndRewrite(arith::TruncFOp op,
68 PatternRewriter &rewriter) const override;
69};
70
71struct TruncfToFloat16RewritePattern final
72 : public OpRewritePattern<arith::TruncFOp> {
73
74 using Base::Base;
75
76 LogicalResult matchAndRewrite(arith::TruncFOp op,
77 PatternRewriter &rewriter) const override;
78};
79
80struct ScalingExtFRewritePattern final
81 : OpRewritePattern<arith::ScalingExtFOp> {
82 using Base::Base;
83
84 LogicalResult matchAndRewrite(arith::ScalingExtFOp op,
85 PatternRewriter &rewriter) const override;
86};
87
88struct ScalingTruncFRewritePattern final
89 : OpRewritePattern<arith::ScalingTruncFOp> {
90 using Base::Base;
91
92 LogicalResult matchAndRewrite(arith::ScalingTruncFOp op,
93 PatternRewriter &rewriter) const override;
94};
95
96} // end namespace
97
98static bool isSupportedF8(Type elementType, Chipset chipset) {
99 if (chipset == kGfx942)
100 return isa<Float8E4M3FNUZType, Float8E5M2FNUZType>(elementType);
101 if (hasOcpFp8(chipset))
102 return isa<Float8E4M3FNType, Float8E5M2Type>(elementType);
103 return false;
104}
105
106static Value castF32To(Type desType, Value f32, Location loc,
107 PatternRewriter &rewriter) {
108 Type elementType = getElementTypeOrSelf(desType);
109 if (elementType.isF32())
110 return f32;
111 if (elementType.getIntOrFloatBitWidth() < 32)
112 return arith::TruncFOp::create(rewriter, loc, desType, f32);
113 if (elementType.getIntOrFloatBitWidth() > 32)
114 return arith::ExtFOp::create(rewriter, loc, TypeRange{desType},
115 ValueRange{f32}, arith::ExtFOp::Properties{});
116 llvm_unreachable("The only 32-bit float type is f32");
117}
118
119LogicalResult
120ExtFOnFloat8RewritePattern::matchAndRewrite(arith::ExtFOp op,
121 PatternRewriter &rewriter) const {
122 Type inType = op.getIn().getType();
123 auto inVecType = dyn_cast<VectorType>(inType);
124 if (inVecType) {
125 if (inVecType.isScalable())
126 return failure();
127 inType = inVecType.getElementType();
128 }
129 if (!isSupportedF8(inType, chipset))
130 return failure();
131
132 Location loc = op.getLoc();
133 Value in = op.getIn();
134 Type outElemType = getElementTypeOrSelf(op.getOut().getType());
135 VectorType extResType = VectorType::get(2, rewriter.getF32Type());
136 if (!inVecType) {
137 Value asFloat = amdgpu::ExtPackedFp8Op::create(
138 rewriter, loc, rewriter.getF32Type(), in, 0);
139 Value result = castF32To(outElemType, asFloat, loc, rewriter);
140 rewriter.replaceOp(op, result);
141 return success();
142 }
143 int64_t numElements = inVecType.getNumElements();
144
145 Value zero = arith::ConstantOp::create(
146 rewriter, loc, outElemType, rewriter.getFloatAttr(outElemType, 0.0));
147 VectorType outType = cast<VectorType>(op.getOut().getType());
148
149 if (inVecType.getShape().empty()) {
150 Value zerodSplat =
151 rewriter.createOrFold<vector::BroadcastOp>(loc, outType, zero);
152 Value scalarIn =
153 vector::ExtractOp::create(rewriter, loc, in, ArrayRef<int64_t>{});
154 Value scalarExt = arith::ExtFOp::create(
155 rewriter, loc, TypeRange{outElemType}, ValueRange{scalarIn},
156 arith::ExtFOp::Properties{});
157 Value result = vector::InsertOp::create(rewriter, loc, scalarExt,
158 zerodSplat, ArrayRef<int64_t>{});
159 rewriter.replaceOp(op, result);
160 return success();
161 }
162
163 VectorType flatTy = VectorType::get(SmallVector<int64_t>{numElements},
164 outType.getElementType());
165 Value result = rewriter.createOrFold<vector::BroadcastOp>(loc, flatTy, zero);
166
167 if (inVecType.getRank() > 1) {
168 inVecType = VectorType::get(SmallVector<int64_t>{numElements},
169 inVecType.getElementType());
170 in = vector::ShapeCastOp::create(rewriter, loc, inVecType, in);
171 }
172
173 for (int64_t i = 0; i < numElements; i += 4) {
174 int64_t elemsThisOp = std::min(numElements, i + 4) - i;
175 Value inSlice = vector::ExtractStridedSliceOp::create(rewriter, loc, in, i,
176 elemsThisOp, 1);
177 for (int64_t j = 0; j < elemsThisOp; j += 2) {
178 if (i + j + 1 < numElements) { // Convert two 8-bit elements
179 Value asFloats = amdgpu::ExtPackedFp8Op::create(
180 rewriter, loc, extResType, inSlice, j / 2);
181 Type desType = VectorType::get(2, outElemType);
182 Value asType = castF32To(desType, asFloats, loc, rewriter);
183 result = vector::InsertStridedSliceOp::create(rewriter, loc, asType,
184 result, i + j, 1);
185 } else { // Convert a 8-bit element
186 Value asFloat = amdgpu::ExtPackedFp8Op::create(
187 rewriter, loc, rewriter.getF32Type(), inSlice, j / 2 * 2);
188 Value asType = castF32To(outElemType, asFloat, loc, rewriter);
189 result = vector::InsertOp::create(rewriter, loc, asType, result, i + j);
190 }
191 }
192 }
193
194 if (inVecType.getRank() != outType.getRank()) {
195 result = vector::ShapeCastOp::create(rewriter, loc, outType, result);
196 }
197
198 rewriter.replaceOp(op, result);
199 return success();
200}
201
202static Value castToF32(Value value, Location loc, PatternRewriter &rewriter) {
203 Type type = value.getType();
204 if (type.isF32())
205 return value;
206 if (type.getIntOrFloatBitWidth() < 32)
207 return arith::ExtFOp::create(
208 rewriter, loc, TypeRange{rewriter.getF32Type()}, ValueRange{value},
209 arith::ExtFOp::Properties{});
210 if (type.getIntOrFloatBitWidth() > 32)
211 return arith::TruncFOp::create(rewriter, loc, rewriter.getF32Type(), value);
212 llvm_unreachable("The only 32-bit float type is f32");
213}
214
215// If `in` is a finite value, clamp it between the maximum and minimum values
216// of `outElemType` so that subsequent conversion instructions don't
217// overflow those out-of-range values to NaN. These semantics are commonly
218// used in machine-learning contexts where failure to clamp would lead to
219// excessive NaN production.
221 Type outElemType, Value source) {
222 Type sourceType = source.getType();
223 const llvm::fltSemantics &sourceSem =
224 cast<FloatType>(getElementTypeOrSelf(sourceType)).getFloatSemantics();
225 const llvm::fltSemantics &targetSem =
226 cast<FloatType>(outElemType).getFloatSemantics();
227
228 APFloat min = APFloat::getLargest(targetSem, /*Negative=*/true);
229 APFloat max = APFloat::getLargest(targetSem, /*Negative=*/false);
230 bool ignoredLosesInfo = false;
231 // We can ignore conversion failures here because this conversion promotes
232 // from a smaller type to a larger one - ex. there can be no loss of precision
233 // when casting fp8 to f16.
234 (void)min.convert(sourceSem, APFloat::rmNearestTiesToEven, &ignoredLosesInfo);
235 (void)max.convert(sourceSem, APFloat::rmNearestTiesToEven, &ignoredLosesInfo);
236
237 Value minCst = createScalarOrSplatConstant(rewriter, loc, sourceType, min);
238 Value maxCst = createScalarOrSplatConstant(rewriter, loc, sourceType, max);
239
241 rewriter, loc, sourceType,
242 APFloat::getInf(sourceSem, /*Negative=*/false));
244 rewriter, loc, sourceType, APFloat::getInf(sourceSem, /*Negative=*/true));
245 Value isInf = rewriter.createOrFold<arith::CmpFOp>(
246 loc, arith::CmpFPredicate::OEQ, source, inf);
247 Value isNegInf = rewriter.createOrFold<arith::CmpFOp>(
248 loc, arith::CmpFPredicate::OEQ, source, negInf);
249 Value isNan = rewriter.createOrFold<arith::CmpFOp>(
250 loc, arith::CmpFPredicate::UNO, source, source);
251 Value isNonFinite = arith::OrIOp::create(
252 rewriter, loc, arith::OrIOp::create(rewriter, loc, isInf, isNegInf),
253 isNan);
254
255 Value clampedBelow = arith::MaximumFOp::create(rewriter, loc, source, minCst);
256 Value clamped =
257 arith::MinimumFOp::create(rewriter, loc, clampedBelow, maxCst);
258 Value res =
259 arith::SelectOp::create(rewriter, loc, isNonFinite, source, clamped);
260 return res;
261}
262
263LogicalResult
264TruncFToFloat8RewritePattern::matchAndRewrite(arith::TruncFOp op,
265 PatternRewriter &rewriter) const {
266 // Only supporting default rounding mode as of now.
267 if (op.getRoundingmodeAttr())
268 return failure();
269 Type outType = op.getOut().getType();
270 auto outVecType = dyn_cast<VectorType>(outType);
271 if (outVecType) {
272 if (outVecType.isScalable())
273 return failure();
274 outType = outVecType.getElementType();
275 }
276 auto inType = dyn_cast<FloatType>(getElementTypeOrSelf(op.getIn().getType()));
277 if (inType && inType.getWidth() <= 8 && saturateFP8)
278 // Conversion between 8-bit floats is not supported with truncation enabled.
279 return failure();
280
281 if (!isSupportedF8(outType, chipset))
282 return failure();
283
284 Location loc = op.getLoc();
285 Value in = op.getIn();
286 Type outElemType = getElementTypeOrSelf(op.getOut().getType());
287 if (saturateFP8)
288 in = clampInput(rewriter, loc, outElemType, in);
289 auto inVectorTy = dyn_cast<VectorType>(in.getType());
290 VectorType truncResType = VectorType::get(4, outElemType);
291 if (!inVectorTy) {
292 Value asFloat = castToF32(in, loc, rewriter);
293 Value asF8s = amdgpu::PackedTrunc2xFp8Op::create(
294 rewriter, loc, truncResType, asFloat, /*sourceB=*/nullptr, 0,
295 /*existing=*/nullptr);
296 Value result = vector::ExtractOp::create(rewriter, loc, asF8s, 0);
297 rewriter.replaceOp(op, result);
298 return success();
299 }
300
301 int64_t numElements = outVecType.getNumElements();
302 Value zero = arith::ConstantOp::create(
303 rewriter, loc, outElemType, rewriter.getFloatAttr(outElemType, 0.0));
304 if (outVecType.getShape().empty()) {
305 Value scalarIn =
306 vector::ExtractOp::create(rewriter, loc, in, ArrayRef<int64_t>{});
307 // Recurse to send the 0-D vector case to the 1-D vector case
308 Value scalarTrunc =
309 arith::TruncFOp::create(rewriter, loc, outElemType, scalarIn);
310 Value result = vector::InsertOp::create(rewriter, loc, scalarTrunc, zero,
311 ArrayRef<int64_t>{});
312 rewriter.replaceOp(op, result);
313 return success();
314 }
315
316 VectorType flatTy = VectorType::get(SmallVector<int64_t>{numElements},
317 outVecType.getElementType());
318 Value result = rewriter.createOrFold<vector::BroadcastOp>(loc, flatTy, zero);
319
320 if (inVectorTy.getRank() > 1) {
321 inVectorTy = VectorType::get(SmallVector<int64_t>{numElements},
322 inVectorTy.getElementType());
323 in = vector::ShapeCastOp::create(rewriter, loc, inVectorTy, in);
324 }
325
326 for (int64_t i = 0; i < numElements; i += 4) {
327 int64_t elemsThisOp = std::min(numElements, i + 4) - i;
328 Value thisResult = nullptr;
329 for (int64_t j = 0; j < elemsThisOp; j += 2) {
330 Value elemA = vector::ExtractOp::create(rewriter, loc, in, i + j);
331 Value asFloatA = castToF32(elemA, loc, rewriter);
332 Value asFloatB = nullptr;
333 if (j + 1 < elemsThisOp) {
334 Value elemB = vector::ExtractOp::create(rewriter, loc, in, i + j + 1);
335 asFloatB = castToF32(elemB, loc, rewriter);
336 }
337 thisResult = amdgpu::PackedTrunc2xFp8Op::create(
338 rewriter, loc, truncResType, asFloatA, asFloatB, j / 2, thisResult);
339 }
340 if (elemsThisOp < 4)
341 thisResult = vector::ExtractStridedSliceOp::create(
342 rewriter, loc, thisResult, 0, elemsThisOp, 1);
343 result = vector::InsertStridedSliceOp::create(rewriter, loc, thisResult,
344 result, i, 1);
345 }
346
347 if (inVectorTy.getRank() != outVecType.getRank()) {
348 result = vector::ShapeCastOp::create(rewriter, loc, outVecType, result);
349 }
350
351 rewriter.replaceOp(op, result);
352 return success();
353}
354
355LogicalResult TruncfToFloat16RewritePattern::matchAndRewrite(
356 arith::TruncFOp op, PatternRewriter &rewriter) const {
357 Type outType = op.getOut().getType();
358 Type inputType = getElementTypeOrSelf(op.getIn());
359 auto outVecType = dyn_cast<VectorType>(outType);
360 if (outVecType) {
361 if (outVecType.isScalable())
362 return failure();
363 outType = outVecType.getElementType();
364 }
365 if (!(outType.isF16() && inputType.isF32()))
366 return failure();
367
368 Location loc = op.getLoc();
369 Value in = op.getIn();
370 Type outElemType = getElementTypeOrSelf(op.getOut().getType());
371 VectorType truncResType = VectorType::get(2, outElemType);
372 auto inVectorTy = dyn_cast<VectorType>(in.getType());
373
374 // Handle the case where input type is not a vector type
375 if (!inVectorTy) {
376 auto sourceB = LLVM::PoisonOp::create(rewriter, loc, rewriter.getF32Type());
377 Value asF16s =
378 ROCDL::CvtPkRtz::create(rewriter, loc, truncResType, in, sourceB);
379 Value result = vector::ExtractOp::create(rewriter, loc, asF16s, 0);
380 rewriter.replaceOp(op, result);
381 return success();
382 }
383 int64_t numElements = outVecType.getNumElements();
384 Value zero = rewriter.createOrFold<arith::ConstantOp>(
385 loc, outElemType, rewriter.getFloatAttr(outElemType, 0.0));
386 Value result =
387 rewriter.createOrFold<vector::BroadcastOp>(loc, outVecType, zero);
388
389 if (inVectorTy.getRank() > 1) {
390 inVectorTy = VectorType::get(SmallVector<int64_t>{numElements},
391 inVectorTy.getElementType());
392 in = vector::ShapeCastOp::create(rewriter, loc, inVectorTy, in);
393 }
394
395 // Handle the vector case. We also handle the (uncommon) case where the vector
396 // length is odd
397 for (int64_t i = 0; i < numElements; i += 2) {
398 int64_t elemsThisOp = std::min(numElements, i + 2) - i;
399 Value thisResult = nullptr;
400 Value elemA = vector::ExtractOp::create(rewriter, loc, in, i);
401 Value elemB = LLVM::PoisonOp::create(rewriter, loc, rewriter.getF32Type());
402
403 if (elemsThisOp == 2) {
404 elemB = vector::ExtractOp::create(rewriter, loc, in, i + 1);
405 }
406
407 thisResult =
408 ROCDL::CvtPkRtz::create(rewriter, loc, truncResType, elemA, elemB);
409 // Place back the truncated result into the possibly larger vector. If we
410 // are operating on a size 2 vector, these operations should be folded away
411 thisResult = vector::ExtractStridedSliceOp::create(
412 rewriter, loc, thisResult, 0, elemsThisOp, 1);
413 result = vector::InsertStridedSliceOp::create(rewriter, loc, thisResult,
414 result, i, 1);
415 }
416
417 if (inVectorTy.getRank() != outVecType.getRank()) {
418 result = vector::ShapeCastOp::create(rewriter, loc, outVecType, result);
419 }
420
421 rewriter.replaceOp(op, result);
422 return success();
423}
424
425/// Get the broadcasted / splatted value for a chain of ops.
427 Value current = value;
428 while (Operation *definingOp = current.getDefiningOp()) {
429 bool skipOp = llvm::TypeSwitch<Operation *, bool>(definingOp)
430 .Case([&current](vector::ShapeCastOp op) {
431 current = op.getSource();
432 return true;
433 })
434 .Case([&current](vector::BroadcastOp op) {
435 current = op.getSource();
436 return false;
437 })
438 .Default(false);
439
440 if (!skipOp) {
441 break;
442 }
443 }
444 return current;
445}
446
447LogicalResult
448ScalingExtFRewritePattern::matchAndRewrite(arith::ScalingExtFOp op,
449 PatternRewriter &rewriter) const {
450 Location loc = op.getLoc();
451 constexpr int64_t opOutWidth = 2;
452
453 Value in = op.getIn();
454 Value scale = op.getScale();
455 Value out = op.getOut();
456
457 Type f32 = rewriter.getF32Type();
458 Type inType = getElementTypeOrSelf(in);
459 Type scaleType = getElementTypeOrSelf(scale);
460 Type outType = getElementTypeOrSelf(out);
461
462 int64_t opInWidth = 32 / inType.getIntOrFloatBitWidth();
463
464 VectorType outVecType = dyn_cast<VectorType>(out.getType());
465 VectorType scaleVecType = dyn_cast<VectorType>(scale.getType());
466
467 if (outVecType && outVecType.isScalable())
468 return failure();
469
470 if (isa<RankedTensorType>(out.getType()) ||
471 isa<RankedTensorType>(in.getType()) ||
472 isa<RankedTensorType>(scale.getType()))
473 return failure();
474
475 Type scaleF32Type =
476 scaleVecType ? VectorType::get(scaleVecType.getShape(), f32) : f32;
477 if (scaleType.getIntOrFloatBitWidth() < 32)
478 scale =
479 arith::ExtFOp::create(rewriter, loc, TypeRange{scaleF32Type},
480 ValueRange{scale}, arith::ExtFOp::Properties{});
481 else if (scaleType.getIntOrFloatBitWidth() > 32)
482 scale = arith::TruncFOp::create(rewriter, loc, scaleF32Type, scale);
483
484 VectorType extScaleResultType = VectorType::get(opOutWidth, outType);
485
486 if (!outVecType) {
487 Value inCast = vector::BroadcastOp::create(rewriter, loc,
488 VectorType::get(1, inType), in);
489 // TODO: replace this with non-packed ScaledExtOp
490 Value scaleExt = amdgpu::ScaledExtPackedOp::create(
491 rewriter, loc, extScaleResultType, inCast, scale, 0);
492 scaleExt = rewriter.replaceOpWithNewOp<vector::ExtractOp>(op, scaleExt, 0);
493 return success();
494 }
495
496 VectorType inVecType = cast<VectorType>(in.getType());
497 Value origScale = getOriginalVectorValue(op.getScale());
498 VectorType origScaleVecType = dyn_cast<VectorType>(origScale.getType());
499
500 ArrayRef<int64_t> inShape = inVecType.getShape();
501 SmallVector<int64_t> originalScaleShape;
502 if (origScaleVecType)
503 llvm::append_range(originalScaleShape, origScaleVecType.getShape());
504
505 originalScaleShape.insert(originalScaleShape.end(),
506 inShape.size() - originalScaleShape.size(), 1);
507
508 auto maybeRatio = computeShapeRatio(inShape, originalScaleShape);
509 assert(maybeRatio &&
510 "failed to derive block size from broadcast or splat operation");
511
512 SmallVector<int64_t> ratio =
513 maybeRatio.value_or(SmallVector<int64_t>(inShape.size(), 1));
514
515 int64_t blockSize = computeProduct(ratio);
516
517 Value zero = arith::ConstantOp::create(rewriter, loc, outType,
518 rewriter.getFloatAttr(outType, 0.0));
519 Value result =
520 rewriter.createOrFold<vector::BroadcastOp>(loc, outVecType, zero);
521
522 for (SmallVector<int64_t> offsets : StaticTileOffsetRange(inShape, ratio)) {
523 SmallVector<int64_t> strides(offsets.size(), 1);
524 Value block = vector::ExtractStridedSliceOp::create(
525 rewriter, loc, in, offsets, ratio, strides);
526 VectorType block1DType = VectorType::get(blockSize, inType);
527 Value block1D =
528 vector::ShapeCastOp::create(rewriter, loc, block1DType, block);
529 Value uniformScale =
530 vector::ExtractOp::create(rewriter, loc, scale, offsets);
531
532 VectorType blockResultType = VectorType::get(blockSize, outType);
533 Value blockResult =
534 rewriter.createOrFold<vector::BroadcastOp>(loc, blockResultType, zero);
535
536 for (int64_t i = 0, inSliceWidth = std::min(opInWidth, blockSize - i);
537 i < blockSize;
538 i += inSliceWidth, inSliceWidth = std::min(opInWidth, blockSize - i)) {
539 Value inSlice = vector::ExtractStridedSliceOp::create(
540 rewriter, loc, block1D, i, inSliceWidth, 1);
541 for (int64_t j = 0,
542 outSliceWidth = std::min(opOutWidth, inSliceWidth - j);
543 j < inSliceWidth; j += outSliceWidth,
544 outSliceWidth = std::min(opOutWidth, inSliceWidth - j)) {
545 // TODO: replace this with non-packed ScaledExtOp for sliceWidth == 1
546 Value scaleExt = amdgpu::ScaledExtPackedOp::create(
547 rewriter, loc, extScaleResultType, inSlice, uniformScale,
548 j / opOutWidth);
549 if (outSliceWidth < opOutWidth) {
550 scaleExt = vector::ExtractStridedSliceOp::create(
551 rewriter, loc, scaleExt, 0, outSliceWidth, 1);
552 }
553 blockResult = vector::InsertStridedSliceOp::create(
554 rewriter, loc, scaleExt, blockResult, i + j, 1);
555 }
556 }
557
558 VectorType resultType = VectorType::get(ratio, outType);
559 Value cast =
560 vector::ShapeCastOp::create(rewriter, loc, resultType, blockResult);
561 result = vector::InsertStridedSliceOp::create(rewriter, loc, cast, result,
562 offsets, strides);
563 }
564
565 rewriter.replaceOp(op, result);
566
567 return success();
568}
569
570LogicalResult
571ScalingTruncFRewritePattern::matchAndRewrite(arith::ScalingTruncFOp op,
572 PatternRewriter &rewriter) const {
573 Location loc = op.getLoc();
574 constexpr int64_t opInWidth = 2;
575
576 Value in = op.getIn();
577 Value scale = op.getScale();
578 Value out = op.getOut();
579
580 Type f32 = rewriter.getF32Type();
581 Type inType = getElementTypeOrSelf(in);
582 Type scaleType = getElementTypeOrSelf(scale);
583 Type outType = getElementTypeOrSelf(out);
584
585 VectorType outVecType = dyn_cast<VectorType>(out.getType());
586 VectorType scaleVecType = dyn_cast<VectorType>(scale.getType());
587 if (outVecType && outVecType.isScalable())
588 return failure();
589
590 if (isa<RankedTensorType>(out.getType()) ||
591 isa<RankedTensorType>(in.getType()) ||
592 isa<RankedTensorType>(scale.getType()))
593 return failure();
594
595 Type scaleF32Type =
596 scaleVecType ? VectorType::get(scaleVecType.getShape(), f32) : f32;
597 if (scaleType.getIntOrFloatBitWidth() < 32)
598 scale =
599 arith::ExtFOp::create(rewriter, loc, TypeRange{scaleF32Type},
600 ValueRange{scale}, arith::ExtFOp::Properties{});
601 else if (scaleType.getIntOrFloatBitWidth() > 32)
602 scale = arith::TruncFOp::create(rewriter, loc, scaleF32Type, scale);
603
604 Value zero = arith::ConstantOp::create(rewriter, loc, outType,
605 rewriter.getFloatAttr(outType, 0.0));
606 int64_t opOutWidth = 32 / outType.getIntOrFloatBitWidth();
607 VectorType truncScaleResultType = VectorType::get(opOutWidth, outType);
608
609 if (!outVecType) {
610 Type inVecType = VectorType::get(1, inType);
611 Value inCast = vector::BroadcastOp::create(rewriter, loc, inVecType, in);
612 // TODO: replace this with non-packed ScaledTruncOp
613 Value scaleTrunc = amdgpu::PackedScaledTruncOp::create(
614 rewriter, loc, truncScaleResultType, inCast, scale, 0,
615 /*existing=*/nullptr);
616 scaleTrunc =
617 rewriter.replaceOpWithNewOp<vector::ExtractOp>(op, scaleTrunc, 0);
618 return success();
619 }
620
621 VectorType inVecType = cast<VectorType>(in.getType());
622 Value origScale = getOriginalVectorValue(op.getScale());
623 VectorType origScaleVecType = dyn_cast<VectorType>(origScale.getType());
624
625 ArrayRef<int64_t> inShape = inVecType.getShape();
626 SmallVector<int64_t> scaleShape;
627 if (origScaleVecType)
628 llvm::append_range(scaleShape, origScaleVecType.getShape());
629
630 scaleShape.insert(scaleShape.end(), inShape.size() - scaleShape.size(), 1);
631
632 auto maybeRatio = computeShapeRatio(inShape, scaleShape);
633 assert(maybeRatio &&
634 "failed to derive block size from broadcast or splat operation");
635
636 SmallVector<int64_t> ratio =
637 maybeRatio.value_or(SmallVector<int64_t>(inShape.size(), 1));
638
639 int64_t blockSize = computeProduct(ratio);
640
641 Value result =
642 rewriter.createOrFold<vector::BroadcastOp>(loc, outVecType, zero);
643
644 for (SmallVector<int64_t> offsets : StaticTileOffsetRange(inShape, ratio)) {
645 SmallVector<int64_t> strides(offsets.size(), 1);
646 Value block = vector::ExtractStridedSliceOp::create(
647 rewriter, loc, in, offsets, ratio, strides);
648 VectorType block1DType = VectorType::get(blockSize, inType);
649 Value block1D =
650 vector::ShapeCastOp::create(rewriter, loc, block1DType, block);
651 Value uniformScale =
652 vector::ExtractOp::create(rewriter, loc, scale, offsets);
653
654 VectorType blockResultType = VectorType::get(blockSize, outType);
655 Value blockResult =
656 rewriter.createOrFold<vector::BroadcastOp>(loc, blockResultType, zero);
657
658 for (int64_t i = 0, outSliceWidth = std::min(opOutWidth, blockSize - i);
659 i < blockSize; i += outSliceWidth,
660 outSliceWidth = std::min(opOutWidth, blockSize - i)) {
661 Value scaleTrunc;
662 // Case where <= 2 elements are being truncated.
663 if (outSliceWidth <= opInWidth) {
664 Value slice = vector::ExtractStridedSliceOp::create(
665 rewriter, loc, block1D, i, outSliceWidth, 1);
666 // TODO: replace this with non-packed ScaledTruncOp for sliceWidth == 1
667 scaleTrunc = amdgpu::PackedScaledTruncOp::create(
668 rewriter, loc, truncScaleResultType, slice, uniformScale, 0,
669 /*existing=*/nullptr);
670 } else {
671 scaleTrunc = vector::BroadcastOp::create(rewriter, loc,
672 truncScaleResultType, zero);
673 for (int64_t j = 0,
674 inSliceWidth = std::min(opInWidth, outSliceWidth - j);
675 j < outSliceWidth; j += opInWidth,
676 inSliceWidth = std::min(opInWidth, outSliceWidth - j)) {
677 Value slice = vector::ExtractStridedSliceOp::create(
678 rewriter, loc, block1D, i + j, inSliceWidth, 1);
679 scaleTrunc = amdgpu::PackedScaledTruncOp::create(
680 rewriter, loc, truncScaleResultType, slice, uniformScale,
681 j / opInWidth, scaleTrunc);
682 }
683 }
684 if (outSliceWidth != opOutWidth) {
685 scaleTrunc = vector::ExtractStridedSliceOp::create(
686 rewriter, loc, scaleTrunc, 0, outSliceWidth, 1);
687 }
688 blockResult = vector::InsertStridedSliceOp::create(
689 rewriter, loc, scaleTrunc, blockResult, i, 1);
690 }
691
692 VectorType resultType = VectorType::get(ratio, outType);
693 Value cast =
694 vector::ShapeCastOp::create(rewriter, loc, resultType, blockResult);
695 result = vector::InsertStridedSliceOp::create(rewriter, loc, cast, result,
696 offsets, strides);
697 }
698
699 rewriter.replaceOp(op, result);
700
701 return success();
702}
703
705 RewritePatternSet &patterns, bool convertFP8Arithmetic,
706 bool saturateFP8Truncf, bool allowPackedF16Rtz, bool supportsScaledExtTrunc,
707 Chipset chipset, PatternBenefit benefit) {
708
709 if (convertFP8Arithmetic) {
710 patterns.add<ExtFOnFloat8RewritePattern>(patterns.getContext(), chipset,
711 benefit);
712 patterns.add<TruncFToFloat8RewritePattern>(
713 patterns.getContext(), saturateFP8Truncf, chipset, benefit);
714 }
715 if (allowPackedF16Rtz)
716 patterns.add<TruncfToFloat16RewritePattern>(patterns.getContext(), benefit);
717
718 if (supportsScaledExtTrunc) {
719 patterns.add<ScalingExtFRewritePattern>(patterns.getContext(), benefit);
720 patterns.add<ScalingTruncFRewritePattern>(patterns.getContext(), benefit);
721 }
722}
723
724void ArithToAMDGPUConversionPass::runOnOperation() {
725 Operation *op = getOperation();
726 MLIRContext *ctx = &getContext();
727 RewritePatternSet patterns(op->getContext());
728 FailureOr<amdgpu::Chipset> maybeChipset = amdgpu::Chipset::parse(chipset);
729 if (failed(maybeChipset)) {
730 emitError(UnknownLoc::get(ctx), "Invalid chipset name: " + chipset);
731 return signalPassFailure();
732 }
733
734 bool convertFP8Arithmetic =
735 *maybeChipset == kGfx942 || hasOcpFp8(*maybeChipset);
736 bool supportsScaledExtTrunc = *maybeChipset == kGfx950;
738 patterns, convertFP8Arithmetic, saturateFP8Truncf, allowPackedF16Rtz,
739 supportsScaledExtTrunc, *maybeChipset);
740 if (failed(applyPatternsGreedily(op, std::move(patterns))))
741 return signalPassFailure();
742}
constexpr Chipset kGfx942
constexpr Chipset kGfx950
return success()
static Value getOriginalVectorValue(Value value)
Get the broadcasted / splatted value for a chain of ops.
static Value castF32To(Type desType, Value f32, Location loc, PatternRewriter &rewriter)
static Value castToF32(Value value, Location loc, PatternRewriter &rewriter)
static bool isSupportedF8(Type elementType, Chipset chipset)
static Value clampInput(PatternRewriter &rewriter, Location loc, Type outElemType, Value source)
b getContext())
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
FloatType getF32Type()
Definition Builders.cpp:51
FloatAttr getFloatAttr(Type type, double value)
Definition Builders.cpp:263
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
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:528
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isF32() const
Definition Types.cpp:40
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
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
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
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
bool hasOcpFp8(const Chipset &chipset)
Definition Chipset.h:52
void populateArithToAMDGPUConversionPatterns(RewritePatternSet &patterns, bool convertFP8Arithmetic, bool saturateFP8Truncf, bool allowPackedF16Rtz, bool supportsScaledExtTrunc, amdgpu::Chipset chipset, PatternBenefit benefit=1)
Add patterns for rewriting arith.extf and arith.truncf on FP8 types to wrappers around AMDGPU–specifi...
Include the generated interface declarations.
Value createScalarOrSplatConstant(OpBuilder &builder, Location loc, Type type, const APInt &value)
Create a constant of type type at location loc whose value is value (an APInt or APFloat whose type m...
Definition Utils.cpp:276
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...
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
int64_t computeProduct(ArrayRef< int64_t > basis)
Self-explicit.
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
std::optional< SmallVector< int64_t > > computeShapeRatio(ArrayRef< int64_t > shape, ArrayRef< int64_t > subShape)
Return the multi-dimensional integral ratio of subShape to the trailing dimensions of shape.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
Represents the amdgpu gfx chipset version, e.g., gfx90a, gfx942, gfx1103.
Definition Chipset.h:22
static FailureOr< Chipset > parse(StringRef name)
Parses the chipset version string and returns the chipset on success, and failure otherwise.
Definition Chipset.cpp:14