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();
57static std::optional<SmallVector<int64_t>>
58getMaybeLaneLayout(xegpu::TensorDescType tdescType) {
59 auto layout = tdescType.getLayoutAttr();
63 layout.getEffectiveLaneLayoutAsInt());
79 if (laneLayout.size() != 2 || laneData.size() != 2)
81 if (laneLayout[0] == 1 || laneLayout[1] != 1)
83 if (laneData[0] != 1 || laneData[1] == 1)
90static bool canBeOptimizedForTranspose(xegpu::TensorDescType tdescType) {
92 int elementTyBitwidth = tdescType.getElementType().getIntOrFloatBitWidth();
93 if (elementTyBitwidth >= 32)
95 auto maybeLaneLayout = getMaybeLaneLayout(tdescType);
96 auto maybeLaneData = getMaybeLaneData(tdescType);
97 if (!maybeLaneData || !maybeLaneLayout)
99 return canBeOptimizedForTranspose(*maybeLaneLayout, *maybeLaneData);
104static xegpu::TensorDescType
105tryOptimize(xegpu::TensorDescType tdescType,
107 if (!canBeOptimizedForTranspose(tdescType))
109 auto laneData = getMaybeLaneData(tdescType)
112 int elementTyBitwidth = tdescType.getElementType().getIntOrFloatBitWidth();
117 requiredShape.back() =
118 requiredShape.back() * tdescType.getArrayLength() / innerLaneData;
119 int newBitWidth = elementTyBitwidth * innerLaneData;
120 Type newElemTy = IntegerType::get(tdescType.getContext(), newBitWidth);
123 auto *blockLoadTarget =
124 dyn_cast<xegpu::uArch::Subgroup2DBlockLoadInstruction>(
127 auto maybeHWParams = blockLoadTarget->getBlockWidthHeightCount(
128 newElemTy,
false,
true);
132 auto [widths, heights, counts] = maybeHWParams.value();
134 if (counts.size() != 1 || counts[0] != 1)
136 int arrayLen = counts[0];
139 int64_t rank = requiredShape.size();
141 static_cast<int>(requiredShape[rank - 2]), heights);
143 static_cast<int>(requiredShape[rank - 1]), widths);
145 if (supportedHeight == -1 || supportedWidth == -1)
150 requiredShape.end() - 2);
151 supportedShape.push_back(supportedHeight);
152 supportedShape.push_back(supportedWidth);
153 auto ctx = tdescType.getContext();
154 auto origLayout = tdescType.getLayoutAttr();
155 auto laneLayoutI64 = origLayout.getEffectiveLaneLayoutAsInt();
157 laneLayoutI64.end());
159 xegpu::LayoutAttr newLayout = xegpu::LayoutAttr::get(
164 origLayout.getOrder());
167 return xegpu::TensorDescType::get(supportedShape, newElemTy, arrayLen,
168 tdescType.getBoundaryCheck(),
169 tdescType.getMemorySpace(), newLayout);
173static Value convertToValue(ConversionPatternRewriter &rewriter,
Location loc,
178 return llvm::cast<Value>(ofr);
182static Value divideByConstant(ConversionPatternRewriter &rewriter,
Location loc,
185 if (llvm::isPowerOf2_64(constant)) {
186 int64_t shiftAmount = llvm::Log2_64(constant);
187 return arith::ShRUIOp::create(
195 return arith::DivUIOp::create(rewriter, loc, val, constantOp).getResult();
201static Value generateLoads(ConversionPatternRewriter &rewriter,
205 xegpu::LoadNdOp origLoadOp) {
207 assert(offsets.size() >= 2 &&
"Expecting at least 2 offsets for 2D LoadNdOp");
208 int64_t rank = data.getType().getRank();
209 Value offsetDim0 = convertToValue(rewriter, loc, offsets[offsets.size() - 2]);
210 Value offsetDim1 = convertToValue(rewriter, loc, offsets[offsets.size() - 1]);
220 int64_t suppDim0 = supportedShape[rank - 2];
221 int64_t suppDim1 = supportedShape[rank - 1];
222 for (
int64_t h = 0; h < shapeRatio[rank - 2]; ++h) {
223 for (
int64_t w = 0; w < shapeRatio[rank - 1]; ++w) {
224 int64_t localOffsetDim0 = h * suppDim0;
225 int64_t localOffsetDim1 = w * suppDim1;
226 Value loadOffsetX = arith::AddIOp::create(
227 rewriter, loc, offsetDim0,
230 Value loadOffsetY = arith::AddIOp::create(
231 rewriter, loc, offsetDim1,
236 loadOffsets[loadOffsets.size() - 2] = loadOffsetX;
237 loadOffsets[loadOffsets.size() - 1] = loadOffsetY;
238 auto loadOp = xegpu::LoadNdOp::create(
240 VectorType::get(supportedShape, data.getType().getElementType()),
241 newTensorDesc, loadOffsets, origLoadOp.getPackedAttr(),
242 origLoadOp.getTransposeAttr(), origLoadOp.getL1HintAttr(),
243 origLoadOp.getL2HintAttr(), origLoadOp.getL3HintAttr(),
244 origLoadOp.getLayoutAttr());
246 auto layoutAttr = newTensorDesc.getType().getLayoutAttr();
247 loadOp.setAnchorLayout(layoutAttr);
251 insertPos[rank - 2] = localOffsetDim0;
252 insertPos[rank - 1] = localOffsetDim1;
254 auto insertOp = vector::InsertStridedSliceOp::create(
255 rewriter, loc, loadOp.getResult(), data, insertPos, insertStrides);
258 data = insertOp.getResult();
268class XeGPUCreateNdDescOpPattern final
269 :
public OpConversionPattern<xegpu::CreateNdDescOp> {
271 using OpConversionPattern<xegpu::CreateNdDescOp>::OpConversionPattern;
273 matchAndRewrite(xegpu::CreateNdDescOp createNdOp, OpAdaptor adaptor,
274 ConversionPatternRewriter &rewriter)
const override {
275 auto tdescTy = createNdOp.getType();
280 (chipStr.value() ==
"pvc" || chipStr.value() ==
"bmg" ||
281 chipStr.value() ==
"cri") &&
282 "Expecting target chip to be pvc, bmg or cri for transpose "
286 auto convertType = tryOptimize(tdescTy, targetuArch);
287 if (convertType == tdescTy)
289 Location loc = createNdOp.getLoc();
290 Value source = createNdOp.getSource();
291 auto memrefType = dyn_cast<MemRefType>(source.
getType());
295 SmallVector<OpFoldResult> mixedSizes;
296 SmallVector<OpFoldResult> mixedStrides;
297 memref::ExtractStridedMetadataOp meta;
299 meta = memref::ExtractStridedMetadataOp::create(rewriter, loc, source);
300 mixedSizes = meta.getConstifiedMixedSizes();
301 mixedStrides = meta.getConstifiedMixedStrides();
303 mixedSizes = createNdOp.getMixedSizes();
304 mixedStrides = createNdOp.getMixedStrides();
309 if (!maybeConstInnerStride || *maybeConstInnerStride != 1)
310 return rewriter.notifyMatchFailure(
311 createNdOp,
"Expecting row-major memref for transpose optimization.");
312 auto optionalLaneData = getMaybeLaneData(tdescTy);
313 assert(optionalLaneData &&
"Expected 2D lane data");
314 auto laneData = optionalLaneData.value();
315 int64_t innerLaneData = laneData[1];
317 SmallVector<OpFoldResult> modifiedShape(mixedSizes);
318 modifiedShape.back() = divideByConstant(
319 rewriter, loc, convertToValue(rewriter, loc, modifiedShape.back()),
322 assert(mixedStrides.size() >= 2 &&
323 "Expected at least 2 strides for CreateNdDescOp");
324 SmallVector<OpFoldResult> modifiedStrides(mixedStrides);
325 for (
size_t i = 0; i + 1 < modifiedStrides.size(); ++i)
326 modifiedStrides[i] = divideByConstant(
327 rewriter, loc, convertToValue(rewriter, loc, modifiedStrides[i]),
334 Value alignedPtr = memref::ExtractAlignedPointerAsIndexOp::create(
335 rewriter, loc, meta.getBaseBuffer());
337 rewriter, loc, memrefType.getElementTypeBitWidth() / 8);
339 arith::MulIOp::create(rewriter, loc, meta.getOffset(), elemBytes);
340 baseIdx = arith::AddIOp::create(rewriter, loc, alignedPtr, offBytes);
342 baseIdx = memref::ExtractAlignedPointerAsIndexOp::create(rewriter, loc,
345 source = arith::IndexCastOp::create(rewriter, loc, rewriter.getI64Type(),
349 auto newCreateNdDescOp = xegpu::CreateNdDescOp::create(
350 rewriter, loc, convertType, source, modifiedShape, modifiedStrides);
351 rewriter.replaceOp(createNdOp, newCreateNdDescOp.getResult());
360class XeGPULoadNdDescOpPattern final
361 :
public OpConversionPattern<xegpu::LoadNdOp> {
363 using OpConversionPattern<xegpu::LoadNdOp>::OpConversionPattern;
365 matchAndRewrite(xegpu::LoadNdOp loadNdOp, OpAdaptor adaptor,
366 ConversionPatternRewriter &rewriter)
const override {
367 auto origTensorDescType = loadNdOp.getTensorDescType();
369 cast<xegpu::TensorDescType>(adaptor.getTensorDesc().getType());
370 if (adaptorType == origTensorDescType)
373 auto laneData = getMaybeLaneData(loadNdOp.getTensorDescType()).value();
374 int64_t innerLaneData = laneData[1];
375 auto offsets = loadNdOp.getMixedOffsets();
377 return rewriter.notifyMatchFailure(loadNdOp,
378 "Expecting offsets in LoadNd");
379 SmallVector<OpFoldResult> modifiedOffsets(offsets);
380 modifiedOffsets.back() = divideByConstant(
381 rewriter, loadNdOp.getLoc(),
382 convertToValue(rewriter, loadNdOp.getLoc(), modifiedOffsets.back()),
386 SmallVector<int64_t> origDataShape(origTensorDescType.getShape());
388 origDataShape.back() /= innerLaneData;
390 SmallVector<int64_t> hwSupportedShape(adaptorType.getShape());
391 VectorType origVectorType =
392 VectorType::get(origDataShape, adaptorType.getElementType());
395 if (origTensorDescType.getArrayLength() > 1) {
396 SmallVector<Value> arraySlices;
397 for (int64_t i = 0; i < origTensorDescType.getArrayLength(); ++i) {
398 Value slice = arith::ConstantOp::create(
399 rewriter, loadNdOp->getLoc(), origVectorType,
400 rewriter.getZeroAttr(origVectorType));
402 Value offsetY = convertToValue(rewriter, loadNdOp->getLoc(),
403 modifiedOffsets.back());
404 modifiedOffsets.back() =
405 arith::AddIOp::create(
406 rewriter, loadNdOp->getLoc(), offsetY,
408 i * origDataShape[1])
411 slice = generateLoads(
416 auto bitcastType = VectorType::get(origTensorDescType.getShape(),
417 origTensorDescType.getElementType());
418 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
422 origTensorDescType.getLayoutAttr());
423 arraySlices.push_back(bitCastOp.getResult());
425 rewriter.replaceOpWithMultiple(loadNdOp, {arraySlices});
428 data = arith::ConstantOp::create(
429 rewriter, loadNdOp->getLoc(),
430 VectorType::get(origDataShape, adaptorType.getElementType()),
431 rewriter.getZeroAttr(origVectorType));
432 data = generateLoads(
436 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
437 loadNdOp.getType(), data);
440 origTensorDescType.getLayoutAttr());
441 rewriter.replaceOp(loadNdOp, bitCastOp);
450class VectorExtractOpPattern final
451 :
public OpConversionPattern<vector::ExtractOp> {
453 using OpConversionPattern<vector::ExtractOp>::OpConversionPattern;
455 matchAndRewrite(vector::ExtractOp extractOp, OneToNOpAdaptor adaptor,
456 ConversionPatternRewriter &rewriter)
const override {
458 if (adaptor.getSource().size() == 1)
460 auto mixedPos = extractOp.getMixedPosition();
461 if (mixedPos.size() != 1)
466 rewriter.replaceOp(extractOp, adaptor.getSource()[*mayBeInt]);
473class MultiRed2dOpPattern
474 :
public OpConversionPattern<vector::MultiDimReductionOp> {
475 using OpConversionPattern::OpConversionPattern;
477 matchAndRewrite(vector::MultiDimReductionOp reductionOp, OpAdaptor adaptor,
478 ConversionPatternRewriter &rewriter)
const override {
479 auto sourceVecType = reductionOp.getSourceVectorType();
480 if (reductionOp.getReductionDims().size() != 2)
481 return rewriter.notifyMatchFailure(reductionOp,
"Expected 2D reduction");
484 auto dims = llvm::to_vector(reductionOp.getReductionDims());
485 auto [intraLaneDim, crossLaneDim] = getReductionDimOrder(dims, resLayout);
487 if (intraLaneDim == -1 || crossLaneDim == -1) {
488 intraLaneDim = dims[0];
489 crossLaneDim = dims[1];
491 auto loc = reductionOp.getLoc();
492 auto acc = reductionOp.getAcc();
504 xegpu::DistributeLayoutAttr postDecompLayout;
507 xegpu::DistributeLayoutAttr srcLayoutForCvt;
508 if (
auto resSlice = dyn_cast_if_present<xegpu::SliceAttr>(resLayout))
509 srcLayoutForCvt = resSlice.getParent();
510 if (!srcLayoutForCvt)
513 if (srcLayoutForCvt) {
519 MLIRContext *ctx = reductionOp.getContext();
520 int64_t adjCrossLaneDim =
521 crossLaneDim > intraLaneDim ? crossLaneDim - 1 : crossLaneDim;
522 auto intermediateLayout = xegpu::SliceAttr::get(
524 postDecompLayout = xegpu::SliceAttr::get(
525 ctx, intermediateLayout,
530 SmallVector<int64_t> accShape(sourceVecType.getShape());
531 accShape.erase(accShape.begin() + intraLaneDim);
532 Type eTy = sourceVecType.getElementType();
534 rewriter, loc, VectorType::get(accShape, eTy), reductionOp.getKind());
536 Value intraLaneReduced = vector::MultiDimReductionOp::create(
537 rewriter, loc, reductionOp.getKind(), reductionOp.getSource(),
538 constNeutralVal, ArrayRef<int64_t>(intraLaneDim));
541 if (crossLaneDim > intraLaneDim)
543 Value crossLaneReduced = vector::MultiDimReductionOp::create(
544 rewriter, loc, reductionOp.getKind(), intraLaneReduced, acc,
545 ArrayRef<int64_t>(crossLaneDim));
546 assert(crossLaneReduced.
getType() == reductionOp.getResult().getType() &&
550 if (resLayout && postDecompLayout) {
556 auto bridgeOp = xegpu::ConvertLayoutOp::create(
557 rewriter, loc, crossLaneReduced.
getType(), crossLaneReduced,
558 postDecompLayout, resLayout);
567 std::pair<int64_t, int64_t>
568 getReductionDimOrder(ArrayRef<int64_t> reductionDims,
569 xegpu::DistributeLayoutAttr layout)
const {
570 assert(layout.isForSubgroup() &&
"Must know the lane layout");
571 assert(reductionDims.size() == 2 &&
"Expected 2D reduction");
572 int64_t intra, cross = -1;
573 xegpu::LayoutAttr layoutAttr = dyn_cast<xegpu::LayoutAttr>(layout);
574 if (
auto layoutSliceAttr = dyn_cast<xegpu::SliceAttr>(layout))
576 dyn_cast<xegpu::LayoutAttr>(layoutSliceAttr.flatten().getParent());
578 SmallVector<int64_t> laneLayout = layoutAttr.getEffectiveLaneLayoutAsInt();
580 assert(laneLayout.size() &&
"Expected a non-empty layout");
582 for (
auto dim : reductionDims) {
583 if (laneLayout[dim] == 1)
588 return {intra, cross};
596 patterns.
add<XeGPUCreateNdDescOpPattern, XeGPULoadNdDescOpPattern,
597 VectorExtractOpPattern, MultiRed2dOpPattern>(
603struct XeGPUPeepHoleOptimizerPass final
605 XeGPUPeepHoleOptimizerPass> {
606 void runOnOperation()
override {
614 bool isTargetSupported =
false;
615 getOperation()->walk([&](gpu::GPUFuncOp funcOp) {
617 if (chipStr && (chipStr.value() ==
"pvc" || chipStr.value() ==
"bmg" ||
618 chipStr.value() ==
"cri"))
619 isTargetSupported =
true;
622 if (!isTargetSupported) {
623 DBGS() <<
"XeGPUPeepHoleOptimizerPass only supports PVC, BMG targets."
631 RewritePatternSet arrayLenPatterns(&context);
634 std::move(arrayLenPatterns)))) {
635 DBGS() <<
"Array length optimization patterns failed.\n";
636 return signalPassFailure();
642 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp>(
643 [&](xegpu::CreateNdDescOp createNdOp) {
644 return !canBeOptimizedForTranspose(createNdOp.getType());
646 target.addDynamicallyLegalOp<xegpu::LoadNdOp>(
647 [&](xegpu::LoadNdOp loadNdOp) {
648 return !canBeOptimizedForTranspose(loadNdOp.getTensorDescType());
653 target.addDynamicallyLegalOp<vector::ExtractOp>(
654 [&](vector::ExtractOp extractOp) {
656 dyn_cast<OpResult>(extractOp.getResult()));
659 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
660 auto laneData = layout.getEffectiveLaneDataAsInt();
661 return !canBeOptimizedForTranspose(laneLayout, laneData);
664 target.addDynamicallyLegalOp<vector::MultiDimReductionOp>(
665 [=](Operation *op) ->
bool {
667 if (!layout || !layout.isForSubgroup())
669 if (
auto reductionOp = dyn_cast<vector::MultiDimReductionOp>(op))
670 return reductionOp.getReductionDims().size() != 2;
674 converter.addConversion([](Type type) {
return type; });
676 target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect,
677 vector::VectorDialect>();
680 target.addLegalOp<xegpu::ConvertLayoutOp>();
684 if (
failed(applyPartialConversion(getOperation(),
target,
685 std::move(patterns)))) {
686 DBGS() <<
"Optimize block loads pass failed.\n";
687 return signalPassFailure();
692 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.
std::optional< SmallVector< int64_t > > getInner2DIfUnitLeadingDims(ArrayRef< int64_t > vals)
Returns the innermost 2 entries of vals if it is at least 2D and all of its leading entries are unit;...
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...
bool hasStaticShapeAndStrides(MemRefType type)
Returns true if type has a static shape and static strides.
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