MLIR 23.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 auto laneData = layout.getEffectiveLaneDataAsInt();
54 if (laneData.size() != 2)
55 return std::nullopt;
56 return laneData;
57}
58
59/// Get the 2D lane layout from a tensor desc type if it exists.
60static std::optional<SmallVector<int64_t>>
61getMaybeLaneLayout(xegpu::TensorDescType tdescType) {
62 auto layout = tdescType.getLayoutAttr();
63 if (!layout)
64 return std::nullopt;
65 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
66 if (laneLayout.size() != 2)
67 return std::nullopt;
68 return laneLayout;
69}
70
71/// A layout can be optimized if its lane layout is transposed (lane[0] != 1 &&
72/// lane[1] == 1), but inner lane data is not equal to [1, 1].
73/// Example:
74/// !xegpu.tensor_desc<16x16xf16,
75/// #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>>
76/// In this case, lane layout is transposed (from the usual [1, SG_SIZE] form)
77/// indicating that this is a load that requires transpose effect. However,
78/// lane data is [1, 2], meaning that each lane must grab 2 f16 elements from
79/// the inner dimension. We convert this to a optimized form by converting the
80/// tensor_desc to i32 type such that lane data becomes [1, 1]. This makes the
81/// later lowering easily use the load with transpose instruction.
82static bool canBeOptimizedForTranspose(ArrayRef<int64_t> laneLayout,
83 ArrayRef<int64_t> laneData) {
84 if (laneLayout.size() != 2 || laneData.size() != 2)
85 return false;
86 if (laneLayout[0] == 1 || laneLayout[1] != 1)
87 return false;
88 if (laneData[0] != 1 || laneData[1] == 1)
89 return false;
90 return true;
91}
92
93/// A tensor desc type can be optimized if its element type is less than 32 bits
94/// and its layout can be optimized.
95static bool canBeOptimizedForTranspose(xegpu::TensorDescType tdescType) {
96 // If the dtype is greater or equal to 32 bits, layout must be valid.
97 int elementTyBitwidth = tdescType.getElementType().getIntOrFloatBitWidth();
98 if (elementTyBitwidth >= 32)
99 return false;
100 auto maybeLaneLayout = getMaybeLaneLayout(tdescType);
101 auto maybeLaneData = getMaybeLaneData(tdescType);
102 if (!maybeLaneData || !maybeLaneLayout)
103 return false;
104 return canBeOptimizedForTranspose(*maybeLaneLayout, *maybeLaneData);
105}
106
107/// Check if a tensor desc type can be optimized for transpose, if so return the
108/// new optimized tensor desc type with a valid transpose layout.
109static xegpu::TensorDescType
110tryOptimize(xegpu::TensorDescType tdescType,
111 const xegpu::uArch::uArch *targetuArch) {
112 if (!canBeOptimizedForTranspose(tdescType))
113 return tdescType;
114 auto laneData = getMaybeLaneData(tdescType)
115 .value(); // Lane data must exist if we reach here.
116 int64_t innerLaneData = laneData[1];
117 int elementTyBitwidth = tdescType.getElementType().getIntOrFloatBitWidth();
118 // Required shape is total shape of the vector result that this tensor desc
119 // must eventually load after adjusting for the new bitwidth and array
120 // length.
121 SmallVector<int64_t> requiredShape(tdescType.getShape());
122 requiredShape.back() =
123 requiredShape.back() * tdescType.getArrayLength() / innerLaneData;
124 int newBitWidth = elementTyBitwidth * innerLaneData;
125 Type newElemTy = IntegerType::get(tdescType.getContext(), newBitWidth);
126 // Supported shape is the max transpose shape that can be supported by
127 // hardware that is less than or equal to required shape.
128 auto *blockLoadTarget =
129 dyn_cast<xegpu::uArch::Subgroup2DBlockLoadInstruction>(
130 targetuArch->getInstruction(
132 auto maybeHWParams = blockLoadTarget->getBlockWidthHeightCount(
133 newElemTy, /** has transform */ false, /** has transpose */ true);
134 // If no HW params found, return the original type.
135 if (!maybeHWParams)
136 return tdescType;
137 auto [widths, heights, counts] = maybeHWParams.value();
138 // TODO: Currently we expect array length to be 1 for transpose case.
139 if (counts.size() != 1 || counts[0] != 1)
140 return tdescType;
141 int arrayLen = counts[0];
142 int supportedHeight =
143 xegpu::getLargestDivisor(static_cast<int>(requiredShape[0]), heights);
144 int supportedWidth =
145 xegpu::getLargestDivisor(static_cast<int>(requiredShape[1]), widths);
146 // If no supported height or width found, return the original type.
147 if (supportedHeight == -1 || supportedWidth == -1)
148 return tdescType;
149
150 SmallVector<int64_t> supportedShape = {supportedHeight, supportedWidth};
151 auto ctx = tdescType.getContext();
152 auto origLayout = tdescType.getLayoutAttr();
153 auto laneLayoutI64 = origLayout.getEffectiveLaneLayoutAsInt();
154 SmallVector<int32_t> laneLayoutI32(laneLayoutI64.begin(),
155 laneLayoutI64.end());
156
157 xegpu::LayoutAttr newLayout = xegpu::LayoutAttr::get(
158 ctx, /*lane_layout=*/DenseI32ArrayAttr::get(ctx, laneLayoutI32),
159 /*lane_data=*/DenseI32ArrayAttr::get(ctx, {1, 1}),
160 /*order=*/origLayout.getOrder());
161
162 // Array length can not be larger than 1 for transpose case.
163 return xegpu::TensorDescType::get(supportedShape, newElemTy, arrayLen,
164 tdescType.getBoundaryCheck(),
165 tdescType.getMemorySpace(), newLayout);
166}
167
168/// Helper to convert an OpFoldResult to Value.
169static Value convertToValue(ConversionPatternRewriter &rewriter, Location loc,
170 OpFoldResult ofr) {
171 std::optional<int64_t> mayBeInt = getConstantIntValue(ofr);
172 if (mayBeInt)
173 return arith::ConstantIndexOp::create(rewriter, loc, *mayBeInt).getResult();
174 return llvm::cast<Value>(ofr);
175}
176
177/// Helper to divide a Value by a constant integer.
178static Value divideByConstant(ConversionPatternRewriter &rewriter, Location loc,
179 Value val, int64_t constant) {
180 // If the constant is a power of 2, use right shift for division.
181 if (llvm::isPowerOf2_64(constant)) {
182 int64_t shiftAmount = llvm::Log2_64(constant);
183 return arith::ShRUIOp::create(
184 rewriter, loc, val,
185 arith::ConstantIndexOp::create(rewriter, loc, shiftAmount)
186 .getResult())
187 .getResult();
188 }
189 auto constantOp =
190 arith::ConstantIndexOp::create(rewriter, loc, constant).getResult();
191 return arith::DivUIOp::create(rewriter, loc, val, constantOp).getResult();
192}
193
194/// This function takes a larger register block `data` and generates multiple
195/// smaller loads (size given by `newTensorDesc`) to fill in the `data` block
196/// starting from `offsets`.
197static Value generateLoads(ConversionPatternRewriter &rewriter,
201 xegpu::LoadNdOp origLoadOp) {
202 Location loc = data.getLoc();
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]);
206 SmallVector<int64_t> supportedShape(newTensorDesc.getType().getShape());
207 // Compute the ratio between original shape and supported shape. We need to
208 // generate loads in this ratio arrangement.
209 auto shapeRatio = computeShapeRatio(data.getType().getShape(),
210 supportedShape)
211 .value(); // `ratio` must be defined if we reach here.
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,
218 arith::ConstantIndexOp::create(rewriter, loc, localOffsetDim0)
219 .getResult());
220 Value loadOffsetY = arith::AddIOp::create(
221 rewriter, loc, offsetDim1,
222 arith::ConstantIndexOp::create(rewriter, loc, localOffsetDim1)
223 .getResult());
224 auto loadOp = xegpu::LoadNdOp::create(
225 rewriter, loc,
226 VectorType::get(supportedShape, data.getType().getElementType()),
227 newTensorDesc, ArrayRef<OpFoldResult>{loadOffsetX, loadOffsetY},
228 origLoadOp.getPackedAttr(), origLoadOp.getTransposeAttr(),
229 origLoadOp.getL1HintAttr(), origLoadOp.getL2HintAttr(),
230 origLoadOp.getL3HintAttr(), origLoadOp.getLayoutAttr());
231 // Set the layout for the loadOp.
232 auto layoutAttr = newTensorDesc.getType().getLayoutAttr();
233 loadOp.setAnchorLayout(layoutAttr);
234 // Insert the loaded block into the right position in data.
235 auto insertOp = vector::InsertStridedSliceOp::create(
236 rewriter, loc, loadOp.getResult(), data,
237 ArrayRef<int64_t>{localOffsetDim0, localOffsetDim1},
238 ArrayRef<int64_t>{1, 1});
239 // InsertOp must have the same layout as newTensorDesc.
240 xegpu::setTemporaryLayout(insertOp->getOpResult(0), layoutAttr);
241 data = insertOp.getResult();
242 }
243 }
244 return data;
245}
246
247/// Checks if a CreateNdDescOp can be optimized for transpose, if so creates a
248/// new CreateNdDescOp with optimized tensor desc type. This involves extracting
249/// the base pointer from the original memory source and adjusting the shape and
250/// strides of the tensor desc to fit with the new optimized transpose layout.
251class XeGPUCreateNdDescOpPattern final
252 : public OpConversionPattern<xegpu::CreateNdDescOp> {
253public:
254 using OpConversionPattern<xegpu::CreateNdDescOp>::OpConversionPattern;
255 LogicalResult
256 matchAndRewrite(xegpu::CreateNdDescOp createNdOp, OpAdaptor adaptor,
257 ConversionPatternRewriter &rewriter) const override {
258 auto tdescTy = createNdOp.getType();
259 // Get the target uArch info.
260 auto chipStr = xegpu::getChipStr(createNdOp);
261 // Check if the chip is supported.
262 assert(chipStr &&
263 (chipStr.value() == "pvc" || chipStr.value() == "bmg" ||
264 chipStr.value() == "cri") &&
265 "Expecting target chip to be pvc, bmg or cri for transpose "
266 "optimization.");
267 const auto *targetuArch = xegpu::uArch::getUArch(chipStr.value());
268
269 auto convertType = tryOptimize(tdescTy, targetuArch);
270 if (convertType == tdescTy)
271 return failure();
272 auto strides = createNdOp.getMixedStrides();
273 auto maybeConstInnerStride = getConstantIntValue(strides.back());
274 // Only row-major memrefs are expected for now.
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());
284 // Inner dimension of the shape must be adjusted based on innerLaneData.
285 SmallVector<OpFoldResult> modifiedShape(createNdOp.getMixedSizes());
286 modifiedShape.back() = divideByConstant(
287 rewriter, createNdOp.getLoc(),
288 convertToValue(rewriter, createNdOp.getLoc(), modifiedShape.back()),
289 innerLaneData);
290 // Similarly, second to last stride must be adjusted.
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]),
298 innerLaneData);
299
300 // If the source is a static memref, we need to extract the pointer to
301 // base address.
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())
308 .getResult();
309 }
310 // Create a new CreateNdDescOp with the modified shape and converted type.
311 auto newCreateNdDescOp = xegpu::CreateNdDescOp::create(
312 rewriter, createNdOp.getLoc(), convertType, source, modifiedShape,
313 modifiedStrides);
314 rewriter.replaceOp(createNdOp, newCreateNdDescOp.getResult());
315 return success();
316 }
317};
318
319/// Checks if a LoadNdOp consumes a tensor desc type that was rewritten for
320/// tranpose optimization. If so, rewrites the LoadNdOp to to align with the
321/// adjusted tensor desc type. This can result in multiple LoadNdOps being
322/// generated to fill in the original load shape.
323class XeGPULoadNdDescOpPattern final
324 : public OpConversionPattern<xegpu::LoadNdOp> {
325public:
326 using OpConversionPattern<xegpu::LoadNdOp>::OpConversionPattern;
327 LogicalResult
328 matchAndRewrite(xegpu::LoadNdOp loadNdOp, OpAdaptor adaptor,
329 ConversionPatternRewriter &rewriter) const override {
330 auto origTensorDescType = loadNdOp.getTensorDescType();
331 auto adaptorType =
332 cast<xegpu::TensorDescType>(adaptor.getTensorDesc().getType());
333 if (adaptorType == origTensorDescType)
334 return failure();
335 // Offsets must be adjusted based on innerLaneData.
336 auto laneData = getMaybeLaneData(loadNdOp.getTensorDescType()).value();
337 int64_t innerLaneData = laneData[1];
338 auto offsets = loadNdOp.getMixedOffsets();
339 if (offsets.empty())
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()),
346 innerLaneData);
347 // Get the 2D data shape of this loadNdOp in its original type including
348 // array length.
349 SmallVector<int64_t> origDataShape(origTensorDescType.getShape());
350 // Adjust the data shape based on innerLaneData.
351 origDataShape.back() /= innerLaneData;
352 // HW supported shape is the new tensor desc shape after conversion.
353 SmallVector<int64_t> hwSupportedShape(adaptorType.getShape());
354 VectorType origVectorType =
355 VectorType::get(origDataShape, adaptorType.getElementType());
356 Value data;
357 // Orig data shape is 3D for the array length case.
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));
364 // Increase the Y offset for each array slice.
365 Value offsetY = convertToValue(rewriter, loadNdOp->getLoc(),
366 modifiedOffsets.back());
367 modifiedOffsets.back() =
368 arith::AddIOp::create(
369 rewriter, loadNdOp->getLoc(), offsetY,
370 arith::ConstantIndexOp::create(rewriter, loadNdOp->getLoc(),
371 i * origDataShape[1])
372 .getResult())
373 .getResult();
374 slice = generateLoads(
375 rewriter, cast<TypedValue<VectorType>>(slice), modifiedOffsets,
376 cast<TypedValue<xegpu::TensorDescType>>(adaptor.getTensorDesc()),
377 loadNdOp);
378 // BitCast back to original load shape without array length.
379 auto bitcastType = VectorType::get(origTensorDescType.getShape(),
380 origTensorDescType.getElementType());
381 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
382 bitcastType, slice);
383 // BitCastOp must have the same layout as the original loadNdOp.
384 xegpu::setTemporaryLayout(bitCastOp->getOpResult(0),
385 origTensorDescType.getLayoutAttr());
386 arraySlices.push_back(bitCastOp.getResult());
387 }
388 rewriter.replaceOpWithMultiple(loadNdOp, {arraySlices});
389 return success();
390 }
391 data = arith::ConstantOp::create(
392 rewriter, loadNdOp->getLoc(),
393 VectorType::get(origDataShape, adaptorType.getElementType()),
394 rewriter.getZeroAttr(origVectorType));
395 data = generateLoads(
396 rewriter, cast<TypedValue<VectorType>>(data), modifiedOffsets,
397 cast<TypedValue<xegpu::TensorDescType>>(adaptor.getTensorDesc()),
398 loadNdOp);
399 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
400 loadNdOp.getType(), data);
401 // BitCastOp must have the same layout as the original loadNdOp.
402 xegpu::setTemporaryLayout(bitCastOp->getOpResult(0),
403 origTensorDescType.getLayoutAttr());
404 rewriter.replaceOp(loadNdOp, bitCastOp);
405 return success();
406 }
407};
408
409/// Vector ExtractOp must be processed if the original tensor desc type has
410/// array length greater than 1. In this case, the LoadNdOp is replaced with
411/// multiple LoadNdOps for each array slice making the extraction unnecessary.
412/// In this case, we simply remove the ExtractOp.
413class VectorExtractOpPattern final
414 : public OpConversionPattern<vector::ExtractOp> {
415public:
416 using OpConversionPattern<vector::ExtractOp>::OpConversionPattern;
417 LogicalResult
418 matchAndRewrite(vector::ExtractOp extractOp, OneToNOpAdaptor adaptor,
419 ConversionPatternRewriter &rewriter) const override {
420 // Check if the source of the extraction is split to multiple values.
421 if (adaptor.getSource().size() == 1)
422 return failure();
423 auto mixedPos = extractOp.getMixedPosition();
424 if (mixedPos.size() != 1)
425 return failure();
426 auto mayBeInt = getConstantIntValue(mixedPos[0]);
427 if (!mayBeInt)
428 return failure();
429 rewriter.replaceOp(extractOp, adaptor.getSource()[*mayBeInt]);
430 return success();
431 }
432};
433
434/// Performs a reduction over 2 dimensions by decomposing it into two 1D
435/// reductions ordered based on layout to minimize cross-lane communication.
436class MultiRed2dOpPattern
437 : public OpConversionPattern<vector::MultiDimReductionOp> {
438 using OpConversionPattern::OpConversionPattern;
439 LogicalResult
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");
445 auto resLayout = xegpu::getDistributeLayoutAttr(reductionOp.getResult());
446 // Retrieve and order dims for 1D decomposition (prefer intra-lane first).
447 auto dims = llvm::to_vector(reductionOp.getReductionDims());
448 auto [intraLaneDim, crossLaneDim] = getReductionDimOrder(dims, resLayout);
449 // Order does not matter
450 if (intraLaneDim == -1 || crossLaneDim == -1) {
451 intraLaneDim = dims[0];
452 crossLaneDim = dims[1];
453 }
454 auto loc = reductionOp.getLoc();
455 auto acc = reductionOp.getAcc();
456
457 // The decomposition below splits the 2D reduction into an intra-lane
458 // then a cross-lane 1D reduction. The natural result layout of the
459 // decomposed sequence (a doubly-sliced layout) differs from the
460 // original 2D reduction's result layout that the rest of the IR was
461 // written/propagated against. To keep the post-peephole IR
462 // self-consistent without depending on a follow-up layout
463 // propagation pass, we always insert a bridge xegpu.convert_layout
464 // from the natural post-decomposition layout to the original
465 // reduction's result layout. Trivial bridges fold away in
466 // canonicalization.
467 xegpu::DistributeLayoutAttr postDecompLayout;
468 if (resLayout) {
469 // Derive the source vector's layout.
470 xegpu::DistributeLayoutAttr srcLayoutForCvt;
471 if (auto resSlice = dyn_cast_if_present<xegpu::SliceAttr>(resLayout))
472 srcLayoutForCvt = resSlice.getParent();
473 if (!srcLayoutForCvt)
474 srcLayoutForCvt =
475 xegpu::getDistributeLayoutAttr(reductionOp.getSource());
476 if (srcLayoutForCvt) {
477 // The natural layout of the post-decomposition reduction result
478 // is a nested SliceAttr: REDUCE_1 (reduces `intraLaneDim` from
479 // the source) yields `slice<src, [intraLaneDim]>`; REDUCE_2
480 // then reduces `adjCrossLaneDim` from that intermediate, giving
481 // `slice<slice<src, [intraLaneDim]>, [adjCrossLaneDim]>`.
482 MLIRContext *ctx = reductionOp.getContext();
483 int64_t adjCrossLaneDim =
484 crossLaneDim > intraLaneDim ? crossLaneDim - 1 : crossLaneDim;
485 auto intermediateLayout = xegpu::SliceAttr::get(
486 ctx, srcLayoutForCvt, DenseI64ArrayAttr::get(ctx, {intraLaneDim}));
487 postDecompLayout = xegpu::SliceAttr::get(
488 ctx, intermediateLayout,
489 DenseI64ArrayAttr::get(ctx, {adjCrossLaneDim}));
490 }
491 }
492
493 SmallVector<int64_t> accShape(sourceVecType.getShape());
494 accShape.erase(accShape.begin() + intraLaneDim);
495 Type eTy = sourceVecType.getElementType();
496 Value constNeutralVal = xegpu::createReductionNeutralValue(
497 rewriter, loc, VectorType::get(accShape, eTy), reductionOp.getKind());
498
499 Value intraLaneReduced = vector::MultiDimReductionOp::create(
500 rewriter, loc, reductionOp.getKind(), reductionOp.getSource(),
501 constNeutralVal, ArrayRef<int64_t>(intraLaneDim));
502
503 // Adjust crossLaneDim after the first reduction.
504 if (crossLaneDim > intraLaneDim)
505 crossLaneDim -= 1;
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() &&
510 "Type mismatch");
511
512 Value replacement = crossLaneReduced;
513 if (resLayout && postDecompLayout) {
514 // Bridge from the natural post-decomposition layout to the
515 // original reduction's result layout. This preserves the contract
516 // any consumer (convert_layout, anchor op, or otherwise) was
517 // written against, so the rewrite is correct independent of
518 // whether layout propagation runs afterwards.
519 auto bridgeOp = xegpu::ConvertLayoutOp::create(
520 rewriter, loc, crossLaneReduced.getType(), crossLaneReduced,
521 postDecompLayout, resLayout);
522 replacement = bridgeOp.getResult();
523 }
524
525 rewriter.replaceOp(reductionOp, replacement);
526 return success();
527 }
528
529private:
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))
538 layoutAttr =
539 dyn_cast<xegpu::LayoutAttr>(layoutSliceAttr.flatten().getParent());
540 assert(layoutAttr);
541 SmallVector<int64_t> laneLayout = layoutAttr.getEffectiveLaneLayoutAsInt();
542
543 assert(laneLayout.size() && "Expected a non-empty layout");
544 // try to pick a dim that does not communicate
545 for (auto dim : reductionDims) {
546 if (laneLayout[dim] == 1)
547 intra = dim;
548 else
549 cross = dim;
550 }
551 return {intra, cross};
552 }
553};
554
555} // namespace
556
558 RewritePatternSet &patterns) {
559 patterns.add<XeGPUCreateNdDescOpPattern, XeGPULoadNdDescOpPattern,
560 VectorExtractOpPattern, MultiRed2dOpPattern>(
561 patterns.getContext());
562}
563
564namespace {
565
566struct XeGPUPeepHoleOptimizerPass final
568 XeGPUPeepHoleOptimizerPass> {
569 void runOnOperation() override {
570 MLIRContext &context = getContext();
571 TypeConverter converter;
572 RewritePatternSet patterns(&context);
573 ConversionTarget target(context);
574
575 // This pass is only meant for PVC, BMG or CRI targets. If unsupported
576 // target is found, exit early.
577 bool isTargetSupported = false;
578 getOperation()->walk([&](gpu::GPUFuncOp funcOp) {
579 auto chipStr = xegpu::getChipStr(funcOp);
580 if (chipStr && (chipStr.value() == "pvc" || chipStr.value() == "bmg" ||
581 chipStr.value() == "cri"))
582 isTargetSupported = true;
583 });
584
585 if (!isTargetSupported) {
586 DBGS() << "XeGPUPeepHoleOptimizerPass only supports PVC, BMG targets."
587 << "\n";
588 return;
589 }
590
591 // Run array length optimization patterns first so that subsequent transpose
592 // peephole patterns operate on the array-length-optimized tensor descs.
593 {
594 RewritePatternSet arrayLenPatterns(&context);
596 if (failed(applyPatternsGreedily(getOperation(),
597 std::move(arrayLenPatterns)))) {
598 DBGS() << "Array length optimization patterns failed.\n";
599 return signalPassFailure();
600 }
601 }
602
603 // CreateNdDescOp and LoadNdOp with optimizable tensor desc types must be
604 // converted.
605 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp>(
606 [&](xegpu::CreateNdDescOp createNdOp) {
607 return !canBeOptimizedForTranspose(createNdOp.getType());
608 });
609 target.addDynamicallyLegalOp<xegpu::LoadNdOp>(
610 [&](xegpu::LoadNdOp loadNdOp) {
611 return !canBeOptimizedForTranspose(loadNdOp.getTensorDescType());
612 });
613 // Vector ExtractOps can have optimizable layouts if they extract from
614 // LoadNdOps with array length greater than 1. These ExtractOps must be
615 // converted.
616 target.addDynamicallyLegalOp<vector::ExtractOp>(
617 [&](vector::ExtractOp extractOp) {
618 auto layout = xegpu::getTemporaryLayout(
619 dyn_cast<OpResult>(extractOp.getResult()));
620 if (!layout)
621 return true;
622 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
623 auto laneData = layout.getEffectiveLaneDataAsInt();
624 return !canBeOptimizedForTranspose(laneLayout, laneData);
625 });
626
627 target.addDynamicallyLegalOp<vector::MultiDimReductionOp>(
628 [=](Operation *op) -> bool {
629 auto layout = xegpu::getDistributeLayoutAttr(op->getResult(0));
630 if (!layout || !layout.isForSubgroup())
631 return true;
632 if (auto reductionOp = dyn_cast<vector::MultiDimReductionOp>(op))
633 return reductionOp.getReductionDims().size() != 2;
634 return true;
635 });
636
637 converter.addConversion([](Type type) { return type; });
638
639 target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect,
640 vector::VectorDialect>();
641 // xegpu.convert_layout is left untouched by this pass; mark it legal
642 // so in-place updates don't trigger re-legalization failures.
643 target.addLegalOp<xegpu::ConvertLayoutOp>();
645 target);
647 if (failed(applyPartialConversion(getOperation(), target,
648 std::move(patterns)))) {
649 DBGS() << "Optimize block loads pass failed.\n";
650 return signalPassFailure();
651 }
652
653 // Apply folding for cleaning up IR.
654 MLIRContext *ctx = &getContext();
655 RewritePatternSet emptyPatterns(ctx);
656 (void)applyPatternsGreedily(getOperation(), std::move(emptyPatterns));
657
658 xegpu::removeTemporaryLayoutAttrs(getOperation());
659 }
660};
661
662} // 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:384
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.
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 &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