[Mlir-commits] [mlir] [mlir][xegpu] Lower lane_data repack convert_layout to bitcast_shuffle (PR #210837)
Jianhui Li
llvmlistbot at llvm.org
Sat Aug 1 15:57:02 PDT 2026
https://github.com/Jianhui-Li updated https://github.com/llvm/llvm-project/pull/210837
>From 44905d20e6827b15257865295f528ac129e0ec83 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Sat, 1 Aug 2026 22:44:37 +0000
Subject: [PATCH] [mlir][xegpu] Lower lane_data repack convert_layout to
lane_shuffle
Extend the SgToLaneConvertLayout pattern to lower a convert_layout that
only repacks lane_data between round-robin and contiguous form (keeping
lane_layout and order unchanged) into xegpu.lane_shuffle. Each lane keeps
the same elements along the repacked dimension, but their assignment to
lanes changes, so the data is moved across lanes with a type-preserving
lane_shuffle (pack for round-robin -> contiguous, unpack for the reverse).
Because lane_shuffle preserves the element type, no bitcast to a storage
integer type is needed. The repacked dimension must be the distributed
one of the two innermost dimensions. When the lane fragment is a single
run it is shape_cast to 1D and shuffled once; when the run is strided the
k-long slice is extracted, shuffled, and inserted back per run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply at anthropic.com>
---
.../Transforms/XeGPUSgToLaneDistribute.cpp | 142 ++++++++++++++++++
.../XeGPU/sg-to-lane-distribute-unit.mlir | 81 ++++++++++
2 files changed, 223 insertions(+)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUSgToLaneDistribute.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUSgToLaneDistribute.cpp
index a5a998f9b4427..13b7fe52a3e6c 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUSgToLaneDistribute.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUSgToLaneDistribute.cpp
@@ -1665,6 +1665,101 @@ shuffleDataAsLaneLayoutChange(ConversionPatternRewriter &rewriter, Location loc,
return res;
}
+/// Repacks `src`'s `lane_data` along `repackDim` between round-robin and
+/// contiguous form with an `xegpu.lane_shuffle`, which moves each lane's run of
+/// `k` elements across lanes while preserving the element type.
+///
+/// `inputData`/`targetData` are the `repackDim` `lane_data` of the input and
+/// target layouts; exactly one must be 1 (round-robin) and the other `k`
+/// (contiguous). Returns failure if that does not hold.
+static FailureOr<Value>
+repackLaneData(ConversionPatternRewriter &rewriter, Location loc, Value src,
+ int64_t repackDim, int64_t inputData, int64_t targetData) {
+ auto srcTy = dyn_cast<VectorType>(src.getType());
+ if (!srcTy)
+ return failure();
+ int64_t rank = srcTy.getRank();
+ Type elemTy = srcTy.getElementType();
+ int64_t k = srcTy.getShape()[repackDim];
+
+ bool roundRobinToContig = inputData == 1 && targetData == k;
+ bool contigToRoundRobin = inputData == k && targetData == 1;
+ if (!roundRobinToContig && !contigToRoundRobin)
+ return failure();
+
+ // Round-robin -> contiguous gathers a lane's strided elements into
+ // consecutive positions (pack); the reverse scatters them back (unpack).
+ xegpu::LaneShuffleMode mode = roundRobinToContig
+ ? xegpu::LaneShuffleMode::Pack
+ : xegpu::LaneShuffleMode::Unpack;
+ VectorType runTy = VectorType::get({k}, elemTy);
+
+ // Common case: the lane fragment is a single run (every dimension other than
+ // `repackDim` is unit), so collapse it to 1D, shuffle once, and restore it.
+ if (srcTy.getNumElements() == k) {
+ if (rank == 1)
+ return Value(xegpu::LaneShuffleOp::create(rewriter, loc, runTy, src,
+ mode));
+ Value flat = vector::ShapeCastOp::create(rewriter, loc, runTy, src);
+ Value shuffled =
+ xegpu::LaneShuffleOp::create(rewriter, loc, runTy, flat, mode);
+ return Value(vector::ShapeCastOp::create(rewriter, loc, srcTy, shuffled));
+ }
+
+ // When `repackDim` is innermost each run is a contiguous sub-vector, so it is
+ // extracted and re-inserted as a whole.
+ if (repackDim == rank - 1) {
+ SmallVector<int64_t> outerShape(srcTy.getShape().drop_back());
+ int64_t numRuns = computeProduct(outerShape);
+ SmallVector<int64_t> outerStrides = computeStrides(outerShape);
+ Value result = arith::ConstantOp::create(rewriter, loc, srcTy,
+ rewriter.getZeroAttr(srcTy));
+ for (int64_t i = 0; i < numRuns; ++i) {
+ SmallVector<int64_t> pos = delinearize(i, outerStrides);
+ Value run = vector::ExtractOp::create(rewriter, loc, src, pos);
+ Value shuffled =
+ xegpu::LaneShuffleOp::create(rewriter, loc, runTy, run, mode);
+ result = vector::InsertOp::create(rewriter, loc, shuffled, result, pos);
+ }
+ return result;
+ }
+
+ // Otherwise each run is strided along `repackDim`: extract the `k`-long slice
+ // (a sub-vector that is unit along every other dim), flatten it to 1D,
+ // shuffle, and insert it back.
+ SmallVector<int64_t> keptShape;
+ SmallVector<int64_t> keptDims;
+ for (int64_t d = 0; d < rank; ++d)
+ if (d != repackDim) {
+ keptShape.push_back(srcTy.getShape()[d]);
+ keptDims.push_back(d);
+ }
+ int64_t numRuns = computeProduct(keptShape);
+ SmallVector<int64_t> keptStrides = computeStrides(keptShape);
+ SmallVector<int64_t> sliceSizes(rank, 1);
+ sliceSizes[repackDim] = k;
+ SmallVector<int64_t> sliceStrides(rank, 1);
+ VectorType sliceTy = VectorType::get(sliceSizes, elemTy);
+ Value result = arith::ConstantOp::create(rewriter, loc, srcTy,
+ rewriter.getZeroAttr(srcTy));
+ for (int64_t i = 0; i < numRuns; ++i) {
+ SmallVector<int64_t> keptPos = delinearize(i, keptStrides);
+ SmallVector<int64_t> offsets(rank, 0);
+ for (auto [dim, coord] : llvm::zip_equal(keptDims, keptPos))
+ offsets[dim] = coord;
+ Value slice = vector::ExtractStridedSliceOp::create(
+ rewriter, loc, src, offsets, sliceSizes, sliceStrides);
+ Value run = vector::ShapeCastOp::create(rewriter, loc, runTy, slice);
+ Value repacked =
+ xegpu::LaneShuffleOp::create(rewriter, loc, runTy, run, mode);
+ Value repackedSlice =
+ vector::ShapeCastOp::create(rewriter, loc, sliceTy, repacked);
+ result = vector::InsertStridedSliceOp::create(
+ rewriter, loc, repackedSlice, result, offsets, sliceStrides);
+ }
+ return result;
+}
+
/// Folds a subgroup-level ConvertLayout op with compatible lane layouts.
struct SgToLaneConvertLayout
: public OpConversionPattern<xegpu::ConvertLayoutOp> {
@@ -1721,6 +1816,53 @@ struct SgToLaneConvertLayout
}
}
+ // Handle a pure `lane_data` repack: `lane_layout` and `order` are unchanged
+ // and exactly one dimension's `lane_data` switches between round-robin
+ // (lane_data 1) and contiguous (lane_data == run length). The elements per
+ // lane are unchanged, but their assignment to lanes is not, so the data is
+ // moved across lanes with `xegpu.lane_shuffle`. The changed dimension must
+ // be one of the two innermost ones, since sg-to-lane distribution is 2D.
+ if (inputLayout.getEffectiveOrderAsInt() ==
+ targetLayout.getEffectiveOrderAsInt() &&
+ inputLayout.getEffectiveLaneLayoutAsInt() ==
+ targetLayout.getEffectiveLaneLayoutAsInt()) {
+ auto laneLayout = inputLayout.getEffectiveLaneLayoutAsInt();
+ auto laneData = inputLayout.getEffectiveLaneDataAsInt();
+ auto targetLaneData = targetLayout.getEffectiveLaneDataAsInt();
+ // Find the single dimension whose lane_data changed; bail out if more
+ // than one differs.
+ int64_t rank = laneData.size();
+ int64_t repackDim = -1;
+ bool multipleChanged = false;
+ for (int64_t d = 0; d < rank; ++d)
+ if (laneData[d] != targetLaneData[d]) {
+ if (repackDim != -1)
+ multipleChanged = true;
+ repackDim = d;
+ }
+
+ // `repackDim` must be the distributed dim (lane_layout != 1) and the
+ // other innermost dim non-distributed (lane_layout == 1).
+ int64_t otherDim = repackDim == rank - 1 ? rank - 2 : rank - 1;
+ bool laneLayoutOk =
+ repackDim != -1 && laneLayout[repackDim] != 1 &&
+ (rank < 2 || laneLayout[otherDim] == 1);
+
+ // Exactly one dimension must change, and it must be one of the two
+ // innermost (>= rank - 2).
+ if (repackDim != -1 && repackDim >= rank - 2 && !multipleChanged &&
+ laneLayoutOk) {
+ FailureOr<Value> res =
+ repackLaneData(rewriter, op.getLoc(), adaptor.getSource(),
+ repackDim, laneData[repackDim],
+ targetLaneData[repackDim]);
+ if (succeeded(res)) {
+ rewriter.replaceOp(op, *res);
+ return success();
+ }
+ }
+ }
+
return rewriter.notifyMatchFailure(
op, "lowering incompatible convert_layout not yet supported");
}
diff --git a/mlir/test/Dialect/XeGPU/sg-to-lane-distribute-unit.mlir b/mlir/test/Dialect/XeGPU/sg-to-lane-distribute-unit.mlir
index a7fe2d9534351..8607d45d5828c 100644
--- a/mlir/test/Dialect/XeGPU/sg-to-lane-distribute-unit.mlir
+++ b/mlir/test/Dialect/XeGPU/sg-to-lane-distribute-unit.mlir
@@ -1045,6 +1045,87 @@ gpu.func @convert_layout_scalar() {
}
}
+// -----
+gpu.module @xevm_module {
+// CHECK-LABEL: gpu.func @convert_layout_repack_innermost_lane_data
+// CHECK-NOT: xegpu.convert_layout
+// CHECK: %[[SRC:.*]] = builtin.unrealized_conversion_cast %{{.*}} : vector<1x64xbf16> to vector<1x4xbf16>
+// CHECK: %[[F:.*]] = vector.shape_cast %[[SRC]] : vector<1x4xbf16> to vector<4xbf16>
+// CHECK: %[[S0:.*]] = xegpu.lane_shuffle %[[F]] pack : vector<4xbf16>
+// CHECK: vector.shape_cast %[[S0]] : vector<4xbf16> to vector<1x4xbf16>
+gpu.func @convert_layout_repack_innermost_lane_data() {
+ %src = "some_op"() : () -> vector<1x64xbf16>
+ %cvt = xegpu.convert_layout %src
+ <{
+ input_layout = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>,
+ target_layout = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 4]>
+ }> : vector<1x64xbf16>
+ "some_use"(%cvt) : (vector<1x64xbf16>) -> ()
+ gpu.return
+}
+}
+
+// -----
+gpu.module @xevm_module {
+// CHECK-LABEL: gpu.func @convert_layout_repack_innermost_lane_data_1d
+// CHECK-NOT: xegpu.convert_layout
+// CHECK: %[[SRC:.*]] = builtin.unrealized_conversion_cast %{{.*}} : vector<64xbf16> to vector<4xbf16>
+// CHECK: xegpu.lane_shuffle %[[SRC]] pack : vector<4xbf16>
+gpu.func @convert_layout_repack_innermost_lane_data_1d() {
+ %src = "some_op"() : () -> vector<64xbf16>
+ %cvt = xegpu.convert_layout %src
+ <{
+ input_layout = #xegpu.layout<lane_layout = [16], lane_data = [1]>,
+ target_layout = #xegpu.layout<lane_layout = [16], lane_data = [4]>
+ }> : vector<64xbf16>
+ "some_use"(%cvt) : (vector<64xbf16>) -> ()
+ gpu.return
+}
+}
+
+// -----
+gpu.module @xevm_module {
+// CHECK-LABEL: gpu.func @convert_layout_repack_second_innermost_lane_data
+// CHECK-NOT: xegpu.convert_layout
+// CHECK: %[[SRC:.*]] = builtin.unrealized_conversion_cast %{{.*}} : vector<64x4xbf16> to vector<4x4xbf16>
+// CHECK: %[[SL0:.*]] = vector.extract_strided_slice %[[SRC]] {offsets = [0, 0], sizes = [4, 1], strides = [1, 1]} : vector<4x4xbf16> to vector<4x1xbf16>
+// CHECK: %[[F0:.*]] = vector.shape_cast %[[SL0]] : vector<4x1xbf16> to vector<4xbf16>
+// CHECK: %[[S0:.*]] = xegpu.lane_shuffle %[[F0]] unpack : vector<4xbf16>
+// CHECK: %[[C0:.*]] = vector.shape_cast %[[S0]] : vector<4xbf16> to vector<4x1xbf16>
+// CHECK: vector.insert_strided_slice %[[C0]], %{{.*}} {offsets = [0, 0], strides = [1, 1]} : vector<4x1xbf16> into vector<4x4xbf16>
+// CHECK-COUNT-3: xegpu.lane_shuffle %{{.*}} unpack : vector<4xbf16>
+gpu.func @convert_layout_repack_second_innermost_lane_data() {
+ %src = "some_op"() : () -> vector<64x4xbf16>
+ %cvt = xegpu.convert_layout %src
+ <{
+ input_layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [4, 1]>,
+ target_layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 1]>
+ }> : vector<64x4xbf16>
+ "some_use"(%cvt) : (vector<64x4xbf16>) -> ()
+ gpu.return
+}
+}
+
+// -----
+gpu.module @xevm_module {
+// CHECK-LABEL: gpu.func @convert_layout_repack_innermost_lane_data_3d
+// CHECK-NOT: xegpu.convert_layout
+// CHECK: %[[SRC:.*]] = builtin.unrealized_conversion_cast %{{.*}} : vector<1x1x64xbf16> to vector<1x1x4xbf16>
+// CHECK: %[[F:.*]] = vector.shape_cast %[[SRC]] : vector<1x1x4xbf16> to vector<4xbf16>
+// CHECK: %[[S0:.*]] = xegpu.lane_shuffle %[[F]] pack : vector<4xbf16>
+// CHECK: vector.shape_cast %[[S0]] : vector<4xbf16> to vector<1x1x4xbf16>
+gpu.func @convert_layout_repack_innermost_lane_data_3d() {
+ %src = "some_op"() : () -> vector<1x1x64xbf16>
+ %cvt = xegpu.convert_layout %src
+ <{
+ input_layout = #xegpu.layout<lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>,
+ target_layout = #xegpu.layout<lane_layout = [1, 1, 16], lane_data = [1, 1, 4]>
+ }> : vector<1x1x64xbf16>
+ "some_use"(%cvt) : (vector<1x1x64xbf16>) -> ()
+ gpu.return
+}
+}
+
// -----
// load_matrix and store_matrix with coordinate computation (offsets [0,0])
gpu.module @xevm_module {
More information about the Mlir-commits
mailing list