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());
293 SmallVector<OpFoldResult> mixedSizes;
294 SmallVector<OpFoldResult> mixedStrides;
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];
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]),
332 Value alignedPtr = memref::ExtractAlignedPointerAsIndexOp::create(
333 rewriter, loc, meta.getBaseBuffer());
335 meta.getOffset(), memrefType);
336 source = arith::IndexCastOp::create(rewriter, loc, rewriter.getI64Type(),
340 auto newCreateNdDescOp = xegpu::CreateNdDescOp::create(
341 rewriter, loc, convertType, source, modifiedShape, modifiedStrides);
342 rewriter.replaceOp(createNdOp, newCreateNdDescOp.getResult());
351class XeGPULoadNdDescOpPattern final
352 :
public OpConversionPattern<xegpu::LoadNdOp> {
354 using OpConversionPattern<xegpu::LoadNdOp>::OpConversionPattern;
356 matchAndRewrite(xegpu::LoadNdOp loadNdOp, OpAdaptor adaptor,
357 ConversionPatternRewriter &rewriter)
const override {
358 auto origTensorDescType = loadNdOp.getTensorDescType();
360 cast<xegpu::TensorDescType>(adaptor.getTensorDesc().getType());
361 if (adaptorType == origTensorDescType)
364 auto laneData = getMaybeLaneData(loadNdOp.getTensorDescType()).value();
365 int64_t innerLaneData = laneData[1];
366 auto offsets = loadNdOp.getMixedOffsets();
368 return rewriter.notifyMatchFailure(loadNdOp,
369 "Expecting offsets in LoadNd");
370 SmallVector<OpFoldResult> modifiedOffsets(offsets);
371 modifiedOffsets.back() = divideByConstant(
372 rewriter, loadNdOp.getLoc(),
373 convertToValue(rewriter, loadNdOp.getLoc(), modifiedOffsets.back()),
377 SmallVector<int64_t> origDataShape(origTensorDescType.getShape());
379 origDataShape.back() /= innerLaneData;
381 SmallVector<int64_t> hwSupportedShape(adaptorType.getShape());
382 VectorType origVectorType =
383 VectorType::get(origDataShape, adaptorType.getElementType());
386 if (origTensorDescType.getArrayLength() > 1) {
387 SmallVector<Value> arraySlices;
388 for (int64_t i = 0; i < origTensorDescType.getArrayLength(); ++i) {
389 Value
slice = arith::ConstantOp::create(
390 rewriter, loadNdOp->getLoc(), origVectorType,
391 rewriter.getZeroAttr(origVectorType));
393 Value offsetY = convertToValue(rewriter, loadNdOp->getLoc(),
394 modifiedOffsets.back());
395 modifiedOffsets.back() =
396 arith::AddIOp::create(
397 rewriter, loadNdOp->getLoc(), offsetY,
399 i * origDataShape[1])
402 slice = generateLoads(
407 auto bitcastType = VectorType::get(origTensorDescType.getShape(),
408 origTensorDescType.getElementType());
409 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
413 origTensorDescType.getLayoutAttr());
414 arraySlices.push_back(bitCastOp.getResult());
416 rewriter.replaceOpWithMultiple(loadNdOp, {arraySlices});
419 data = arith::ConstantOp::create(
420 rewriter, loadNdOp->getLoc(),
421 VectorType::get(origDataShape, adaptorType.getElementType()),
422 rewriter.getZeroAttr(origVectorType));
423 data = generateLoads(
427 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
428 loadNdOp.getType(), data);
431 origTensorDescType.getLayoutAttr());
432 rewriter.replaceOp(loadNdOp, bitCastOp);
441class VectorExtractOpPattern final
442 :
public OpConversionPattern<vector::ExtractOp> {
444 using OpConversionPattern<vector::ExtractOp>::OpConversionPattern;
446 matchAndRewrite(vector::ExtractOp extractOp, OneToNOpAdaptor adaptor,
447 ConversionPatternRewriter &rewriter)
const override {
449 if (adaptor.getSource().size() == 1)
451 auto mixedPos = extractOp.getMixedPosition();
452 if (mixedPos.size() != 1)
457 rewriter.replaceOp(extractOp, adaptor.getSource()[*mayBeInt]);
464class MultiRed2dOpPattern
465 :
public OpConversionPattern<vector::MultiDimReductionOp> {
468 matchAndRewrite(vector::MultiDimReductionOp reductionOp, OpAdaptor adaptor,
469 ConversionPatternRewriter &rewriter)
const override {
470 auto sourceVecType = reductionOp.getSourceVectorType();
471 if (reductionOp.getReductionDims().size() != 2)
472 return rewriter.notifyMatchFailure(reductionOp,
"Expected 2D reduction");
475 auto dims = llvm::to_vector(reductionOp.getReductionDims());
476 auto [intraLaneDim, crossLaneDim] = getReductionDimOrder(dims, resLayout);
478 if (intraLaneDim == -1 || crossLaneDim == -1) {
479 intraLaneDim = dims[0];
480 crossLaneDim = dims[1];
482 auto loc = reductionOp.getLoc();
483 auto acc = reductionOp.getAcc();
495 xegpu::DistributeLayoutAttr postDecompLayout;
498 xegpu::DistributeLayoutAttr srcLayoutForCvt;
499 if (
auto resSlice = dyn_cast_if_present<xegpu::SliceAttr>(resLayout))
500 srcLayoutForCvt = resSlice.getParent();
501 if (!srcLayoutForCvt)
504 if (srcLayoutForCvt) {
510 MLIRContext *ctx = reductionOp.getContext();
511 int64_t adjCrossLaneDim =
512 crossLaneDim > intraLaneDim ? crossLaneDim - 1 : crossLaneDim;
513 auto intermediateLayout = xegpu::SliceAttr::get(
515 postDecompLayout = xegpu::SliceAttr::get(
516 ctx, intermediateLayout,
521 SmallVector<int64_t> accShape(sourceVecType.getShape());
522 accShape.erase(accShape.begin() + intraLaneDim);
523 Type eTy = sourceVecType.getElementType();
525 rewriter, loc, VectorType::get(accShape, eTy), reductionOp.getKind(),
526 reductionOp.getFastmathAttr());
528 Value intraLaneReduced = vector::MultiDimReductionOp::create(
529 rewriter, loc, reductionOp.getKind(), reductionOp.getSource(),
530 constNeutralVal, ArrayRef<int64_t>(intraLaneDim),
531 reductionOp.getFastmath());
534 if (crossLaneDim > intraLaneDim)
536 Value crossLaneReduced = vector::MultiDimReductionOp::create(
537 rewriter, loc, reductionOp.getKind(), intraLaneReduced, acc,
538 ArrayRef<int64_t>(crossLaneDim), reductionOp.getFastmath());
539 assert(crossLaneReduced.
getType() == reductionOp.getResult().getType() &&
543 if (resLayout && postDecompLayout) {
549 auto bridgeOp = xegpu::ConvertLayoutOp::create(
550 rewriter, loc, crossLaneReduced.
getType(), crossLaneReduced,
551 postDecompLayout, resLayout);
560 std::pair<int64_t, int64_t>
561 getReductionDimOrder(ArrayRef<int64_t> reductionDims,
562 xegpu::DistributeLayoutAttr layout)
const {
563 assert(layout.isForSubgroup() &&
"Must know the lane layout");
564 assert(reductionDims.size() == 2 &&
"Expected 2D reduction");
565 int64_t intra = -1, cross = -1;
566 xegpu::LayoutAttr layoutAttr = dyn_cast<xegpu::LayoutAttr>(layout);
567 if (
auto layoutSliceAttr = dyn_cast<xegpu::SliceAttr>(layout))
569 dyn_cast<xegpu::LayoutAttr>(layoutSliceAttr.flatten().getParent());
571 SmallVector<int64_t> laneLayout = layoutAttr.getEffectiveLaneLayoutAsInt();
573 assert(laneLayout.size() &&
"Expected a non-empty layout");
575 for (
auto dim : reductionDims) {
576 if (laneLayout[dim] == 1)
581 return {intra, cross};
589 patterns.
add<XeGPUCreateNdDescOpPattern, XeGPULoadNdDescOpPattern,
590 VectorExtractOpPattern, MultiRed2dOpPattern>(
596struct XeGPUPeepHoleOptimizerPass final
598 XeGPUPeepHoleOptimizerPass> {
599 void runOnOperation()
override {
607 bool isTargetSupported =
false;
608 getOperation()->walk([&](gpu::GPUFuncOp funcOp) {
610 if (chipStr && (chipStr.value() ==
"pvc" || chipStr.value() ==
"bmg" ||
611 chipStr.value() ==
"cri"))
612 isTargetSupported =
true;
615 if (!isTargetSupported) {
616 DBGS() <<
"XeGPUPeepHoleOptimizerPass only supports PVC, BMG targets."
624 RewritePatternSet arrayLenPatterns(&context);
627 std::move(arrayLenPatterns)))) {
628 DBGS() <<
"Array length optimization patterns failed.\n";
629 return signalPassFailure();
635 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp>(
636 [&](xegpu::CreateNdDescOp createNdOp) {
637 return !canBeOptimizedForTranspose(createNdOp.getType());
639 target.addDynamicallyLegalOp<xegpu::LoadNdOp>(
640 [&](xegpu::LoadNdOp loadNdOp) {
641 return !canBeOptimizedForTranspose(loadNdOp.getTensorDescType());
646 target.addDynamicallyLegalOp<vector::ExtractOp>(
647 [&](vector::ExtractOp extractOp) {
649 dyn_cast<OpResult>(extractOp.getResult()));
652 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
653 auto laneData = layout.getEffectiveLaneDataAsInt();
654 return !canBeOptimizedForTranspose(laneLayout, laneData);
657 target.addDynamicallyLegalOp<vector::MultiDimReductionOp>(
658 [=](Operation *op) ->
bool {
660 if (!layout || !layout.isForSubgroup())
662 if (
auto reductionOp = dyn_cast<vector::MultiDimReductionOp>(op))
663 return reductionOp.getReductionDims().size() != 2;
667 converter.addConversion([](Type type) {
return type; });
669 target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect,
670 vector::VectorDialect>();
673 target.addLegalOp<xegpu::ConvertLayoutOp>();
677 if (
failed(applyPartialConversion(getOperation(),
target,
678 std::move(patterns)))) {
679 DBGS() <<
"Optimize block loads pass failed.\n";
680 return signalPassFailure();
685 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`
static SmallVector< Value > slice(OpBuilder &b, Location loc, Value value, VectorType nativeType)
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)
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.
Value addElemOffsetInBytes(OpBuilder &builder, Location loc, Value baseAddr, Value elemOffset, MemRefType memrefTy)
Returns baseAddr + elemOffset * bitWidth / 8, i.e.
std::optional< std::string > getChipStr(Operation *op)
Retrieves the chip string from the XeVM target attribute of the parent GPU module operation.
Value createReductionNeutralValue(OpBuilder &builder, Location loc, Type type, vector::CombiningKind kind, arith::FastMathFlagsAttr fastmath=nullptr)
Creates a constant filled with the neutral (identity) value for the given reduction kind.
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