[Mlir-commits] [mlir] [MLIR][XeGPU] Enhance unrolling of convert layout (PR #209822)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Wed Jul 15 21:55:18 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-gpu
Author: Nishant Patel (nbpatel)
<details>
<summary>Changes</summary>
This PR adds rewriteWithRegrouping to unroll ConvertLayoutOp by extracting at the input inst_data granularity and inserting at the target inst_data granularity for cancellation during canonicalization.
Assisted by Claude
---
Full diff: https://github.com/llvm/llvm-project/pull/209822.diff
2 Files Affected:
- (modified) mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp (+110-14)
- (modified) mlir/test/Dialect/XeGPU/xegpu-blocking.mlir (+27-5)
``````````diff
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
index 74c358cef90df..bcb9946373298 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
@@ -957,8 +957,92 @@ struct UnrollStoreMatrixOp : public UnrollPattern<xegpu::StoreMatrixOp> {
/// after inst_data stripped. If it does, it will unroll the vector into
/// multiple smaller vectors according to the target shape, and create multiple
/// ConvertLayoutOp with the unrolled vectors and the stripped layouts.
+///
+/// When the input and target layouts have different inst_data, the source is
+/// extracted at the input inst_data granularity and the result is inserted at
+/// the target inst_data granularity, enabling slice cancellation during
+/// canonicalization.
struct UnrollConvertLayoutOp : public UnrollPattern<xegpu::ConvertLayoutOp> {
using UnrollPattern<xegpu::ConvertLayoutOp>::UnrollPattern;
+
+ /// Extracts source in `inTile` slices, regroups into `convTile`-sized
+ /// ConvertLayoutOps, and inserts the result in `outTile` slices.
+ /// Returns failure if the tiles do not evenly divide.
+ LogicalResult
+ rewriteWithRegrouping(xegpu::ConvertLayoutOp op, VectorType valueTy,
+ ArrayRef<int64_t> convTile, ArrayRef<int64_t> inTile,
+ ArrayRef<int64_t> outTile,
+ xegpu::DistributeLayoutAttr inputLayout,
+ xegpu::DistributeLayoutAttr targetLayout, Location loc,
+ PatternRewriter &rewriter) const {
+ ArrayRef<int64_t> vecShape = valueTy.getShape();
+ if (!computeShapeRatio(vecShape, convTile) ||
+ !computeShapeRatio(convTile, inTile) ||
+ !computeShapeRatio(convTile, outTile))
+ return failure();
+
+ Type elemTy = valueTy.getElementType();
+ int64_t rank = valueTy.getRank();
+ VectorType convTy = VectorType::get(convTile, elemTy);
+ SmallVector<int64_t> strides(rank, 1);
+
+ Value source = op.getSource();
+ auto zeroOf = [&](VectorType ty) -> Value {
+ return arith::ConstantOp::create(
+ rewriter, loc, ty,
+ DenseElementsAttr::get(ty, rewriter.getZeroAttr(elemTy)));
+ };
+ auto addOffsets = [](ArrayRef<int64_t> a,
+ ArrayRef<int64_t> b) -> SmallVector<int64_t> {
+ SmallVector<int64_t> res(a);
+ for (auto [r, v] : llvm::zip_equal(res, b))
+ r += v;
+ return res;
+ };
+
+ Value result = zeroOf(valueTy);
+ for (SmallVector<int64_t> convOff :
+ StaticTileOffsetRange(vecShape, convTile)) {
+ // Build the convert tile from inTile-sized slices of the source.
+ Value conv;
+ if (convTile == inTile) {
+ conv = vector::ExtractStridedSliceOp::create(
+ rewriter, loc, source, convOff, convTile, strides);
+ } else {
+ conv = zeroOf(convTy);
+ for (SmallVector<int64_t> inLocal :
+ StaticTileOffsetRange(convTile, inTile)) {
+ Value piece = vector::ExtractStridedSliceOp::create(
+ rewriter, loc, source, addOffsets(convOff, inLocal), inTile,
+ strides);
+ conv = vector::InsertStridedSliceOp::create(rewriter, loc, piece,
+ conv, inLocal, strides);
+ }
+ }
+
+ conv = xegpu::ConvertLayoutOp::create(rewriter, loc, convTy, conv,
+ inputLayout, targetLayout);
+
+ // Write the converted tile into the result as outTile-sized slices.
+ if (convTile == outTile) {
+ result = vector::InsertStridedSliceOp::create(rewriter, loc, conv,
+ result, convOff, strides);
+ } else {
+ for (SmallVector<int64_t> outLocal :
+ StaticTileOffsetRange(convTile, outTile)) {
+ Value piece = vector::ExtractStridedSliceOp::create(
+ rewriter, loc, conv, outLocal, outTile, strides);
+ result = vector::InsertStridedSliceOp::create(
+ rewriter, loc, piece, result, addOffsets(convOff, outLocal),
+ strides);
+ }
+ }
+ }
+
+ rewriter.replaceOp(op, result);
+ return success();
+ }
+
LogicalResult matchAndRewrite(xegpu::ConvertLayoutOp op,
PatternRewriter &rewriter) const override {
Location loc = op.getLoc();
@@ -974,8 +1058,10 @@ struct UnrollConvertLayoutOp : public UnrollPattern<xegpu::ConvertLayoutOp> {
return success();
}
- if (inputLayout.getEffectiveInstDataAsInt().empty() ||
- targetLayout.getEffectiveInstDataAsInt().empty())
+ // Capture inst_data granularities before stripping them.
+ SmallVector<int64_t> inTile = inputLayout.getEffectiveInstDataAsInt();
+ SmallVector<int64_t> outTile = targetLayout.getEffectiveInstDataAsInt();
+ if (inTile.empty() || outTile.empty())
return rewriter.notifyMatchFailure(op, "Not a target ConvertLayoutOp.");
inputLayout = inputLayout.dropInstData();
@@ -988,20 +1074,30 @@ struct UnrollConvertLayoutOp : public UnrollPattern<xegpu::ConvertLayoutOp> {
if (!targetShape || targetShape->size() != (size_t)valueTy.getRank())
return failure();
- Value newSource = op.getSource();
+ // Nothing to convert if layouts match after stripping inst_data.
+ if (!inputLayout || !targetLayout || inputLayout.isEqualTo(targetLayout)) {
+ rewriter.replaceOp(op, op.getSource());
+ return success();
+ }
+
+ // Try regrouping: extract at inTile, convert, insert at outTile.
+ if (succeeded(rewriteWithRegrouping(op, valueTy, *targetShape, inTile,
+ outTile, inputLayout, targetLayout, loc,
+ rewriter)))
+ return success();
+
+ // Fallback: pack/unpack at the convert tile granularity.
+ SmallVector<Type> convertedValTypes =
+ getUnrolledTypes(valueTy, *targetShape);
+ SmallVector<Value> convertedValues =
+ pack(op.getOperand(), convertedValTypes, *targetShape, loc, rewriter);
SmallVector<Value> newOps;
- if (inputLayout && targetLayout && !inputLayout.isEqualTo(targetLayout)) {
- SmallVector<Type> convertedValTypes =
- getUnrolledTypes(valueTy, *targetShape);
- SmallVector<Value> convertedValues =
- pack(op.getOperand(), convertedValTypes, *targetShape, loc, rewriter);
- for (auto [v, t] : llvm::zip(convertedValues, convertedValTypes)) {
- auto newOp = xegpu::ConvertLayoutOp::create(rewriter, loc, t, v,
- inputLayout, targetLayout);
- newOps.push_back(newOp);
- }
- newSource = unpack(newOps, op.getType(), *targetShape, loc, rewriter);
+ for (auto [v, t] : llvm::zip(convertedValues, convertedValTypes)) {
+ auto newOp = xegpu::ConvertLayoutOp::create(rewriter, loc, t, v,
+ inputLayout, targetLayout);
+ newOps.push_back(newOp);
}
+ Value newSource = unpack(newOps, op.getType(), *targetShape, loc, rewriter);
rewriter.replaceOp(op, newSource);
return success();
diff --git a/mlir/test/Dialect/XeGPU/xegpu-blocking.mlir b/mlir/test/Dialect/XeGPU/xegpu-blocking.mlir
index b0b16c2adba6e..27a88efcf443c 100644
--- a/mlir/test/Dialect/XeGPU/xegpu-blocking.mlir
+++ b/mlir/test/Dialect/XeGPU/xegpu-blocking.mlir
@@ -463,19 +463,19 @@ gpu.module @test_kernel {
//CHECK: gpu.func @convert_layout([[arg0:%.+]]: vector<8x32x2xf16>) -> vector<8x32x2xf16> {
//CHECK: [[cst:%.+]] = arith.constant dense<0.000000e+00> : vector<8x32x2xf16>
//CHECK: [[e0:%.+]] = vector.extract_strided_slice [[arg0]] {offsets = [0, 0, 0], sizes = [4, 32, 2], strides = [1, 1, 1]} : vector<8x32x2xf16> to vector<4x32x2xf16>
- //CHECK: [[e1:%.+]] = vector.extract_strided_slice [[arg0]] {offsets = [4, 0, 0], sizes = [4, 32, 2], strides = [1, 1, 1]} : vector<8x32x2xf16> to vector<4x32x2xf16>
//CHECK: [[c0:%.+]] = xegpu.convert_layout [[e0]] <{input_layout = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 2]>, target_layout = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 1]>}> : vector<4x32x2xf16>
- //CHECK: [[c1:%.+]] = xegpu.convert_layout [[e1]] <{input_layout = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 2]>, target_layout = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 1]>}> : vector<4x32x2xf16>
//CHECK: [[e2:%.+]] = vector.extract_strided_slice [[c0]] {offsets = [0, 0, 0], sizes = [4, 16, 2], strides = [1, 1, 1]} : vector<4x32x2xf16> to vector<4x16x2xf16>
+ //CHECK: [[e3:%.+]] = vector.extract_strided_slice [[c0]] {offsets = [0, 16, 0], sizes = [4, 16, 2], strides = [1, 1, 1]} : vector<4x32x2xf16> to vector<4x16x2xf16>
+ //CHECK: [[e1:%.+]] = vector.extract_strided_slice [[arg0]] {offsets = [4, 0, 0], sizes = [4, 32, 2], strides = [1, 1, 1]} : vector<8x32x2xf16> to vector<4x32x2xf16>
+ //CHECK: [[c1:%.+]] = xegpu.convert_layout [[e1]] <{input_layout = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 2]>, target_layout = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 1]>}> : vector<4x32x2xf16>
+ //CHECK: [[e4:%.+]] = vector.extract_strided_slice [[c1]] {offsets = [0, 0, 0], sizes = [4, 16, 2], strides = [1, 1, 1]} : vector<4x32x2xf16> to vector<4x16x2xf16>
+ //CHECK: [[e5:%.+]] = vector.extract_strided_slice [[c1]] {offsets = [0, 16, 0], sizes = [4, 16, 2], strides = [1, 1, 1]} : vector<4x32x2xf16> to vector<4x16x2xf16>
//CHECK: [[m0:%.+]] = math.exp [[e2]] {layout_result_0 = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 1]>} : vector<4x16x2xf16>
//CHECK: [[i0:%.+]] = vector.insert_strided_slice [[m0]], [[cst]] {offsets = [0, 0, 0], strides = [1, 1, 1]} : vector<4x16x2xf16> into vector<8x32x2xf16>
- //CHECK: [[e3:%.+]] = vector.extract_strided_slice [[c0]] {offsets = [0, 16, 0], sizes = [4, 16, 2], strides = [1, 1, 1]} : vector<4x32x2xf16> to vector<4x16x2xf16>
//CHECK: [[m1:%.+]] = math.exp [[e3]] {layout_result_0 = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 1]>} : vector<4x16x2xf16>
//CHECK: [[i1:%.+]] = vector.insert_strided_slice [[m1]], [[i0]] {offsets = [0, 16, 0], strides = [1, 1, 1]} : vector<4x16x2xf16> into vector<8x32x2xf16>
- //CHECK: [[e4:%.+]] = vector.extract_strided_slice [[c1]] {offsets = [0, 0, 0], sizes = [4, 16, 2], strides = [1, 1, 1]} : vector<4x32x2xf16> to vector<4x16x2xf16>
//CHECK: [[m2:%.+]] = math.exp [[e4]] {layout_result_0 = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 1]>} : vector<4x16x2xf16>
//CHECK: [[i2:%.+]] = vector.insert_strided_slice [[m2]], [[i1]] {offsets = [4, 0, 0], strides = [1, 1, 1]} : vector<4x16x2xf16> into vector<8x32x2xf16>
- //CHECK: [[e5:%.+]] = vector.extract_strided_slice [[c1]] {offsets = [0, 16, 0], sizes = [4, 16, 2], strides = [1, 1, 1]} : vector<4x32x2xf16> to vector<4x16x2xf16>
//CHECK: [[m3:%.+]] = math.exp [[e5]] {layout_result_0 = #xegpu.layout<lane_layout = [1, 16, 1], lane_data = [4, 1, 1]>} : vector<4x16x2xf16>
//CHECK: [[i3:%.+]] = vector.insert_strided_slice [[m3]], [[i2]] {offsets = [4, 16, 0], strides = [1, 1, 1]} : vector<4x16x2xf16> into vector<8x32x2xf16>
//CHECK: gpu.return [[i3]] : vector<8x32x2xf16>
@@ -488,6 +488,28 @@ gpu.module @test_kernel {
}
}
+// -----
+
+// Test regrouping when input and target inst_data differ ([8, 16] vs [8, 64]).
+#in = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>
+#tgt = #xegpu.layout<inst_data = [8, 64], lane_layout = [1, 16], lane_data = [1, 4]>
+
+gpu.module @test_kernel {
+ //CHECK-LABEL: gpu.func @convert_layout_regroup
+ //CHECK-SAME: ([[arg0:%.+]]: vector<16x64xbf16>)
+ //CHECK: vector.extract_strided_slice [[arg0]] {offsets = [0, 0], sizes = [8, 16]
+ //CHECK: math.exp {{.*}} : vector<8x16xbf16>
+ //CHECK-NOT: vector.extract_strided_slice [[arg0]] {{.*}}sizes = [8, 64]
+ //CHECK: vector.insert_strided_slice {{.*}} : vector<8x16xbf16> into vector<8x64xbf16>
+ //CHECK: xegpu.convert_layout {{.*}} : vector<8x64xbf16>
+ //CHECK: vector.insert_strided_slice {{.*}} : vector<8x64xbf16> into vector<16x64xbf16>
+ gpu.func @convert_layout_regroup(%a: vector<16x64xbf16>) -> vector<16x64xbf16> {
+ %p = math.exp %a {layout_result_0 = #in} : vector<16x64xbf16>
+ %0 = xegpu.convert_layout %p <{input_layout = #in, target_layout = #tgt}> : vector<16x64xbf16>
+ gpu.return %0 : vector<16x64xbf16>
+ }
+}
+
// -----
gpu.module @test_kernel {
//CHECK-LABEL: unroll_load_matrix
``````````
</details>
https://github.com/llvm/llvm-project/pull/209822
More information about the Mlir-commits
mailing list