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)
111 int64_t innerLaneData = laneData[1];
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)
290 Value source = createNdOp.getSource();
291 auto memrefType = dyn_cast<MemRefType>(source.
getType());
295 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];
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");
325 for (
size_t i = 0; i + 1 < modifiedStrides.size(); ++i)
326 modifiedStrides[i] = divideByConstant(
327 rewriter, loc, convertToValue(rewriter, loc, modifiedStrides[i]),
332 Value alignedPtr = memref::ExtractAlignedPointerAsIndexOp::create(
333 rewriter, loc, meta.getBaseBuffer());
335 rewriter, loc, memrefType.getElementTypeBitWidth() / 8);
337 arith::MulIOp::create(rewriter, loc, meta.getOffset(), elemBytes);
339 arith::AddIOp::create(rewriter, loc, alignedPtr, offsetBytes);
340 source = arith::IndexCastOp::create(rewriter, loc, rewriter.getI64Type(),
344 auto newCreateNdDescOp = xegpu::CreateNdDescOp::create(
345 rewriter, loc, convertType, source, modifiedShape, modifiedStrides);
346 rewriter.replaceOp(createNdOp, newCreateNdDescOp.getResult());
355class XeGPULoadNdDescOpPattern final
356 :
public OpConversionPattern<xegpu::LoadNdOp> {
358 using OpConversionPattern<xegpu::LoadNdOp>::OpConversionPattern;
360 matchAndRewrite(xegpu::LoadNdOp loadNdOp, OpAdaptor adaptor,
361 ConversionPatternRewriter &rewriter)
const override {
362 auto origTensorDescType = loadNdOp.getTensorDescType();
364 cast<xegpu::TensorDescType>(adaptor.getTensorDesc().getType());
365 if (adaptorType == origTensorDescType)
368 auto laneData = getMaybeLaneData(loadNdOp.getTensorDescType()).value();
369 int64_t innerLaneData = laneData[1];
370 auto offsets = loadNdOp.getMixedOffsets();
372 return rewriter.notifyMatchFailure(loadNdOp,
373 "Expecting offsets in LoadNd");
375 modifiedOffsets.back() = divideByConstant(
376 rewriter, loadNdOp.getLoc(),
377 convertToValue(rewriter, loadNdOp.getLoc(), modifiedOffsets.back()),
383 origDataShape.back() /= innerLaneData;
386 VectorType origVectorType =
387 VectorType::get(origDataShape, adaptorType.getElementType());
390 if (origTensorDescType.getArrayLength() > 1) {
392 for (
int64_t i = 0; i < origTensorDescType.getArrayLength(); ++i) {
393 Value slice = arith::ConstantOp::create(
394 rewriter, loadNdOp->getLoc(), origVectorType,
395 rewriter.getZeroAttr(origVectorType));
397 Value offsetY = convertToValue(rewriter, loadNdOp->getLoc(),
398 modifiedOffsets.back());
399 modifiedOffsets.back() =
400 arith::AddIOp::create(
401 rewriter, loadNdOp->getLoc(), offsetY,
403 i * origDataShape[1])
406 slice = generateLoads(
411 auto bitcastType = VectorType::get(origTensorDescType.getShape(),
412 origTensorDescType.getElementType());
413 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
417 origTensorDescType.getLayoutAttr());
418 arraySlices.push_back(bitCastOp.getResult());
420 rewriter.replaceOpWithMultiple(loadNdOp, {arraySlices});
423 data = arith::ConstantOp::create(
424 rewriter, loadNdOp->getLoc(),
425 VectorType::get(origDataShape, adaptorType.getElementType()),
426 rewriter.getZeroAttr(origVectorType));
427 data = generateLoads(
431 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
432 loadNdOp.getType(), data);
435 origTensorDescType.getLayoutAttr());
436 rewriter.replaceOp(loadNdOp, bitCastOp);
445class VectorExtractOpPattern final
446 :
public OpConversionPattern<vector::ExtractOp> {
448 using OpConversionPattern<vector::ExtractOp>::OpConversionPattern;
451 ConversionPatternRewriter &rewriter)
const override {
453 if (adaptor.getSource().size() == 1)
455 auto mixedPos = extractOp.getMixedPosition();
456 if (mixedPos.size() != 1)
461 rewriter.replaceOp(extractOp, adaptor.getSource()[*mayBeInt]);
468class MultiRed2dOpPattern
469 :
public OpConversionPattern<vector::MultiDimReductionOp> {
470 using OpConversionPattern::OpConversionPattern;
472 matchAndRewrite(vector::MultiDimReductionOp reductionOp, OpAdaptor adaptor,
473 ConversionPatternRewriter &rewriter)
const override {
474 auto sourceVecType = reductionOp.getSourceVectorType();
475 if (reductionOp.getReductionDims().size() != 2)
476 return rewriter.notifyMatchFailure(reductionOp,
"Expected 2D reduction");
479 auto dims = llvm::to_vector(reductionOp.getReductionDims());
480 auto [intraLaneDim, crossLaneDim] = getReductionDimOrder(dims, resLayout);
482 if (intraLaneDim == -1 || crossLaneDim == -1) {
483 intraLaneDim = dims[0];
484 crossLaneDim = dims[1];
486 auto loc = reductionOp.getLoc();
487 auto acc = reductionOp.getAcc();
499 xegpu::DistributeLayoutAttr postDecompLayout;
502 xegpu::DistributeLayoutAttr srcLayoutForCvt;
503 if (
auto resSlice = dyn_cast_if_present<xegpu::SliceAttr>(resLayout))
504 srcLayoutForCvt = resSlice.getParent();
505 if (!srcLayoutForCvt)
508 if (srcLayoutForCvt) {
516 crossLaneDim > intraLaneDim ? crossLaneDim - 1 : crossLaneDim;
517 auto intermediateLayout = xegpu::SliceAttr::get(
519 postDecompLayout = xegpu::SliceAttr::get(
520 ctx, intermediateLayout,
526 accShape.erase(accShape.begin() + intraLaneDim);
527 Type eTy = sourceVecType.getElementType();
529 rewriter, loc, VectorType::get(accShape, eTy), reductionOp.getKind());
531 Value intraLaneReduced = vector::MultiDimReductionOp::create(
532 rewriter, loc, reductionOp.getKind(), reductionOp.getSource(),
536 if (crossLaneDim > intraLaneDim)
538 Value crossLaneReduced = vector::MultiDimReductionOp::create(
539 rewriter, loc, reductionOp.getKind(), intraLaneReduced,
acc,
541 assert(crossLaneReduced.
getType() == reductionOp.getResult().getType() &&
545 if (resLayout && postDecompLayout) {
551 auto bridgeOp = xegpu::ConvertLayoutOp::create(
552 rewriter, loc, crossLaneReduced.
getType(), crossLaneReduced,
553 postDecompLayout, resLayout);
562 std::pair<int64_t, int64_t>
564 xegpu::DistributeLayoutAttr layout)
const {
565 assert(layout.isForSubgroup() &&
"Must know the lane layout");
566 assert(reductionDims.size() == 2 &&
"Expected 2D reduction");
567 int64_t intra = -1, cross = -1;
568 xegpu::LayoutAttr layoutAttr = dyn_cast<xegpu::LayoutAttr>(layout);
569 if (
auto layoutSliceAttr = dyn_cast<xegpu::SliceAttr>(layout))
571 dyn_cast<xegpu::LayoutAttr>(layoutSliceAttr.flatten().getParent());
575 assert(laneLayout.size() &&
"Expected a non-empty layout");
577 for (
auto dim : reductionDims) {
578 if (laneLayout[dim] == 1)
583 return {intra, cross};
591 patterns.
add<XeGPUCreateNdDescOpPattern, XeGPULoadNdDescOpPattern,
592 VectorExtractOpPattern, MultiRed2dOpPattern>(
598struct XeGPUPeepHoleOptimizerPass final
599 :
public xegpu::impl::XeGPUPeepHoleOptimizerBase<
600 XeGPUPeepHoleOptimizerPass> {
601 void runOnOperation()
override {
609 bool isTargetSupported =
false;
610 getOperation()->walk([&](gpu::GPUFuncOp funcOp) {
612 if (chipStr && (chipStr.value() ==
"pvc" || chipStr.value() ==
"bmg" ||
613 chipStr.value() ==
"cri"))
614 isTargetSupported =
true;
617 if (!isTargetSupported) {
618 DBGS() <<
"XeGPUPeepHoleOptimizerPass only supports PVC, BMG targets."
626 RewritePatternSet arrayLenPatterns(&context);
629 std::move(arrayLenPatterns)))) {
630 DBGS() <<
"Array length optimization patterns failed.\n";
631 return signalPassFailure();
637 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp>(
638 [&](xegpu::CreateNdDescOp createNdOp) {
639 return !canBeOptimizedForTranspose(createNdOp.getType());
641 target.addDynamicallyLegalOp<xegpu::LoadNdOp>(
642 [&](xegpu::LoadNdOp loadNdOp) {
643 return !canBeOptimizedForTranspose(loadNdOp.getTensorDescType());
648 target.addDynamicallyLegalOp<vector::ExtractOp>(
649 [&](vector::ExtractOp extractOp) {
651 dyn_cast<OpResult>(extractOp.getResult()));
654 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
655 auto laneData = layout.getEffectiveLaneDataAsInt();
656 return !canBeOptimizedForTranspose(laneLayout, laneData);
659 target.addDynamicallyLegalOp<vector::MultiDimReductionOp>(
660 [=](Operation *op) ->
bool {
662 if (!layout || !layout.isForSubgroup())
664 if (
auto reductionOp = dyn_cast<vector::MultiDimReductionOp>(op))
665 return reductionOp.getReductionDims().size() != 2;
669 converter.addConversion([](Type type) {
return type; });
671 target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect,
672 vector::VectorDialect>();
675 target.addLegalOp<xegpu::ConvertLayoutOp>();
679 if (
failed(applyPartialConversion(getOperation(),
target,
680 std::move(patterns)))) {
681 DBGS() <<
"Optimize block loads pass failed.\n";
682 return signalPassFailure();
687 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...
DistributeLayoutAttr getDistributeLayoutAttr(const Value value)
Retrieves the DistributeLayoutAttr associated with a given Value, or nullptr if none is found.
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