29#include "llvm/ADT/STLExtras.h"
30#include "llvm/ADT/SmallVector.h"
35#define GEN_PASS_DEF_XEGPUPEEPHOLEOPTIMIZER
36#include "mlir/Dialect/XeGPU/Transforms/Passes.h.inc"
40#define DEBUG_TYPE "xegpu-optimize-peephole"
41#define DBGS() (llvm::dbgs() << "[" DEBUG_TYPE "]: ")
48static std::optional<SmallVector<int64_t>>
49getMaybeLaneData(xegpu::TensorDescType tdescType) {
50 auto layout = tdescType.getLayoutAttr();
53 auto laneData = layout.getEffectiveLaneDataAsInt();
54 if (laneData.size() != 2)
60static std::optional<SmallVector<int64_t>>
61getMaybeLaneLayout(xegpu::TensorDescType tdescType) {
62 auto layout = tdescType.getLayoutAttr();
65 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
66 if (laneLayout.size() != 2)
84 if (laneLayout.size() != 2 || laneData.size() != 2)
86 if (laneLayout[0] == 1 || laneLayout[1] != 1)
88 if (laneData[0] != 1 || laneData[1] == 1)
95static bool canBeOptimizedForTranspose(xegpu::TensorDescType tdescType) {
97 int elementTyBitwidth = tdescType.getElementType().getIntOrFloatBitWidth();
98 if (elementTyBitwidth >= 32)
100 auto maybeLaneLayout = getMaybeLaneLayout(tdescType);
101 auto maybeLaneData = getMaybeLaneData(tdescType);
102 if (!maybeLaneData || !maybeLaneLayout)
104 return canBeOptimizedForTranspose(*maybeLaneLayout, *maybeLaneData);
109static xegpu::TensorDescType
110tryOptimize(xegpu::TensorDescType tdescType,
112 if (!canBeOptimizedForTranspose(tdescType))
114 auto laneData = getMaybeLaneData(tdescType)
117 int elementTyBitwidth = tdescType.getElementType().getIntOrFloatBitWidth();
122 requiredShape.back() =
123 requiredShape.back() * tdescType.getArrayLength() / innerLaneData;
124 int newBitWidth = elementTyBitwidth * innerLaneData;
125 Type newElemTy = IntegerType::get(tdescType.getContext(), newBitWidth);
128 auto *blockLoadTarget =
129 dyn_cast<xegpu::uArch::Subgroup2DBlockLoadInstruction>(
132 auto maybeHWParams = blockLoadTarget->getBlockWidthHeightCount(
133 newElemTy,
false,
true);
137 auto [widths, heights, counts] = maybeHWParams.value();
139 if (counts.size() != 1 || counts[0] != 1)
141 int arrayLen = counts[0];
142 int supportedHeight =
147 if (supportedHeight == -1 || supportedWidth == -1)
151 auto ctx = tdescType.getContext();
152 auto origLayout = tdescType.getLayoutAttr();
153 auto laneLayoutI64 = origLayout.getEffectiveLaneLayoutAsInt();
155 laneLayoutI64.end());
157 xegpu::LayoutAttr newLayout = xegpu::LayoutAttr::get(
160 origLayout.getOrder());
163 return xegpu::TensorDescType::get(supportedShape, newElemTy, arrayLen,
164 tdescType.getBoundaryCheck(),
165 tdescType.getMemorySpace(), newLayout);
169static Value convertToValue(ConversionPatternRewriter &rewriter,
Location loc,
174 return llvm::cast<Value>(ofr);
178static Value divideByConstant(ConversionPatternRewriter &rewriter,
Location loc,
181 if (llvm::isPowerOf2_64(constant)) {
182 int64_t shiftAmount = llvm::Log2_64(constant);
183 return arith::ShRUIOp::create(
191 return arith::DivUIOp::create(rewriter, loc, val, constantOp).getResult();
197static Value generateLoads(ConversionPatternRewriter &rewriter,
201 xegpu::LoadNdOp origLoadOp) {
203 assert(offsets.size() >= 2 &&
"Expecting at least 2 offsets for 2D LoadNdOp");
204 Value offsetDim0 = convertToValue(rewriter, loc, offsets[offsets.size() - 2]);
205 Value offsetDim1 = convertToValue(rewriter, loc, offsets[offsets.size() - 1]);
212 for (
int64_t h = 0; h < shapeRatio[0]; ++h) {
213 for (
int64_t w = 0; w < shapeRatio[1]; ++w) {
214 int64_t localOffsetDim0 = h * supportedShape[0];
215 int64_t localOffsetDim1 = w * supportedShape[1];
216 Value loadOffsetX = arith::AddIOp::create(
217 rewriter, loc, offsetDim0,
220 Value loadOffsetY = arith::AddIOp::create(
221 rewriter, loc, offsetDim1,
224 auto loadOp = xegpu::LoadNdOp::create(
226 VectorType::get(supportedShape, data.getType().getElementType()),
228 origLoadOp.getPackedAttr(), origLoadOp.getTransposeAttr(),
229 origLoadOp.getL1HintAttr(), origLoadOp.getL2HintAttr(),
230 origLoadOp.getL3HintAttr(), origLoadOp.getLayoutAttr());
232 auto layoutAttr = newTensorDesc.getType().getLayoutAttr();
233 loadOp.setAnchorLayout(layoutAttr);
235 auto insertOp = vector::InsertStridedSliceOp::create(
236 rewriter, loc, loadOp.getResult(), data,
241 data = insertOp.getResult();
251class XeGPUCreateNdDescOpPattern final
252 :
public OpConversionPattern<xegpu::CreateNdDescOp> {
254 using OpConversionPattern<xegpu::CreateNdDescOp>::OpConversionPattern;
256 matchAndRewrite(xegpu::CreateNdDescOp createNdOp, OpAdaptor adaptor,
257 ConversionPatternRewriter &rewriter)
const override {
258 auto tdescTy = createNdOp.getType();
263 (chipStr.value() ==
"pvc" || chipStr.value() ==
"bmg" ||
264 chipStr.value() ==
"cri") &&
265 "Expecting target chip to be pvc, bmg or cri for transpose "
269 auto convertType = tryOptimize(tdescTy, targetuArch);
270 if (convertType == tdescTy)
272 auto strides = createNdOp.getMixedStrides();
275 if (!maybeConstInnerStride || *maybeConstInnerStride != 1)
276 return rewriter.notifyMatchFailure(
277 createNdOp,
"Expecting row-major memref for transpose optimization.");
278 Value source = createNdOp.getSource();
279 auto optionalLaneData = getMaybeLaneData(tdescTy);
280 assert(optionalLaneData &&
"Expected 2D lane data");
281 auto laneData = optionalLaneData.value();
282 int64_t innerLaneData = laneData[1];
283 auto memrefType = dyn_cast<MemRefType>(source.
getType());
285 SmallVector<OpFoldResult> modifiedShape(createNdOp.getMixedSizes());
286 modifiedShape.back() = divideByConstant(
287 rewriter, createNdOp.getLoc(),
288 convertToValue(rewriter, createNdOp.getLoc(), modifiedShape.back()),
291 assert(strides.size() >= 2 &&
292 "Expected at least 2 strides for CreateNdDescOp");
293 SmallVector<OpFoldResult> modifiedStrides(strides);
294 modifiedStrides[modifiedStrides.size() - 2] = divideByConstant(
295 rewriter, createNdOp.getLoc(),
296 convertToValue(rewriter, createNdOp.getLoc(),
297 modifiedStrides[modifiedStrides.size() - 2]),
302 if (memrefType && memrefType.hasStaticShape()) {
303 auto extractOp = memref::ExtractAlignedPointerAsIndexOp::create(
304 rewriter, createNdOp.getLoc(), source);
305 source = arith::IndexCastOp::create(rewriter, createNdOp.getLoc(),
306 rewriter.getI64Type(),
307 extractOp.getResult())
311 auto newCreateNdDescOp = xegpu::CreateNdDescOp::create(
312 rewriter, createNdOp.getLoc(), convertType, source, modifiedShape,
314 rewriter.replaceOp(createNdOp, newCreateNdDescOp.getResult());
323class XeGPULoadNdDescOpPattern final
324 :
public OpConversionPattern<xegpu::LoadNdOp> {
326 using OpConversionPattern<xegpu::LoadNdOp>::OpConversionPattern;
328 matchAndRewrite(xegpu::LoadNdOp loadNdOp, OpAdaptor adaptor,
329 ConversionPatternRewriter &rewriter)
const override {
330 auto origTensorDescType = loadNdOp.getTensorDescType();
332 cast<xegpu::TensorDescType>(adaptor.getTensorDesc().getType());
333 if (adaptorType == origTensorDescType)
336 auto laneData = getMaybeLaneData(loadNdOp.getTensorDescType()).value();
337 int64_t innerLaneData = laneData[1];
338 auto offsets = loadNdOp.getMixedOffsets();
340 return rewriter.notifyMatchFailure(loadNdOp,
341 "Expecting offsets in LoadNd");
342 SmallVector<OpFoldResult> modifiedOffsets(offsets);
343 modifiedOffsets.back() = divideByConstant(
344 rewriter, loadNdOp.getLoc(),
345 convertToValue(rewriter, loadNdOp.getLoc(), modifiedOffsets.back()),
349 SmallVector<int64_t> origDataShape(origTensorDescType.getShape());
351 origDataShape.back() /= innerLaneData;
353 SmallVector<int64_t> hwSupportedShape(adaptorType.getShape());
354 VectorType origVectorType =
355 VectorType::get(origDataShape, adaptorType.getElementType());
358 if (origTensorDescType.getArrayLength() > 1) {
359 SmallVector<Value> arraySlices;
360 for (int64_t i = 0; i < origTensorDescType.getArrayLength(); ++i) {
361 Value slice = arith::ConstantOp::create(
362 rewriter, loadNdOp->getLoc(), origVectorType,
363 rewriter.getZeroAttr(origVectorType));
365 Value offsetY = convertToValue(rewriter, loadNdOp->getLoc(),
366 modifiedOffsets.back());
367 modifiedOffsets.back() =
368 arith::AddIOp::create(
369 rewriter, loadNdOp->getLoc(), offsetY,
371 i * origDataShape[1])
374 slice = generateLoads(
379 auto bitcastType = VectorType::get(origTensorDescType.getShape(),
380 origTensorDescType.getElementType());
381 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
385 origTensorDescType.getLayoutAttr());
386 arraySlices.push_back(bitCastOp.getResult());
388 rewriter.replaceOpWithMultiple(loadNdOp, {arraySlices});
391 data = arith::ConstantOp::create(
392 rewriter, loadNdOp->getLoc(),
393 VectorType::get(origDataShape, adaptorType.getElementType()),
394 rewriter.getZeroAttr(origVectorType));
395 data = generateLoads(
399 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
400 loadNdOp.getType(), data);
403 origTensorDescType.getLayoutAttr());
404 rewriter.replaceOp(loadNdOp, bitCastOp);
413class VectorExtractOpPattern final
414 :
public OpConversionPattern<vector::ExtractOp> {
416 using OpConversionPattern<vector::ExtractOp>::OpConversionPattern;
418 matchAndRewrite(vector::ExtractOp extractOp, OneToNOpAdaptor adaptor,
419 ConversionPatternRewriter &rewriter)
const override {
421 if (adaptor.getSource().size() == 1)
423 auto mixedPos = extractOp.getMixedPosition();
424 if (mixedPos.size() != 1)
429 rewriter.replaceOp(extractOp, adaptor.getSource()[*mayBeInt]);
436class MultiRed2dOpPattern
437 :
public OpConversionPattern<vector::MultiDimReductionOp> {
438 using OpConversionPattern::OpConversionPattern;
440 matchAndRewrite(vector::MultiDimReductionOp reductionOp, OpAdaptor adaptor,
441 ConversionPatternRewriter &rewriter)
const override {
442 auto sourceVecType = reductionOp.getSourceVectorType();
443 if (reductionOp.getReductionDims().size() != 2)
444 return rewriter.notifyMatchFailure(reductionOp,
"Expected 2D reduction");
447 auto dims = llvm::to_vector(reductionOp.getReductionDims());
448 auto [intraLaneDim, crossLaneDim] = getReductionDimOrder(dims, resLayout);
450 if (intraLaneDim == -1 || crossLaneDim == -1) {
451 intraLaneDim = dims[0];
452 crossLaneDim = dims[1];
454 auto loc = reductionOp.getLoc();
455 auto acc = reductionOp.getAcc();
467 xegpu::DistributeLayoutAttr postDecompLayout;
470 xegpu::DistributeLayoutAttr srcLayoutForCvt;
471 if (
auto resSlice = dyn_cast_if_present<xegpu::SliceAttr>(resLayout))
472 srcLayoutForCvt = resSlice.getParent();
473 if (!srcLayoutForCvt)
476 if (srcLayoutForCvt) {
482 MLIRContext *ctx = reductionOp.getContext();
483 int64_t adjCrossLaneDim =
484 crossLaneDim > intraLaneDim ? crossLaneDim - 1 : crossLaneDim;
485 auto intermediateLayout = xegpu::SliceAttr::get(
487 postDecompLayout = xegpu::SliceAttr::get(
488 ctx, intermediateLayout,
493 SmallVector<int64_t> accShape(sourceVecType.getShape());
494 accShape.erase(accShape.begin() + intraLaneDim);
495 Type eTy = sourceVecType.getElementType();
497 rewriter, loc, VectorType::get(accShape, eTy), reductionOp.getKind());
499 Value intraLaneReduced = vector::MultiDimReductionOp::create(
500 rewriter, loc, reductionOp.getKind(), reductionOp.getSource(),
501 constNeutralVal, ArrayRef<int64_t>(intraLaneDim));
504 if (crossLaneDim > intraLaneDim)
506 Value crossLaneReduced = vector::MultiDimReductionOp::create(
507 rewriter, loc, reductionOp.getKind(), intraLaneReduced, acc,
508 ArrayRef<int64_t>(crossLaneDim));
509 assert(crossLaneReduced.
getType() == reductionOp.getResult().getType() &&
513 if (resLayout && postDecompLayout) {
519 auto bridgeOp = xegpu::ConvertLayoutOp::create(
520 rewriter, loc, crossLaneReduced.
getType(), crossLaneReduced,
521 postDecompLayout, resLayout);
530 std::pair<int64_t, int64_t>
531 getReductionDimOrder(ArrayRef<int64_t> reductionDims,
532 xegpu::DistributeLayoutAttr layout)
const {
533 assert(layout.isForSubgroup() &&
"Must know the lane layout");
534 assert(reductionDims.size() == 2 &&
"Expected 2D reduction");
535 int64_t intra, cross = -1;
536 xegpu::LayoutAttr layoutAttr = dyn_cast<xegpu::LayoutAttr>(layout);
537 if (
auto layoutSliceAttr = dyn_cast<xegpu::SliceAttr>(layout))
539 dyn_cast<xegpu::LayoutAttr>(layoutSliceAttr.flatten().getParent());
541 SmallVector<int64_t> laneLayout = layoutAttr.getEffectiveLaneLayoutAsInt();
543 assert(laneLayout.size() &&
"Expected a non-empty layout");
545 for (
auto dim : reductionDims) {
546 if (laneLayout[dim] == 1)
551 return {intra, cross};
559 patterns.
add<XeGPUCreateNdDescOpPattern, XeGPULoadNdDescOpPattern,
560 VectorExtractOpPattern, MultiRed2dOpPattern>(
566struct XeGPUPeepHoleOptimizerPass final
568 XeGPUPeepHoleOptimizerPass> {
569 void runOnOperation()
override {
577 bool isTargetSupported =
false;
578 getOperation()->walk([&](gpu::GPUFuncOp funcOp) {
580 if (chipStr && (chipStr.value() ==
"pvc" || chipStr.value() ==
"bmg" ||
581 chipStr.value() ==
"cri"))
582 isTargetSupported =
true;
585 if (!isTargetSupported) {
586 DBGS() <<
"XeGPUPeepHoleOptimizerPass only supports PVC, BMG targets."
594 RewritePatternSet arrayLenPatterns(&context);
597 std::move(arrayLenPatterns)))) {
598 DBGS() <<
"Array length optimization patterns failed.\n";
599 return signalPassFailure();
605 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp>(
606 [&](xegpu::CreateNdDescOp createNdOp) {
607 return !canBeOptimizedForTranspose(createNdOp.getType());
609 target.addDynamicallyLegalOp<xegpu::LoadNdOp>(
610 [&](xegpu::LoadNdOp loadNdOp) {
611 return !canBeOptimizedForTranspose(loadNdOp.getTensorDescType());
616 target.addDynamicallyLegalOp<vector::ExtractOp>(
617 [&](vector::ExtractOp extractOp) {
619 dyn_cast<OpResult>(extractOp.getResult()));
622 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
623 auto laneData = layout.getEffectiveLaneDataAsInt();
624 return !canBeOptimizedForTranspose(laneLayout, laneData);
627 target.addDynamicallyLegalOp<vector::MultiDimReductionOp>(
628 [=](Operation *op) ->
bool {
630 if (!layout || !layout.isForSubgroup())
632 if (
auto reductionOp = dyn_cast<vector::MultiDimReductionOp>(op))
633 return reductionOp.getReductionDims().size() != 2;
637 converter.addConversion([](Type type) {
return type; });
639 target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect,
640 vector::VectorDialect>();
643 target.addLegalOp<xegpu::ConvertLayoutOp>();
647 if (
failed(applyPartialConversion(getOperation(),
target,
648 std::move(patterns)))) {
649 DBGS() <<
"Optimize block loads pass failed.\n";
650 return signalPassFailure();
655 RewritePatternSet emptyPatterns(ctx);
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be the output argument nBegin is set to its * replacement(set to `begin` if no invalidation happens). Since outgoing *copies could have been inserted at `end`
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
MLIRContext is the top-level object for a collection of MLIR operations.
This class represents a single result from folding an operation.
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.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
static DenseArrayAttrImpl get(MLIRContext *context, ArrayRef< int32_t > content)
void populateSCFStructuralTypeConversionsAndLegality(const TypeConverter &typeConverter, RewritePatternSet &patterns, ConversionTarget &target, PatternBenefit benefit=1)
Populates patterns for SCF structural type conversions and sets up the provided ConversionTarget with...
const uArch * getUArch(llvm::StringRef archName)
void populateXeGPUArrayLengthOptimizationPatterns(RewritePatternSet &patterns)
Appends patterns for array length optimization into patterns.
void removeTemporaryLayoutAttrs(Operation *op)
Removes the temporary layout attributes for each OpOperand and OpResult of the given operation.
void setTemporaryLayout(const T &operandOrResult, const DistributeLayoutAttr layout)
Value createReductionNeutralValue(OpBuilder &builder, Location loc, Type type, vector::CombiningKind kind)
Creates a constant filled with the neutral (identity) value for the given reduction kind.
int getLargestDivisor(T dim, ArrayRef< T > candidates, ArrayRef< T > candidateMultiples={})
Helper Function to find a proper instruction multiple for the user-supplied sg-level data shape (dive...
DistributeLayoutAttr getDistributeLayoutAttr(const Value value)
Retrieves the DistributeLayoutAttr associated with a given Value.
std::optional< std::string > getChipStr(Operation *op)
Retrieves the chip string from the XeVM target attribute of the parent GPU module operation.
DistributeLayoutAttr getTemporaryLayout(const T &operandOrResult)
get and set distribute layout attribute for non-anchor operations (and offsets/masks of load/store op...
void populateXeGPUPeepHoleOptimizerPatterns(RewritePatternSet &patterns)
Appends patterns for optimizing block load operations into patterns.
Include the generated interface declarations.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
LogicalResult applyPatternsGreedily(Region ®ion, 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...
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.
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.
const Instruction * getInstruction(InstructionKind instKind) const