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 SmallVector<OpFoldResult> mixedSizes;
294 SmallVector<OpFoldResult> mixedStrides;
295 memref::ExtractStridedMetadataOp meta;
296 if (memrefType) {
297 // extract_aligned_pointer_as_index returns the base pointer without the
298 // memref offset, so always extract strided metadata to fold it in.
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 // Compute base = aligned pointer + offset (in bytes).
332 Value alignedPtr = memref::ExtractAlignedPointerAsIndexOp::create(
333 rewriter, loc, meta.getBaseBuffer());
334 Value baseIdx = xegpu::addElemOffsetInBytes(rewriter, loc, alignedPtr,
335 meta.getOffset(), memrefType);
336 source = arith::IndexCastOp::create(rewriter, loc, rewriter.getI64Type(),
337 baseIdx);
338 }
339 // Create a new CreateNdDescOp with the modified shape and converted type.
340 auto newCreateNdDescOp = xegpu::CreateNdDescOp::create(
341 rewriter, loc, convertType, source, modifiedShape, modifiedStrides);
342 rewriter.replaceOp(createNdOp, newCreateNdDescOp.getResult());
343 return success();
344 }
345};
346
347/// Checks if a LoadNdOp consumes a tensor desc type that was rewritten for
348/// tranpose optimization. If so, rewrites the LoadNdOp to to align with the
349/// adjusted tensor desc type. This can result in multiple LoadNdOps being
350/// generated to fill in the original load shape.
351class XeGPULoadNdDescOpPattern final
352 : public OpConversionPattern<xegpu::LoadNdOp> {
353public:
354 using OpConversionPattern<xegpu::LoadNdOp>::OpConversionPattern;
355 LogicalResult
356 matchAndRewrite(xegpu::LoadNdOp loadNdOp, OpAdaptor adaptor,
357 ConversionPatternRewriter &rewriter) const override {
358 auto origTensorDescType = loadNdOp.getTensorDescType();
359 auto adaptorType =
360 cast<xegpu::TensorDescType>(adaptor.getTensorDesc().getType());
361 if (adaptorType == origTensorDescType)
362 return failure();
363 // Offsets must be adjusted based on innerLaneData.
364 auto laneData = getMaybeLaneData(loadNdOp.getTensorDescType()).value();
365 int64_t innerLaneData = laneData[1];
366 auto offsets = loadNdOp.getMixedOffsets();
367 if (offsets.empty())
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()),
374 innerLaneData);
375 // Get the 2D data shape of this loadNdOp in its original type including
376 // array length.
377 SmallVector<int64_t> origDataShape(origTensorDescType.getShape());
378 // Adjust the data shape based on innerLaneData.
379 origDataShape.back() /= innerLaneData;
380 // HW supported shape is the new tensor desc shape after conversion.
381 SmallVector<int64_t> hwSupportedShape(adaptorType.getShape());
382 VectorType origVectorType =
383 VectorType::get(origDataShape, adaptorType.getElementType());
384 Value data;
385 // Orig data shape is 3D for the array length case.
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));
392 // Increase the Y offset for each array slice.
393 Value offsetY = convertToValue(rewriter, loadNdOp->getLoc(),
394 modifiedOffsets.back());
395 modifiedOffsets.back() =
396 arith::AddIOp::create(
397 rewriter, loadNdOp->getLoc(), offsetY,
398 arith::ConstantIndexOp::create(rewriter, loadNdOp->getLoc(),
399 i * origDataShape[1])
400 .getResult())
401 .getResult();
402 slice = generateLoads(
403 rewriter, cast<TypedValue<VectorType>>(slice), modifiedOffsets,
404 cast<TypedValue<xegpu::TensorDescType>>(adaptor.getTensorDesc()),
405 loadNdOp);
406 // BitCast back to original load shape without array length.
407 auto bitcastType = VectorType::get(origTensorDescType.getShape(),
408 origTensorDescType.getElementType());
409 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
410 bitcastType, slice);
411 // BitCastOp must have the same layout as the original loadNdOp.
412 xegpu::setTemporaryLayout(bitCastOp->getOpResult(0),
413 origTensorDescType.getLayoutAttr());
414 arraySlices.push_back(bitCastOp.getResult());
415 }
416 rewriter.replaceOpWithMultiple(loadNdOp, {arraySlices});
417 return success();
418 }
419 data = arith::ConstantOp::create(
420 rewriter, loadNdOp->getLoc(),
421 VectorType::get(origDataShape, adaptorType.getElementType()),
422 rewriter.getZeroAttr(origVectorType));
423 data = generateLoads(
424 rewriter, cast<TypedValue<VectorType>>(data), modifiedOffsets,
425 cast<TypedValue<xegpu::TensorDescType>>(adaptor.getTensorDesc()),
426 loadNdOp);
427 auto bitCastOp = vector::BitCastOp::create(rewriter, loadNdOp->getLoc(),
428 loadNdOp.getType(), data);
429 // BitCastOp must have the same layout as the original loadNdOp.
430 xegpu::setTemporaryLayout(bitCastOp->getOpResult(0),
431 origTensorDescType.getLayoutAttr());
432 rewriter.replaceOp(loadNdOp, bitCastOp);
433 return success();
434 }
435};
436
437/// Vector ExtractOp must be processed if the original tensor desc type has
438/// array length greater than 1. In this case, the LoadNdOp is replaced with
439/// multiple LoadNdOps for each array slice making the extraction unnecessary.
440/// In this case, we simply remove the ExtractOp.
441class VectorExtractOpPattern final
442 : public OpConversionPattern<vector::ExtractOp> {
443public:
444 using OpConversionPattern<vector::ExtractOp>::OpConversionPattern;
445 LogicalResult
446 matchAndRewrite(vector::ExtractOp extractOp, OneToNOpAdaptor adaptor,
447 ConversionPatternRewriter &rewriter) const override {
448 // Check if the source of the extraction is split to multiple values.
449 if (adaptor.getSource().size() == 1)
450 return failure();
451 auto mixedPos = extractOp.getMixedPosition();
452 if (mixedPos.size() != 1)
453 return failure();
454 auto mayBeInt = getConstantIntValue(mixedPos[0]);
455 if (!mayBeInt)
456 return failure();
457 rewriter.replaceOp(extractOp, adaptor.getSource()[*mayBeInt]);
458 return success();
459 }
460};
461
462/// Performs a reduction over 2 dimensions by decomposing it into two 1D
463/// reductions ordered based on layout to minimize cross-lane communication.
464class MultiRed2dOpPattern
465 : public OpConversionPattern<vector::MultiDimReductionOp> {
466 using Base::Base;
467 LogicalResult
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");
473 auto resLayout = xegpu::getDistributeLayoutAttr(reductionOp.getResult());
474 // Retrieve and order dims for 1D decomposition (prefer intra-lane first).
475 auto dims = llvm::to_vector(reductionOp.getReductionDims());
476 auto [intraLaneDim, crossLaneDim] = getReductionDimOrder(dims, resLayout);
477 // Order does not matter
478 if (intraLaneDim == -1 || crossLaneDim == -1) {
479 intraLaneDim = dims[0];
480 crossLaneDim = dims[1];
481 }
482 auto loc = reductionOp.getLoc();
483 auto acc = reductionOp.getAcc();
484
485 // The decomposition below splits the 2D reduction into an intra-lane
486 // then a cross-lane 1D reduction. The natural result layout of the
487 // decomposed sequence (a doubly-sliced layout) differs from the
488 // original 2D reduction's result layout that the rest of the IR was
489 // written/propagated against. To keep the post-peephole IR
490 // self-consistent without depending on a follow-up layout
491 // propagation pass, we always insert a bridge xegpu.convert_layout
492 // from the natural post-decomposition layout to the original
493 // reduction's result layout. Trivial bridges fold away in
494 // canonicalization.
495 xegpu::DistributeLayoutAttr postDecompLayout;
496 if (resLayout) {
497 // Derive the source vector's layout.
498 xegpu::DistributeLayoutAttr srcLayoutForCvt;
499 if (auto resSlice = dyn_cast_if_present<xegpu::SliceAttr>(resLayout))
500 srcLayoutForCvt = resSlice.getParent();
501 if (!srcLayoutForCvt)
502 srcLayoutForCvt =
503 xegpu::getDistributeLayoutAttr(reductionOp.getSource());
504 if (srcLayoutForCvt) {
505 // The natural layout of the post-decomposition reduction result
506 // is a nested SliceAttr: REDUCE_1 (reduces `intraLaneDim` from
507 // the source) yields `slice<src, [intraLaneDim]>`; REDUCE_2
508 // then reduces `adjCrossLaneDim` from that intermediate, giving
509 // `slice<slice<src, [intraLaneDim]>, [adjCrossLaneDim]>`.
510 MLIRContext *ctx = reductionOp.getContext();
511 int64_t adjCrossLaneDim =
512 crossLaneDim > intraLaneDim ? crossLaneDim - 1 : crossLaneDim;
513 auto intermediateLayout = xegpu::SliceAttr::get(
514 ctx, srcLayoutForCvt, DenseI64ArrayAttr::get(ctx, {intraLaneDim}));
515 postDecompLayout = xegpu::SliceAttr::get(
516 ctx, intermediateLayout,
517 DenseI64ArrayAttr::get(ctx, {adjCrossLaneDim}));
518 }
519 }
520
521 SmallVector<int64_t> accShape(sourceVecType.getShape());
522 accShape.erase(accShape.begin() + intraLaneDim);
523 Type eTy = sourceVecType.getElementType();
524 Value constNeutralVal = xegpu::createReductionNeutralValue(
525 rewriter, loc, VectorType::get(accShape, eTy), reductionOp.getKind(),
526 reductionOp.getFastmathAttr());
527
528 Value intraLaneReduced = vector::MultiDimReductionOp::create(
529 rewriter, loc, reductionOp.getKind(), reductionOp.getSource(),
530 constNeutralVal, ArrayRef<int64_t>(intraLaneDim),
531 reductionOp.getFastmath());
532
533 // Adjust crossLaneDim after the first reduction.
534 if (crossLaneDim > intraLaneDim)
535 crossLaneDim -= 1;
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() &&
540 "Type mismatch");
541
542 Value replacement = crossLaneReduced;
543 if (resLayout && postDecompLayout) {
544 // Bridge from the natural post-decomposition layout to the
545 // original reduction's result layout. This preserves the contract
546 // any consumer (convert_layout, anchor op, or otherwise) was
547 // written against, so the rewrite is correct independent of
548 // whether layout propagation runs afterwards.
549 auto bridgeOp = xegpu::ConvertLayoutOp::create(
550 rewriter, loc, crossLaneReduced.getType(), crossLaneReduced,
551 postDecompLayout, resLayout);
552 replacement = bridgeOp.getResult();
553 }
554
555 rewriter.replaceOp(reductionOp, replacement);
556 return success();
557 }
558
559private:
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))
568 layoutAttr =
569 dyn_cast<xegpu::LayoutAttr>(layoutSliceAttr.flatten().getParent());
570 assert(layoutAttr);
571 SmallVector<int64_t> laneLayout = layoutAttr.getEffectiveLaneLayoutAsInt();
572
573 assert(laneLayout.size() && "Expected a non-empty layout");
574 // try to pick a dim that does not communicate
575 for (auto dim : reductionDims) {
576 if (laneLayout[dim] == 1)
577 intra = dim;
578 else
579 cross = dim;
580 }
581 return {intra, cross};
582 }
583};
584
585} // namespace
586
588 RewritePatternSet &patterns) {
589 patterns.add<XeGPUCreateNdDescOpPattern, XeGPULoadNdDescOpPattern,
590 VectorExtractOpPattern, MultiRed2dOpPattern>(
591 patterns.getContext());
592}
593
594namespace {
595
596struct XeGPUPeepHoleOptimizerPass final
598 XeGPUPeepHoleOptimizerPass> {
599 void runOnOperation() override {
600 MLIRContext &context = getContext();
601 TypeConverter converter;
602 RewritePatternSet patterns(&context);
603 ConversionTarget target(context);
604
605 // This pass is only meant for PVC, BMG or CRI targets. If unsupported
606 // target is found, exit early.
607 bool isTargetSupported = false;
608 getOperation()->walk([&](gpu::GPUFuncOp funcOp) {
609 auto chipStr = xegpu::getChipStr(funcOp);
610 if (chipStr && (chipStr.value() == "pvc" || chipStr.value() == "bmg" ||
611 chipStr.value() == "cri"))
612 isTargetSupported = true;
613 });
614
615 if (!isTargetSupported) {
616 DBGS() << "XeGPUPeepHoleOptimizerPass only supports PVC, BMG targets."
617 << "\n";
618 return;
619 }
620
621 // Run array length optimization patterns first so that subsequent transpose
622 // peephole patterns operate on the array-length-optimized tensor descs.
623 {
624 RewritePatternSet arrayLenPatterns(&context);
626 if (failed(applyPatternsGreedily(getOperation(),
627 std::move(arrayLenPatterns)))) {
628 DBGS() << "Array length optimization patterns failed.\n";
629 return signalPassFailure();
630 }
631 }
632
633 // CreateNdDescOp and LoadNdOp with optimizable tensor desc types must be
634 // converted.
635 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp>(
636 [&](xegpu::CreateNdDescOp createNdOp) {
637 return !canBeOptimizedForTranspose(createNdOp.getType());
638 });
639 target.addDynamicallyLegalOp<xegpu::LoadNdOp>(
640 [&](xegpu::LoadNdOp loadNdOp) {
641 return !canBeOptimizedForTranspose(loadNdOp.getTensorDescType());
642 });
643 // Vector ExtractOps can have optimizable layouts if they extract from
644 // LoadNdOps with array length greater than 1. These ExtractOps must be
645 // converted.
646 target.addDynamicallyLegalOp<vector::ExtractOp>(
647 [&](vector::ExtractOp extractOp) {
648 auto layout = xegpu::getTemporaryLayout(
649 dyn_cast<OpResult>(extractOp.getResult()));
650 if (!layout)
651 return true;
652 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
653 auto laneData = layout.getEffectiveLaneDataAsInt();
654 return !canBeOptimizedForTranspose(laneLayout, laneData);
655 });
656
657 target.addDynamicallyLegalOp<vector::MultiDimReductionOp>(
658 [=](Operation *op) -> bool {
659 auto layout = xegpu::getDistributeLayoutAttr(op->getResult(0));
660 if (!layout || !layout.isForSubgroup())
661 return true;
662 if (auto reductionOp = dyn_cast<vector::MultiDimReductionOp>(op))
663 return reductionOp.getReductionDims().size() != 2;
664 return true;
665 });
666
667 converter.addConversion([](Type type) { return type; });
668
669 target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect,
670 vector::VectorDialect>();
671 // xegpu.convert_layout is left untouched by this pass; mark it legal
672 // so in-place updates don't trigger re-legalization failures.
673 target.addLegalOp<xegpu::ConvertLayoutOp>();
675 target);
677 if (failed(applyPartialConversion(getOperation(), target,
678 std::move(patterns)))) {
679 DBGS() << "Optimize block loads pass failed.\n";
680 return signalPassFailure();
681 }
682
683 // Apply folding for cleaning up IR.
684 MLIRContext *ctx = &getContext();
685 RewritePatternSet emptyPatterns(ctx);
686 (void)applyPatternsGreedily(getOperation(), std::move(emptyPatterns));
687
688 xegpu::removeTemporaryLayoutAttrs(getOperation());
689 }
690};
691
692} // 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`
static SmallVector< Value > slice(OpBuilder &b, Location loc, Value value, VectorType nativeType)
#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:71
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:434
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:853
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)
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 &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:496
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:146