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