MLIR 24.0.0git
XeGPUPeepHoleOptimizer.cpp
Go to the documentation of this file.
1//===- XeGPUPeepHoleOptimizer.cpp - XeGPU optimize block loads -*- C++ -*-===//
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
25#include "mlir/IR/Types.h"
26#include "mlir/IR/Value.h"
29#include "llvm/ADT/STLExtras.h"
30#include "llvm/ADT/SmallVector.h"
31#include <optional>
32
33namespace mlir {
34namespace xegpu {
35#define GEN_PASS_DEF_XEGPUPEEPHOLEOPTIMIZER
36#include "mlir/Dialect/XeGPU/Transforms/Passes.h.inc"
37} // namespace xegpu
38} // namespace mlir
39
40#define DEBUG_TYPE "xegpu-optimize-peephole"
41#define DBGS() (llvm::dbgs() << "[" DEBUG_TYPE "]: ")
42
43using namespace mlir;
44
45namespace {
46
47/// Get the 2D lane data from a tensor desc type if it exists.
48static std::optional<SmallVector<int64_t>>
49getMaybeLaneData(xegpu::TensorDescType tdescType) {
50 auto layout = tdescType.getLayoutAttr();
51 if (!layout)
52 return std::nullopt;
53 return xegpu::getInner2DIfUnitLeadingDims(layout.getEffectiveLaneDataAsInt());
54}
55
56/// Get the 2D lane layout from a tensor desc type if it exists.
57static std::optional<SmallVector<int64_t>>
58getMaybeLaneLayout(xegpu::TensorDescType tdescType) {
59 auto layout = tdescType.getLayoutAttr();
60 if (!layout)
61 return std::nullopt;
63 layout.getEffectiveLaneLayoutAsInt());
64}
65
66/// A layout can be optimized if its lane layout is transposed (lane[0] != 1 &&
67/// lane[1] == 1), but inner lane data is not equal to [1, 1].
68/// Example:
69/// !xegpu.tensor_desc<16x16xf16,
70/// #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>>
71/// In this case, lane layout is transposed (from the usual [1, SG_SIZE] form)
72/// indicating that this is a load that requires transpose effect. However,
73/// lane data is [1, 2], meaning that each lane must grab 2 f16 elements from
74/// the inner dimension. We convert this to a optimized form by converting the
75/// tensor_desc to i32 type such that lane data becomes [1, 1]. This makes the
76/// later lowering easily use the load with transpose instruction.
77static bool canBeOptimizedForTranspose(ArrayRef<int64_t> laneLayout,
78 ArrayRef<int64_t> laneData) {
79 if (laneLayout.size() != 2 || laneData.size() != 2)
80 return false;
81 if (laneLayout[0] == 1 || laneLayout[1] != 1)
82 return false;
83 if (laneData[0] != 1 || laneData[1] == 1)
84 return false;
85 return true;
86}
87
88/// A tensor desc type can be optimized if its element type is less than 32 bits
89/// and its layout can be optimized.
90static bool canBeOptimizedForTranspose(xegpu::TensorDescType tdescType) {
91 // If the dtype is greater or equal to 32 bits, layout must be valid.
92 int elementTyBitwidth = tdescType.getElementType().getIntOrFloatBitWidth();
93 if (elementTyBitwidth >= 32)
94 return false;
95 auto maybeLaneLayout = getMaybeLaneLayout(tdescType);
96 auto maybeLaneData = getMaybeLaneData(tdescType);
97 if (!maybeLaneData || !maybeLaneLayout)
98 return false;
99 return canBeOptimizedForTranspose(*maybeLaneLayout, *maybeLaneData);
100}
101
102/// Check if a tensor desc type can be optimized for transpose, if so return the
103/// new optimized tensor desc type with a valid transpose layout.
104static xegpu::TensorDescType
105tryOptimize(xegpu::TensorDescType tdescType,
106 const xegpu::uArch::uArch *targetuArch) {
107 if (!canBeOptimizedForTranspose(tdescType))
108 return tdescType;
109 auto laneData = getMaybeLaneData(tdescType)
110 .value(); // Lane data must exist if we reach here.
111 int64_t innerLaneData = laneData[1];
112 int elementTyBitwidth = tdescType.getElementType().getIntOrFloatBitWidth();
113 // Required shape is total shape of the vector result that this tensor desc
114 // must eventually load after adjusting for the new bitwidth and array
115 // length.
116 SmallVector<int64_t> requiredShape(tdescType.getShape());
117 requiredShape.back() =
118 requiredShape.back() * tdescType.getArrayLength() / innerLaneData;
119 int newBitWidth = elementTyBitwidth * innerLaneData;
120 Type newElemTy = IntegerType::get(tdescType.getContext(), newBitWidth);
121 // Supported shape is the max transpose shape that can be supported by
122 // hardware that is less than or equal to required shape.
123 auto *blockLoadTarget =
124 dyn_cast<xegpu::uArch::Subgroup2DBlockLoadInstruction>(
125 targetuArch->getInstruction(
127 auto maybeHWParams = blockLoadTarget->getBlockWidthHeightCount(
128 newElemTy, /** has transform */ false, /** has transpose */ true);
129 // If no HW params found, return the original type.
130 if (!maybeHWParams)
131 return tdescType;
132 auto [widths, heights, counts] = maybeHWParams.value();
133 // TODO: Currently we expect array length to be 1 for transpose case.
134 if (counts.size() != 1 || counts[0] != 1)
135 return tdescType;
136 int arrayLen = counts[0];
137 // The transpose acts on the innermost 2 dims; any leading dims are unit
138 // batch.
139 int64_t rank = requiredShape.size();
140 int supportedHeight = xegpu::getLargestDivisor(
141 static_cast<int>(requiredShape[rank - 2]), heights);
142 int supportedWidth = xegpu::getLargestDivisor(
143 static_cast<int>(requiredShape[rank - 1]), widths);
144 // If no supported height or width found, return the original type.
145 if (supportedHeight == -1 || supportedWidth == -1)
146 return tdescType;
147
148 // Preserve leading (unit) batch dims; only the inner 2 dims are reshaped.
149 SmallVector<int64_t> supportedShape(requiredShape.begin(),
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();
156 SmallVector<int32_t> laneLayoutI32(laneLayoutI64.begin(),
157 laneLayoutI64.end());
158
159 xegpu::LayoutAttr newLayout = xegpu::LayoutAttr::get(
160 ctx, /*lane_layout=*/DenseI32ArrayAttr::get(ctx, laneLayoutI32),
161 /*lane_data=*/
163 SmallVector<int32_t>(laneLayoutI32.size(), 1)),
164 /*order=*/origLayout.getOrder());
165
166 // Array length can not be larger than 1 for transpose case.
167 return xegpu::TensorDescType::get(supportedShape, newElemTy, arrayLen,
168 tdescType.getBoundaryCheck(),
169 tdescType.getMemorySpace(), newLayout);
170}
171
172/// Helper to convert an OpFoldResult to Value.
173static Value convertToValue(ConversionPatternRewriter &rewriter, Location loc,
174 OpFoldResult ofr) {
175 std::optional<int64_t> mayBeInt = getConstantIntValue(ofr);
176 if (mayBeInt)
177 return arith::ConstantIndexOp::create(rewriter, loc, *mayBeInt).getResult();
178 return llvm::cast<Value>(ofr);
179}
180
181/// Helper to divide a Value by a constant integer.
182static Value divideByConstant(ConversionPatternRewriter &rewriter, Location loc,
183 Value val, int64_t constant) {
184 // If the constant is a power of 2, use right shift for division.
185 if (llvm::isPowerOf2_64(constant)) {
186 int64_t shiftAmount = llvm::Log2_64(constant);
187 return arith::ShRUIOp::create(
188 rewriter, loc, val,
189 arith::ConstantIndexOp::create(rewriter, loc, shiftAmount)
190 .getResult())
191 .getResult();
192 }
193 auto constantOp =
194 arith::ConstantIndexOp::create(rewriter, loc, constant).getResult();
195 return arith::DivUIOp::create(rewriter, loc, val, constantOp).getResult();
196}
197
198/// This function takes a larger register block `data` and generates multiple
199/// smaller loads (size given by `newTensorDesc`) to fill in the `data` block
200/// starting from `offsets`.
201static Value generateLoads(ConversionPatternRewriter &rewriter,
205 xegpu::LoadNdOp origLoadOp) {
206 Location loc = data.getLoc();
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]);
211 SmallVector<int64_t> supportedShape(newTensorDesc.getType().getShape());
212 // Compute the ratio between original shape and supported shape. We need to
213 // generate loads in this ratio arrangement.
214 auto shapeRatio = computeShapeRatio(data.getType().getShape(),
215 supportedShape)
216 .value(); // `ratio` must be defined if we reach here.
217 // The loop below only walks the last 2 entries of `shapeRatio`; leading
218 // (batch) dims are unit, so their ratio is 1 and their offsets pass through
219 // unchanged.
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,
228 arith::ConstantIndexOp::create(rewriter, loc, localOffsetDim0)
229 .getResult());
230 Value loadOffsetY = arith::AddIOp::create(
231 rewriter, loc, offsetDim1,
232 arith::ConstantIndexOp::create(rewriter, loc, localOffsetDim1)
233 .getResult());
234 // Keep the leading (batch) offsets; replace only the inner 2.
235 SmallVector<OpFoldResult> loadOffsets(offsets.begin(), offsets.end());
236 loadOffsets[loadOffsets.size() - 2] = loadOffsetX;
237 loadOffsets[loadOffsets.size() - 1] = loadOffsetY;
238 auto loadOp = xegpu::LoadNdOp::create(
239 rewriter, loc,
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());
245 // Set the layout for the loadOp.
246 auto layoutAttr = newTensorDesc.getType().getLayoutAttr();
247 loadOp.setAnchorLayout(layoutAttr);
248 // Insert the loaded block into the right position in data (leading dims
249 // at 0, inner 2 dims at the local tile offset).
250 SmallVector<int64_t> insertPos(rank, 0);
251 insertPos[rank - 2] = localOffsetDim0;
252 insertPos[rank - 1] = localOffsetDim1;
253 SmallVector<int64_t> insertStrides(rank, 1);
254 auto insertOp = vector::InsertStridedSliceOp::create(
255 rewriter, loc, loadOp.getResult(), data, insertPos, insertStrides);
256 // InsertOp must have the same layout as newTensorDesc.
257 xegpu::setTemporaryLayout(insertOp->getOpResult(0), layoutAttr);
258 data = insertOp.getResult();
259 }
260 }
261 return data;
262}
263
264/// Checks if a CreateNdDescOp can be optimized for transpose, if so creates a
265/// new CreateNdDescOp with optimized tensor desc type. This involves extracting
266/// the base pointer from the original memory source and adjusting the shape and
267/// strides of the tensor desc to fit with the new optimized transpose layout.
268class XeGPUCreateNdDescOpPattern final
269 : public OpConversionPattern<xegpu::CreateNdDescOp> {
270public:
271 using OpConversionPattern<xegpu::CreateNdDescOp>::OpConversionPattern;
272 LogicalResult
273 matchAndRewrite(xegpu::CreateNdDescOp createNdOp, OpAdaptor adaptor,
274 ConversionPatternRewriter &rewriter) const override {
275 auto tdescTy = createNdOp.getType();
276 // Get the target uArch info.
277 auto chipStr = xegpu::getChipStr(createNdOp);
278 // Check if the chip is supported.
279 assert(chipStr &&
280 (chipStr.value() == "pvc" || chipStr.value() == "bmg" ||
281 chipStr.value() == "cri") &&
282 "Expecting target chip to be pvc, bmg or cri for transpose "
283 "optimization.");
284 const auto *targetuArch = xegpu::uArch::getUArch(chipStr.value());
285
286 auto convertType = tryOptimize(tdescTy, targetuArch);
287 if (convertType == tdescTy)
288 return failure();
289 Location loc = createNdOp.getLoc();
290 Value source = createNdOp.getSource();
291 auto memrefType = dyn_cast<MemRefType>(source.getType());
292
293 bool dynamicMemref =
294 memrefType && !xegpu::hasStaticShapeAndStrides(memrefType);
295 SmallVector<OpFoldResult> mixedSizes;
296 SmallVector<OpFoldResult> mixedStrides;
297 memref::ExtractStridedMetadataOp meta;
298 if (dynamicMemref) {
299 meta = memref::ExtractStridedMetadataOp::create(rewriter, loc, source);
300 mixedSizes = meta.getConstifiedMixedSizes();
301 mixedStrides = meta.getConstifiedMixedStrides();
302 } else {
303 mixedSizes = createNdOp.getMixedSizes();
304 mixedStrides = createNdOp.getMixedStrides();
305 }
306
307 auto maybeConstInnerStride = getConstantIntValue(mixedStrides.back());
308 // Only row-major memrefs are expected for now.
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];
316 // Inner dimension of the shape must be adjusted based on innerLaneData.
317 SmallVector<OpFoldResult> modifiedShape(mixedSizes);
318 modifiedShape.back() = divideByConstant(
319 rewriter, loc, convertToValue(rewriter, loc, modifiedShape.back()),
320 innerLaneData);
321 // Repacking to a wider element rescales every stride but the innermost.
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]),
328 innerLaneData);
329
330 if (memrefType) {
331 Value baseIdx;
332 if (dynamicMemref) {
333 // Base = aligned base pointer + structural offset (in bytes).
334 Value alignedPtr = memref::ExtractAlignedPointerAsIndexOp::create(
335 rewriter, loc, meta.getBaseBuffer());
336 Value elemBytes = arith::ConstantIndexOp::create(
337 rewriter, loc, memrefType.getElementTypeBitWidth() / 8);
338 Value offBytes =
339 arith::MulIOp::create(rewriter, loc, meta.getOffset(), elemBytes);
340 baseIdx = arith::AddIOp::create(rewriter, loc, alignedPtr, offBytes);
341 } else {
342 baseIdx = memref::ExtractAlignedPointerAsIndexOp::create(rewriter, loc,
343 source);
344 }
345 source = arith::IndexCastOp::create(rewriter, loc, rewriter.getI64Type(),
346 baseIdx);
347 }
348 // Create a new CreateNdDescOp with the modified shape and converted type.
349 auto newCreateNdDescOp = xegpu::CreateNdDescOp::create(
350 rewriter, loc, convertType, source, modifiedShape, modifiedStrides);
351 rewriter.replaceOp(createNdOp, newCreateNdDescOp.getResult());
352 return success();
353 }
354};
355
356/// Checks if a LoadNdOp consumes a tensor desc type that was rewritten for
357/// tranpose optimization. If so, rewrites the LoadNdOp to to align with the
358/// adjusted tensor desc type. This can result in multiple LoadNdOps being
359/// generated to fill in the original load shape.
360class XeGPULoadNdDescOpPattern final
361 : public OpConversionPattern<xegpu::LoadNdOp> {
362public:
363 using OpConversionPattern<xegpu::LoadNdOp>::OpConversionPattern;
364 LogicalResult
365 matchAndRewrite(xegpu::LoadNdOp loadNdOp, OpAdaptor adaptor,
366 ConversionPatternRewriter &rewriter) const override {
367 auto origTensorDescType = loadNdOp.getTensorDescType();
368 auto adaptorType =
369 cast<xegpu::TensorDescType>(adaptor.getTensorDesc().getType());
370 if (adaptorType == origTensorDescType)
371 return failure();
372 // Offsets must be adjusted based on innerLaneData.
373 auto laneData = getMaybeLaneData(loadNdOp.getTensorDescType()).value();
374 int64_t innerLaneData = laneData[1];
375 auto offsets = loadNdOp.getMixedOffsets();
376 if (offsets.empty())
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()),
383 innerLaneData);
384 // Get the 2D data shape of this loadNdOp in its original type including
385 // array length.
386 SmallVector<int64_t> origDataShape(origTensorDescType.getShape());
387 // Adjust the data shape based on innerLaneData.
388 origDataShape.back() /= innerLaneData;
389 // HW supported shape is the new tensor desc shape after conversion.
390 SmallVector<int64_t> hwSupportedShape(adaptorType.getShape());
391 VectorType origVectorType =
392 VectorType::get(origDataShape, adaptorType.getElementType());
393 Value data;
394 // Orig data shape is 3D for the array length case.
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));
401 // Increase the Y offset for each array slice.
402 Value offsetY = convertToValue(rewriter, loadNdOp->getLoc(),
403 modifiedOffsets.back());
404 modifiedOffsets.back() =
405 arith::AddIOp::create(
406 rewriter, loadNdOp->getLoc(), offsetY,
407 arith::ConstantIndexOp::create(rewriter, loadNdOp->getLoc(),
408 i * origDataShape[1])
409 .getResult())
410 .getResult();
411 slice = generateLoads(
412 rewriter, cast<TypedValue<VectorType>>(slice), modifiedOffsets,
413 cast<TypedValue<xegpu::TensorDescType>>(adaptor.getTensorDesc()),
414 loadNdOp);
415 // BitCast back to original load shape without array length.
416 auto bitcastType = VectorType::get(origTensorDescType.getShape(),
417 origTensorDescType.getElementType());
418 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
419 bitcastType, slice);
420 // BitCastOp must have the same layout as the original loadNdOp.
421 xegpu::setTemporaryLayout(bitCastOp->getOpResult(0),
422 origTensorDescType.getLayoutAttr());
423 arraySlices.push_back(bitCastOp.getResult());
424 }
425 rewriter.replaceOpWithMultiple(loadNdOp, {arraySlices});
426 return success();
427 }
428 data = arith::ConstantOp::create(
429 rewriter, loadNdOp->getLoc(),
430 VectorType::get(origDataShape, adaptorType.getElementType()),
431 rewriter.getZeroAttr(origVectorType));
432 data = generateLoads(
433 rewriter, cast<TypedValue<VectorType>>(data), modifiedOffsets,
434 cast<TypedValue<xegpu::TensorDescType>>(adaptor.getTensorDesc()),
435 loadNdOp);
436 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
437 loadNdOp.getType(), data);
438 // BitCastOp must have the same layout as the original loadNdOp.
439 xegpu::setTemporaryLayout(bitCastOp->getOpResult(0),
440 origTensorDescType.getLayoutAttr());
441 rewriter.replaceOp(loadNdOp, bitCastOp);
442 return success();
443 }
444};
445
446/// Vector ExtractOp must be processed if the original tensor desc type has
447/// array length greater than 1. In this case, the LoadNdOp is replaced with
448/// multiple LoadNdOps for each array slice making the extraction unnecessary.
449/// In this case, we simply remove the ExtractOp.
450class VectorExtractOpPattern final
451 : public OpConversionPattern<vector::ExtractOp> {
452public:
453 using OpConversionPattern<vector::ExtractOp>::OpConversionPattern;
454 LogicalResult
455 matchAndRewrite(vector::ExtractOp extractOp, OneToNOpAdaptor adaptor,
456 ConversionPatternRewriter &rewriter) const override {
457 // Check if the source of the extraction is split to multiple values.
458 if (adaptor.getSource().size() == 1)
459 return failure();
460 auto mixedPos = extractOp.getMixedPosition();
461 if (mixedPos.size() != 1)
462 return failure();
463 auto mayBeInt = getConstantIntValue(mixedPos[0]);
464 if (!mayBeInt)
465 return failure();
466 rewriter.replaceOp(extractOp, adaptor.getSource()[*mayBeInt]);
467 return success();
468 }
469};
470
471/// Performs a reduction over 2 dimensions by decomposing it into two 1D
472/// reductions ordered based on layout to minimize cross-lane communication.
473class MultiRed2dOpPattern
474 : public OpConversionPattern<vector::MultiDimReductionOp> {
475 using OpConversionPattern::OpConversionPattern;
476 LogicalResult
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");
482 auto resLayout = xegpu::getDistributeLayoutAttr(reductionOp.getResult());
483 // Retrieve and order dims for 1D decomposition (prefer intra-lane first).
484 auto dims = llvm::to_vector(reductionOp.getReductionDims());
485 auto [intraLaneDim, crossLaneDim] = getReductionDimOrder(dims, resLayout);
486 // Order does not matter
487 if (intraLaneDim == -1 || crossLaneDim == -1) {
488 intraLaneDim = dims[0];
489 crossLaneDim = dims[1];
490 }
491 auto loc = reductionOp.getLoc();
492 auto acc = reductionOp.getAcc();
493
494 // The decomposition below splits the 2D reduction into an intra-lane
495 // then a cross-lane 1D reduction. The natural result layout of the
496 // decomposed sequence (a doubly-sliced layout) differs from the
497 // original 2D reduction's result layout that the rest of the IR was
498 // written/propagated against. To keep the post-peephole IR
499 // self-consistent without depending on a follow-up layout
500 // propagation pass, we always insert a bridge xegpu.convert_layout
501 // from the natural post-decomposition layout to the original
502 // reduction's result layout. Trivial bridges fold away in
503 // canonicalization.
504 xegpu::DistributeLayoutAttr postDecompLayout;
505 if (resLayout) {
506 // Derive the source vector's layout.
507 xegpu::DistributeLayoutAttr srcLayoutForCvt;
508 if (auto resSlice = dyn_cast_if_present<xegpu::SliceAttr>(resLayout))
509 srcLayoutForCvt = resSlice.getParent();
510 if (!srcLayoutForCvt)
511 srcLayoutForCvt =
512 xegpu::getDistributeLayoutAttr(reductionOp.getSource());
513 if (srcLayoutForCvt) {
514 // The natural layout of the post-decomposition reduction result
515 // is a nested SliceAttr: REDUCE_1 (reduces `intraLaneDim` from
516 // the source) yields `slice<src, [intraLaneDim]>`; REDUCE_2
517 // then reduces `adjCrossLaneDim` from that intermediate, giving
518 // `slice<slice<src, [intraLaneDim]>, [adjCrossLaneDim]>`.
519 MLIRContext *ctx = reductionOp.getContext();
520 int64_t adjCrossLaneDim =
521 crossLaneDim > intraLaneDim ? crossLaneDim - 1 : crossLaneDim;
522 auto intermediateLayout = xegpu::SliceAttr::get(
523 ctx, srcLayoutForCvt, DenseI64ArrayAttr::get(ctx, {intraLaneDim}));
524 postDecompLayout = xegpu::SliceAttr::get(
525 ctx, intermediateLayout,
526 DenseI64ArrayAttr::get(ctx, {adjCrossLaneDim}));
527 }
528 }
529
530 SmallVector<int64_t> accShape(sourceVecType.getShape());
531 accShape.erase(accShape.begin() + intraLaneDim);
532 Type eTy = sourceVecType.getElementType();
533 Value constNeutralVal = xegpu::createReductionNeutralValue(
534 rewriter, loc, VectorType::get(accShape, eTy), reductionOp.getKind());
535
536 Value intraLaneReduced = vector::MultiDimReductionOp::create(
537 rewriter, loc, reductionOp.getKind(), reductionOp.getSource(),
538 constNeutralVal, ArrayRef<int64_t>(intraLaneDim));
539
540 // Adjust crossLaneDim after the first reduction.
541 if (crossLaneDim > intraLaneDim)
542 crossLaneDim -= 1;
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() &&
547 "Type mismatch");
548
549 Value replacement = crossLaneReduced;
550 if (resLayout && postDecompLayout) {
551 // Bridge from the natural post-decomposition layout to the
552 // original reduction's result layout. This preserves the contract
553 // any consumer (convert_layout, anchor op, or otherwise) was
554 // written against, so the rewrite is correct independent of
555 // whether layout propagation runs afterwards.
556 auto bridgeOp = xegpu::ConvertLayoutOp::create(
557 rewriter, loc, crossLaneReduced.getType(), crossLaneReduced,
558 postDecompLayout, resLayout);
559 replacement = bridgeOp.getResult();
560 }
561
562 rewriter.replaceOp(reductionOp, replacement);
563 return success();
564 }
565
566private:
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))
575 layoutAttr =
576 dyn_cast<xegpu::LayoutAttr>(layoutSliceAttr.flatten().getParent());
577 assert(layoutAttr);
578 SmallVector<int64_t> laneLayout = layoutAttr.getEffectiveLaneLayoutAsInt();
579
580 assert(laneLayout.size() && "Expected a non-empty layout");
581 // try to pick a dim that does not communicate
582 for (auto dim : reductionDims) {
583 if (laneLayout[dim] == 1)
584 intra = dim;
585 else
586 cross = dim;
587 }
588 return {intra, cross};
589 }
590};
591
592} // namespace
593
595 RewritePatternSet &patterns) {
596 patterns.add<XeGPUCreateNdDescOpPattern, XeGPULoadNdDescOpPattern,
597 VectorExtractOpPattern, MultiRed2dOpPattern>(
598 patterns.getContext());
599}
600
601namespace {
602
603struct XeGPUPeepHoleOptimizerPass final
605 XeGPUPeepHoleOptimizerPass> {
606 void runOnOperation() override {
607 MLIRContext &context = getContext();
608 TypeConverter converter;
609 RewritePatternSet patterns(&context);
610 ConversionTarget target(context);
611
612 // This pass is only meant for PVC, BMG or CRI targets. If unsupported
613 // target is found, exit early.
614 bool isTargetSupported = false;
615 getOperation()->walk([&](gpu::GPUFuncOp funcOp) {
616 auto chipStr = xegpu::getChipStr(funcOp);
617 if (chipStr && (chipStr.value() == "pvc" || chipStr.value() == "bmg" ||
618 chipStr.value() == "cri"))
619 isTargetSupported = true;
620 });
621
622 if (!isTargetSupported) {
623 DBGS() << "XeGPUPeepHoleOptimizerPass only supports PVC, BMG targets."
624 << "\n";
625 return;
626 }
627
628 // Run array length optimization patterns first so that subsequent transpose
629 // peephole patterns operate on the array-length-optimized tensor descs.
630 {
631 RewritePatternSet arrayLenPatterns(&context);
633 if (failed(applyPatternsGreedily(getOperation(),
634 std::move(arrayLenPatterns)))) {
635 DBGS() << "Array length optimization patterns failed.\n";
636 return signalPassFailure();
637 }
638 }
639
640 // CreateNdDescOp and LoadNdOp with optimizable tensor desc types must be
641 // converted.
642 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp>(
643 [&](xegpu::CreateNdDescOp createNdOp) {
644 return !canBeOptimizedForTranspose(createNdOp.getType());
645 });
646 target.addDynamicallyLegalOp<xegpu::LoadNdOp>(
647 [&](xegpu::LoadNdOp loadNdOp) {
648 return !canBeOptimizedForTranspose(loadNdOp.getTensorDescType());
649 });
650 // Vector ExtractOps can have optimizable layouts if they extract from
651 // LoadNdOps with array length greater than 1. These ExtractOps must be
652 // converted.
653 target.addDynamicallyLegalOp<vector::ExtractOp>(
654 [&](vector::ExtractOp extractOp) {
655 auto layout = xegpu::getTemporaryLayout(
656 dyn_cast<OpResult>(extractOp.getResult()));
657 if (!layout)
658 return true;
659 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
660 auto laneData = layout.getEffectiveLaneDataAsInt();
661 return !canBeOptimizedForTranspose(laneLayout, laneData);
662 });
663
664 target.addDynamicallyLegalOp<vector::MultiDimReductionOp>(
665 [=](Operation *op) -> bool {
666 auto layout = xegpu::getDistributeLayoutAttr(op->getResult(0));
667 if (!layout || !layout.isForSubgroup())
668 return true;
669 if (auto reductionOp = dyn_cast<vector::MultiDimReductionOp>(op))
670 return reductionOp.getReductionDims().size() != 2;
671 return true;
672 });
673
674 converter.addConversion([](Type type) { return type; });
675
676 target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect,
677 vector::VectorDialect>();
678 // xegpu.convert_layout is left untouched by this pass; mark it legal
679 // so in-place updates don't trigger re-legalization failures.
680 target.addLegalOp<xegpu::ConvertLayoutOp>();
682 target);
684 if (failed(applyPartialConversion(getOperation(), target,
685 std::move(patterns)))) {
686 DBGS() << "Optimize block loads pass failed.\n";
687 return signalPassFailure();
688 }
689
690 // Apply folding for cleaning up IR.
691 MLIRContext *ctx = &getContext();
692 RewritePatternSet emptyPatterns(ctx);
693 (void)applyPatternsGreedily(getOperation(), std::move(emptyPatterns));
694
695 xegpu::removeTemporaryLayoutAttrs(getOperation());
696 }
697};
698
699} // namespace
return success()
b getContext())
*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`
#define DBGS()
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:63
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...
Definition Types.h:74
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
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
static DenseArrayAttrImpl get(MLIRContext *context, ArrayRef< int32_t > content)
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
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)
Definition uArchCommon.h:24
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 &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...
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.
Definition Value.h:494
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
Definition uArchBase.h:115