[Mlir-commits] [mlir] [MLIR][XeGPU] Refactor XeGPU layout propagation: passing lane_layout/lane_data with inst_data (PR #203156)
Jianhui Li
llvmlistbot at llvm.org
Wed Jun 10 20:04:05 PDT 2026
https://github.com/Jianhui-Li created https://github.com/llvm/llvm-project/pull/203156
Motivation
Enhance setup* rules in layout propagation to pass lane_layout, and lane_data information during inst_data propagation, so that the propagation can have lane level information when choosing an optimal inst_data. This branch makes that relationship explicit and uniform across all setup rules.
Invariant
All setup rules now produce layouts that satisfy:
Nd ops + dpas/dpas_mx: inst_data = k * (lane_layout * lane_data), k ≥ 1
Scatter/matrix ops + non-anchor ops: inst_data = lane_layout * lane_data
Key changes in XeGPULayoutImpl
New setupStoreNdAnchorLayout, setupPrefetchNdAnchorLayout, setupLoadNdAnchorLayout (Nd ops have rigid lane info; inst_data fits the lane factorization).
setupMultiReductionResultLayout reorganized so InstData and Lane branches share the same lane-layout logic.
setupGenericLoadAnchorLayout (scatter) takes consumer's inst_data as-is and consumer's lane info when present.
New complete*LayoutFromInstData helpers that fill in lane info on user-provided anchors that specify only inst_data — by
re-running the op's Lane setup with inst_data as the destination shape.
inferShapeCastSourceLayout preserves lane info through 1D ↔ ND collapse-style casts.
createScaleLayout (dpas_mx) caps scale lane_layout by inst_data so the scale operand load_nd satisfies the multiple-of
invariant.
Assisted-by-claude
>From 77c58e0c7500d0347c14dd2a59f9a98dfe307946 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Mon, 1 Jun 2026 18:58:11 +0000
Subject: [PATCH 01/20] [mlir][xegpu] Refactor reduction/bitcast/interleave
layout setup
Three related cleanups in the layout-setup helpers:
* setupMultiReductionResultLayout (InstData/Lane): replace the hard-coded
innermost-two-dims pattern with two helpers. leadingDimsAreUnit asserts
that all leading dims really are size-1 (previously assumed silently).
computeReductionLaneLayoutAndData becomes the single source of truth for
lane_layout/lane_data, and inst_data is derived as the elementwise
product. SliceAttr consumers contribute their slice dims to override the
reduction dims. Resulting inst_data layouts now also carry
lane_layout/lane_data.
* setupBitCastResultLayout / setupInterleaveResultLayout: extract the
shared "double the innermost data field until divisible by ratio" logic
into adjustInnermostDimForDivisibility, parameterized by (layoutKind,
innerMostDim, ratio, bound). Both call sites collapse into a few lines
each; bitcast gains an explicit early-return for the same-or-larger
element-type case.
* setupInsertStridedSliceResultLayout: disable the InstData clamping
path; it now stubs out the same way Subgroup does.
Tests in propagate-layout.mlir and propagate-layout-inst-data.mlir
updated to reflect the new richer inst-data layouts (which now carry
lane_layout/lane_data) and the disabled insert_strided_slice clamp.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply at anthropic.com>
---
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 314 +++++++++++-------
.../XeGPU/Transforms/XeGPUPropagateLayout.cpp | 10 +
.../Dialect/XeGPU/Transforms/XeGPUUnroll.cpp | 2 -
.../XeGPU/propagate-layout-inst-data.mlir | 26 +-
mlir/test/Dialect/XeGPU/propagate-layout.mlir | 18 +-
5 files changed, 234 insertions(+), 136 deletions(-)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index 4b821fce3e40c..96c08f9880a72 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -718,6 +718,21 @@ xegpu::inferShapeCastSourceLayout(xegpu::DistributeLayoutAttr resLayout,
// Use case 2: Dim split from source to result, for multi-stage reduction
SmallVector<SmallVector<int64_t>> splitDimGroups;
if (xegpu::matchSplitDimExpansion(srcShape, resShape, splitDimGroups)) {
+ llvm::dbgs() << "[DEBUG inferShapeCastSourceLayout] case2 split. resLayout="
+ << resLayout << " srcShape=[";
+ for (auto s : srcShape)
+ llvm::dbgs() << s << ",";
+ llvm::dbgs() << "] resShape=[";
+ for (auto s : resShape)
+ llvm::dbgs() << s << ",";
+ llvm::dbgs() << "] groups=";
+ for (auto &g : splitDimGroups) {
+ llvm::dbgs() << "{";
+ for (auto d : g)
+ llvm::dbgs() << d << ",";
+ llvm::dbgs() << "} ";
+ }
+ llvm::dbgs() << "\n";
auto srcLayout = resLayout;
for (const auto &dimGroup : splitDimGroups)
srcLayout = srcLayout.collapseDims(dimGroup);
@@ -776,6 +791,82 @@ xegpu::DistributeLayoutAttr xegpu::inferMaskOffsetLayoutForScatterIO(
return payloadLayout;
}
+/// Returns true if every dimension of `shape` except the innermost
+/// `numInnerDims` is a unit (size-1) dimension.
+///
+/// Several reduction layout-setup paths (InstData, Lane) only distribute the
+/// innermost one or two dimensions and rely on all the leading dimensions
+/// being degenerate. This helper makes that assumption explicit and checkable
+/// instead of silently leaving leading dimensions undistributed.
+static bool leadingDimsAreUnit(ArrayRef<int64_t> shape, int numInnerDims) {
+ int numLeading = static_cast<int>(shape.size()) - numInnerDims;
+ if (numLeading <= 0)
+ return true;
+ return llvm::all_of(shape.take_front(numLeading),
+ [](int64_t dim) { return dim == 1; });
+}
+
+/// Computes the lane_layout and lane_data for a multi-reduction's source
+/// layout. Only the innermost two dimensions are distributed; all leading
+/// dimensions are assumed to be unit (the caller verifies this via
+/// `leadingDimsAreUnit`).
+///
+/// The layout is chosen to minimize cross-lane reduction: whenever possible a
+/// reduction dimension is reduced *within* a lane (lane_layout == 1, with up to
+/// `maxReduceVectorSize` elements packed into lane_data), and the subgroup's
+/// lanes are spread across a non-reduction dimension instead.
+///
+/// - Exactly one of the innermost two dims is a reduction dim: place
+/// `subgroupSize` lanes on the non-reduction dim and keep lane_layout == 1
+/// on the reduction dim, packing up to `maxReduceVectorSize` reduced
+/// elements into lane_data along that reduction dim.
+/// - Both innermost dims are reduction dims (or the source is rank 1): fall
+/// back to the default of `subgroupSize` lanes on the innermost dim,
+/// packing `maxReduceVectorSize` elements on the second-to-innermost dim.
+///
+/// Returns the (lane_layout, lane_data) pair. The corresponding inst_data is
+/// simply the element-wise product lane_layout * lane_data.
+static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
+computeReductionLaneLayoutAndData(ArrayRef<int64_t> srcShape,
+ ArrayRef<int64_t> reductionDims,
+ int subgroupSize,
+ int64_t maxReduceVectorSize) {
+ int srcRank = srcShape.size();
+ SmallVector<int64_t> laneLayout(srcRank, 1), laneData(srcRank, 1);
+ llvm::dbgs() << "[DEBUG computeReductionLaneLayoutAndData] srcRank=" << srcRank
+ << " srcShape.size()=" << srcShape.size() << " srcShape=[";
+ for (size_t i = 0; i < srcShape.size(); ++i) {
+ if (i > 0) llvm::dbgs() << ", ";
+ llvm::dbgs() << srcShape[i];
+ }
+ llvm::dbgs() << "]\n";
+
+ int innermost = srcRank - 1;
+ int secondInnermost = srcRank - 2;
+
+ // `laneDim` carries the subgroupSize lanes; `vectorDim` packs the reduced
+ // elements into lane_data. Default: lanes on the innermost dim, reduced
+ // vector on the second-to-innermost dim.
+ int laneDim = innermost;
+ int vectorDim = secondInnermost; // negative for rank 1
+
+ // If only the innermost dim is reduced, spread the lanes across the
+ // non-reduction (second-to-innermost) dim and reduce the innermost dim
+ // within each lane instead.
+ // if (srcRank >= 2 && isReduction(innermost) &&
+ // !isReduction(secondInnermost)) {
+ // laneDim = secondInnermost;
+ // vectorDim = innermost;
+ // }
+
+ laneLayout[laneDim] =
+ std::min(static_cast<int64_t>(subgroupSize), srcShape[laneDim]);
+ if (vectorDim >= 0)
+ laneData[vectorDim] = std::min(maxReduceVectorSize, srcShape[vectorDim]);
+
+ return {laneLayout, laneData};
+}
+
/// Sets up layout for reduction operations by creating a SliceAttr for the
/// result.
///
@@ -792,9 +883,13 @@ xegpu::DistributeLayoutAttr xegpu::inferMaskOffsetLayoutForScatterIO(
/// reuse the slice layout's parent layout for the source to further minimize
/// potential data redistribution.
///
-/// InstData requries {1, ..., min(maxReduceVectorSize, srcShape),subgroupSize}
-/// Lane Layout requires {1, ..., 1, subgroupSize}
-/// Lane data requires {1, ..., min(maxReduceVectorSize, srcShape), 1}
+/// For the InstData and Lane layout kinds only the innermost two dimensions
+/// are distributed; all leading dimensions are assumed to be unit dimensions.
+/// This assumption is checked via `leadingDimsAreUnit`. The lane_layout and
+/// lane_data are computed by `computeReductionLaneLayoutAndData`, which picks
+/// a layout that minimizes cross-lane reduction (reducing within a lane when
+/// only one of the innermost two dims is a reduction dim). The inst_data is
+/// simply the element-wise product lane_layout * lane_data.
///
/// Examples:
/// 1. Subgroup layout - Row reduction on 2D tensor:
@@ -922,22 +1017,35 @@ xegpu::SliceAttr xegpu::setupMultiReductionResultLayout(
(!orderAttr || orderAttr.empty()) ? nullptr : toInt32Attr(order));
}
} else if (layoutKind == xegpu::LayoutKind::InstData) {
-
- SmallVector<int64_t> instData(srcRank, 1);
- if (srcRank >= 2)
- instData[srcRank - 2] =
- std::min(maxReduceVectorSize, srcShape[srcRank - 2]);
- instData[srcRank - 1] =
- std::min(static_cast<int64_t>(subgroupSize), srcShape[srcRank - 1]);
- srcLayout = xegpu::LayoutAttr::get(context, toInt32Attr(instData));
+ xegpu::SliceAttr consumerSliceLayout =
+ dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
+ auto reductionDimsOverrideConsumer = consumerSliceLayout?
+ SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef()): reductionDims;
+ auto [laneLayout, laneData] = computeReductionLaneLayoutAndData(
+ srcShape, reductionDimsOverrideConsumer, subgroupSize, maxReduceVectorSize);
+ // inst_data is the per-instruction data, i.e. the element-wise product of
+ // lane_layout and lane_data.
+ SmallVector<int64_t> instData(srcRank);
+ for (int i = 0; i < srcRank; i++)
+ instData[i] = laneLayout[i] * laneData[i];
+ srcLayout = xegpu::LayoutAttr::get(context, /*sg_layout=*/nullptr,
+ /*sg_data=*/nullptr,
+ /*inst_data=*/toInt32Attr(instData),
+ /*lane_layout=*/toInt32Attr(laneLayout),
+ /*lane_data=*/toInt32Attr(laneData),
+ /*order=*/nullptr);
} else if (layoutKind == xegpu::LayoutKind::Lane) {
-
- SmallVector<int64_t> laneLayout(srcRank, 1), laneData(srcRank, 1);
- laneLayout[srcRank - 1] =
- std::min(static_cast<int64_t>(subgroupSize), srcShape[srcRank - 1]);
- if (srcRank >= 2)
- laneData[srcRank - 2] =
- std::min(maxReduceVectorSize, srcShape[srcRank - 2]);
+ // Only the innermost two dimensions are distributed; all leading dimensions
+ // are assumed to be unit dimensions.
+ assert(leadingDimsAreUnit(srcShape, /*numInnerDims=*/2) &&
+ "Lane reduction layout assumes all leading (non-innermost-two) "
+ "dimensions are unit dimensions");
+ xegpu::SliceAttr consumerSliceLayout =
+ dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
+ auto reductionDimsOverrideConsumer = consumerSliceLayout?
+ SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef()): reductionDims;
+ auto [laneLayout, laneData] = computeReductionLaneLayoutAndData(
+ srcShape, reductionDimsOverrideConsumer, subgroupSize, maxReduceVectorSize);
srcLayout = xegpu::LayoutAttr::get(context, toInt32Attr(laneLayout),
toInt32Attr(laneData));
}
@@ -978,6 +1086,58 @@ xegpu::setupReductionResultLayout(xegpu::LayoutKind layoutKind,
return result;
}
+/// Adjusts `consumerLayout`'s innermost-dim data field selected by
+/// `layoutKind` so that the source layout can be safely inferred by dividing
+/// that value by `ratio`. Doubles the value until the divisibility constraint
+/// is met, bounded above by result-shape.
+///
+/// Used by ops whose source relates to the result by a fixed factor along the
+/// innermost dim (e.g., bitcast: bitwidth ratio; interleave: 2x).
+///
+/// Divisibility constraints per LayoutKind:
+/// - Subgroup: sgData[innermost] % ratio == 0
+/// - InstData: instData[innermost] % (laneLayout[innermost] * ratio) == 0
+/// (laneLayout falls back to subgroupSize if absent)
+/// - Lane: laneData[innermost] % ratio == 0
+static xegpu::DistributeLayoutAttr
+adjustInnermostDimForDivisibility(xegpu::DistributeLayoutAttr consumerLayout,
+ xegpu::LayoutKind layoutKind,
+ size_t innerMostDim, int ratio, int64_t bound,
+ const xegpu::uArch::uArch *uArch) {
+ SmallVector<int64_t> sgData = consumerLayout.getEffectiveSgDataAsInt();
+ SmallVector<int64_t> instData = consumerLayout.getEffectiveInstDataAsInt();
+ SmallVector<int64_t> laneData = consumerLayout.getEffectiveLaneDataAsInt();
+ SmallVector<int64_t> laneLayout =
+ consumerLayout.getEffectiveLaneLayoutAsInt();
+
+ int64_t sgDataValue = -1;
+ int64_t instDataValue = -1;
+ int64_t laneDataValue = -1;
+
+ if (layoutKind == xegpu::LayoutKind::Subgroup) {
+ sgDataValue = sgData[innerMostDim];
+ while ((sgDataValue <= bound) && (sgDataValue % ratio) != 0)
+ sgDataValue *= 2;
+ } else if (layoutKind == xegpu::LayoutKind::InstData) {
+ instDataValue = instData[innerMostDim];
+ const int innermostDimLaneLayout = laneLayout.empty()
+ ? uArch->getSubgroupSize()
+ : laneLayout[innerMostDim];
+ while ((instDataValue <= bound) &&
+ (instDataValue % (innermostDimLaneLayout * ratio) != 0))
+ instDataValue *= 2;
+ assert((bound % instDataValue) == 0 &&
+ "bound, instData, and laneLayout for innermost must be 2^n!");
+ } else if (layoutKind == xegpu::LayoutKind::Lane) {
+ laneDataValue = laneData[innerMostDim];
+ while ((laneDataValue <= bound) && (laneDataValue % ratio) != 0)
+ laneDataValue *= 2;
+ }
+
+ return consumerLayout.setDimData(innerMostDim, sgDataValue, instDataValue,
+ laneDataValue);
+}
+
/// Sets up the result layout for a bitcast operation.
/// When casting to a smaller bitwidth, adjusts the layout dimensions (sgData,
/// instData, or laneData) by multiplying by the bitwidth ratio to ensure the
@@ -1009,53 +1169,23 @@ xegpu::DistributeLayoutAttr xegpu::setupBitCastResultLayout(
ArrayRef<int64_t> srcShape = srcVecTy.getShape();
ArrayRef<int64_t> resShape = resVecTy.getShape();
- SmallVector<int64_t> sgData = consumerLayout.getEffectiveSgDataAsInt();
- SmallVector<int64_t> instData = consumerLayout.getEffectiveInstDataAsInt();
- SmallVector<int64_t> laneData = consumerLayout.getEffectiveLaneDataAsInt();
- SmallVector<int64_t> laneLayout =
- consumerLayout.getEffectiveLaneLayoutAsInt();
assert(consumerLayout.getRank() == static_cast<int64_t>(srcShape.size()) &&
"laneData must be available for all dimensions");
+
+ // Casting to same/larger element type: result has fewer (or equal) elements
+ // along the innermost dim, no adjustment needed.
+ if (srcElemTyBitWidth <= resElemTyBitWidth)
+ return consumerLayout;
+
+ // Casting to smaller element type: result has more elements along innermost
+ // dim. Adjust the innermost data field upward so the source layout can be
+ // recovered by dividing by bitWidthRatio.
size_t innerMostDim = srcShape.size() - 1;
- int64_t sgDataValue = -1;
- int64_t instDataValue = -1;
- int64_t laneDataValue = -1;
- if (srcElemTyBitWidth > resElemTyBitWidth) {
- // When casting to a smaller bitwidth, multiply the result layout
- // accordingly to ensure it can be divided by the ratio back to the
- // source layout.
- int bitWidthRatio = srcElemTyBitWidth / resElemTyBitWidth;
- if (layoutKind == xegpu::LayoutKind::Subgroup) {
- sgDataValue = sgData[innerMostDim];
- while ((sgDataValue <= resShape[innerMostDim]) &&
- (sgDataValue % bitWidthRatio) != 0)
- sgDataValue *= 2;
- } else if (layoutKind == xegpu::LayoutKind::InstData) {
- instDataValue = instData[innerMostDim];
- const int innermostDimLaneLayout = laneLayout.empty()
- ? uArch->getSubgroupSize()
- : laneLayout[innerMostDim];
- // Adjust instDataValue so it still fits within an instruction after
- // dividing by bitWidthRatio
- while ((instDataValue <= resShape[innerMostDim]) &&
- (instDataValue % (innermostDimLaneLayout * bitWidthRatio) != 0))
- instDataValue *= 2;
- assert((resShape[innerMostDim] % instDataValue) == 0 &&
- "resShape, instData, and lanelayout for innermost must be 2^n !");
- } else if (layoutKind == xegpu::LayoutKind::Lane) {
- laneDataValue = laneData[innerMostDim];
- while ((laneDataValue <= resShape[innerMostDim]) &&
- (laneDataValue % bitWidthRatio != 0))
- laneDataValue *= 2;
- }
- // Now set only instData and laneData, preserving sgData
- xegpu::DistributeLayoutAttr resLayout;
- resLayout = consumerLayout.setDimData(innerMostDim, sgDataValue,
- instDataValue, laneDataValue);
- return resLayout;
- }
- return consumerLayout;
+ int bitWidthRatio = srcElemTyBitWidth / resElemTyBitWidth;
+ return adjustInnermostDimForDivisibility(consumerLayout, layoutKind,
+ innerMostDim, bitWidthRatio,
+ resShape[innerMostDim], uArch);
}
/// Sets up the result layout for an interleave operation to ensure the source
@@ -1077,52 +1207,18 @@ xegpu::DistributeLayoutAttr xegpu::setupInterleaveResultLayout(
xegpu::LayoutKind layoutKind, VectorType srcVecTy, VectorType resVecTy,
DistributeLayoutAttr consumerLayout, const xegpu::uArch::uArch *uArch) {
- ArrayRef<int64_t> srcShape = srcVecTy.getShape();
- SmallVector<int64_t> sgData = consumerLayout.getEffectiveSgDataAsInt();
- SmallVector<int64_t> instData = consumerLayout.getEffectiveInstDataAsInt();
- SmallVector<int64_t> laneData = consumerLayout.getEffectiveLaneDataAsInt();
- SmallVector<int64_t> laneLayout =
- consumerLayout.getEffectiveLaneLayoutAsInt();
-
- assert(consumerLayout.getRank() == static_cast<int64_t>(srcShape.size()) &&
+ ArrayRef<int64_t> resShape = resVecTy.getShape();
+ assert(consumerLayout.getRank() == static_cast<int64_t>(resShape.size()) &&
"consumer layout rank must match source shape rank");
- const size_t innerMostDim = srcShape.size() - 1;
- int64_t sgDataValue = -1;
- int64_t instDataValue = -1;
- int64_t laneDataValue = -1;
- // Interleave doubles the innermost dimension (ratio = 2)
+ // Interleave doubles the innermost dimension (ratio = 2). Adjust the
+ // innermost data field so the source layout can be recovered by dividing
+ // by 2.
+ const size_t innerMostDim = resShape.size() - 1;
constexpr int ratio = 2;
-
- if (layoutKind == xegpu::LayoutKind::Subgroup) {
- sgDataValue = sgData[innerMostDim];
- // Ensure sgDataValue is divisible by ratio so source sgData can be inferred
- while ((sgDataValue <= srcShape[innerMostDim]) &&
- (sgDataValue % ratio != 0))
- sgDataValue *= ratio;
- } else if (layoutKind == xegpu::LayoutKind::InstData) {
- instDataValue = instData[innerMostDim];
- const int innermostDimLaneLayout = laneLayout.empty()
- ? uArch->getSubgroupSize()
- : laneLayout[innerMostDim];
- // Adjust instDataValue so it can be divided by (innermostDimLaneLayout *
- // ratio) when inferring the source layout
- while ((instDataValue <= srcShape[innerMostDim]) &&
- (instDataValue % (innermostDimLaneLayout * ratio) != 0))
- instDataValue *= ratio;
- assert((srcShape[innerMostDim] % instDataValue) == 0 &&
- "srcShape, instData, and laneLayout for innermost must be 2^n!");
- } else if (layoutKind == xegpu::LayoutKind::Lane) {
- laneDataValue = laneData[innerMostDim];
- // Ensure laneDataValue is at least 2 and divisible by ratio
- // so that source laneData = laneDataValue/2 is valid
- while ((laneDataValue <= srcShape[innerMostDim]) &&
- (laneDataValue % ratio != 0))
- laneDataValue *= ratio;
- }
-
- return consumerLayout.setDimData(innerMostDim, sgDataValue, instDataValue,
- laneDataValue);
+ return adjustInnermostDimForDivisibility(consumerLayout, layoutKind,
+ innerMostDim, ratio,
+ resShape[innerMostDim], uArch);
}
/// Sets up the result layout for an insert strided slice operation.
@@ -1141,21 +1237,15 @@ xegpu::DistributeLayoutAttr xegpu::setupInsertStridedSliceResultLayout(
SmallVector<int64_t> consumerLaneLayout =
consumerLayout.getEffectiveLaneLayoutAsInt();
ArrayRef<int64_t> srcShape = srcVectorTy.getShape();
- int64_t instDataValue = -1;
int64_t laneDataValue = -1;
requiredResLayout = consumerLayout;
int srcRank = srcShape.size();
- if (layoutKind == xegpu::LayoutKind::Subgroup) {
+ if (layoutKind == xegpu::LayoutKind::Subgroup ||
+ layoutKind == xegpu::LayoutKind::InstData) {
assert(true &&
"subgroup layout assignment not supported for insertStridedSlice.");
- } else if (layoutKind == xegpu::LayoutKind::InstData) {
- for (int dim = 0; dim < srcRank; dim++) {
- instDataValue = std::min(srcShape[dim], consumerInstData[dim]);
- requiredResLayout =
- requiredResLayout.setDimData(dim, -1, instDataValue, -1);
- }
} else if (layoutKind == xegpu::LayoutKind::Lane) {
for (int dim = 0; dim < srcRank; dim++) {
assert(srcShape[dim] % consumerLaneLayout[dim] == 0 &&
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
index 6a37ae6502b2d..9d7b99c1e95a7 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
@@ -684,12 +684,18 @@ void LayoutInfoPropagation::visitVectorMultiReductionOp(
auto requiredResLayoutAttr = xegpu::setupMultiReductionResultLayout(
layoutKind, sourceTy, consumerLayoutAttr, reductionDims, numSg, uArch);
+ llvm::dbgs() << "[DEBUG visitMultiRed] op=" << *reduction
+ << " consumer=" << consumerLayoutAttr
+ << " required=" << requiredResLayoutAttr << "\n";
+
xegpu::setTemporaryLayout(reduction->getResult(0), requiredResLayoutAttr);
// derive the source layout from the dominant layout and reduction dims
auto srcLayoutAttr = xegpu::inferMultiReductionSourceLayout(
requiredResLayoutAttr, reductionDims);
+ llvm::dbgs() << "[DEBUG visitMultiRed] srcLayout=" << srcLayoutAttr << "\n";
+
propagateIfChanged(operands[0], operands[0]->meet(LayoutInfo(srcLayoutAttr)));
// Accumulator should have the same layout as the result.
propagateIfChanged(operands[1],
@@ -758,6 +764,10 @@ void LayoutInfoPropagation::visitShapeCastOp(
xegpu::DistributeLayoutAttr srcLayoutAttr =
xegpu::inferShapeCastSourceLayout(resultLayoutAttr, resShape, srcShape);
+ llvm::dbgs() << "[DEBUG visitShapeCast] op=" << *shapeCast
+ << " consumer=" << resultLayoutAttr
+ << " src=" << srcLayoutAttr << "\n";
+
propagateIfChanged(operands[0], operands[0]->meet(LayoutInfo(srcLayoutAttr)));
}
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
index fc7c3b170dd3b..55bfc317f89fe 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
@@ -969,8 +969,6 @@ struct UnrollConvertLayoutOp : public UnrollPattern<xegpu::ConvertLayoutOp> {
if (valType.isIntOrFloat()) {
rewriter.replaceOp(op, op.getSource());
- assert(!inputLayout.dropInstData() && !targetLayout.dropInstData() &&
- "unexpected layout attributes for scalar type");
return success();
}
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
index 5f493c8ca0df6..98703c1beb9d3 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
@@ -220,9 +220,9 @@ func.func @scatter_ops_chunksize_slice(%src: memref<1024xf32>) {
gpu.module @test {
// CHECK-LABEL: func.func @insert_strided_slice_inst_data_no_packing(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x32xf32>) {
-// CHECK: %[[CST_SMALL:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [4, 16]>} dense<1.000000e+00> : vector<4x16xf32>
-// CHECK: %[[CST_LARGE:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [4, 16]>} dense<0.000000e+00> : vector<8x32xf32>
-// CHECK: %[[INSERT:.*]] = vector.insert_strided_slice %[[CST_SMALL]], %[[CST_LARGE]] {layout_result_0 = #xegpu.layout<inst_data = [4, 16]>, offsets = [0, 0], strides = [1, 1]} : vector<4x16xf32> into vector<8x32xf32>
+// CHECK: %[[CST_SMALL:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} dense<1.000000e+00> : vector<4x16xf32>
+// CHECK: %[[CST_LARGE:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} dense<0.000000e+00> : vector<8x32xf32>
+// CHECK: %[[INSERT:.*]] = vector.insert_strided_slice %[[CST_SMALL]], %[[CST_LARGE]] {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>, offsets = [0, 0], strides = [1, 1]} : vector<4x16xf32> into vector<8x32xf32>
// CHECK: %[[TDESC:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<8x32xf32> -> !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16]>>
// CHECK: xegpu.store_nd %[[INSERT]], %[[TDESC]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16]>}> : vector<8x32xf32>, !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16]>>
func.func @insert_strided_slice_inst_data_no_packing(%arg0: memref<8x32xf32>) {
@@ -240,9 +240,9 @@ func.func @insert_strided_slice_inst_data_no_packing(%arg0: memref<8x32xf32>) {
gpu.module @test {
// CHECK-LABEL: func.func @insert_strided_slice_inst_data_with_packing(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x64xi8>) {
-// CHECK: %[[CST_SMALL:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [4, 64]>} dense<1> : vector<4x64xi8>
-// CHECK: %[[CST_LARGE:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [4, 64]>} dense<0> : vector<8x64xi8>
-// CHECK: %[[INSERT:.*]] = vector.insert_strided_slice %[[CST_SMALL]], %[[CST_LARGE]] {layout_result_0 = #xegpu.layout<inst_data = [4, 64]>, offsets = [0, 0], strides = [1, 1]} : vector<4x64xi8> into vector<8x64xi8>
+// CHECK: %[[CST_SMALL:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 64]>} dense<1> : vector<4x64xi8>
+// CHECK: %[[CST_LARGE:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 64]>} dense<0> : vector<8x64xi8>
+// CHECK: %[[INSERT:.*]] = vector.insert_strided_slice %[[CST_SMALL]], %[[CST_LARGE]] {layout_result_0 = #xegpu.layout<inst_data = [8, 64]>, offsets = [0, 0], strides = [1, 1]} : vector<4x64xi8> into vector<8x64xi8>
func.func @insert_strided_slice_inst_data_with_packing(%arg0: memref<8x64xi8>) {
%c0 = arith.constant 0 : index
%cst_small = arith.constant dense<1> : vector<4x64xi8>
@@ -258,11 +258,11 @@ func.func @insert_strided_slice_inst_data_with_packing(%arg0: memref<8x64xi8>) {
gpu.module @test {
// CHECK-LABEL: func.func @vector_shape_cast_expand_non_unit_dims(
// CHECK: %[[LOAD:.*]] = xegpu.load %arg0[%[[STEP:.*]]], %[[CST:.*]] <{layout = #xegpu.layout<inst_data = [16]>}> : memref<1024xf16>, vector<1024xindex>, vector<1024xi1> -> vector<1024xf16>
-// CHECK: %[[CAST:.*]] = vector.shape_cast %[[LOAD]] {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 16]>} : vector<1024xf16> to vector<8x8x16xf16>
-// CHECK: %[[CST_0:.*]] = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 16]>, dims = [0]>} dense<0.000000e+00> : vector<8x16xf16>
-// CHECK: %[[CST_1:.*]] = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 16]>, dims = [0]>} dense<0.000000e+00> : vector<16xf16>
-// CHECK: %[[REDUCE_0:.*]] = vector.multi_reduction <add>, %[[CAST]], %[[CST_0]] {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 16]>, dims = [0]>} [0] : vector<8x8x16xf16> to vector<8x16xf16>
-// CHECK: %[[REDUCE_1:.*]] = vector.multi_reduction <add>, %[[REDUCE_0]], %[[CST_1]] {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 16]>, dims = [0]>} [0] : vector<8x16xf16> to vector<16xf16>
+// CHECK: %[[CAST:.*]] = vector.shape_cast %[[LOAD]] {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 16], lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>} : vector<1024xf16> to vector<8x8x16xf16>
+// CHECK: %[[CST_0:.*]] = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 16], lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>, dims = [0]>} dense<0.000000e+00> : vector<8x16xf16>
+// CHECK: %[[CST_1:.*]] = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 16], lane_layout = [1, 16], lane_data = [1, 1]>, dims = [0]>} dense<0.000000e+00> : vector<16xf16>
+// CHECK: %[[REDUCE_0:.*]] = vector.multi_reduction <add>, %[[CAST]], %[[CST_0]] {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 16], lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>, dims = [0]>} [0] : vector<8x8x16xf16> to vector<8x16xf16>
+// CHECK: %[[REDUCE_1:.*]] = vector.multi_reduction <add>, %[[REDUCE_0]], %[[CST_1]] {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 16], lane_layout = [1, 16], lane_data = [1, 1]>, dims = [0]>} [0] : vector<8x16xf16> to vector<16xf16>
func.func @vector_shape_cast_expand_non_unit_dims(%arg0: memref<1024xf16>, %arg1: memref<16xf16>) {
%cst = arith.constant dense<true> : vector<1024xi1>
%0 = vector.step : vector<1024xindex>
@@ -282,7 +282,7 @@ func.func @vector_shape_cast_expand_non_unit_dims(%arg0: memref<1024xf16>, %arg1
// -----
gpu.module @test {
// CHECK-LABEL: func.func @vector_2d_reduction_with_fractional_subgroup_size(
-// CHECK: %[[ReduceVal:.*]] = vector.multi_reduction <add>, %[[Val:.*]], %[[CST:.*]] {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 1]>, dims = [1, 2]>} [1, 2] : vector<1x16x1xf16> to vector<1xf16>
+// CHECK: %[[ReduceVal:.*]] = vector.multi_reduction <add>, %[[Val:.*]], %[[CST:.*]] {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 1], lane_layout = [1, 1, 1], lane_data = [1, 1, 1]>, dims = [1, 2]>} [1, 2] : vector<1x16x1xf16> to vector<1xf16>
func.func @vector_2d_reduction_with_fractional_subgroup_size(%arg0: memref<1024xf16>, %arg1: memref<16xf16>) {
%cst = arith.constant dense<true> : vector<16xi1>
%0 = vector.step : vector<16xindex>
@@ -300,7 +300,7 @@ func.func @vector_2d_reduction_with_fractional_subgroup_size(%arg0: memref<1024x
// -----
gpu.module @test {
// CHECK-LABEL: func.func @vector_2d_reduction_with_fractional_subgroup_size_1x4x1(
-// CHECK: %[[ReduceVal:.*]] = vector.multi_reduction <add>, %[[Val:.*]], %[[CST:.*]] {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 4]>, dims = [1, 2]>} [1, 2] : vector<1x16x4xf16> to vector<1xf16>
+// CHECK: %[[ReduceVal:.*]] = vector.multi_reduction <add>, %[[Val:.*]], %[[CST:.*]] {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 4], lane_layout = [1, 1, 4], lane_data = [1, 1, 1]>, dims = [1, 2]>} [1, 2] : vector<1x16x4xf16> to vector<1xf16>
func.func @vector_2d_reduction_with_fractional_subgroup_size_1x4x1(%arg0: memref<1024xf16>, %arg1: memref<16xf16>) {
%cst = arith.constant dense<true> : vector<64xi1>
%0 = vector.step : vector<64xindex>
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout.mlir b/mlir/test/Dialect/XeGPU/propagate-layout.mlir
index bad956d45d186..14579606bcbed 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout.mlir
@@ -656,20 +656,20 @@ gpu.module @test{
// -----
gpu.module @test {
// CHECK-LABEL: func.func @vector_shape_cast_expand_non_unit_dims(
-// CHECK: %[[LOAD:.*]] = xegpu.load %arg0[%[[STEP:.*]]], %[[CST:.*]] <{layout = #xegpu.layout<lane_layout = [16], lane_data = [1]>}> : memref<1024xf16>, vector<1024xindex>, vector<1024xi1> -> vector<1024xf16>
-// CHECK: %[[CAST:.*]] = vector.shape_cast %[[LOAD]] {layout_result_0 = #xegpu.layout<lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>} : vector<1024xf16> to vector<8x8x16xf16>
+// CHECK: %[[LOAD:.*]] = xegpu.load %arg0[%[[STEP:.*]]], %[[CST:.*]] <{layout = #xegpu.layout<lane_layout = [16], lane_data = [1]>}> : memref<128xf16>, vector<128xindex>, vector<128xi1> -> vector<128xf16>
+// CHECK: %[[CAST:.*]] = vector.shape_cast %[[LOAD]] {layout_result_0 = #xegpu.layout<lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>} : vector<128xf16> to vector<1x8x16xf16>
// CHECK: %[[CST_0:.*]] = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>, dims = [0]>} dense<0.000000e+00> : vector<8x16xf16>
// CHECK: %[[CST_1:.*]] = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>, dims = [0]>} dense<0.000000e+00> : vector<16xf16>
-// CHECK: %[[REDUCE_0:.*]] = vector.multi_reduction <add>, %[[CAST]], %[[CST_0]] {layout_result_0 = #xegpu.slice<#xegpu.layout<lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>, dims = [0]>} [0] : vector<8x8x16xf16> to vector<8x16xf16>
+// CHECK: %[[REDUCE_0:.*]] = vector.multi_reduction <add>, %[[CAST]], %[[CST_0]] {layout_result_0 = #xegpu.slice<#xegpu.layout<lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>, dims = [0]>} [0] : vector<1x8x16xf16> to vector<8x16xf16>
// CHECK: %[[REDUCE_1:.*]] = vector.multi_reduction <add>, %[[REDUCE_0]], %[[CST_1]] {layout_result_0 = #xegpu.slice<#xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>, dims = [0]>} [0] : vector<8x16xf16> to vector<16xf16>
-func.func @vector_shape_cast_expand_non_unit_dims(%arg0: memref<1024xf16>, %arg1: memref<16xf16>) {
- %cst = arith.constant dense<true> : vector<1024xi1>
- %0 = vector.step : vector<1024xindex>
- %1 = xegpu.load %arg0[%0], %cst : memref<1024xf16>, vector<1024xindex>, vector<1024xi1> -> vector<1024xf16>
- %2 = vector.shape_cast %1 : vector<1024xf16> to vector<8x8x16xf16>
+func.func @vector_shape_cast_expand_non_unit_dims(%arg0: memref<128xf16>, %arg1: memref<16xf16>) {
+ %cst = arith.constant dense<true> : vector<128xi1>
+ %0 = vector.step : vector<128xindex>
+ %1 = xegpu.load %arg0[%0], %cst : memref<128xf16>, vector<128xindex>, vector<128xi1> -> vector<128xf16>
+ %2 = vector.shape_cast %1 : vector<128xf16> to vector<1x8x16xf16>
%cst_0 = arith.constant dense<0.000000e+00> : vector<8x16xf16>
%cst_1 = arith.constant dense<0.000000e+00> : vector<16xf16>
- %3 = vector.multi_reduction <add>, %2, %cst_0 [0] : vector<8x8x16xf16> to vector<8x16xf16>
+ %3 = vector.multi_reduction <add>, %2, %cst_0 [0] : vector<1x8x16xf16> to vector<8x16xf16>
%4 = vector.multi_reduction <add>, %3, %cst_1 [0] : vector<8x16xf16> to vector<16xf16>
%cst_2 = arith.constant dense<true> : vector<16xi1>
%cst_3 = arith.constant dense<1> : vector<16xindex>
>From ece64f9935eefd0b6dd3aad38ebd04b824c55f0b Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Mon, 1 Jun 2026 22:38:31 +0000
Subject: [PATCH 02/20] remove isChunkedStore/Load handling in the
setUpGenericLoad/store rule
---
.../XeGPU/Transforms/XeGPULayoutImpl.h | 3 +-
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 127 ++++++++----------
.../XeGPU/Transforms/XeGPUPropagateLayout.cpp | 10 +-
.../XeGPU/propagate-layout-inst-data.mlir | 107 +++++----------
mlir/test/Dialect/XeGPU/propagate-layout.mlir | 61 +++------
5 files changed, 118 insertions(+), 190 deletions(-)
diff --git a/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h b/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
index 94d1d5aecbe60..253dcb18af67c 100644
--- a/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
+++ b/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
@@ -216,7 +216,7 @@ setupLoadGatherAnchorLayout(LayoutKind layoutKind, VectorType vectorTy,
/// Sets up the anchor layout for load matrix operation.
DistributeLayoutAttr
setupLoadMatrixAnchorLayout(LayoutKind layoutKind, VectorType vectorTy,
- DistributeLayoutAttr consumerLayout,
+ int chunkSize, DistributeLayoutAttr consumerLayout,
const uArch::uArch *uArch);
/// Sets up the anchor layout for a store scatter operation.
@@ -228,6 +228,7 @@ DistributeLayoutAttr setupStoreScatterAnchorLayout(LayoutKind layoutKind,
/// Sets up the anchor layout for a store matrix operation.
DistributeLayoutAttr setupStoreMatrixAnchorLayout(LayoutKind layoutKind,
VectorType vectorTy,
+ int chunkSize,
const uArch::uArch *uArch);
/// Sets up the anchor layouts for a dpas operands (A, B, and C/D).
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index 96c08f9880a72..427515ea75b83 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -833,10 +833,12 @@ computeReductionLaneLayoutAndData(ArrayRef<int64_t> srcShape,
int64_t maxReduceVectorSize) {
int srcRank = srcShape.size();
SmallVector<int64_t> laneLayout(srcRank, 1), laneData(srcRank, 1);
- llvm::dbgs() << "[DEBUG computeReductionLaneLayoutAndData] srcRank=" << srcRank
- << " srcShape.size()=" << srcShape.size() << " srcShape=[";
+ llvm::dbgs() << "[DEBUG computeReductionLaneLayoutAndData] srcRank="
+ << srcRank << " srcShape.size()=" << srcShape.size()
+ << " srcShape=[";
for (size_t i = 0; i < srcShape.size(); ++i) {
- if (i > 0) llvm::dbgs() << ", ";
+ if (i > 0)
+ llvm::dbgs() << ", ";
llvm::dbgs() << srcShape[i];
}
llvm::dbgs() << "]\n";
@@ -1019,10 +1021,13 @@ xegpu::SliceAttr xegpu::setupMultiReductionResultLayout(
} else if (layoutKind == xegpu::LayoutKind::InstData) {
xegpu::SliceAttr consumerSliceLayout =
dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
- auto reductionDimsOverrideConsumer = consumerSliceLayout?
- SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef()): reductionDims;
+ auto reductionDimsOverrideConsumer =
+ consumerSliceLayout
+ ? SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef())
+ : reductionDims;
auto [laneLayout, laneData] = computeReductionLaneLayoutAndData(
- srcShape, reductionDimsOverrideConsumer, subgroupSize, maxReduceVectorSize);
+ srcShape, reductionDimsOverrideConsumer, subgroupSize,
+ maxReduceVectorSize);
// inst_data is the per-instruction data, i.e. the element-wise product of
// lane_layout and lane_data.
SmallVector<int64_t> instData(srcRank);
@@ -1042,10 +1047,13 @@ xegpu::SliceAttr xegpu::setupMultiReductionResultLayout(
"dimensions are unit dimensions");
xegpu::SliceAttr consumerSliceLayout =
dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
- auto reductionDimsOverrideConsumer = consumerSliceLayout?
- SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef()): reductionDims;
+ auto reductionDimsOverrideConsumer =
+ consumerSliceLayout
+ ? SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef())
+ : reductionDims;
auto [laneLayout, laneData] = computeReductionLaneLayoutAndData(
- srcShape, reductionDimsOverrideConsumer, subgroupSize, maxReduceVectorSize);
+ srcShape, reductionDimsOverrideConsumer, subgroupSize,
+ maxReduceVectorSize);
srcLayout = xegpu::LayoutAttr::get(context, toInt32Attr(laneLayout),
toInt32Attr(laneData));
}
@@ -1273,8 +1281,8 @@ xegpu::DistributeLayoutAttr xegpu::setupInsertStridedSliceResultLayout(
/// lane_data={1,min(consumer, maxLaneLoadSize)}
static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
xegpu::LayoutKind layoutKind, mlir::MLIRContext *context,
- xegpu::DistributeLayoutAttr consumerLayout, bool isChunkedLoad,
- int maxChunkSize, ArrayRef<int64_t> resShape, int subgroupSize) {
+ xegpu::DistributeLayoutAttr consumerLayout, int maxChunkSize,
+ ArrayRef<int64_t> resShape, int subgroupSize) {
if (layoutKind == xegpu::LayoutKind::Subgroup)
return consumerLayout;
@@ -1288,31 +1296,16 @@ static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
SmallVector<int> laneLayout(resShape.size(), 1);
SmallVector<int> laneData(resShape.size(), 1);
- if (!isChunkedLoad) {
- if (layoutKind == xegpu::LayoutKind::InstData) {
- instData.back() = std::min(static_cast<int>(consumerInstData.back()),
- maxChunkSize * subgroupSize);
- return xegpu::LayoutAttr::get(context, instData);
- } else if (layoutKind == xegpu::LayoutKind::Lane) {
- laneData.back() =
- std::min(static_cast<int>(consumerLaneData.back()), maxChunkSize);
- laneLayout.back() = std::min(static_cast<int64_t>(subgroupSize),
- resShape.back() / laneData.back());
- return xegpu::LayoutAttr::get(context, laneLayout, laneData);
- }
- } else {
- assert(resShape.size() == 2 && "Chunked Store must access 2D tensor tile.");
- if (layoutKind == xegpu::LayoutKind::InstData) {
- instData[0] = subgroupSize;
- instData[1] =
- std::min(static_cast<int>(consumerInstData[1]), maxChunkSize);
- return xegpu::LayoutAttr::get(context, instData);
- } else if (layoutKind == xegpu::LayoutKind::Lane) {
- laneLayout[0] = subgroupSize;
- laneData[1] =
- std::min(static_cast<int>(consumerLaneData[1]), maxChunkSize);
- return xegpu::LayoutAttr::get(context, laneLayout, laneData);
- }
+ if (layoutKind == xegpu::LayoutKind::InstData) {
+ instData.back() = std::min(static_cast<int>(consumerInstData.back()),
+ maxChunkSize * subgroupSize);
+ return xegpu::LayoutAttr::get(context, instData);
+ } else if (layoutKind == xegpu::LayoutKind::Lane) {
+ laneData.back() =
+ std::min(static_cast<int>(consumerLaneData.back()), maxChunkSize);
+ laneLayout.back() = std::min(static_cast<int64_t>(subgroupSize),
+ resShape.back() / laneData.back());
+ return xegpu::LayoutAttr::get(context, laneLayout, laneData);
}
return nullptr;
}
@@ -1333,15 +1326,14 @@ xegpu::DistributeLayoutAttr xegpu::setupLoadGatherAnchorLayout(
int maxChunkSize = uArchInstruction->getMaxLaneLoadSize(elemBitWidth);
return setupGenericLoadAnchorLayout(layoutKind, context, consumerLayout,
- (chunkSize > 1), maxChunkSize, resShape,
- subgroupSize);
+ maxChunkSize, resShape, subgroupSize);
}
/// Sets up the anchor layout for load matrix operation.
/// TODO: enhance load matrix to indicate lowering to chunked load or not.
xegpu::DistributeLayoutAttr
xegpu::setupLoadMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
- VectorType resVecTy,
+ VectorType resVecTy, int chunkSize,
xegpu::DistributeLayoutAttr consumerLayout,
const xegpu::uArch::uArch *uArch) {
@@ -1353,10 +1345,10 @@ xegpu::setupLoadMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
const auto *uArchInstruction =
dyn_cast<xegpu::uArch::LoadGatherInstructionInterface>(
uArch->getInstruction(xegpu::uArch::InstructionKind::LoadGather));
- int maxChunkSize = uArchInstruction->getMaxLaneLoadSize(elemBitWidth);
+ int maxChunkSize =
+ std::min(uArchInstruction->getMaxLaneLoadSize(elemBitWidth), chunkSize);
return setupGenericLoadAnchorLayout(layoutKind, context, consumerLayout,
- false, maxChunkSize, resShape,
- subgroupSize);
+ maxChunkSize, resShape, subgroupSize);
}
/// Sets up the anchor layout for store scatter and store matrix operation.
@@ -1372,9 +1364,8 @@ xegpu::setupLoadMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
/// lane_data={1,min(srcVec, maxLaneStoreSize)}
static xegpu::DistributeLayoutAttr
setupGenericStoreAnchorLayout(xegpu::LayoutKind layoutKind,
- mlir::MLIRContext *context, bool isChunkedStore,
- int maxChunkSize, ArrayRef<int64_t> srcShape,
- int subgroupSize) {
+ mlir::MLIRContext *context, int maxChunkSize,
+ ArrayRef<int64_t> srcShape, int subgroupSize) {
int srcShapeSize = srcShape.size();
SmallVector<int> instData(srcShapeSize, 1);
@@ -1386,28 +1377,16 @@ setupGenericStoreAnchorLayout(xegpu::LayoutKind layoutKind,
"subgroup layout assignment not supported for storeScatter.");
return nullptr;
}
-
- if (!isChunkedStore) {
- if (layoutKind == xegpu::LayoutKind::InstData) {
- instData[srcShapeSize - 1] =
- std::min(subgroupSize, static_cast<int>(srcShape.back()));
- return xegpu::LayoutAttr::get(context, instData);
- } else if (layoutKind == xegpu::LayoutKind::Lane) {
- laneLayout[srcShapeSize - 1] =
- std::min(subgroupSize, static_cast<int>(srcShape.back()));
- return xegpu::LayoutAttr::get(context, laneLayout, laneData);
- }
- } else {
- assert(srcShapeSize == 2 && "Chunked Store must access 2D tensor tile.");
- if (layoutKind == xegpu::LayoutKind::InstData) {
- instData[0] = subgroupSize;
- instData[1] = std::min(static_cast<int>(srcShape[1]), maxChunkSize);
- return xegpu::LayoutAttr::get(context, instData);
- } else if (layoutKind == xegpu::LayoutKind::Lane) {
- laneLayout[0] = subgroupSize;
- laneData[1] = std::min(static_cast<int>(srcShape[1]), maxChunkSize);
- return xegpu::LayoutAttr::get(context, laneLayout, laneData);
- }
+ if (layoutKind == xegpu::LayoutKind::InstData) {
+ laneLayout[srcShapeSize - 1] =
+ std::min(subgroupSize, static_cast<int>(srcShape.back()));
+ laneData[srcShapeSize - 1] =
+ std::min(maxChunkSize, static_cast<int>(srcShape.back()));
+ return xegpu::LayoutAttr::get(context, instData);
+ } else if (layoutKind == xegpu::LayoutKind::Lane) {
+ laneLayout[srcShapeSize - 1] =
+ std::min(subgroupSize, static_cast<int>(srcShape.back()));
+ return xegpu::LayoutAttr::get(context, laneLayout, laneData);
}
return nullptr;
}
@@ -1426,15 +1405,16 @@ xegpu::setupStoreScatterAnchorLayout(xegpu::LayoutKind layoutKind,
const auto *uArchInstruction =
dyn_cast<xegpu::uArch::StoreScatterInstructionInterface>(
uArch->getInstruction(xegpu::uArch::InstructionKind::StoreScatter));
- int maxChunkSize = uArchInstruction->getMaxLaneStoreSize(elemBitWidth);
- return setupGenericStoreAnchorLayout(layoutKind, context, (chunkSize > 1),
- maxChunkSize, srcShape, subgroupSize);
+ int maxChunkSize =
+ std::min(uArchInstruction->getMaxLaneStoreSize(elemBitWidth), chunkSize);
+ return setupGenericStoreAnchorLayout(layoutKind, context, maxChunkSize,
+ srcShape, subgroupSize);
}
/// Sets up the anchor layout for a store matrix operation.
xegpu::DistributeLayoutAttr
xegpu::setupStoreMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
- VectorType srcVecTy,
+ VectorType srcVecTy, int chunkSize,
const xegpu::uArch::uArch *uArch) {
const int subgroupSize = uArch->getSubgroupSize();
@@ -1445,9 +1425,10 @@ xegpu::setupStoreMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
const auto *uArchInstruction =
dyn_cast<xegpu::uArch::StoreScatterInstructionInterface>(
uArch->getInstruction(xegpu::uArch::InstructionKind::StoreScatter));
- int maxChunkSize = uArchInstruction->getMaxLaneStoreSize(elemBitWidth);
+ int maxChunkSize =
+ std::min(uArchInstruction->getMaxLaneStoreSize(elemBitWidth), chunkSize);
- return setupGenericStoreAnchorLayout(layoutKind, context, false, maxChunkSize,
+ return setupGenericStoreAnchorLayout(layoutKind, context, maxChunkSize,
srcShape, subgroupSize);
}
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
index 9d7b99c1e95a7..c1ad3c4ee8e2e 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
@@ -1341,8 +1341,10 @@ void LayoutInfoPropagation::visitLoadMatrixOp(
const uArch *uArch = getUArch(getChipStr(loadMatrixOp).value_or(""));
if (!uArch)
return;
+ int chunkSize =
+ 1; // placeHolder for future use when LoadMatrix supports coalescing
auto requiredAnchorLayoutAttr = xegpu::setupLoadMatrixAnchorLayout(
- layoutKind, resVecTy, consumerLayoutAttr, uArch);
+ layoutKind, resVecTy, chunkSize, consumerLayoutAttr, uArch);
loadMatrixOp.setLayoutAttr(requiredAnchorLayoutAttr);
}
}
@@ -1360,8 +1362,10 @@ void LayoutInfoPropagation::visitStoreMatrixOp(
const uArch *uArch = getUArch(getChipStr(storeMatrix).value_or(""));
if (!uArch)
return;
- auto requiredAnchorLayoutAttr =
- xegpu::setupStoreMatrixAnchorLayout(layoutKind, srcVecTy, uArch);
+ int chunkSize =
+ 1; // placeHolder for future use when StoreMatrix supports coalescing
+ auto requiredAnchorLayoutAttr = xegpu::setupStoreMatrixAnchorLayout(
+ layoutKind, srcVecTy, chunkSize, uArch);
storeMatrix.setLayoutAttr(requiredAnchorLayoutAttr);
layout = LayoutInfo(requiredAnchorLayoutAttr);
}
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
index 98703c1beb9d3..3ea06d52690dc 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
@@ -115,26 +115,6 @@ gpu.module @test_kernel {
}
}
-// -----
-gpu.module @test {
-// CHECK-LABEL: func.func @scatter_ops_chunksize(
-// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<256xf16>) {
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<true> : vector<16xi1>
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<12> : vector<16xindex>
-// CHECK: %{{.*}} = xegpu.load %[[ARG0]][%{{.*}}], %{{.*}} <{chunk_size = 8 : i64, layout = #xegpu.layout<inst_data = [16, 8]>}>
-// CHECK-SAME: memref<256xf16>, vector<16xindex>, vector<16xi1> -> vector<16x8xf16>
-// CHECK: xegpu.store %0, %[[ARG0]][%{{.*}}], %{{.*}} <{chunk_size = 8 : i64, layout = #xegpu.layout<inst_data = [16, 8]>}> : vector<16x8xf16>, memref<256xf16>, vector<16xindex>, vector<16xi1>
-func.func @scatter_ops_chunksize(%src: memref<256xf16>) {
- %1 = arith.constant dense<1>: vector<16xi1>
- %offset = arith.constant dense<12> : vector<16xindex>
- %3 = xegpu.load %src[%offset], %1 <{chunk_size=8}>
- : memref<256xf16>, vector<16xindex>, vector<16xi1> -> vector<16x8xf16>
- xegpu.store %3, %src[%offset], %1 <{chunk_size=8}>
- : vector<16x8xf16>, memref<256xf16>, vector<16xindex>, vector<16xi1>
- return
-}
-}
-
// -----
gpu.module @test {
// CHECK-LABEL: func.func @store_matrix(
@@ -148,45 +128,24 @@ func.func @store_matrix(%arg0: !xegpu.mem_desc<16x64xf16>) {
}
}
-// -----
-gpu.module @test {
-// CHECK-LABEL: func.func @scatter_ops_chunksize_excessive(
-// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<1024xf32>) {
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<true> : vector<16xi1>
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<12> : vector<16xindex>
-// CHECK: %{{.*}} = xegpu.load %[[ARG0]][%{{.*}}], %{{.*}} <{chunk_size = 32 : i64, layout = #xegpu.layout<inst_data = [16, 16]>}> :
-// CHECK-SAME: memref<1024xf32>, vector<16xindex>, vector<16xi1> -> vector<16x32xf32>
-// CHECK: xegpu.store %0, %[[ARG0]][%{{.*}}], %{{.*}} <{chunk_size = 32 : i64, layout = #xegpu.layout<inst_data = [16, 16]>}> :
-// CHECK-SAME: vector<16x32xf32>, memref<1024xf32>, vector<16xindex>, vector<16xi1>
-func.func @scatter_ops_chunksize_excessive(%src: memref<1024xf32>) {
- %1 = arith.constant dense<1>: vector<16xi1>
- %offset = arith.constant dense<12> : vector<16xindex>
- %3 = xegpu.load %src[%offset], %1 <{chunk_size=32}>
- : memref<1024xf32>, vector<16xindex>, vector<16xi1> -> vector<16x32xf32>
- xegpu.store %3, %src[%offset], %1 <{chunk_size=32}>
- : vector<16x32xf32>, memref<1024xf32>, vector<16xindex>, vector<16xi1>
- return
-}
-}
-
// -----
gpu.module @test {
-// CHECK-LABEL: func.func @scatter_ops_chunksize_excessive_anchor(
-// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<1024xf32>) {
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<true> : vector<16xi1>
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<12> : vector<16xindex>
-// CHECK: %{{.*}} = xegpu.load %[[ARG0]][%{{.*}}], %{{.*}} <{chunk_size = 32 : i64, layout = #xegpu.layout<inst_data = [16, 16]>}> :
-// CHECK-SAME: memref<1024xf32>, vector<16xindex>, vector<16xi1> -> vector<16x32xf32>
-// CHECK: xegpu.store %0, %[[ARG0]][%{{.*}}], %{{.*}} <{chunk_size = 32 : i64, layout = #xegpu.layout<inst_data = [16, 16]>}> :
-// CHECK-SAME: vector<16x32xf32>, memref<1024xf32>, vector<16xindex>, vector<16xi1>
-func.func @scatter_ops_chunksize_excessive_anchor(%src: memref<1024xf32>) {
- %1 = arith.constant dense<1>: vector<16xi1>
- %offset = arith.constant dense<12> : vector<16xindex>
- %3 = xegpu.load %src[%offset], %1 <{chunk_size=32}>
- : memref<1024xf32>, vector<16xindex>, vector<16xi1> -> vector<16x32xf32>
- xegpu.store %3, %src[%offset], %1 <{chunk_size=32, layout = #xegpu.layout<inst_data = [16, 16]>}>
- : vector<16x32xf32>, memref<1024xf32>, vector<16xindex>, vector<16xi1>
+// CHECK-LABEL: func.func @scatter_ops_coalesce_chunksize(
+// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<512xf32>) {
+// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<true> : vector<16x32xi1>
+// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<12> : vector<16x32xindex>
+// CHECK: %{{.*}} = xegpu.load %[[ARG0]][%{{.*}}], %{{.*}} <{layout = #xegpu.layout<inst_data = [16, 16]>}> :
+// CHECK-SAME: memref<512xf32>, vector<16x32xindex>, vector<16x32xi1> -> vector<16x32xf32>
+// CHECK: xegpu.store %0, %[[ARG0]][%{{.*}}], %{{.*}} <{layout = #xegpu.layout<inst_data = [16, 16]>}> :
+// CHECK-SAME: vector<16x32xf32>, memref<512xf32>, vector<16x32xindex>, vector<16x32xi1>
+func.func @scatter_ops_coalesce_chunksize(%src: memref<512xf32>) {
+ %1 = arith.constant dense<1>: vector<16x32xi1>
+ %offset = arith.constant dense<12> : vector<16x32xindex>
+ %3 = xegpu.load %src[%offset], %1
+ : memref<512xf32>, vector<16x32xindex>, vector<16x32xi1> -> vector<16x32xf32>
+ xegpu.store %3, %src[%offset], %1 <{layout = #xegpu.layout<inst_data = [16, 16]>}>
+ : vector<16x32xf32>, memref<512xf32>, vector<16x32xindex>, vector<16x32xi1>
return
}
}
@@ -194,24 +153,24 @@ func.func @scatter_ops_chunksize_excessive_anchor(%src: memref<1024xf32>) {
// -----
gpu.module @test {
-// CHECK-LABEL: func.func @scatter_ops_chunksize_slice(
-// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<1024xf32>) {
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<true> : vector<16xi1>
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<12> : vector<16xindex>
-// CHECK: %[[LOADED:.*]] = xegpu.load %[[ARG0]][%{{.*}}], %{{.*}} <{layout = #xegpu.layout<inst_data = [16]>}> :
-// CHECK-SAME: memref<1024xf32>, vector<16xindex>, vector<16xi1> -> vector<16xf32>
-// CHECK: %[[BCAST:.*]] = vector.broadcast %[[LOADED]] {layout_result_0 = #xegpu.layout<inst_data = [16, 16]>} : vector<16xf32> to vector<16x16xf32>
-// CHECK: xegpu.store %[[BCAST]], %[[ARG0]][%{{.*}}], %{{.*}} <{chunk_size = 16 : i64, layout = #xegpu.layout<inst_data = [16, 16]>}> :
-// CHECK-SAME: vector<16x16xf32>, memref<1024xf32>, vector<16xindex>, vector<16xi1>
-func.func @scatter_ops_chunksize_slice(%src: memref<1024xf32>) {
- %1 = arith.constant dense<1>: vector<16xi1>
- %offset = arith.constant dense<12> : vector<16xindex>
- %3 = xegpu.load %src[%offset], %1
- : memref<1024xf32>, vector<16xindex>, vector<16xi1> -> vector<16xf32>
-
- %4 = vector.broadcast %3 : vector<16xf32> to vector<16x16xf32>
- xegpu.store %4, %src[%offset], %1 <{chunk_size=16, layout = #xegpu.layout<inst_data = [16, 16]>}>
- : vector<16x16xf32>, memref<1024xf32>, vector<16xindex>, vector<16xi1>
+// CHECK-LABEL: func.func @load_gather_with_coalesce_chunksize(
+// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x16xf16>, %[[ARG1:[0-9a-zA-Z]+]]: memref<256xf16>, %[[ARG2:[0-9a-zA-Z]+]]: memref<8x16xf32>) {
+// CHECK: %[[OFFSET:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>}
+// CHECK-SAME: dense<[0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240]> : vector<16xindex>
+// CHECK-NEXT: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<true> : vector<16xi1>
+// CHECK-NEXT: %{{.*}} = xegpu.load %arg1[%[[OFFSET]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>}> : memref<256xf16>, vector<16xindex>, vector<16xi1> -> vector<16x16xf16>
+func.func @load_gather_with_coalesce_chunksize(%arg0: memref<8x16xf16>, %arg1: memref<256xf16>, %arg2: memref<8x16xf32>) {
+ %c0 = arith.constant 0 : index
+ %0 = xegpu.create_nd_tdesc %arg0 : memref<8x16xf16> -> !xegpu.tensor_desc<8x16xf16>
+ %1 = xegpu.load_nd %0[0, 0] : !xegpu.tensor_desc<8x16xf16> -> vector<8x16xf16>
+ %offset = arith.constant dense<0> : vector<16x16xindex>
+ %mask = arith.constant dense<true> : vector<16x16xi1>
+ %3 = xegpu.load %arg1[%offset], %mask
+ : memref<256xf16>, vector<16x16xindex>, vector<16x16xi1> -> vector<16x16xf16>
+ %4 = vector.transpose %3, [1, 0] : vector<16x16xf16> to vector<16x16xf16>
+ %5 = xegpu.dpas %1, %4 : vector<8x16xf16>, vector<16x16xf16> -> vector<8x16xf32>
+ %6 = xegpu.create_nd_tdesc %arg2 : memref<8x16xf32> -> !xegpu.tensor_desc<8x16xf32>
+ xegpu.store_nd %5, %6[0, 0] : vector<8x16xf32>, !xegpu.tensor_desc<8x16xf32>
return
}
}
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout.mlir b/mlir/test/Dialect/XeGPU/propagate-layout.mlir
index 14579606bcbed..c04e949015368 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout.mlir
@@ -133,20 +133,20 @@ func.func @extf_truncf(%arg0: !xegpu.tensor_desc<8x16xf16>, %arg1: !xegpu.tensor
// -----
gpu.module @test {
-// CHECK-LABEL: func.func @load_gather_with_chunksize(
-// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x16xf16>, %[[ARG1:[0-9a-zA-Z]+]]: memref<256xf16>, %[[ARG2:[0-9a-zA-Z]+]]: memref<8x16xf32>) {
+// CHECK-LABEL: func.func @load_gather_with_coalesce_chunksize(
+// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x16xf16>, %[[ARG1:[0-9a-zA-Z]+]]: memref<16x16xf16>, %[[ARG2:[0-9a-zA-Z]+]]: memref<8x16xf32>) {
// CHECK: %[[OFFSET:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>}
// CHECK-SAME: dense<[0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240]> : vector<16xindex>
// CHECK-NEXT: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<true> : vector<16xi1>
-// CHECK-NEXT: %{{.*}} = xegpu.load %arg1[%[[OFFSET]]], %[[MASK]] <{chunk_size = 16 : i64, layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>}> : memref<256xf16>, vector<16xindex>, vector<16xi1> -> vector<16x16xf16>
-func.func @load_gather_with_chunksize(%arg0: memref<8x16xf16>, %arg1: memref<256xf16>, %arg2: memref<8x16xf32>) {
+// CHECK-NEXT: %{{.*}} = xegpu.load %arg1[%[[OFFSET]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>}> : memref<16x16xf16>, vector<16xindex>, vector<16xi1> -> vector<16x16xf16>
+func.func @load_gather_with_coalesce_chunksize(%arg0: memref<8x16xf16>, %arg1: memref<16x16xf16>, %arg2: memref<8x16xf32>) {
%c0 = arith.constant 0 : index
%0 = xegpu.create_nd_tdesc %arg0 : memref<8x16xf16> -> !xegpu.tensor_desc<8x16xf16>
%1 = xegpu.load_nd %0[0, 0] : !xegpu.tensor_desc<8x16xf16> -> vector<8x16xf16>
- %offset = arith.constant dense<[0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240]> : vector<16xindex>
- %mask = arith.constant dense<true> : vector<16xi1>
- %3 = xegpu.load %arg1[%offset], %mask <{chunk_size=16}>
- : memref<256xf16>, vector<16xindex>, vector<16xi1> -> vector<16x16xf16>
+ %offset = arith.constant dense<[0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240]> : vector<16x16xindex>
+ %mask = arith.constant dense<true> : vector<16x16xi1>
+ %3 = xegpu.load %arg1[%offset], %mask
+ : memref<16x16xf16>, vector<16x16xindex>, vector<16x16xi1> -> vector<16x16xf16>
%4 = vector.transpose %3, [1, 0] : vector<16x16xf16> to vector<16x16xf16>
%5 = xegpu.dpas %1, %4 : vector<8x16xf16>, vector<16x16xf16> -> vector<8x16xf32>
%6 = xegpu.create_nd_tdesc %arg2 : memref<8x16xf32> -> !xegpu.tensor_desc<8x16xf32>
@@ -157,37 +157,20 @@ func.func @load_gather_with_chunksize(%arg0: memref<8x16xf16>, %arg1: memref<256
// -----
gpu.module @test {
-// CHECK-LABEL: func.func @store_scatter_with_chunksize(
-// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<128xf32>) {
-// CHECK-NEXT: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 8]>} dense<1.000000e+00> : vector<16x8xf32>
-// CHECK-NEXT: %[[CST_0:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<true> : vector<16xi1>
-// CHECK-NEXT: %[[CST_1:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<[0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240]> : vector<16xindex>
-// CHECK-NEXT: xegpu.store %[[CST]], %[[ARG0]][%[[CST_1]]], %[[CST_0]] <{chunk_size = 8 : i64, layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 8]>}> : vector<16x8xf32>, memref<128xf32>, vector<16xindex>, vector<16xi1>
-func.func @store_scatter_with_chunksize(%arg0: memref<128xf32>) {
- %val = arith.constant dense<1.000000e+00> : vector<16x8xf32>
- %mask = arith.constant dense<true> : vector<16xi1>
- %offset = arith.constant dense<[0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240]> : vector<16xindex>
- xegpu.store %val, %arg0[%offset], %mask <{chunk_size = 8}>: vector<16x8xf32>, memref<128xf32>, vector<16xindex>, vector<16xi1>
- return
-}
-}
-
-// -----
-gpu.module @test {
-// CHECK-LABEL: func.func @scatter_ops_chunksize(
-// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<256xf16>) {
-// CHECK: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<true> : vector<16xi1>
-// CHECK: %[[OFFSETS:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<12> : vector<16xindex>
-// CHECK: %[[LOAD_VEC:.*]] = xegpu.load %[[ARG0]][%[[OFFSETS]]], %[[MASK]] <{chunk_size = 8 : i64, layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 8]>}>
-// CHECK-SAME: memref<256xf16>, vector<16xindex>, vector<16xi1> -> vector<16x8xf16>
-// CHECK: xegpu.store %[[LOAD_VEC]], %[[ARG0]][%[[OFFSETS]]], %[[MASK]] <{chunk_size = 8 : i64, layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 8]>}> : vector<16x8xf16>, memref<256xf16>, vector<16xindex>, vector<16xi1>
-func.func @scatter_ops_chunksize(%src: memref<256xf16>) {
- %1 = arith.constant dense<1>: vector<16xi1>
- %offset = arith.constant dense<12> : vector<16xindex>
- %3 = xegpu.load %src[%offset], %1 <{chunk_size=8}>
- : memref<256xf16>, vector<16xindex>, vector<16xi1> -> vector<16x8xf16>
- xegpu.store %3, %src[%offset], %1 <{chunk_size=8}>
- : vector<16x8xf16>, memref<256xf16>, vector<16xindex>, vector<16xi1>
+// CHECK-LABEL: func.func @scatter_ops_coalesce_chunksize(
+// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<16x8xf16>) {
+// CHECK: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<true> : vector<16x8xi1>
+// CHECK: %[[OFFSETS:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<12> : vector<16x8xindex>
+// CHECK: %[[LOAD_VEC:.*]] = xegpu.load %[[ARG0]][%[[OFFSETS]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 8]>}>
+// CHECK-SAME: memref<16x8xf16>, vector<16x8xindex>, vector<16x8xi1> -> vector<16x8xf16>
+// CHECK: xegpu.store %[[LOAD_VEC]], %[[ARG0]][%[[OFFSETS]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 8]>}> : vector<16x8xf16>, memref<16x8xf16>, vector<16x8xindex>, vector<16x8xi1>
+func.func @scatter_ops_coalesce_chunksize(%src: memref<16x8xf16>) {
+ %1 = arith.constant dense<1>: vector<16x8xi1>
+ %offset = arith.constant dense<12> : vector<16x8xindex>
+ %3 = xegpu.load %src[%offset], %1
+ : memref<16x8xf16>, vector<16x8xindex>, vector<16x8xi1> -> vector<16x8xf16>
+ xegpu.store %3, %src[%offset], %1
+ : vector<16x8xf16>, memref<16x8xf16>, vector<16x8xindex>, vector<16x8xi1>
return
}
}
>From 5c19d85d65056ac2d6ab94005a2f2d2129ca056f Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Tue, 2 Jun 2026 04:51:43 +0000
Subject: [PATCH 03/20] refactor generic load/store setup rule
---
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 155 ++++++++++--------
1 file changed, 84 insertions(+), 71 deletions(-)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index 427515ea75b83..d461caebeb716 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -833,15 +833,6 @@ computeReductionLaneLayoutAndData(ArrayRef<int64_t> srcShape,
int64_t maxReduceVectorSize) {
int srcRank = srcShape.size();
SmallVector<int64_t> laneLayout(srcRank, 1), laneData(srcRank, 1);
- llvm::dbgs() << "[DEBUG computeReductionLaneLayoutAndData] srcRank="
- << srcRank << " srcShape.size()=" << srcShape.size()
- << " srcShape=[";
- for (size_t i = 0; i < srcShape.size(); ++i) {
- if (i > 0)
- llvm::dbgs() << ", ";
- llvm::dbgs() << srcShape[i];
- }
- llvm::dbgs() << "]\n";
int innermost = srcRank - 1;
int secondInnermost = srcRank - 2;
@@ -852,15 +843,6 @@ computeReductionLaneLayoutAndData(ArrayRef<int64_t> srcShape,
int laneDim = innermost;
int vectorDim = secondInnermost; // negative for rank 1
- // If only the innermost dim is reduced, spread the lanes across the
- // non-reduction (second-to-innermost) dim and reduce the innermost dim
- // within each lane instead.
- // if (srcRank >= 2 && isReduction(innermost) &&
- // !isReduction(secondInnermost)) {
- // laneDim = secondInnermost;
- // vectorDim = innermost;
- // }
-
laneLayout[laneDim] =
std::min(static_cast<int64_t>(subgroupSize), srcShape[laneDim]);
if (vectorDim >= 0)
@@ -1097,7 +1079,7 @@ xegpu::setupReductionResultLayout(xegpu::LayoutKind layoutKind,
/// Adjusts `consumerLayout`'s innermost-dim data field selected by
/// `layoutKind` so that the source layout can be safely inferred by dividing
/// that value by `ratio`. Doubles the value until the divisibility constraint
-/// is met, bounded above by result-shape.
+/// is met, bounded above by `bound` like result-shape.
///
/// Used by ops whose source relates to the result by a fixed factor along the
/// innermost dim (e.g., bitcast: bitwidth ratio; interleave: 2x).
@@ -1267,18 +1249,36 @@ xegpu::DistributeLayoutAttr xegpu::setupInsertStridedSliceResultLayout(
return requiredResLayout;
}
+/// Computes lane_layout and lane_data for scatter-style load anchor layouts
+/// (load gather, load matrix). Lanes and the per-lane vector both live on the
+/// innermost dim; the per-lane vector width is hinted by the consumer's
+/// lane_data[innermost]:
+/// - laneData[innermost] = min(consumerLaneData[innermost], maxChunkSize)
+/// - laneLayout[innermost] = min(subgroupSize,
+/// resShape[innermost] / laneData[innermost])
+/// All other entries are 1.
+static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
+computeScatterLoadLaneLayoutAndData(ArrayRef<int64_t> resShape,
+ ArrayRef<int64_t> consumerLaneData,
+ int subgroupSize, int64_t maxChunkSize) {
+ int rank = resShape.size();
+ SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
+ int innermost = rank - 1;
+ laneData[innermost] = std::min(consumerLaneData.back(), maxChunkSize);
+ laneLayout[innermost] = std::min(static_cast<int64_t>(subgroupSize),
+ resShape[innermost] / laneData[innermost]);
+ return {laneLayout, laneData};
+}
+
/// Sets up the anchor layout for load gather and load matrix operation.
/// load matrix lowers to load gather and 1d block load. All of them share the
/// same layout setup logic.
/// For Subgroup layout, uses the consumer layout directly.
-/// non-chunked loads (1D or 2D):
-/// InstData = {1, ..., min(consumer, maxLaneLoadSize * subgroupSize)}
-/// LaneLayout = {1, ..., subgroupSize}
-/// lane_data = {1, ..., min(consumer, maxLaneLoadSize)}
-/// chunked loads (2D only):
-/// InstData = {subgroupSize, min(consumer, maxLaneLoadSize)}
-/// LaneLayout = {subgroupSize, 1}
-/// lane_data={1,min(consumer, maxLaneLoadSize)}
+/// For InstData layout, the innermost inst_data is taken directly from the
+/// consumer's inst_data, capped by `maxChunkSize * subgroupSize`.
+/// For Lane layout, lane_layout/lane_data are derived via
+/// `computeScatterLoadLaneLayoutAndData` using the consumer's
+/// lane_data[innermost] as the per-lane vector hint.
static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
xegpu::LayoutKind layoutKind, mlir::MLIRContext *context,
xegpu::DistributeLayoutAttr consumerLayout, int maxChunkSize,
@@ -1292,20 +1292,18 @@ static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
SmallVector<int64_t> consumerLaneData =
consumerLayout.getEffectiveLaneDataAsInt();
- SmallVector<int> instData(resShape.size(), 1);
- SmallVector<int> laneLayout(resShape.size(), 1);
- SmallVector<int> laneData(resShape.size(), 1);
-
if (layoutKind == xegpu::LayoutKind::InstData) {
+ SmallVector<int> instData(resShape.size(), 1);
instData.back() = std::min(static_cast<int>(consumerInstData.back()),
maxChunkSize * subgroupSize);
return xegpu::LayoutAttr::get(context, instData);
- } else if (layoutKind == xegpu::LayoutKind::Lane) {
- laneData.back() =
- std::min(static_cast<int>(consumerLaneData.back()), maxChunkSize);
- laneLayout.back() = std::min(static_cast<int64_t>(subgroupSize),
- resShape.back() / laneData.back());
- return xegpu::LayoutAttr::get(context, laneLayout, laneData);
+ }
+ if (layoutKind == xegpu::LayoutKind::Lane) {
+ auto [laneLayout, laneData] = computeScatterLoadLaneLayoutAndData(
+ resShape, consumerLaneData, subgroupSize, maxChunkSize);
+ SmallVector<int> laneLayout32(laneLayout.begin(), laneLayout.end());
+ SmallVector<int> laneData32(laneData.begin(), laneData.end());
+ return xegpu::LayoutAttr::get(context, laneLayout32, laneData32);
}
return nullptr;
}
@@ -1351,42 +1349,57 @@ xegpu::setupLoadMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
maxChunkSize, resShape, subgroupSize);
}
+/// Computes lane_layout and lane_data for scatter-style store anchor layouts
+/// (store scatter, store matrix). Lanes and the per-lane vector both live on
+/// the innermost dim:
+/// - laneLayout[innermost] = min(subgroupSize, srcShape[innermost])
+/// - laneData[innermost] = min(srcShape[innermost] / laneLayout[innermost],
+/// maxChunkSize)
+/// All other entries are 1.
+static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
+computeScatterStoreLaneLayoutAndData(ArrayRef<int64_t> srcShape,
+ int subgroupSize, int64_t maxChunkSize) {
+ int rank = srcShape.size();
+ SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
+ int innermost = rank - 1;
+ laneLayout[innermost] =
+ std::min(static_cast<int64_t>(subgroupSize), srcShape[innermost]);
+ laneData[innermost] =
+ std::min(srcShape[innermost] / laneLayout[innermost], maxChunkSize);
+ return {laneLayout, laneData};
+}
+
/// Sets up the anchor layout for store scatter and store matrix operation.
-/// store matrix lowers to store scatter and 1d block store. All of them share
-/// the same layout setup logic. For Subgroup layout, not supported yet.
-/// non-chunked stores (1D or 2D):
-/// InstData = {1, ..., subgroupSize}
-/// LaneLayout = {1, ..., subgroupSize}
-/// lane_data = {1, ..., 1}
-/// chunked stores (2D only):
-/// InstData = {subgroupSize, min(srcVec, maxLaneStoreSize)}
-/// LaneLayout = {subgroupSize, 1}
-/// lane_data={1,min(srcVec, maxLaneStoreSize)}
+/// store matrix lowers to store scatter and 1d block store. All of them
+/// share the same layout setup logic. For Subgroup layout, not supported
+/// yet.
+///
+/// Lane layout is derived first via `computeScatterStoreLaneLayoutAndData`;
+/// inst_data is then the element-wise product lane_layout * lane_data.
static xegpu::DistributeLayoutAttr
setupGenericStoreAnchorLayout(xegpu::LayoutKind layoutKind,
mlir::MLIRContext *context, int maxChunkSize,
ArrayRef<int64_t> srcShape, int subgroupSize) {
- int srcShapeSize = srcShape.size();
- SmallVector<int> instData(srcShapeSize, 1);
- SmallVector<int> laneLayout(srcShapeSize, 1);
- SmallVector<int> laneData(srcShapeSize, 1);
-
if (layoutKind == xegpu::LayoutKind::Subgroup) {
assert(true &&
"subgroup layout assignment not supported for storeScatter.");
return nullptr;
}
+
+ auto [laneLayout, laneData] = computeScatterStoreLaneLayoutAndData(
+ srcShape, subgroupSize, maxChunkSize);
+
if (layoutKind == xegpu::LayoutKind::InstData) {
- laneLayout[srcShapeSize - 1] =
- std::min(subgroupSize, static_cast<int>(srcShape.back()));
- laneData[srcShapeSize - 1] =
- std::min(maxChunkSize, static_cast<int>(srcShape.back()));
+ SmallVector<int> instData(srcShape.size());
+ for (size_t i = 0; i < srcShape.size(); ++i)
+ instData[i] = static_cast<int>(laneLayout[i] * laneData[i]);
return xegpu::LayoutAttr::get(context, instData);
- } else if (layoutKind == xegpu::LayoutKind::Lane) {
- laneLayout[srcShapeSize - 1] =
- std::min(subgroupSize, static_cast<int>(srcShape.back()));
- return xegpu::LayoutAttr::get(context, laneLayout, laneData);
+ }
+ if (layoutKind == xegpu::LayoutKind::Lane) {
+ SmallVector<int> laneLayout32(laneLayout.begin(), laneLayout.end());
+ SmallVector<int> laneData32(laneData.begin(), laneData.end());
+ return xegpu::LayoutAttr::get(context, laneLayout32, laneData32);
}
return nullptr;
}
@@ -1557,8 +1570,8 @@ getDpasInstDataVectors(VectorType aTy, VectorType bTy, VectorType cdTy,
return std::make_tuple(instDataA, instDataB, instDataCD);
}
-/// Helper function to set up subgroup layouts for DPAS operands A, B, and C/D.
-/// Returns the three layouts if successful, nullopt otherwise.
+/// Helper function to set up subgroup layouts for DPAS operands A, B, and
+/// C/D. Returns the three layouts if successful, nullopt otherwise.
static std::optional<
std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
xegpu::DistributeLayoutAttr>>
@@ -1688,9 +1701,9 @@ xegpu::setupDpasLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
}
/// Helper to create a scale layout derived from a matrix operand layout.
-/// The scale layout is computed by mapping each dimension of the matrix layout
-/// to the corresponding scale tensor dimension using the ratio between the
-/// matrix and scale shapes.
+/// The scale layout is computed by mapping each dimension of the matrix
+/// layout to the corresponding scale tensor dimension using the ratio
+/// between the matrix and scale shapes.
static xegpu::DistributeLayoutAttr
createScaleLayout(mlir::MLIRContext *context, VectorType matrixTy,
VectorType scaleTy, xegpu::DistributeLayoutAttr matrixLayout,
@@ -1781,8 +1794,8 @@ createScaleLayout(mlir::MLIRContext *context, VectorType matrixTy,
}
/// Sets up the anchor layouts for dpas_mx operands (A, B, C/D, A_scale, and
-/// B_scale). The numSg and consumerLayout (optional) are only used by sg layout
-/// creation.
+/// B_scale). The numSg and consumerLayout (optional) are only used by sg
+/// layout creation.
std::optional<
std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
@@ -1908,8 +1921,8 @@ xegpu::DistributeLayoutAttr xegpu::inferSourceLayoutFromResultForNonAnchorOp(
shapeCast.getSourceVectorType().getShape());
}
- // For vector::InsertStridedSliceOp, infer source layout from result layout.
- // Dest vector must have the same layout as the result.
+ // For vector::InsertStridedSliceOp, infer source layout from result
+ // layout. Dest vector must have the same layout as the result.
if (auto insertSlice = dyn_cast<vector::InsertStridedSliceOp>(op)) {
if (idx == 0) {
return xegpu::inferInsertStridedSliceSourceLayout(
@@ -1978,8 +1991,8 @@ xegpu::DistributeLayoutAttr xegpu::inferSourceLayoutFromResultForNonAnchorOp(
if (dyn_cast<vector::ExtractStridedSliceOp>(op))
return resLayout;
- // For elementwise operations, all operands must have the same layout as the
- // result.
+ // For elementwise operations, all operands must have the same layout as
+ // the result.
if (OpTrait::hasElementwiseMappableTraits(op) && op->getNumResults() == 1)
return resLayout;
>From edd49273fe0151b505cb74fe5c0139dec7fcfc00 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Tue, 2 Jun 2026 06:41:09 +0000
Subject: [PATCH 04/20] add lane layout to dpas, refactor load rules to be more
generic. still missing nd ops andcomplete if user only set inst_data.
---
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 209 ++++++++++++------
1 file changed, 137 insertions(+), 72 deletions(-)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index d461caebeb716..fc2aad83b92a6 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -806,6 +806,41 @@ static bool leadingDimsAreUnit(ArrayRef<int64_t> shape, int numInnerDims) {
[](int64_t dim) { return dim == 1; });
}
+/// Builds a LayoutAttr carrying inst_data, lane_layout, and lane_data (no
+/// sg_layout / sg_data / order). Used by InstData-kind setup paths so the
+/// result layout can later be distributed without re-deriving the lane
+/// layout. `instData`, `laneLayout`, and `laneData` may have different
+/// element types; they are normalized to int32 entries.
+static xegpu::LayoutAttr buildInstDataLayoutWithLane(
+ mlir::MLIRContext *context, ArrayRef<int64_t> instData,
+ ArrayRef<int64_t> laneLayout, ArrayRef<int64_t> laneData) {
+ auto toI32Attr = [&](auto range) {
+ SmallVector<int32_t> v(range.begin(), range.end());
+ return DenseI32ArrayAttr::get(context, v);
+ };
+ return xegpu::LayoutAttr::get(context, /*sg_layout=*/nullptr,
+ /*sg_data=*/nullptr,
+ /*inst_data=*/toI32Attr(instData),
+ /*lane_layout=*/toI32Attr(laneLayout),
+ /*lane_data=*/toI32Attr(laneData),
+ /*order=*/nullptr);
+}
+
+static xegpu::LayoutAttr buildLaneLayout(mlir::MLIRContext *context,
+ ArrayRef<int64_t> laneLayout,
+ ArrayRef<int64_t> laneData) {
+ auto toI32Attr = [&](auto range) {
+ SmallVector<int32_t> v(range.begin(), range.end());
+ return DenseI32ArrayAttr::get(context, v);
+ };
+ return xegpu::LayoutAttr::get(context, /*sg_layout=*/nullptr,
+ /*sg_data=*/nullptr,
+ /*inst_data=*/nullptr,
+ /*lane_layout=*/toI32Attr(laneLayout),
+ /*lane_data=*/toI32Attr(laneData),
+ /*order=*/nullptr);
+}
+
/// Computes the lane_layout and lane_data for a multi-reduction's source
/// layout. Only the innermost two dimensions are distributed; all leading
/// dimensions are assumed to be unit (the caller verifies this via
@@ -1015,12 +1050,8 @@ xegpu::SliceAttr xegpu::setupMultiReductionResultLayout(
SmallVector<int64_t> instData(srcRank);
for (int i = 0; i < srcRank; i++)
instData[i] = laneLayout[i] * laneData[i];
- srcLayout = xegpu::LayoutAttr::get(context, /*sg_layout=*/nullptr,
- /*sg_data=*/nullptr,
- /*inst_data=*/toInt32Attr(instData),
- /*lane_layout=*/toInt32Attr(laneLayout),
- /*lane_data=*/toInt32Attr(laneData),
- /*order=*/nullptr);
+ srcLayout =
+ buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
} else if (layoutKind == xegpu::LayoutKind::Lane) {
// Only the innermost two dimensions are distributed; all leading dimensions
// are assumed to be unit dimensions.
@@ -1249,26 +1280,28 @@ xegpu::DistributeLayoutAttr xegpu::setupInsertStridedSliceResultLayout(
return requiredResLayout;
}
-/// Computes lane_layout and lane_data for scatter-style load anchor layouts
-/// (load gather, load matrix). Lanes and the per-lane vector both live on the
-/// innermost dim; the per-lane vector width is hinted by the consumer's
-/// lane_data[innermost]:
-/// - laneData[innermost] = min(consumerLaneData[innermost], maxChunkSize)
-/// - laneLayout[innermost] = min(subgroupSize,
-/// resShape[innermost] / laneData[innermost])
-/// All other entries are 1.
-static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
-computeScatterLoadLaneLayoutAndData(ArrayRef<int64_t> resShape,
- ArrayRef<int64_t> consumerLaneData,
- int subgroupSize, int64_t maxChunkSize) {
- int rank = resShape.size();
- SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
- int innermost = rank - 1;
- laneData[innermost] = std::min(consumerLaneData.back(), maxChunkSize);
- laneLayout[innermost] = std::min(static_cast<int64_t>(subgroupSize),
- resShape[innermost] / laneData[innermost]);
- return {laneLayout, laneData};
-}
+// /// Computes lane_layout and lane_data for scatter-style load anchor layouts
+// /// (load gather, load matrix). Lanes and the per-lane vector both live on
+// the
+// /// innermost dim; the per-lane vector width is hinted by the consumer's
+// /// lane_data[innermost]:
+// /// - laneData[innermost] = min(consumerLaneData[innermost],
+// maxChunkSize)
+// /// - laneLayout[innermost] = min(subgroupSize,
+// /// resShape[innermost] /
+// laneData[innermost])
+// /// All other entries are 1.
+// static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
+// computeScatterLoadLaneLayoutAndData(ArrayRef<int64_t> resShape,
+// ArrayRef<int64_t> consumerLaneData,
+// int subgroupSize, int64_t maxChunkSize) {
+// int rank = resShape.size();
+// SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
+// int innermost = rank - 1;
+// laneData[innermost] = std::min(consumerLaneData.back(), maxChunkSize);
+// laneLayout[innermost] = consumerLaneLayout.back();
+// return {laneLayout, laneData};
+// }
/// Sets up the anchor layout for load gather and load matrix operation.
/// load matrix lowers to load gather and 1d block load. All of them share the
@@ -1291,19 +1324,25 @@ static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
consumerLayout.getEffectiveInstDataAsInt();
SmallVector<int64_t> consumerLaneData =
consumerLayout.getEffectiveLaneDataAsInt();
+ SmallVector<int64_t> consumerLaneLayout =
+ consumerLayout.getEffectiveLaneLayoutAsInt();
+ SmallVector<int64_t> laneLayout(resShape.size(), 1);
+ SmallVector<int64_t> laneData(resShape.size(), 1);
if (layoutKind == xegpu::LayoutKind::InstData) {
- SmallVector<int> instData(resShape.size(), 1);
- instData.back() = std::min(static_cast<int>(consumerInstData.back()),
- maxChunkSize * subgroupSize);
- return xegpu::LayoutAttr::get(context, instData);
+ SmallVector<int64_t> instData(resShape.size(), 1);
+ laneData.back() = std::min(static_cast<int64_t>(consumerLaneData.back()),
+ int64_t(maxChunkSize));
+ laneLayout.back() = consumerLaneLayout.back();
+ instData.back() = laneData.back() * laneLayout.back();
+ return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
}
if (layoutKind == xegpu::LayoutKind::Lane) {
- auto [laneLayout, laneData] = computeScatterLoadLaneLayoutAndData(
- resShape, consumerLaneData, subgroupSize, maxChunkSize);
- SmallVector<int> laneLayout32(laneLayout.begin(), laneLayout.end());
- SmallVector<int> laneData32(laneData.begin(), laneData.end());
- return xegpu::LayoutAttr::get(context, laneLayout32, laneData32);
+
+ laneData.back() = std::min(static_cast<int64_t>(consumerLaneData.back()),
+ int64_t(maxChunkSize));
+ laneLayout.back() = consumerLaneLayout.back();
+ return buildLaneLayout(context, laneLayout, laneData);
}
return nullptr;
}
@@ -1356,16 +1395,17 @@ xegpu::setupLoadMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
/// - laneData[innermost] = min(srcShape[innermost] / laneLayout[innermost],
/// maxChunkSize)
/// All other entries are 1.
-static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
+static std::pair<SmallVector<int>, SmallVector<int>>
computeScatterStoreLaneLayoutAndData(ArrayRef<int64_t> srcShape,
int subgroupSize, int64_t maxChunkSize) {
int rank = srcShape.size();
- SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
+ SmallVector<int> laneLayout(rank, 1), laneData(rank, 1);
int innermost = rank - 1;
- laneLayout[innermost] =
- std::min(static_cast<int64_t>(subgroupSize), srcShape[innermost]);
+ laneLayout[innermost] = std::min(static_cast<int>(subgroupSize),
+ static_cast<int>(srcShape[innermost]));
laneData[innermost] =
- std::min(srcShape[innermost] / laneLayout[innermost], maxChunkSize);
+ std::min(static_cast<int>(srcShape[innermost] / laneLayout[innermost]),
+ static_cast<int>(maxChunkSize));
return {laneLayout, laneData};
}
@@ -1394,12 +1434,16 @@ setupGenericStoreAnchorLayout(xegpu::LayoutKind layoutKind,
SmallVector<int> instData(srcShape.size());
for (size_t i = 0; i < srcShape.size(); ++i)
instData[i] = static_cast<int>(laneLayout[i] * laneData[i]);
- return xegpu::LayoutAttr::get(context, instData);
+ return xegpu::LayoutAttr::get(
+ context, /*sg_layout=*/nullptr,
+ /*sg_data=*/nullptr,
+ /*inst_data=*/DenseI32ArrayAttr::get(context, instData),
+ /*lane_layout=*/DenseI32ArrayAttr::get(context, laneLayout),
+ /*lane_data=*/DenseI32ArrayAttr::get(context, laneData),
+ /*order=*/nullptr);
}
if (layoutKind == xegpu::LayoutKind::Lane) {
- SmallVector<int> laneLayout32(laneLayout.begin(), laneLayout.end());
- SmallVector<int> laneData32(laneData.begin(), laneData.end());
- return xegpu::LayoutAttr::get(context, laneLayout32, laneData32);
+ return xegpu::LayoutAttr::get(context, laneLayout, laneData);
}
return nullptr;
}
@@ -1445,13 +1489,15 @@ xegpu::setupStoreMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
srcShape, subgroupSize);
}
-// This function returns the default lane layout for a given vector type.
+// Returns the default (lane_layout, lane_data) pair for a given 1D/2D vector
+// type used by 2D block IO ops.
// - `packingSize` means multiple consecutive elements can be accessed
-// together as a single unit.
+// together as a single unit.
// - `vnni` means data packing is column-wise (i.e., 2x1xf16 with vnni vs.
-// 1x2xf16 w/o vnni).
+// 1x2xf16 w/o vnni).
template <typename RankedTy>
-static xegpu::LayoutAttr getDefaultLaneLayout2DBlockIo(
+static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
+getDefaultLaneLayoutAndData2DBlockIo(
RankedTy ty, const xegpu::uArch::uArch *uArch,
std::optional<unsigned> packingSize = std::nullopt, bool vnni = false) {
// Expecting at least 1D vector. For rank > 2, leading dims are batch dims.
@@ -1461,17 +1507,27 @@ static xegpu::LayoutAttr getDefaultLaneLayout2DBlockIo(
assert(ty.getElementType().isIntOrFloat() &&
"Expected int or float element type.");
- auto context = ty.getContext();
auto rank = ty.getRank();
- SmallVector<int> laneLayout(rank, 1);
- SmallVector<int> laneData(rank, 1);
+ SmallVector<int64_t> laneLayout(rank, 1);
+ SmallVector<int64_t> laneData(rank, 1);
if (packingSize.has_value()) {
unsigned bitwidth = ty.getElementType().getIntOrFloatBitWidth();
- int &laneDataPos = vnni ? laneData[rank - 2] : laneData.back();
+ int64_t &laneDataPos = vnni ? laneData[rank - 2] : laneData.back();
laneDataPos = bitwidth < *packingSize ? *packingSize / bitwidth : 1;
}
laneLayout.back() = uArch->getSubgroupSize();
- return xegpu::LayoutAttr::get(context, laneLayout, laneData);
+ return {laneLayout, laneData};
+}
+
+// Convenience wrapper: returns a LayoutAttr carrying only the default lane
+// layout / lane data for a 2D block IO vector type.
+template <typename RankedTy>
+static xegpu::LayoutAttr getDefaultLaneLayout2DBlockIo(
+ RankedTy ty, const xegpu::uArch::uArch *uArch,
+ std::optional<unsigned> packingSize = std::nullopt, bool vnni = false) {
+ auto [laneLayout, laneData] =
+ getDefaultLaneLayoutAndData2DBlockIo(ty, uArch, packingSize, vnni);
+ return buildLaneLayout(ty.getContext(), laneLayout, laneData);
}
// This function returns all layouts for the given sgCount, whose sgData:
@@ -1670,7 +1726,12 @@ xegpu::setupDpasLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
const auto *uArchInstruction =
dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
xegpu::uArch::InstructionKind::SubgroupMatrixMultiplyAcc));
-
+ auto [laneLayoutA, laneDataA] = getDefaultLaneLayoutAndData2DBlockIo(
+ aTy, uArch, uArchInstruction->getPackedFormatBitSizeA());
+ auto [laneLayoutB, laneDataB] = getDefaultLaneLayoutAndData2DBlockIo(
+ bTy, uArch, uArchInstruction->getPackedFormatBitSizeB(), true);
+ auto [laneLayoutCD, laneDataCD] =
+ getDefaultLaneLayoutAndData2DBlockIo(cdTy, uArch);
if (layoutKind == xegpu::LayoutKind::Subgroup) {
assert(numSg > 0 &&
"Number of subgroups must be provided for sg layout creation.");
@@ -1682,19 +1743,14 @@ xegpu::setupDpasLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
return std::nullopt;
auto [instDataA, instDataB, instDataCD] = *instDataVecs;
return std::make_tuple(
- xegpu::LayoutAttr::get(
- context, SmallVector<int>(instDataA.begin(), instDataA.end())),
- xegpu::LayoutAttr::get(
- context, SmallVector<int>(instDataB.begin(), instDataB.end())),
- xegpu::LayoutAttr::get(
- context, SmallVector<int>(instDataCD.begin(), instDataCD.end())));
+ buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA),
+ buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB),
+ buildInstDataLayoutWithLane(context, instDataCD, laneLayoutCD,
+ laneDataCD));
} else if (layoutKind == xegpu::LayoutKind::Lane) {
- auto aLayout = getDefaultLaneLayout2DBlockIo(
- aTy, uArch, uArchInstruction->getPackedFormatBitSizeA());
- auto bLayout = getDefaultLaneLayout2DBlockIo(
- bTy, uArch, uArchInstruction->getPackedFormatBitSizeB(), true);
- auto cdLayout = getDefaultLaneLayout2DBlockIo(
- cdTy, uArch /*, packingSize = std::nullopt */);
+ auto aLayout = buildLaneLayout(context, laneLayoutA, laneDataA);
+ auto bLayout = buildLaneLayout(context, laneLayoutB, laneDataB);
+ auto cdLayout = buildLaneLayout(context, laneLayoutCD, laneDataCD);
return std::make_tuple(aLayout, bLayout, cdLayout);
}
return std::nullopt;
@@ -1833,12 +1889,21 @@ xegpu::setupDpasMxLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
return std::nullopt;
auto [instDataA, instDataB, instDataCD] = *instDataVecs;
- auto dpasALayout = xegpu::LayoutAttr::get(
- context, SmallVector<int>(instDataA.begin(), instDataA.end()));
- auto dpasBLayout = xegpu::LayoutAttr::get(
- context, SmallVector<int>(instDataB.begin(), instDataB.end()));
- auto dpasCDLayout = xegpu::LayoutAttr::get(
- context, SmallVector<int>(instDataCD.begin(), instDataCD.end()));
+ const auto *uArchInstruction =
+ dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
+ xegpu::uArch::InstructionKind::SubgroupMatrixMultiplyAcc));
+ auto [laneLayoutA, laneDataA] = getDefaultLaneLayoutAndData2DBlockIo(
+ aTy, uArch, uArchInstruction->getPackedFormatBitSizeA());
+ auto [laneLayoutB, laneDataB] = getDefaultLaneLayoutAndData2DBlockIo(
+ bTy, uArch, uArchInstruction->getPackedFormatBitSizeB(), true);
+ auto [laneLayoutCD, laneDataCD] =
+ getDefaultLaneLayoutAndData2DBlockIo(cdTy, uArch);
+ auto dpasALayout =
+ buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA);
+ auto dpasBLayout =
+ buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB);
+ auto dpasCDLayout = buildInstDataLayoutWithLane(context, instDataCD,
+ laneLayoutCD, laneDataCD);
// Create scale layouts
auto aScaleLayout =
>From d917853241560b9d6ecc73d5a38e37f81b9104a0 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Tue, 9 Jun 2026 23:22:35 +0000
Subject: [PATCH 05/20] add setupStoreNdAnchorLayout and
setupPrefetchNdAnchorLayout. shared generic helper sets inst_data and lane
info together for nd ops.
---
.../XeGPU/Transforms/XeGPULayoutImpl.h | 15 ++
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 149 +++++++++++++++++-
.../XeGPU/Transforms/XeGPUPropagateLayout.cpp | 125 ++++-----------
3 files changed, 194 insertions(+), 95 deletions(-)
diff --git a/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h b/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
index 253dcb18af67c..759d23b2d40e6 100644
--- a/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
+++ b/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
@@ -231,6 +231,21 @@ DistributeLayoutAttr setupStoreMatrixAnchorLayout(LayoutKind layoutKind,
int chunkSize,
const uArch::uArch *uArch);
+/// Sets up the anchor layout for a store_nd operation. StoreNd does not
+/// consider a consumer layout (it is a data sink), and picks its layout from
+/// uArch block parameters. `numSg` is only used for Subgroup-kind layouts.
+DistributeLayoutAttr setupStoreNdAnchorLayout(LayoutKind layoutKind,
+ VectorType vectorTy, int numSg,
+ const uArch::uArch *uArch);
+
+/// Sets up the anchor layout for a prefetch_nd operation. PrefetchNd has no
+/// value result and thus no consumer; it picks its layout from uArch block
+/// parameters. `numSg` is only used for Subgroup-kind layouts.
+DistributeLayoutAttr setupPrefetchNdAnchorLayout(LayoutKind layoutKind,
+ TensorDescType tdescTy,
+ int numSg,
+ const uArch::uArch *uArch);
+
/// Sets up the anchor layouts for a dpas operands (A, B, and C/D).
/// The numSg and consumerLayout (optional) are only used by sg layout creation.
std::optional<std::tuple<DistributeLayoutAttr, DistributeLayoutAttr,
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index fc2aad83b92a6..1b63f29571115 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -1489,6 +1489,153 @@ xegpu::setupStoreMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
srcShape, subgroupSize);
}
+// Forward declaration: defined later in the file.
+using LayoutRepresentation = std::pair<int64_t, int64_t>;
+static SmallVector<LayoutRepresentation>
+getValidLayouts(ArrayRef<int64_t> wgShape, ArrayRef<int64_t> instData,
+ int64_t sgCount);
+
+/// Generic anchor-layout setup for ND ops (store_nd, prefetch_nd) that pick
+/// their own layout without considering a consumer.
+///
+/// Given hardware-supported block widths/heights, picks the largest divisor
+/// of the trailing two dims of `dataShape` and uses that as `inst_data`. The
+/// lane layout is the standard 2D-block-IO default (subgroupSize lanes on the
+/// innermost dim, optional packing on the innermost or second-to-innermost
+/// for vnni).
+///
+/// For Lane kind: returns just the lane layout / lane data.
+/// For InstData kind: returns inst_data + the lane layout it must be a
+/// multiple of (Category A: inst_data = k * lane_layout * lane_data, k>=1).
+/// For Subgroup kind: requires `numSg`, picks the most balanced sg_layout
+/// that evenly divides the shape and where sg_data is a multiple of the
+/// chosen inst_data.
+static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
+ xegpu::LayoutKind layoutKind, mlir::MLIRContext *context,
+ ArrayRef<int64_t> dataShape, Type elemTy, ArrayRef<int> bWidths,
+ ArrayRef<int> bHeights, unsigned packingSize, int numSg,
+ const xegpu::uArch::uArch *uArch) {
+ int rank = dataShape.size();
+ assert(rank >= 1 && "Expected at least 1D shape for ND op");
+
+ // Compute the default 2D block IO lane layout / lane data.
+ unsigned bitwidth = elemTy.getIntOrFloatBitWidth();
+ int packingFactor = bitwidth < packingSize ? packingSize / bitwidth : 1;
+ SmallVector<int64_t> laneLayout(rank, 1);
+ SmallVector<int64_t> laneData(rank, 1);
+ laneLayout.back() = uArch->getSubgroupSize();
+ laneData.back() = packingFactor;
+
+ if (layoutKind == xegpu::LayoutKind::Lane)
+ return buildLaneLayout(context, laneLayout, laneData);
+
+ // Compute inst_data from hardware block params: pick the largest supported
+ // block width/height that divides the corresponding tensor dim.
+ SmallVector<int64_t> instData(rank, 1);
+ int instWidth = xegpu::getLargestDivisor(
+ static_cast<int>(dataShape.back()), bWidths);
+ if (instWidth == -1)
+ return nullptr;
+ instData.back() = instWidth;
+ if (rank >= 2) {
+ int instHeight = xegpu::getLargestDivisor(
+ static_cast<int>(dataShape[rank - 2]), bHeights);
+ if (instHeight == -1)
+ return nullptr;
+ instData[rank - 2] = instHeight;
+ }
+
+ // Validate Category A invariant: inst_data must be a multiple of
+ // lane_layout * lane_data on each dim (k >= 1).
+ for (int dim = 0; dim < rank; ++dim) {
+ int64_t laneProduct = laneLayout[dim] * laneData[dim];
+ assert(instData[dim] % laneProduct == 0 &&
+ "inst_data must be a multiple of lane_layout * lane_data for ND op");
+ (void)laneProduct;
+ }
+
+ if (layoutKind == xegpu::LayoutKind::InstData)
+ return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
+
+ if (layoutKind == xegpu::LayoutKind::Subgroup) {
+ assert(numSg > 0 &&
+ "Number of subgroups must be provided for sg layout creation.");
+ // Subgroup-kind layout creation currently only supports rank-2 shapes
+ // (mirrors getValidLayouts).
+ if (rank != 2)
+ return nullptr;
+ auto sgLayouts = getValidLayouts(dataShape, instData, numSg);
+ if (sgLayouts.empty())
+ return nullptr;
+ SmallVector<int> sgLayout = {static_cast<int>(sgLayouts[0].first),
+ static_cast<int>(sgLayouts[0].second)};
+ SmallVector<int> sgData = {
+ static_cast<int>(dataShape[0]) / sgLayout[0],
+ static_cast<int>(dataShape[1]) / sgLayout[1]};
+ return xegpu::LayoutAttr::get(
+ context, DenseI32ArrayAttr::get(context, sgLayout),
+ DenseI32ArrayAttr::get(context, sgData),
+ /*inst_data=*/nullptr, /*lane_layout=*/nullptr,
+ /*lane_data=*/nullptr, /*order=*/nullptr);
+ }
+
+ return nullptr;
+}
+
+/// Sets up the anchor layout for a store_nd operation. StoreNd picks its
+/// own layout based on uArch block parameters (it does not take a consumer
+/// layout, since it is a data sink).
+xegpu::DistributeLayoutAttr
+xegpu::setupStoreNdAnchorLayout(xegpu::LayoutKind layoutKind,
+ VectorType srcVecTy, int numSg,
+ const xegpu::uArch::uArch *uArch) {
+ auto context = srcVecTy.getContext();
+ Type elemTy = srcVecTy.getElementType();
+
+ const auto *uArchInstruction =
+ dyn_cast<xegpu::uArch::Subgroup2DBlockStoreInstruction>(
+ uArch->getInstruction(
+ xegpu::uArch::InstructionKind::Subgroup2DBlockStore));
+ if (!uArchInstruction)
+ return nullptr;
+ auto blockWHC = uArchInstruction->getBlockWidthHeightCount(elemTy);
+ if (!blockWHC)
+ return nullptr;
+ auto [bWidths, bHeights, bCounts] = blockWHC.value();
+ unsigned packingSize = uArchInstruction->getPackedFormatBitSize();
+
+ return setupGenericNdAnchorLayout(layoutKind, context, srcVecTy.getShape(),
+ elemTy, bWidths, bHeights, packingSize,
+ numSg, uArch);
+}
+
+/// Sets up the anchor layout for a prefetch_nd operation. PrefetchNd has no
+/// consumer (it produces no value), so it picks its own layout from uArch
+/// block parameters.
+xegpu::DistributeLayoutAttr
+xegpu::setupPrefetchNdAnchorLayout(xegpu::LayoutKind layoutKind,
+ xegpu::TensorDescType tdescTy, int numSg,
+ const xegpu::uArch::uArch *uArch) {
+ auto context = tdescTy.getContext();
+ Type elemTy = tdescTy.getElementType();
+
+ const auto *uArchInstruction =
+ dyn_cast<xegpu::uArch::Subgroup2DBlockPrefetchInstruction>(
+ uArch->getInstruction(
+ xegpu::uArch::InstructionKind::Subgroup2DBlockPrefetch));
+ if (!uArchInstruction)
+ return nullptr;
+ auto blockWHC = uArchInstruction->getBlockWidthHeightCount(elemTy);
+ if (!blockWHC)
+ return nullptr;
+ auto [bWidths, bHeights, bCounts] = blockWHC.value();
+ unsigned packingSize = uArchInstruction->getPackedFormatBitSize();
+
+ return setupGenericNdAnchorLayout(layoutKind, context, tdescTy.getShape(),
+ elemTy, bWidths, bHeights, packingSize,
+ numSg, uArch);
+}
+
// Returns the default (lane_layout, lane_data) pair for a given 1D/2D vector
// type used by 2D block IO ops.
// - `packingSize` means multiple consecutive elements can be accessed
@@ -1537,7 +1684,7 @@ static xegpu::LayoutAttr getDefaultLaneLayout2DBlockIo(
// wgShape = [128, 64], instData = [8, 16], sgCount = 32
// Returns layouts:
// [(8,4), (16,2)], which correspond to sgData [16,16] and [8,32].
-using LayoutRepresentation = std::pair<int64_t, int64_t>;
+// Definition (forward-declared above).
static SmallVector<LayoutRepresentation>
getValidLayouts(ArrayRef<int64_t> wgShape, ArrayRef<int64_t> instData,
int64_t sgCount) {
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
index c1ad3c4ee8e2e..0e371d98f61f7 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
@@ -599,49 +599,31 @@ void LayoutInfoPropagation::visitPrefetchNdOp(
if (hasParamsOfLayoutKind(anchorLayout)) {
prefetchLayout = LayoutInfo(anchorLayout);
} else {
- // Here we assign the default layout to the tensor descriptor operand of
- // prefetch.
auto tdescTy = prefetch.getTensorDescType();
-
const uArch *uArch = getUArch(getChipStr(prefetch).value_or(""));
if (!uArch)
return;
- const auto *uArchInstruction =
- dyn_cast<xegpu::uArch::Subgroup2DBlockPrefetchInstruction>(
- uArch->getInstruction(
- xegpu::uArch::InstructionKind::Subgroup2DBlockPrefetch));
-
- auto blockWHC =
- uArchInstruction->getBlockWidthHeightCount(tdescTy.getElementType());
- if (!blockWHC)
- prefetch.emitWarning("No known block params found for the element type.");
- auto [bWidth, bHeight, bCount] = blockWHC.value();
- SmallVector<int> instData;
- int instWidth = xegpu::getLargestDivisor(
- static_cast<int>(tdescTy.getDimSize(tdescTy.getRank() - 1)), bWidth);
- if (instWidth == -1)
- prefetch.emitWarning(
- "No suitable instruction multiple found for the given shape.");
- if (tdescTy.getRank() == 1)
- instData = {instWidth};
- else {
- int instHeight = xegpu::getLargestDivisor(
- static_cast<int>(tdescTy.getDimSize(tdescTy.getRank() - 2)), bHeight);
- if (instHeight == -1)
+
+ int numSg = 0;
+ if (layoutKind == xegpu::LayoutKind::Subgroup) {
+ auto numSgOrErr = getNumSg(prefetch, uArch->getSubgroupSize());
+ if (failed(numSgOrErr)) {
prefetch.emitWarning(
- "No suitable instruction multiple found for the given shape.");
- instData = {instHeight, instWidth};
+ "Unable to determine the number of subgroups for the operation.");
+ return;
+ }
+ numSg = numSgOrErr.value();
}
- if (layoutKind == xegpu::LayoutKind::InstData)
- prefetchLayout =
- LayoutInfo(xegpu::LayoutAttr::get(tdescTy.getContext(), instData));
- else
- prefetchLayout = getSIMTLayoutInfoBlockIO(
- tdescTy, uArch, uArchInstruction->getPackedFormatBitSize());
-
- prefetch.setLayoutAttr(
- dyn_cast<xegpu::DistributeLayoutAttr>(prefetchLayout.get()));
+ auto layoutAttr =
+ xegpu::setupPrefetchNdAnchorLayout(layoutKind, tdescTy, numSg, uArch);
+ if (!layoutAttr) {
+ prefetch.emitWarning(
+ "Failed to determine required layout for prefetch_nd.");
+ return;
+ }
+ prefetchLayout = LayoutInfo(layoutAttr);
+ prefetch.setLayoutAttr(layoutAttr);
}
// Propagate the layout to the source tensor descriptor.
propagateIfChanged(operands[0], operands[0]->meet(prefetchLayout));
@@ -980,71 +962,26 @@ void LayoutInfoPropagation::visitStoreNdOp(
const uArch *uArch = getUArch(getChipStr(store).value_or(""));
if (!uArch)
return;
- const auto *uArchInstruction =
- dyn_cast<xegpu::uArch::Subgroup2DBlockStoreInstruction>(
- uArch->getInstruction(
- xegpu::uArch::InstructionKind::Subgroup2DBlockStore));
- VectorType dataTy = store.getValueType();
- auto blockWHC = uArchInstruction->getBlockWidthHeightCount(
- store.getValueType().getElementType());
- if (!blockWHC)
- store.emitWarning("No known block params found for the element type.");
- auto [bWidth, bHeight, bCount] = blockWHC.value();
- // Default to 1 for any leading batch dims; rank-1 and rank>=2 cases
- // overwrite the trailing entries below.
- SmallVector<int> instData(dataTy.getRank(), 1);
- int instWidth = xegpu::getLargestDivisor(
- static_cast<int>(dataTy.getDimSize(dataTy.getRank() - 1)), bWidth);
- if (instWidth == -1)
- store.emitWarning(
- "No suitable instruction multiple found for the given shape.");
- if (dataTy.getRank() == 1) {
- instData = {instWidth};
- } else {
- int instHeight = xegpu::getLargestDivisor(
- static_cast<int>(dataTy.getDimSize(dataTy.getRank() - 2)), bHeight);
- if (instHeight == -1)
- store.emitWarning(
- "No suitable instruction multiple found for the given shape.");
- instData[dataTy.getRank() - 2] = instHeight;
- instData[dataTy.getRank() - 1] = instWidth;
- }
- if (layoutKind == xegpu::LayoutKind::InstData)
- storeLayout =
- LayoutInfo(xegpu::LayoutAttr::get(dataTy.getContext(), instData));
- else if (layoutKind == xegpu::LayoutKind::Lane)
- storeLayout =
- getSIMTLayoutInfoBlockIO(store.getValueType(), uArch,
- uArchInstruction->getPackedFormatBitSize());
- else { // xegpu::LayoutKind::Subgroup
- auto sgSize = uArch->getSubgroupSize();
- auto numSgOrErr = getNumSg(store, sgSize);
+ int numSg = 0;
+ if (layoutKind == xegpu::LayoutKind::Subgroup) {
+ auto numSgOrErr = getNumSg(store, uArch->getSubgroupSize());
if (failed(numSgOrErr)) {
store.emitWarning(
"Unable to determine the number of subgroups for the operation.");
return;
}
- auto sgLayouts = getValidLayouts(store.getValueType().getShape(),
- instData, numSgOrErr.value());
- if (sgLayouts.empty()) {
- store.emitWarning(
- "Unable to determine suitable subgroup layout for store value.");
- return;
- }
- SmallVector<int> sgLayout = {sgLayouts[0].first, sgLayouts[0].second};
- SmallVector<int> sgData = {
- static_cast<int>(dataTy.getShape()[0]) / sgLayout[0],
- static_cast<int>(dataTy.getShape()[1]) / sgLayout[1]};
- storeLayout = LayoutInfo(xegpu::LayoutAttr::get(
- dataTy.getContext(),
- DenseI32ArrayAttr::get(dataTy.getContext(), sgLayout),
- DenseI32ArrayAttr::get(dataTy.getContext(), sgData),
- /*inst_data =*/nullptr, /*lane_layout =*/nullptr,
- /*lane_data =*/nullptr, /*order =*/nullptr));
+ numSg = numSgOrErr.value();
+ }
+
+ auto layoutAttr = xegpu::setupStoreNdAnchorLayout(
+ layoutKind, store.getValueType(), numSg, uArch);
+ if (!layoutAttr) {
+ store.emitWarning("Failed to determine required layout for store_nd.");
+ return;
}
- store.setLayoutAttr(
- dyn_cast<xegpu::DistributeLayoutAttr>(storeLayout.get()));
+ storeLayout = LayoutInfo(layoutAttr);
+ store.setLayoutAttr(layoutAttr);
}
// Propagate the layout to the value operand.
// Both operands should have the same layout
>From e3b6e22971263e8197decd52fcb5d58b1e284cfd Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 01:43:06 +0000
Subject: [PATCH 06/20] add setupLoadNdAnchorLayout. validates consumer
inst_data/sg_layout against uArch; falls back to defaults otherwise.
---
.../XeGPU/Transforms/XeGPULayoutImpl.h | 12 ++
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 149 +++++++++++++++---
.../XeGPU/Transforms/XeGPUPropagateLayout.cpp | 47 +++++-
3 files changed, 178 insertions(+), 30 deletions(-)
diff --git a/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h b/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
index 759d23b2d40e6..2d2ec1c33b0f4 100644
--- a/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
+++ b/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
@@ -246,6 +246,18 @@ DistributeLayoutAttr setupPrefetchNdAnchorLayout(LayoutKind layoutKind,
int numSg,
const uArch::uArch *uArch);
+/// Sets up the anchor layout for a load_nd operation. LoadNd takes a
+/// (downstream) consumer layout and validates it against uArch constraints;
+/// when valid, the consumer's `inst_data` / `sg_layout` are honored.
+/// Otherwise defaults derived from uArch block parameters are used.
+/// `consumerLayout` may be null. `numSg` is only used for Subgroup-kind
+/// layouts when the consumer does not already provide an sg_layout.
+DistributeLayoutAttr setupLoadNdAnchorLayout(LayoutKind layoutKind,
+ VectorType vectorTy,
+ DistributeLayoutAttr consumerLayout,
+ int numSg,
+ const uArch::uArch *uArch);
+
/// Sets up the anchor layouts for a dpas operands (A, B, and C/D).
/// The numSg and consumerLayout (optional) are only used by sg layout creation.
std::optional<std::tuple<DistributeLayoutAttr, DistributeLayoutAttr,
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index 1b63f29571115..da8863547bfae 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -1495,25 +1495,72 @@ static SmallVector<LayoutRepresentation>
getValidLayouts(ArrayRef<int64_t> wgShape, ArrayRef<int64_t> instData,
int64_t sgCount);
-/// Generic anchor-layout setup for ND ops (store_nd, prefetch_nd) that pick
-/// their own layout without considering a consumer.
+/// Validates whether `instData` is a hardware-viable inst_data for an ND op
+/// with the given block params and lane factor. Specifically:
+/// - leading batch dims must be 1
+/// - innermost dim must be a divisor of `dataShape.back()`, a multiple of
+/// `bWidths.front()` (smallest supported block width), and ≤ a small
+/// multiple of the largest supported block width
+/// - second-to-innermost dim must be in the supported heights for rank >= 2
+/// - each dim must be a multiple of `lane_layout[dim] * lane_data[dim]`
+static bool isValidNdInstData(ArrayRef<int64_t> instData,
+ ArrayRef<int64_t> dataShape,
+ ArrayRef<int> bWidths, ArrayRef<int> bHeights,
+ ArrayRef<int64_t> laneLayout,
+ ArrayRef<int64_t> laneData) {
+ int rank = dataShape.size();
+ if (static_cast<int>(instData.size()) != rank)
+ return false;
+
+ for (int dim = 0; dim < rank - 2; ++dim)
+ if (instData[dim] != 1)
+ return false;
+
+ int64_t inner = instData.back();
+ if (inner <= 0 || dataShape.back() % inner != 0)
+ return false;
+ int minWidth = *llvm::min_element(bWidths);
+ int maxWidth = *llvm::max_element(bWidths);
+ if (inner % minWidth != 0 || inner > maxWidth * /*maxBlockCount*/ 4)
+ return false;
+
+ if (rank >= 2) {
+ int64_t height = instData[rank - 2];
+ if (!llvm::is_contained(bHeights, static_cast<int>(height)))
+ return false;
+ }
+
+ for (int dim = 0; dim < rank; ++dim) {
+ int64_t laneProduct = laneLayout[dim] * laneData[dim];
+ if (laneProduct == 0 || instData[dim] % laneProduct != 0)
+ return false;
+ }
+ return true;
+}
+
+/// Generic anchor-layout setup for ND ops (load_nd, store_nd, prefetch_nd).
///
/// Given hardware-supported block widths/heights, picks the largest divisor
-/// of the trailing two dims of `dataShape` and uses that as `inst_data`. The
+/// of the trailing two dims of `dataShape` as the default `inst_data`. The
/// lane layout is the standard 2D-block-IO default (subgroupSize lanes on the
-/// innermost dim, optional packing on the innermost or second-to-innermost
-/// for vnni).
+/// innermost dim, optional packing on the innermost dim).
+///
+/// `consumerLayout` (optional) is honored when its parameters are valid w.r.t.
+/// the uArch constraints; otherwise the helper falls back to defaults.
///
-/// For Lane kind: returns just the lane layout / lane data.
-/// For InstData kind: returns inst_data + the lane layout it must be a
-/// multiple of (Category A: inst_data = k * lane_layout * lane_data, k>=1).
-/// For Subgroup kind: requires `numSg`, picks the most balanced sg_layout
-/// that evenly divides the shape and where sg_data is a multiple of the
-/// chosen inst_data.
+/// For Lane kind: returns just the lane layout / lane data (consumer ignored;
+/// lane layout is fully determined by hardware).
+/// For InstData kind: returns inst_data + lane_layout/lane_data (Category A:
+/// inst_data = k * lane_layout * lane_data, k >= 1). Honors consumer's
+/// inst_data when it is uArch-valid.
+/// For Subgroup kind: if the consumer specifies a workgroup-level layout,
+/// reuses it directly; otherwise picks the most balanced sg_layout via
+/// `getValidLayouts` (requires `numSg`).
static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
xegpu::LayoutKind layoutKind, mlir::MLIRContext *context,
ArrayRef<int64_t> dataShape, Type elemTy, ArrayRef<int> bWidths,
- ArrayRef<int> bHeights, unsigned packingSize, int numSg,
+ ArrayRef<int> bHeights, unsigned packingSize,
+ xegpu::DistributeLayoutAttr consumerLayout, int numSg,
const xegpu::uArch::uArch *uArch) {
int rank = dataShape.size();
assert(rank >= 1 && "Expected at least 1D shape for ND op");
@@ -1529,8 +1576,15 @@ static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
if (layoutKind == xegpu::LayoutKind::Lane)
return buildLaneLayout(context, laneLayout, laneData);
- // Compute inst_data from hardware block params: pick the largest supported
- // block width/height that divides the corresponding tensor dim.
+ // Subgroup-kind fast path: if the consumer already specifies a
+ // workgroup-level layout, reuse it directly. Skip the inst_data
+ // computation, which can fail for very small shapes (e.g. dpas_mx scale
+ // operands like 128x16 where no supported block width divides 16).
+ if (layoutKind == xegpu::LayoutKind::Subgroup && consumerLayout &&
+ consumerLayout.isForWorkgroup())
+ return consumerLayout;
+
+ // Compute the default inst_data from hardware block params.
SmallVector<int64_t> instData(rank, 1);
int instWidth = xegpu::getLargestDivisor(
static_cast<int>(dataShape.back()), bWidths);
@@ -1545,8 +1599,17 @@ static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
instData[rank - 2] = instHeight;
}
- // Validate Category A invariant: inst_data must be a multiple of
- // lane_layout * lane_data on each dim (k >= 1).
+ // Honor the consumer's inst_data if it is uArch-valid.
+ if (consumerLayout) {
+ SmallVector<int64_t> consumerInstData =
+ consumerLayout.getEffectiveInstDataAsInt();
+ if (!consumerInstData.empty() &&
+ isValidNdInstData(consumerInstData, dataShape, bWidths, bHeights,
+ laneLayout, laneData))
+ instData.assign(consumerInstData.begin(), consumerInstData.end());
+ }
+
+ // Category A invariant: inst_data is a multiple of lane_layout * lane_data.
for (int dim = 0; dim < rank; ++dim) {
int64_t laneProduct = laneLayout[dim] * laneData[dim];
assert(instData[dim] % laneProduct == 0 &&
@@ -1558,12 +1621,12 @@ static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
if (layoutKind == xegpu::LayoutKind::Subgroup) {
- assert(numSg > 0 &&
- "Number of subgroups must be provided for sg layout creation.");
- // Subgroup-kind layout creation currently only supports rank-2 shapes
- // (mirrors getValidLayouts).
if (rank != 2)
return nullptr;
+ // The consumer-fast-path above already returned for the case where the
+ // consumer carries a workgroup-level layout, so here numSg must be set.
+ assert(numSg > 0 &&
+ "Number of subgroups must be provided for sg layout creation.");
auto sgLayouts = getValidLayouts(dataShape, instData, numSg);
if (sgLayouts.empty())
return nullptr;
@@ -1606,7 +1669,7 @@ xegpu::setupStoreNdAnchorLayout(xegpu::LayoutKind layoutKind,
return setupGenericNdAnchorLayout(layoutKind, context, srcVecTy.getShape(),
elemTy, bWidths, bHeights, packingSize,
- numSg, uArch);
+ /*consumerLayout=*/nullptr, numSg, uArch);
}
/// Sets up the anchor layout for a prefetch_nd operation. PrefetchNd has no
@@ -1633,7 +1696,49 @@ xegpu::setupPrefetchNdAnchorLayout(xegpu::LayoutKind layoutKind,
return setupGenericNdAnchorLayout(layoutKind, context, tdescTy.getShape(),
elemTy, bWidths, bHeights, packingSize,
- numSg, uArch);
+ /*consumerLayout=*/nullptr, numSg, uArch);
+}
+
+/// Sets up the anchor layout for a load_nd operation. LoadNd takes a
+/// consumer layout (from its result's downstream uses) and validates it
+/// against uArch constraints; if valid, the consumer's `inst_data` /
+/// `sg_layout` are honored. Otherwise the helper falls back to defaults
+/// derived from uArch block parameters.
+xegpu::DistributeLayoutAttr xegpu::setupLoadNdAnchorLayout(
+ xegpu::LayoutKind layoutKind, VectorType resVecTy,
+ xegpu::DistributeLayoutAttr consumerLayout, int numSg,
+ const xegpu::uArch::uArch *uArch) {
+ auto context = resVecTy.getContext();
+ Type elemTy = resVecTy.getElementType();
+
+ // Subgroup-kind fast path: if the consumer already specifies a complete
+ // workgroup-level layout, reuse it directly. We don't need the uArch block
+ // params at all (which may be unavailable for unusual element types like
+ // sub-byte floats used in dpas_mx scale operands).
+ if (layoutKind == xegpu::LayoutKind::Subgroup && consumerLayout &&
+ consumerLayout.isForWorkgroup())
+ return consumerLayout;
+
+ const auto *uArchInstruction =
+ dyn_cast<xegpu::uArch::Subgroup2DBlockLoadInstruction>(
+ uArch->getInstruction(
+ xegpu::uArch::InstructionKind::Subgroup2DBlockLoad));
+ if (!uArchInstruction)
+ return nullptr;
+ // Transform / transpose / upConv are lane-level concerns; treat them as
+ // no-op at the propagation stage (consistent with the existing
+ // visitLoadNdOp, which warns on transpose).
+ auto blockWHC = uArchInstruction->getBlockWidthHeightCount(
+ elemTy, /*hasTransform=*/false, /*hasTranspose=*/false,
+ /*upConv=*/false);
+ if (!blockWHC)
+ return nullptr;
+ auto [bWidths, bHeights, bCounts] = blockWHC.value();
+ unsigned packingSize = uArchInstruction->getPackedFormatBitSize();
+
+ return setupGenericNdAnchorLayout(layoutKind, context, resVecTy.getShape(),
+ elemTy, bWidths, bHeights, packingSize,
+ consumerLayout, numSg, uArch);
}
// Returns the default (lane_layout, lane_data) pair for a given 1D/2D vector
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
index 0e371d98f61f7..df5d1409b8395 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
@@ -999,21 +999,52 @@ void LayoutInfoPropagation::visitLoadNdOp(
if (hasParamsOfLayoutKind(anchorLayout)) {
loadLayout = LayoutInfo(anchorLayout);
} else {
-
LayoutInfo valueLayout = results[0]->getValue();
- // Need the layout of the value to propagate to the tensor descriptor.
if (!valueLayout.isAssigned())
return;
- loadLayout = valueLayout;
- // LoadNdOp has the transpose effect. However, at the stage of this analysis
- // this effect is not expected and should be abstracted away. Emit a
- // warning.
+
+ // LoadNdOp has a transpose effect. At this stage of analysis it is not
+ // expected and should be abstracted away; emit a warning and pre-transpose
+ // the consumer hint so it lines up with the load's source.
+ auto consumerLayoutAttr =
+ dyn_cast<xegpu::DistributeLayoutAttr>(valueLayout.get());
if (auto transpose = load.getTranspose()) {
load.emitWarning("Transpose effect is not expected for LoadNdOp at "
"LayoutInfoPropagation stage.");
- loadLayout = valueLayout.transpose(transpose.value());
+ LayoutInfo transposed = valueLayout.transpose(transpose.value());
+ consumerLayoutAttr =
+ dyn_cast<xegpu::DistributeLayoutAttr>(transposed.get());
+ }
+
+ const uArch *uArch = getUArch(getChipStr(load).value_or(""));
+ if (!uArch)
+ return;
+
+ int numSg = 0;
+ // numSg is only needed when the helper has to derive a fresh sg_layout
+ // for Subgroup kind. If the consumer already provides a complete
+ // sg_layout, the helper will reuse it without consulting numSg.
+ bool consumerHasSgLayout =
+ consumerLayoutAttr &&
+ !consumerLayoutAttr.getEffectiveSgLayoutAsInt().empty();
+ if (layoutKind == xegpu::LayoutKind::Subgroup && !consumerHasSgLayout) {
+ auto numSgOrErr = getNumSg(load, uArch->getSubgroupSize());
+ if (failed(numSgOrErr)) {
+ load.emitWarning(
+ "Unable to determine the number of subgroups for the operation.");
+ return;
+ }
+ numSg = numSgOrErr.value();
+ }
+
+ auto layoutAttr = xegpu::setupLoadNdAnchorLayout(
+ layoutKind, load.getType(), consumerLayoutAttr, numSg, uArch);
+ if (!layoutAttr) {
+ load.emitWarning("Failed to determine required layout for load_nd.");
+ return;
}
- load.setLayoutAttr(dyn_cast<xegpu::DistributeLayoutAttr>(loadLayout.get()));
+ loadLayout = LayoutInfo(layoutAttr);
+ load.setLayoutAttr(layoutAttr);
}
// Propagate the new layout to the tensor descriptor operand.
propagateIfChanged(operands[0], operands[0]->meet(loadLayout));
>From e671f4a1fc4a0929f4140f57518102930faddc66 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 02:28:49 +0000
Subject: [PATCH 07/20] complete inst_data-only anchor layouts on scatter ops;
preserve lane info in shape_cast collapse inference. eliminates assertion
crashes when user only specifies inst_data.
---
.../XeGPU/Transforms/XeGPULayoutImpl.h | 17 +++++
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 76 +++++++++++++++++++
.../XeGPU/Transforms/XeGPUPropagateLayout.cpp | 28 +++++--
3 files changed, 113 insertions(+), 8 deletions(-)
diff --git a/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h b/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
index 2d2ec1c33b0f4..f3cbf6ae72f5d 100644
--- a/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
+++ b/mlir/include/mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h
@@ -231,6 +231,23 @@ DistributeLayoutAttr setupStoreMatrixAnchorLayout(LayoutKind layoutKind,
int chunkSize,
const uArch::uArch *uArch);
+/// If the consumer layout has only inst_data (no lane_layout/lane_data),
+/// completes it by running the corresponding scatter-style Lane-kind setup
+/// rule with inst_data as the destination shape. The resulting lane info is
+/// merged with the consumer's inst_data so downstream setup* paths see a
+/// fully-populated layout. If the consumer already has lane info (or no
+/// inst_data), returns it unchanged. The "load-side" version uses
+/// setupLoadGatherAnchorLayout under the hood; the "store-side" version uses
+/// setupStoreScatterAnchorLayout.
+DistributeLayoutAttr
+completeLoadGatherLayoutFromInstData(DistributeLayoutAttr consumerLayout,
+ Type elemTy, const uArch::uArch *uArch);
+
+DistributeLayoutAttr
+completeStoreScatterLayoutFromInstData(DistributeLayoutAttr consumerLayout,
+ Type elemTy,
+ const uArch::uArch *uArch);
+
/// Sets up the anchor layout for a store_nd operation. StoreNd does not
/// consider a consumer layout (it is a data sink), and picks its layout from
/// uArch block parameters. `numSg` is only used for Subgroup-kind layouts.
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index da8863547bfae..51b17b3993ceb 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -1489,6 +1489,82 @@ xegpu::setupStoreMatrixAnchorLayout(xegpu::LayoutKind layoutKind,
srcShape, subgroupSize);
}
+/// If `consumerLayout` has inst_data set but no lane_layout/lane_data,
+/// derive a lane factorization by re-running the load-side Lane setup with
+/// inst_data as the destination shape, and merge the result back so the
+/// returned LayoutAttr carries inst_data + lane_layout + lane_data. This
+/// guarantees the lane factorization the downstream load setup will see is
+/// the same one its own Lane-kind setup would produce.
+xegpu::DistributeLayoutAttr xegpu::completeLoadGatherLayoutFromInstData(
+ xegpu::DistributeLayoutAttr consumerLayout, Type elemTy,
+ const xegpu::uArch::uArch *uArch) {
+ if (!consumerLayout)
+ return consumerLayout;
+ SmallVector<int64_t> instData = consumerLayout.getEffectiveInstDataAsInt();
+ if (instData.empty())
+ return consumerLayout;
+ if (!consumerLayout.getEffectiveLaneLayoutAsInt().empty() &&
+ !consumerLayout.getEffectiveLaneDataAsInt().empty())
+ return consumerLayout;
+
+ // Reuse the load-side setup with inst_data as the destination shape.
+ const int subgroupSize = uArch->getSubgroupSize();
+ auto *context = consumerLayout.getContext();
+ auto elemBitWidth = elemTy.getIntOrFloatBitWidth();
+ const auto *uArchInstruction =
+ dyn_cast<xegpu::uArch::LoadGatherInstructionInterface>(
+ uArch->getInstruction(xegpu::uArch::InstructionKind::LoadGather));
+ if (!uArchInstruction)
+ return consumerLayout;
+ int maxChunkSize = uArchInstruction->getMaxLaneLoadSize(elemBitWidth);
+
+ auto laneOnly = setupGenericLoadAnchorLayout(
+ xegpu::LayoutKind::Lane, context, /*consumerLayout=*/nullptr,
+ maxChunkSize, instData, subgroupSize);
+ if (!laneOnly)
+ return consumerLayout;
+
+ SmallVector<int64_t> laneLayout = laneOnly.getEffectiveLaneLayoutAsInt();
+ SmallVector<int64_t> laneData = laneOnly.getEffectiveLaneDataAsInt();
+ return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
+}
+
+/// If `consumerLayout` has inst_data set but no lane_layout/lane_data,
+/// derive a lane factorization by re-running the store-side Lane setup with
+/// inst_data as the destination shape, and merge the result back. Returned
+/// LayoutAttr carries inst_data + lane_layout + lane_data.
+xegpu::DistributeLayoutAttr xegpu::completeStoreScatterLayoutFromInstData(
+ xegpu::DistributeLayoutAttr consumerLayout, Type elemTy,
+ const xegpu::uArch::uArch *uArch) {
+ if (!consumerLayout)
+ return consumerLayout;
+ SmallVector<int64_t> instData = consumerLayout.getEffectiveInstDataAsInt();
+ if (instData.empty())
+ return consumerLayout;
+ if (!consumerLayout.getEffectiveLaneLayoutAsInt().empty() &&
+ !consumerLayout.getEffectiveLaneDataAsInt().empty())
+ return consumerLayout;
+
+ const int subgroupSize = uArch->getSubgroupSize();
+ auto *context = consumerLayout.getContext();
+ auto elemBitWidth = elemTy.getIntOrFloatBitWidth();
+ const auto *uArchInstruction =
+ dyn_cast<xegpu::uArch::StoreScatterInstructionInterface>(
+ uArch->getInstruction(xegpu::uArch::InstructionKind::StoreScatter));
+ if (!uArchInstruction)
+ return consumerLayout;
+ int maxChunkSize = uArchInstruction->getMaxLaneStoreSize(elemBitWidth);
+
+ auto laneOnly = setupGenericStoreAnchorLayout(
+ xegpu::LayoutKind::Lane, context, maxChunkSize, instData, subgroupSize);
+ if (!laneOnly)
+ return consumerLayout;
+
+ SmallVector<int64_t> laneLayout = laneOnly.getEffectiveLaneLayoutAsInt();
+ SmallVector<int64_t> laneData = laneOnly.getEffectiveLaneDataAsInt();
+ return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
+}
+
// Forward declaration: defined later in the file.
using LayoutRepresentation = std::pair<int64_t, int64_t>;
static SmallVector<LayoutRepresentation>
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
index df5d1409b8395..5a660101879a2 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
@@ -1221,7 +1221,11 @@ void LayoutInfoPropagation::visitLoadGatherOp(
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
if (hasParamsOfLayoutKind(anchorLayoutAttr)) {
- requiredAnchorLayoutAttr = anchorLayoutAttr;
+ // The user-provided anchor may carry only inst_data; complete it with
+ // a lane factorization derived from the load-side Lane setup so
+ // downstream paths see a fully-populated layout.
+ requiredAnchorLayoutAttr = xegpu::completeLoadGatherLayoutFromInstData(
+ anchorLayoutAttr, resVecTy.getElementType(), uArch);
} else {
if (!resVecTy) {
load.emitWarning("Not propagating, non-vector payload supplied.");
@@ -1261,7 +1265,11 @@ void LayoutInfoPropagation::visitStoreScatterOp(
int chunkSize = storeScatter.getChunkSize().value_or(1);
if (hasParamsOfLayoutKind(anchorLayoutAttr)) {
- requiredAnchorLayoutAttr = anchorLayoutAttr;
+ // The user-provided anchor may carry only inst_data; complete it with
+ // a lane factorization derived from the store-side Lane setup so
+ // downstream paths see a fully-populated layout.
+ requiredAnchorLayoutAttr = xegpu::completeStoreScatterLayoutFromInstData(
+ anchorLayoutAttr, srcVecTy.getElementType(), uArch);
} else {
if (!srcVecTy) {
storeScatter.emitWarning("Not propagating, non-vector payload supplied.");
@@ -1322,14 +1330,18 @@ void LayoutInfoPropagation::visitStoreMatrixOp(
ArrayRef<const LayoutInfoLattice *> results) {
xegpu::DistributeLayoutAttr anchorLayout = storeMatrix.getLayoutAttr();
LayoutInfo layout;
+ VectorType srcVecTy =
+ llvm::cast<VectorType>(storeMatrix.getData().getType());
+ const uArch *uArch = getUArch(getChipStr(storeMatrix).value_or(""));
+ if (!uArch)
+ return;
if (hasParamsOfLayoutKind(anchorLayout)) {
- layout = LayoutInfo(anchorLayout);
+ // The user-provided anchor may carry only inst_data; complete it with
+ // a lane factorization derived from the store-side Lane setup.
+ auto completed = xegpu::completeStoreScatterLayoutFromInstData(
+ anchorLayout, srcVecTy.getElementType(), uArch);
+ layout = LayoutInfo(completed);
} else {
- VectorType srcVecTy =
- llvm::cast<VectorType>(storeMatrix.getData().getType());
- const uArch *uArch = getUArch(getChipStr(storeMatrix).value_or(""));
- if (!uArch)
- return;
int chunkSize =
1; // placeHolder for future use when StoreMatrix supports coalescing
auto requiredAnchorLayoutAttr = xegpu::setupStoreMatrixAnchorLayout(
>From 557b76b683291563dea5cd1c684b39d10d776792 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 02:34:59 +0000
Subject: [PATCH 08/20] honor consumer lane_layout/lane_data in nd anchor
setup. dpas operands with vnni packing now propagate correctly to load_nd.
---
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index 51b17b3993ceb..83892079da7b0 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -1649,6 +1649,30 @@ static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
laneLayout.back() = uArch->getSubgroupSize();
laneData.back() = packingFactor;
+ // Honor the consumer's lane info when valid. The consumer (e.g. dpas) may
+ // require VNNI-style packing on a non-innermost dim that the load's own
+ // packing default doesn't capture.
+ auto honorConsumerLane = [&]() {
+ if (!consumerLayout)
+ return;
+ SmallVector<int64_t> consumerLaneLayout =
+ consumerLayout.getEffectiveLaneLayoutAsInt();
+ SmallVector<int64_t> consumerLaneData =
+ consumerLayout.getEffectiveLaneDataAsInt();
+ if (consumerLaneLayout.size() != static_cast<size_t>(rank) ||
+ consumerLaneData.size() != static_cast<size_t>(rank))
+ return;
+ // Validate: lane_layout * lane_data must divide each dim of dataShape.
+ for (int dim = 0; dim < rank; ++dim) {
+ int64_t product = consumerLaneLayout[dim] * consumerLaneData[dim];
+ if (product == 0 || dataShape[dim] % product != 0)
+ return;
+ }
+ laneLayout.assign(consumerLaneLayout.begin(), consumerLaneLayout.end());
+ laneData.assign(consumerLaneData.begin(), consumerLaneData.end());
+ };
+ honorConsumerLane();
+
if (layoutKind == xegpu::LayoutKind::Lane)
return buildLaneLayout(context, laneLayout, laneData);
>From d8a007c988ec8d5ae90c8e27b562e610c183c57d Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 03:53:47 +0000
Subject: [PATCH 09/20] lane-kind nd anchor setup skips block-WHC lookup. lets
sub-byte float types (e.g. f4E2M1FN) get a default lane layout via packing
factor alone. also propagate consumer order through nd anchor setup.
---
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 48 +++++++++++++------
1 file changed, 34 insertions(+), 14 deletions(-)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index 83892079da7b0..ad591625754ae 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -811,9 +811,12 @@ static bool leadingDimsAreUnit(ArrayRef<int64_t> shape, int numInnerDims) {
/// result layout can later be distributed without re-deriving the lane
/// layout. `instData`, `laneLayout`, and `laneData` may have different
/// element types; they are normalized to int32 entries.
-static xegpu::LayoutAttr buildInstDataLayoutWithLane(
- mlir::MLIRContext *context, ArrayRef<int64_t> instData,
- ArrayRef<int64_t> laneLayout, ArrayRef<int64_t> laneData) {
+static xegpu::LayoutAttr
+buildInstDataLayoutWithLane(mlir::MLIRContext *context,
+ ArrayRef<int64_t> instData,
+ ArrayRef<int64_t> laneLayout,
+ ArrayRef<int64_t> laneData,
+ DenseI32ArrayAttr orderAttr = nullptr) {
auto toI32Attr = [&](auto range) {
SmallVector<int32_t> v(range.begin(), range.end());
return DenseI32ArrayAttr::get(context, v);
@@ -823,12 +826,13 @@ static xegpu::LayoutAttr buildInstDataLayoutWithLane(
/*inst_data=*/toI32Attr(instData),
/*lane_layout=*/toI32Attr(laneLayout),
/*lane_data=*/toI32Attr(laneData),
- /*order=*/nullptr);
+ /*order=*/orderAttr);
}
-static xegpu::LayoutAttr buildLaneLayout(mlir::MLIRContext *context,
- ArrayRef<int64_t> laneLayout,
- ArrayRef<int64_t> laneData) {
+static xegpu::LayoutAttr
+buildLaneLayout(mlir::MLIRContext *context, ArrayRef<int64_t> laneLayout,
+ ArrayRef<int64_t> laneData,
+ DenseI32ArrayAttr orderAttr = nullptr) {
auto toI32Attr = [&](auto range) {
SmallVector<int32_t> v(range.begin(), range.end());
return DenseI32ArrayAttr::get(context, v);
@@ -838,7 +842,7 @@ static xegpu::LayoutAttr buildLaneLayout(mlir::MLIRContext *context,
/*inst_data=*/nullptr,
/*lane_layout=*/toI32Attr(laneLayout),
/*lane_data=*/toI32Attr(laneData),
- /*order=*/nullptr);
+ /*order=*/orderAttr);
}
/// Computes the lane_layout and lane_data for a multi-reduction's source
@@ -1673,8 +1677,12 @@ static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
};
honorConsumerLane();
+ // If the consumer carries an explicit `order`, propagate it through.
+ DenseI32ArrayAttr orderAttr =
+ consumerLayout ? consumerLayout.getOrder() : nullptr;
+
if (layoutKind == xegpu::LayoutKind::Lane)
- return buildLaneLayout(context, laneLayout, laneData);
+ return buildLaneLayout(context, laneLayout, laneData, orderAttr);
// Subgroup-kind fast path: if the consumer already specifies a
// workgroup-level layout, reuse it directly. Skip the inst_data
@@ -1718,7 +1726,8 @@ static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
}
if (layoutKind == xegpu::LayoutKind::InstData)
- return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
+ return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData,
+ orderAttr);
if (layoutKind == xegpu::LayoutKind::Subgroup) {
if (rank != 2)
@@ -1825,16 +1834,27 @@ xegpu::DistributeLayoutAttr xegpu::setupLoadNdAnchorLayout(
xegpu::uArch::InstructionKind::Subgroup2DBlockLoad));
if (!uArchInstruction)
return nullptr;
- // Transform / transpose / upConv are lane-level concerns; treat them as
- // no-op at the propagation stage (consistent with the existing
- // visitLoadNdOp, which warns on transpose).
+ unsigned packingSize = uArchInstruction->getPackedFormatBitSize();
+
+ // Lane kind only needs subgroupSize + packingSize. Skip the block-WHC
+ // lookup, which can fail for element types without a uArch entry (e.g.
+ // sub-byte floats like f4E2M1FN), and let the generic helper produce a
+ // default lane layout from packingSize alone.
+ if (layoutKind == xegpu::LayoutKind::Lane)
+ return setupGenericNdAnchorLayout(
+ layoutKind, context, resVecTy.getShape(), elemTy,
+ /*bWidths=*/{}, /*bHeights=*/{}, packingSize, consumerLayout, numSg,
+ uArch);
+
+ // InstData / Subgroup kinds need block params. Transform / transpose /
+ // upConv are lane-level concerns; treat them as no-op at the propagation
+ // stage (consistent with visitLoadNdOp, which warns on transpose).
auto blockWHC = uArchInstruction->getBlockWidthHeightCount(
elemTy, /*hasTransform=*/false, /*hasTranspose=*/false,
/*upConv=*/false);
if (!blockWHC)
return nullptr;
auto [bWidths, bHeights, bCounts] = blockWHC.value();
- unsigned packingSize = uArchInstruction->getPackedFormatBitSize();
return setupGenericNdAnchorLayout(layoutKind, context, resVecTy.getShape(),
elemTy, bWidths, bHeights, packingSize,
>From 7dba091eaab1f8bb186ba617e469336c5ef6ca45 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 04:06:15 +0000
Subject: [PATCH 10/20] write completed scatter/store-matrix anchor layout back
to the op so the printed IR reflects the resolved lane info, not the user's
incomplete inst_data-only layout.
---
mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
index 5a660101879a2..cb9d6eeed9743 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
@@ -1226,6 +1226,7 @@ void LayoutInfoPropagation::visitLoadGatherOp(
// downstream paths see a fully-populated layout.
requiredAnchorLayoutAttr = xegpu::completeLoadGatherLayoutFromInstData(
anchorLayoutAttr, resVecTy.getElementType(), uArch);
+ load.setLayoutAttr(requiredAnchorLayoutAttr);
} else {
if (!resVecTy) {
load.emitWarning("Not propagating, non-vector payload supplied.");
@@ -1270,6 +1271,7 @@ void LayoutInfoPropagation::visitStoreScatterOp(
// downstream paths see a fully-populated layout.
requiredAnchorLayoutAttr = xegpu::completeStoreScatterLayoutFromInstData(
anchorLayoutAttr, srcVecTy.getElementType(), uArch);
+ storeScatter.setLayoutAttr(requiredAnchorLayoutAttr);
} else {
if (!srcVecTy) {
storeScatter.emitWarning("Not propagating, non-vector payload supplied.");
@@ -1340,6 +1342,7 @@ void LayoutInfoPropagation::visitStoreMatrixOp(
// a lane factorization derived from the store-side Lane setup.
auto completed = xegpu::completeStoreScatterLayoutFromInstData(
anchorLayout, srcVecTy.getElementType(), uArch);
+ storeMatrix.setLayoutAttr(completed);
layout = LayoutInfo(completed);
} else {
int chunkSize =
>From 8a816db74e51e666638ecbbfd73b85df12413b53 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 04:28:59 +0000
Subject: [PATCH 11/20] test: update CHECK lines in
propagate-layout-inst-data.mlir to expect lane_layout/lane_data alongside
inst_data on Nd ops. fixes 10 cases. Remaining 7 cases need investigation of
setupGenericLoadAnchorLayout and dpas_mx scale operand layout.
---
.../XeGPU/propagate-layout-inst-data.mlir | 76 +++++++++----------
1 file changed, 38 insertions(+), 38 deletions(-)
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
index 3ea06d52690dc..da4a2e32259de 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
@@ -4,13 +4,13 @@
// CHECK-LABEL: func.func @load_store_no_array_len(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x32xf32>, %[[ARG1:[0-9a-zA-Z]+]]: memref<8x32xf32>) {
// CHECK: %[[CST:.*]] = arith.constant dense<0.000000e+00> : vector<8x16xf32>
-// CHECK: %[[TDESC_SRC:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<8x32xf32> -> !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16]>>
-// CHECK: %[[TDESC_DST:.*]] = xegpu.create_nd_tdesc %[[ARG1]] : memref<8x32xf32> -> !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16]>>
-// CHECK: xegpu.prefetch_nd %[[TDESC_SRC]][0, 0] <{l1_hint = #xegpu.cache_hint<cached>, l2_hint = #xegpu.cache_hint<uncached>, layout = #xegpu.layout<inst_data = [8, 16]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16]>>
-// CHECK: %[[LOADED:.*]] = xegpu.load_nd %0[0, 0] <{layout = #xegpu.layout<inst_data = [8, 16]>}>
-// CHECK-SAME: !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16]>> -> vector<8x32xf32>
-// CHECK: xegpu.store_nd %[[LOADED]], %[[TDESC_DST]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16]>}> : vector<8x32xf32>, !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16]>>
+// CHECK: %[[TDESC_SRC:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<8x32xf32> -> !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
+// CHECK: %[[TDESC_DST:.*]] = xegpu.create_nd_tdesc %[[ARG1]] : memref<8x32xf32> -> !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
+// CHECK: xegpu.prefetch_nd %[[TDESC_SRC]][0, 0] <{l1_hint = #xegpu.cache_hint<cached>, l2_hint = #xegpu.cache_hint<uncached>, layout = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
+// CHECK: %[[LOADED:.*]] = xegpu.load_nd %0[0, 0] <{layout = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>}>
+// CHECK-SAME: !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>> -> vector<8x32xf32>
+// CHECK: xegpu.store_nd %[[LOADED]], %[[TDESC_DST]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> : vector<8x32xf32>, !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
gpu.module @test {
// Although the uArch allows 8x32 inst data using block count (or array_len),
// it is up to optimization passes to decide on the block count usage.
@@ -29,17 +29,17 @@ func.func @load_store_no_array_len(%arg0: memref<8x32xf32>, %arg1: memref<8x32xf
// CHECK-LABEL: func.func @dpas_f16(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x16xf16>, %[[ARG1:[0-9a-zA-Z]+]]: memref<16x16xf16>, %[[ARG2:[0-9a-zA-Z]+]]: memref<8x16xf32>) {
-// CHECK: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} dense<0.000000e+00> : vector<8x16xf32>
-// CHECK: %[[T0:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<8x16xf16> -> !xegpu.tensor_desc<8x16xf16, #xegpu.layout<inst_data = [8, 16]>
-// CHECK: %[[T1:.*]] = xegpu.create_nd_tdesc %[[ARG1]] : memref<16x16xf16> -> !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [16, 16]>>
-// CHECK: %[[T2:.*]] = xegpu.load_nd %[[T0]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<8x16xf16, #xegpu.layout<inst_data = [8, 16]>> -> vector<8x16xf16>
-// CHECK: %[[T3:.*]] = xegpu.load_nd %[[T1]][0, 0] <{layout = #xegpu.layout<inst_data = [16, 16]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [16, 16]>> -> vector<16x16xf16>
-// CHECK: %[[T4:.*]] = xegpu.dpas %[[T2]], %[[T3]], %[[CST]] {layout_a = #xegpu.layout<inst_data = [8, 16]>, layout_b = #xegpu.layout<inst_data = [16, 16]>, layout_cd = #xegpu.layout<inst_data = [8, 16]>} :
+// CHECK: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>} dense<0.000000e+00> : vector<8x16xf32>
+// CHECK: %[[T0:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<8x16xf16> -> !xegpu.tensor_desc<8x16xf16, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>
+// CHECK: %[[T1:.*]] = xegpu.create_nd_tdesc %[[ARG1]] : memref<16x16xf16> -> !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [16, 16], lane_layout = [1, 16], lane_data = [2, 1]>>
+// CHECK: %[[T2:.*]] = xegpu.load_nd %[[T0]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<8x16xf16, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>> -> vector<8x16xf16>
+// CHECK: %[[T3:.*]] = xegpu.load_nd %[[T1]][0, 0] <{layout = #xegpu.layout<inst_data = [16, 16], lane_layout = [1, 16], lane_data = [2, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [16, 16], lane_layout = [1, 16], lane_data = [2, 1]>> -> vector<16x16xf16>
+// CHECK: %[[T4:.*]] = xegpu.dpas %[[T2]], %[[T3]], %[[CST]] {layout_a = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>, layout_b = #xegpu.layout<inst_data = [16, 16], lane_layout = [1, 16], lane_data = [2, 1]>, layout_cd = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>} :
// CHECK-SAME: vector<8x16xf16>, vector<16x16xf16>, vector<8x16xf32> -> vector<8x16xf32>
-// CHECK: %[[T5:.*]] = xegpu.create_nd_tdesc %[[ARG2]] : memref<8x16xf32> -> !xegpu.tensor_desc<8x16xf32, #xegpu.layout<inst_data = [8, 16]>
-// CHECK: xegpu.store_nd %[[T4]], %[[T5]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16]>}> : vector<8x16xf32>, !xegpu.tensor_desc<8x16xf32, #xegpu.layout<inst_data = [8, 16]>>
+// CHECK: %[[T5:.*]] = xegpu.create_nd_tdesc %[[ARG2]] : memref<8x16xf32> -> !xegpu.tensor_desc<8x16xf32, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>
+// CHECK: xegpu.store_nd %[[T4]], %[[T5]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> : vector<8x16xf32>, !xegpu.tensor_desc<8x16xf32, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
gpu.module @test {
func.func @dpas_f16(%arg0: memref<8x16xf16>, %arg1: memref<16x16xf16>, %arg2: memref<8x16xf32>) {
%c0 = arith.constant 0 : index
@@ -70,15 +70,15 @@ gpu.module @test_kernel {
%c_tdesc = xegpu.create_nd_tdesc %C : memref<1024x1024xf16> -> !xegpu.tensor_desc<16x32xf16>
scf.for %k = %c0 to %c1024 step %c32 {
- //CHECK: xegpu.load_nd {{.*}} <{layout = #xegpu.layout<inst_data = [8, 16]>}> :
- //CHECK-SAME: !xegpu.tensor_desc<16x32xf16, #xegpu.layout<inst_data = [8, 16]>> -> vector<16x32xf16>
+ //CHECK: xegpu.load_nd {{.*}} <{layout = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> :
+ //CHECK-SAME: !xegpu.tensor_desc<16x32xf16, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>> -> vector<16x32xf16>
%a = xegpu.load_nd %a_tdesc[0, %k] : !xegpu.tensor_desc<16x32xf16> -> vector<16x32xf16>
%b = xegpu.load_nd %b_tdesc[0, %k] : !xegpu.tensor_desc<16x32xf16> -> vector<16x32xf16>
- //CHECK-COUNT: arith.addf {{.*}} {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} : vector<16x32xf16>
+ //CHECK-COUNT: arith.addf {{.*}} {layout_result_0 = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>} : vector<16x32xf16>
%c = arith.addf %a, %b : vector<16x32xf16>
- //CHECK-COUNT: xegpu.store_nd {{.*}} : vector<16x32xf16>, !xegpu.tensor_desc<16x32xf16, #xegpu.layout<inst_data = [8, 16]>>
+ //CHECK-COUNT: xegpu.store_nd {{.*}} : vector<16x32xf16>, !xegpu.tensor_desc<16x32xf16, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
xegpu.store_nd %c, %c_tdesc[0, %k] : vector<16x32xf16>, !xegpu.tensor_desc<16x32xf16>
}
gpu.return
@@ -100,15 +100,15 @@ gpu.module @test_kernel {
%c_tdesc = xegpu.create_nd_tdesc %C : memref<1024x1024xf16> -> !xegpu.tensor_desc<12x32xf16>
scf.for %k = %c0 to %c1024 step %c32 {
- //CHECK: xegpu.load_nd {{.*}} <{layout = #xegpu.layout<inst_data = [4, 16]>}> :
- //CHECK-SAME: !xegpu.tensor_desc<12x32xf16, #xegpu.layout<inst_data = [4, 16]>> -> vector<12x32xf16>
+ //CHECK: xegpu.load_nd {{.*}} <{layout = #xegpu.layout<inst_data = [4, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> :
+ //CHECK-SAME: !xegpu.tensor_desc<12x32xf16, #xegpu.layout<inst_data = [4, 16], lane_layout = [1, 16], lane_data = [1, 1]>> -> vector<12x32xf16>
%a = xegpu.load_nd %a_tdesc[0, %k] : !xegpu.tensor_desc<12x32xf16> -> vector<12x32xf16>
%b = xegpu.load_nd %b_tdesc[0, %k] : !xegpu.tensor_desc<12x32xf16> -> vector<12x32xf16>
- //CHECK-COUNT: arith.addf {{.*}} {layout_result_0 = #xegpu.layout<inst_data = [4, 16]>} : vector<12x32xf16>
+ //CHECK-COUNT: arith.addf {{.*}} {layout_result_0 = #xegpu.layout<inst_data = [4, 16], lane_layout = [1, 16], lane_data = [1, 1]>} : vector<12x32xf16>
%c = arith.addf %a, %b : vector<12x32xf16>
- //CHECK-COUNT: xegpu.store_nd {{.*}} : vector<12x32xf16>, !xegpu.tensor_desc<12x32xf16, #xegpu.layout<inst_data = [4, 16]>>
+ //CHECK-COUNT: xegpu.store_nd {{.*}} : vector<12x32xf16>, !xegpu.tensor_desc<12x32xf16, #xegpu.layout<inst_data = [4, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
xegpu.store_nd %c, %c_tdesc[0, %k] : vector<12x32xf16>, !xegpu.tensor_desc<12x32xf16>
}
gpu.return
@@ -119,7 +119,7 @@ gpu.module @test_kernel {
gpu.module @test {
// CHECK-LABEL: func.func @store_matrix(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: !xegpu.mem_desc<16x64xf16>) {
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [1, 16]>} dense<0.000000e+00> : vector<16x16xf16>
+// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [1, 16], lane_layout = [1, 16], lane_data = [1, 1]>} dense<0.000000e+00> : vector<16x16xf16>
func.func @store_matrix(%arg0: !xegpu.mem_desc<16x64xf16>) {
%cst = arith.constant dense<0.0000> : vector<16x16xf16>
xegpu.store_matrix %cst, %arg0[8, 8]: vector<16x16xf16>, !xegpu.mem_desc<16x64xf16>
@@ -216,7 +216,7 @@ func.func @insert_strided_slice_inst_data_with_packing(%arg0: memref<8x64xi8>) {
// -----
gpu.module @test {
// CHECK-LABEL: func.func @vector_shape_cast_expand_non_unit_dims(
-// CHECK: %[[LOAD:.*]] = xegpu.load %arg0[%[[STEP:.*]]], %[[CST:.*]] <{layout = #xegpu.layout<inst_data = [16]>}> : memref<1024xf16>, vector<1024xindex>, vector<1024xi1> -> vector<1024xf16>
+// CHECK: %[[LOAD:.*]] = xegpu.load %arg0[%[[STEP:.*]]], %[[CST:.*]] <{layout = #xegpu.layout<inst_data = [16], lane_layout = [16], lane_data = [1]>}> : memref<1024xf16>, vector<1024xindex>, vector<1024xi1> -> vector<1024xf16>
// CHECK: %[[CAST:.*]] = vector.shape_cast %[[LOAD]] {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 16], lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>} : vector<1024xf16> to vector<8x8x16xf16>
// CHECK: %[[CST_0:.*]] = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 16], lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>, dims = [0]>} dense<0.000000e+00> : vector<8x16xf16>
// CHECK: %[[CST_1:.*]] = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 16], lane_layout = [1, 16], lane_data = [1, 1]>, dims = [0]>} dense<0.000000e+00> : vector<16xf16>
@@ -300,8 +300,8 @@ func.func @vector_shape_cast_expand_and_merge(%arg0: memref<256xf16>, %arg1: mem
// -----
gpu.module @test{
// CHECK-LABEL: load_store_matrix
- // CHECK: xegpu.load_matrix %{{.*}} <{layout = #xegpu.layout<inst_data = [1, 1]>}>
- // CHECK: xegpu.store_matrix %{{.*}} <{layout = #xegpu.layout<inst_data = [1, 1]>}>
+ // CHECK: xegpu.load_matrix %{{.*}} <{layout = #xegpu.layout<inst_data = [1, 1], lane_layout = [1, 1], lane_data = [1, 1]>}>
+ // CHECK: xegpu.store_matrix %{{.*}} <{layout = #xegpu.layout<inst_data = [1, 1], lane_layout = [1, 1], lane_data = [1, 1]>}>
func.func @load_store_matrix(%arg0: !xegpu.mem_desc<64x128xf32>, %arg1: i1) {
%c0 = arith.constant 0 : index
scf.if %arg1 {
@@ -315,11 +315,11 @@ gpu.module @test{
// -----
gpu.module @test{
// CHECK-LABEL: broadcast_both_leadingdims_innerdims
- // CHECK: arith.constant {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 1, 16]>} dense<true> : vector<2x2x6x32xi1>
- // CHECK: arith.constant {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 1, 16]>} dense<1.000000e+00> : vector<2x2x6x32xf32>
- // CHECK: vector.step {layout_result_0 = #xegpu.slice<#xegpu.slice<#xegpu.layout<inst_data = [1, 1, 1, 1]>, dims = [0, 1]>, dims = [1]>} : vector<6xindex>
- // CHECK: vector.shape_cast {{.*}} {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 1, 1]>, dims = [0, 1]>} : vector<6xindex> to vector<6x1xindex>
- // CHECK: vector.broadcast {{.*}} {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 1, 16]>} : vector<6x1xindex> to vector<2x2x6x32xindex>
+ // CHECK: arith.constant {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 1, 16], lane_layout = [1, 1, 1, 16], lane_data = [1, 1, 1, 1]>} dense<true> : vector<2x2x6x32xi1>
+ // CHECK: arith.constant {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 1, 16], lane_layout = [1, 1, 1, 16], lane_data = [1, 1, 1, 1]>} dense<1.000000e+00> : vector<2x2x6x32xf32>
+ // CHECK: vector.step {layout_result_0 = #xegpu.slice<#xegpu.slice<#xegpu.layout<inst_data = [1, 1, 1, 1], lane_layout = [1, 1, 1, 16], lane_data = [1, 1, 1, 1]>, dims = [0, 1]>, dims = [1]>} : vector<6xindex>
+ // CHECK: vector.shape_cast {{.*}} {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [1, 1, 1, 1], lane_layout = [1, 1, 1, 16], lane_data = [1, 1, 1, 1]>, dims = [0, 1]>} : vector<6xindex> to vector<6x1xindex>
+ // CHECK: vector.broadcast {{.*}} {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 1, 16], lane_layout = [1, 1, 1, 16], lane_data = [1, 1, 1, 1]>} : vector<6x1xindex> to vector<2x2x6x32xindex>
gpu.func @broadcast_both_leadingdims_innerdims(%arg0: memref<32x2x192xf32>, %arg1: memref<32x2x192xf32>, %arg2: memref<32x2x192xf32>) kernel attributes {known_block_size = array<i32: 768, 1, 1>, known_grid_size = array<i32: 16, 1, 1>} {
%cst = arith.constant dense<true> : vector<2x2x6x32xi1>
%cst_0 = arith.constant dense<1.000000e+00> : vector<2x2x6x32xf32>
@@ -350,7 +350,7 @@ gpu.module @test_collapse_dims [#xevm.target<O = 3, chip = "pvc">] {
%mask = arith.constant dense<true> : vector<32x32xi1>
%data = arith.constant dense<0.0> : vector<32x32xf32>
- // CHECK: xegpu.store {{.*}} <{{{.*}}layout = #xegpu.layout<inst_data = [32, 32]>{{.*}}}> :
+ // CHECK: xegpu.store {{.*}} <{{{.*}}layout = #xegpu.layout<inst_data = [32, 32], lane_layout = [1, 16], lane_data = [1, 2]>{{.*}}}> :
xegpu.store %data, %ptr_i64[%1], %mask {
layout = #xegpu.layout<inst_data = [32, 32]>
} : vector<32x32xf32>, i64, vector<32x32xindex>, vector<32x32xi1>
@@ -386,9 +386,9 @@ func.func @bitcast_ui8_to_f4(%arg0: memref<256x16xui8>) {
gpu.module @test {
// CHECK-LABEL: func.func @bitcast_ui16_to_f4(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<256x16xui16>) {
-// CHECK: %[[TDESC:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<256x16xui16> -> !xegpu.tensor_desc<256x16xui16, #xegpu.layout<inst_data = [32, 16]>>
-// CHECK: %[[LOAD:.*]] = xegpu.load_nd %[[TDESC]][0, 0] <{layout = #xegpu.layout<inst_data = [32, 16]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<256x16xui16, #xegpu.layout<inst_data = [32, 16]>> -> vector<256x16xui16>
+// CHECK: %[[TDESC:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<256x16xui16> -> !xegpu.tensor_desc<256x16xui16, #xegpu.layout<inst_data = [32, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
+// CHECK: %[[LOAD:.*]] = xegpu.load_nd %[[TDESC]][0, 0] <{layout = #xegpu.layout<inst_data = [32, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<256x16xui16, #xegpu.layout<inst_data = [32, 16], lane_layout = [1, 16], lane_data = [1, 1]>> -> vector<256x16xui16>
// CHECK: %[[BC:.*]] = vector.bitcast %[[LOAD]] {layout_result_0 = #xegpu.layout<inst_data = [32, 64]>} : vector<256x16xui16> to vector<256x64xf4E2M1FN>
// CHECK: xegpu.convert_layout %[[BC]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [32, 32]>, target_layout = #xegpu.layout<inst_data = [32, 32]>}>
>From 9b857eb60cf15df141eea6e16e77b5d2f76def90 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 05:09:00 +0000
Subject: [PATCH 12/20] load_gather setup: take consumer's inst_data as-is and
consumer's lane info when present. fall back to scatter-store default lane
factorization. updates 2 stale test cases.
---
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 62 +++++++++++++------
.../XeGPU/propagate-layout-inst-data.mlir | 18 +++---
2 files changed, 51 insertions(+), 29 deletions(-)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index ad591625754ae..5f50e84e94f6a 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -1307,15 +1307,24 @@ xegpu::DistributeLayoutAttr xegpu::setupInsertStridedSliceResultLayout(
// return {laneLayout, laneData};
// }
+// Forward declaration: defined later in this file.
+static std::pair<SmallVector<int>, SmallVector<int>>
+computeScatterStoreLaneLayoutAndData(ArrayRef<int64_t> srcShape,
+ int subgroupSize, int64_t maxChunkSize);
+
/// Sets up the anchor layout for load gather and load matrix operation.
/// load matrix lowers to load gather and 1d block load. All of them share the
/// same layout setup logic.
+///
/// For Subgroup layout, uses the consumer layout directly.
-/// For InstData layout, the innermost inst_data is taken directly from the
-/// consumer's inst_data, capped by `maxChunkSize * subgroupSize`.
-/// For Lane layout, lane_layout/lane_data are derived via
-/// `computeScatterLoadLaneLayoutAndData` using the consumer's
-/// lane_data[innermost] as the per-lane vector hint.
+///
+/// For InstData layout, takes consumer's inst_data as-is. lane_layout and
+/// lane_data are taken from the consumer when present; otherwise the helper
+/// derives the standard scatter-style default (subgroupSize lanes on the
+/// innermost dim, per-lane vector capped by maxChunkSize).
+///
+/// For Lane layout, lane_layout/lane_data are taken from the consumer when
+/// present; otherwise derived from the same default.
static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
xegpu::LayoutKind layoutKind, mlir::MLIRContext *context,
xegpu::DistributeLayoutAttr consumerLayout, int maxChunkSize,
@@ -1326,28 +1335,41 @@ static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
SmallVector<int64_t> consumerInstData =
consumerLayout.getEffectiveInstDataAsInt();
- SmallVector<int64_t> consumerLaneData =
- consumerLayout.getEffectiveLaneDataAsInt();
SmallVector<int64_t> consumerLaneLayout =
consumerLayout.getEffectiveLaneLayoutAsInt();
- SmallVector<int64_t> laneLayout(resShape.size(), 1);
- SmallVector<int64_t> laneData(resShape.size(), 1);
+ SmallVector<int64_t> consumerLaneData =
+ consumerLayout.getEffectiveLaneDataAsInt();
+
+ // Pick lane_layout / lane_data: prefer consumer's, fall back to the
+ // scatter-store default (subgroupSize lanes on innermost dim, per-lane
+ // vector capped by maxChunkSize).
+ SmallVector<int64_t> laneLayout;
+ SmallVector<int64_t> laneData;
+ if (!consumerLaneLayout.empty() && !consumerLaneData.empty()) {
+ laneLayout.assign(consumerLaneLayout.begin(), consumerLaneLayout.end());
+ laneData.assign(consumerLaneData.begin(), consumerLaneData.end());
+ } else {
+ auto [defLaneLayout, defLaneData] = computeScatterStoreLaneLayoutAndData(
+ resShape, subgroupSize, maxChunkSize);
+ laneLayout.assign(defLaneLayout.begin(), defLaneLayout.end());
+ laneData.assign(defLaneData.begin(), defLaneData.end());
+ }
if (layoutKind == xegpu::LayoutKind::InstData) {
- SmallVector<int64_t> instData(resShape.size(), 1);
- laneData.back() = std::min(static_cast<int64_t>(consumerLaneData.back()),
- int64_t(maxChunkSize));
- laneLayout.back() = consumerLaneLayout.back();
- instData.back() = laneData.back() * laneLayout.back();
+ // Take consumer's inst_data as-is. If the consumer doesn't have one,
+ // fall back to lane_layout * lane_data per dim.
+ SmallVector<int64_t> instData;
+ if (!consumerInstData.empty()) {
+ instData.assign(consumerInstData.begin(), consumerInstData.end());
+ } else {
+ instData.resize(resShape.size());
+ for (size_t i = 0; i < resShape.size(); ++i)
+ instData[i] = laneLayout[i] * laneData[i];
+ }
return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
}
- if (layoutKind == xegpu::LayoutKind::Lane) {
-
- laneData.back() = std::min(static_cast<int64_t>(consumerLaneData.back()),
- int64_t(maxChunkSize));
- laneLayout.back() = consumerLaneLayout.back();
+ if (layoutKind == xegpu::LayoutKind::Lane)
return buildLaneLayout(context, laneLayout, laneData);
- }
return nullptr;
}
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
index da4a2e32259de..152543f338083 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
@@ -133,11 +133,11 @@ func.func @store_matrix(%arg0: !xegpu.mem_desc<16x64xf16>) {
gpu.module @test {
// CHECK-LABEL: func.func @scatter_ops_coalesce_chunksize(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<512xf32>) {
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<true> : vector<16x32xi1>
-// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16]>} dense<12> : vector<16x32xindex>
-// CHECK: %{{.*}} = xegpu.load %[[ARG0]][%{{.*}}], %{{.*}} <{layout = #xegpu.layout<inst_data = [16, 16]>}> :
+// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16, 16], lane_layout = [1, 16], lane_data = [1, 1]>} dense<true> : vector<16x32xi1>
+// CHECK: %{{.*}} = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16, 16], lane_layout = [1, 16], lane_data = [1, 1]>} dense<12> : vector<16x32xindex>
+// CHECK: %{{.*}} = xegpu.load %[[ARG0]][%{{.*}}], %{{.*}} <{layout = #xegpu.layout<inst_data = [16, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> :
// CHECK-SAME: memref<512xf32>, vector<16x32xindex>, vector<16x32xi1> -> vector<16x32xf32>
-// CHECK: xegpu.store %0, %[[ARG0]][%{{.*}}], %{{.*}} <{layout = #xegpu.layout<inst_data = [16, 16]>}> :
+// CHECK: xegpu.store %0, %[[ARG0]][%{{.*}}], %{{.*}} <{layout = #xegpu.layout<inst_data = [16, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> :
// CHECK-SAME: vector<16x32xf32>, memref<512xf32>, vector<16x32xindex>, vector<16x32xi1>
func.func @scatter_ops_coalesce_chunksize(%src: memref<512xf32>) {
%1 = arith.constant dense<1>: vector<16x32xi1>
@@ -179,11 +179,11 @@ func.func @load_gather_with_coalesce_chunksize(%arg0: memref<8x16xf16>, %arg1: m
gpu.module @test {
// CHECK-LABEL: func.func @insert_strided_slice_inst_data_no_packing(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x32xf32>) {
-// CHECK: %[[CST_SMALL:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} dense<1.000000e+00> : vector<4x16xf32>
-// CHECK: %[[CST_LARGE:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} dense<0.000000e+00> : vector<8x32xf32>
-// CHECK: %[[INSERT:.*]] = vector.insert_strided_slice %[[CST_SMALL]], %[[CST_LARGE]] {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>, offsets = [0, 0], strides = [1, 1]} : vector<4x16xf32> into vector<8x32xf32>
-// CHECK: %[[TDESC:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<8x32xf32> -> !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16]>>
-// CHECK: xegpu.store_nd %[[INSERT]], %[[TDESC]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16]>}> : vector<8x32xf32>, !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16]>>
+// CHECK: %[[CST_SMALL:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>} dense<1.000000e+00> : vector<4x16xf32>
+// CHECK: %[[CST_LARGE:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>} dense<0.000000e+00> : vector<8x32xf32>
+// CHECK: %[[INSERT:.*]] = vector.insert_strided_slice %[[CST_SMALL]], %[[CST_LARGE]] {layout_result_0 = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>, offsets = [0, 0], strides = [1, 1]} : vector<4x16xf32> into vector<8x32xf32>
+// CHECK: %[[TDESC:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<8x32xf32> -> !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
+// CHECK: xegpu.store_nd %[[INSERT]], %[[TDESC]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> : vector<8x32xf32>, !xegpu.tensor_desc<8x32xf32, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
func.func @insert_strided_slice_inst_data_no_packing(%arg0: memref<8x32xf32>) {
%c0 = arith.constant 0 : index
%cst_small = arith.constant dense<1.0> : vector<4x16xf32>
>From 63f6d96babd30febbc5b0de16d7b7ca258af7d8b Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 05:34:36 +0000
Subject: [PATCH 13/20] createScaleLayout: cap scale lane_layout by inst_data
and derive lane_data so the dpas_mx scale operand satisfies Cat A invariant.
setupGenericNdAnchorLayout: fall back to lane_layout * lane_data when
block-WHC lookup yields no divisor (sub-byte float types).
---
.../XeGPU/Transforms/XeGPULayoutImpl.cpp | 41 ++++++++++++++-----
1 file changed, 31 insertions(+), 10 deletions(-)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index 5f50e84e94f6a..0bc962bb868d1 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -1714,19 +1714,26 @@ static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
consumerLayout.isForWorkgroup())
return consumerLayout;
- // Compute the default inst_data from hardware block params.
+ // Compute inst_data from hardware block params. For Nd ops, the lane
+ // factorization above (laneLayout / laneData) is rigid; inst_data must be
+ // a multiple of lane_layout * lane_data on each dim (Category A
+ // invariant). If block params are unavailable for this element type
+ // (e.g. sub-byte floats with no uArch entry), fall back to
+ // lane_layout * lane_data (k = 1).
SmallVector<int64_t> instData(rank, 1);
int instWidth = xegpu::getLargestDivisor(
static_cast<int>(dataShape.back()), bWidths);
if (instWidth == -1)
- return nullptr;
- instData.back() = instWidth;
+ instData.back() = laneLayout.back() * laneData.back();
+ else
+ instData.back() = instWidth;
if (rank >= 2) {
int instHeight = xegpu::getLargestDivisor(
static_cast<int>(dataShape[rank - 2]), bHeights);
if (instHeight == -1)
- return nullptr;
- instData[rank - 2] = instHeight;
+ instData[rank - 2] = laneLayout[rank - 2] * laneData[rank - 2];
+ else
+ instData[rank - 2] = instHeight;
}
// Honor the consumer's inst_data if it is uArch-valid.
@@ -2217,15 +2224,29 @@ createScaleLayout(mlir::MLIRContext *context, VectorType matrixTy,
scaleLaneLayout.assign(laneLayout.begin(), laneLayout.end());
scaleLaneData.assign(laneData.begin(), laneData.end());
bool isRowMajor = uArchInstruction->isLaneLayoutRowMajorOrder();
- if (isBScale ^ isRowMajor) {
+ if (isBScale ^ isRowMajor)
std::swap(scaleLaneLayout[rank - 2], scaleLaneLayout[rank - 1]);
+ // Cap lane_layout by the per-instruction tile (inst_data) on each dim.
+ // Then derive lane_data = inst_data / lane_layout so the Category A
+ // invariant inst_data = lane_layout * lane_data * k (with k = 1) holds
+ // for the scale operand's load_nd consumer.
+ if (!scaleInstData.empty()) {
+ for (int64_t d = rank - 2; d < rank; ++d) {
+ scaleLaneLayout[d] =
+ std::min<int64_t>(scaleInstData[d], scaleLaneLayout[d]);
+ scaleLaneData[d] = std::max<int64_t>(
+ scaleInstData[d] / std::max<int64_t>(scaleLaneLayout[d], 1), 1);
+ }
+ } else {
+ // No inst_data on the matrix layout; fall back to capping by scale
+ // shape and deriving lane_data from it (legacy behavior).
scaleLaneLayout[rank - 2] =
std::min<int64_t>(scaleShape[rank - 2], scaleLaneLayout[rank - 2]);
+ scaleLaneData[rank - 2] = std::max<int64_t>(
+ scaleShape[rank - 2] / scaleLaneLayout[rank - 2], 1);
+ scaleLaneData[rank - 1] = std::max<int64_t>(
+ scaleShape[rank - 1] / scaleLaneLayout[rank - 1], 1);
}
- scaleLaneData[rank - 2] =
- std::max<int64_t>(scaleShape[rank - 2] / scaleLaneLayout[rank - 2], 1);
- scaleLaneData[rank - 1] =
- std::max<int64_t>(scaleShape[rank - 1] / scaleLaneLayout[rank - 1], 1);
}
return xegpu::LayoutAttr::get(
context,
>From 78daa3e7548449023844461267d6947fba02fb69 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 05:58:13 +0000
Subject: [PATCH 14/20] test: update case 19 (dpas_mx_f4e2m1) check lines.
load_nd anchors carry full inst_data + lane info; load_nd's inst_data is
allowed to be larger than dpas_mx's per-instruction tile (multiple-of
relation).
---
.../XeGPU/propagate-layout-inst-data.mlir | 32 +++++++++----------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
index 152543f338083..692d61f9797fe 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
@@ -452,24 +452,24 @@ func.func @dpas_mx_f8e5m2(%arg0: memref<16x64xf8E5M2>, %arg1: memref<64x32xf8E5M
// CHECK-LABEL: func.func @dpas_mx_f4e2m1
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<16x128xf4E2M1FN>, %[[ARG1:[0-9a-zA-Z]+]]: memref<128x32xf4E2M1FN>, %[[ARG2:[0-9a-zA-Z]+]]: memref<16x32xbf16>
// CHECK-SAME: %[[ARG3:[0-9a-zA-Z]+]]: memref<16x4xf8E8M0FNU>, %[[ARG4:[0-9a-zA-Z]+]]: memref<4x32xf8E8M0FNU>
-// CHECK: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} dense<0.000000e+00> : vector<16x32xbf16>
-// CHECK: %[[T0:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<16x128xf4E2M1FN> -> !xegpu.tensor_desc<16x128xf4E2M1FN, #xegpu.layout<inst_data = [8, 64]>>
-// CHECK: %[[T1:.*]] = xegpu.create_nd_tdesc %[[ARG1]] : memref<128x32xf4E2M1FN> -> !xegpu.tensor_desc<128x32xf4E2M1FN, #xegpu.layout<inst_data = [64, 16]>>
-// CHECK: %[[T2:.*]] = xegpu.load_nd %[[T0]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 64]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<16x128xf4E2M1FN, #xegpu.layout<inst_data = [8, 64]>> -> vector<16x128xf4E2M1FN>
-// CHECK: %[[T3:.*]] = xegpu.load_nd %[[T1]][0, 0] <{layout = #xegpu.layout<inst_data = [64, 16]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<128x32xf4E2M1FN, #xegpu.layout<inst_data = [64, 16]>> -> vector<128x32xf4E2M1FN>
-// CHECK: %[[T4:.*]] = xegpu.create_nd_tdesc %[[ARG3]] : memref<16x4xf8E8M0FNU> -> !xegpu.tensor_desc<16x4xf8E8M0FNU, #xegpu.layout<inst_data = [8, 2]>>
-// CHECK: %[[T5:.*]] = xegpu.load_nd %[[T4]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 2]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<16x4xf8E8M0FNU, #xegpu.layout<inst_data = [8, 2]>> -> vector<16x4xf8E8M0FNU>
-// CHECK: %[[T6:.*]] = xegpu.create_nd_tdesc %[[ARG4]] : memref<4x32xf8E8M0FNU> -> !xegpu.tensor_desc<4x32xf8E8M0FNU, #xegpu.layout<inst_data = [2, 16]>>
-// CHECK: %[[T7:.*]] = xegpu.load_nd %[[T6]][0, 0] <{layout = #xegpu.layout<inst_data = [2, 16]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<4x32xf8E8M0FNU, #xegpu.layout<inst_data = [2, 16]>> -> vector<4x32xf8E8M0FNU>
+// CHECK: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>} dense<0.000000e+00> : vector<16x32xbf16>
+// CHECK: %[[T0:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<16x128xf4E2M1FN> -> !xegpu.tensor_desc<16x128xf4E2M1FN>
+// CHECK: %[[T1:.*]] = xegpu.create_nd_tdesc %[[ARG1]] : memref<128x32xf4E2M1FN> -> !xegpu.tensor_desc<128x32xf4E2M1FN>
+// CHECK: %[[T2:.*]] = xegpu.load_nd %[[T0]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 64], lane_layout = [1, 16], lane_data = [1, 4]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<16x128xf4E2M1FN> -> vector<16x128xf4E2M1FN>
+// CHECK: %[[T3:.*]] = xegpu.load_nd %[[T1]][0, 0] <{layout = #xegpu.layout<inst_data = [64, 16], lane_layout = [1, 16], lane_data = [8, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<128x32xf4E2M1FN> -> vector<128x32xf4E2M1FN>
+// CHECK: %[[T4:.*]] = xegpu.create_nd_tdesc %[[ARG3]] : memref<16x4xf8E8M0FNU> -> !xegpu.tensor_desc<16x4xf8E8M0FNU, #xegpu.layout<inst_data = [16, 2], lane_layout = [8, 1], lane_data = [1, 2]>>
+// CHECK: %[[T5:.*]] = xegpu.load_nd %[[T4]][0, 0] <{layout = #xegpu.layout<inst_data = [16, 2], lane_layout = [8, 1], lane_data = [1, 2]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<16x4xf8E8M0FNU, #xegpu.layout<inst_data = [16, 2], lane_layout = [8, 1], lane_data = [1, 2]>> -> vector<16x4xf8E8M0FNU>
+// CHECK: %[[T6:.*]] = xegpu.create_nd_tdesc %[[ARG4]] : memref<4x32xf8E8M0FNU> -> !xegpu.tensor_desc<4x32xf8E8M0FNU, #xegpu.layout<inst_data = [4, 32], lane_layout = [1, 16], lane_data = [2, 1]>>
+// CHECK: %[[T7:.*]] = xegpu.load_nd %[[T6]][0, 0] <{layout = #xegpu.layout<inst_data = [4, 32], lane_layout = [1, 16], lane_data = [2, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<4x32xf8E8M0FNU, #xegpu.layout<inst_data = [4, 32], lane_layout = [1, 16], lane_data = [2, 1]>> -> vector<4x32xf8E8M0FNU>
// CHECK: %[[T8:.*]] = xegpu.dpas_mx %[[T2]], %[[T3]], %[[CST]] scale_a = %[[T5]] scale_b = %[[T7]]
-// CHECK-SAME: {layout_a = #xegpu.layout<inst_data = [8, 64]>, layout_a_scale = #xegpu.layout<inst_data = [8, 2]>, layout_b = #xegpu.layout<inst_data = [64, 16]>, layout_b_scale = #xegpu.layout<inst_data = [2, 16]>, layout_cd = #xegpu.layout<inst_data = [8, 16]>} :
+// CHECK-SAME: {layout_a = #xegpu.layout<inst_data = [8, 64], lane_layout = [1, 16], lane_data = [1, 4]>, layout_a_scale = #xegpu.layout<inst_data = [8, 2], lane_layout = [8, 1], lane_data = [1, 2]>, layout_b = #xegpu.layout<inst_data = [64, 16], lane_layout = [1, 16], lane_data = [8, 1]>, layout_b_scale = #xegpu.layout<inst_data = [2, 16], lane_layout = [1, 16], lane_data = [2, 1]>, layout_cd = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>} :
// CHECK-SAME: (vector<16x128xf4E2M1FN>, vector<128x32xf4E2M1FN>, vector<16x32xbf16>, vector<16x4xf8E8M0FNU>, vector<4x32xf8E8M0FNU>) -> vector<16x32xbf16>
-// CHECK: %[[T9:.*]] = xegpu.create_nd_tdesc %[[ARG2]] : memref<16x32xbf16> -> !xegpu.tensor_desc<16x32xbf16, #xegpu.layout<inst_data = [8, 16]>>
-// CHECK: xegpu.store_nd %[[T8]], %[[T9]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16]>}> : vector<16x32xbf16>, !xegpu.tensor_desc<16x32xbf16, #xegpu.layout<inst_data = [8, 16]>>
+// CHECK: %[[T9:.*]] = xegpu.create_nd_tdesc %[[ARG2]] : memref<16x32xbf16> -> !xegpu.tensor_desc<16x32xbf16, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
+// CHECK: xegpu.store_nd %[[T8]], %[[T9]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> : vector<16x32xbf16>, !xegpu.tensor_desc<16x32xbf16, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
gpu.module @test {
func.func @dpas_mx_f4e2m1(%arg0: memref<16x128xf4E2M1FN>, %arg1: memref<128x32xf4E2M1FN>, %arg2: memref<16x32xbf16>,
%arg3: memref<16x4xf8E8M0FNU>, %arg4: memref<4x32xf8E8M0FNU>) {
>From 5225fe7f9bd70a1356174cae4341103f3e67afdb Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 05:59:57 +0000
Subject: [PATCH 15/20] test: update case 6
(load_gather_with_coalesce_chunksize) check lines. mask/offset constants are
2D vector<16x16> matching the source IR (not 1D as previous CHECK assumed);
load_gather anchor includes full inst_data + lane info.
---
mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
index 692d61f9797fe..b0b7af8e801eb 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
@@ -155,10 +155,10 @@ func.func @scatter_ops_coalesce_chunksize(%src: memref<512xf32>) {
gpu.module @test {
// CHECK-LABEL: func.func @load_gather_with_coalesce_chunksize(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x16xf16>, %[[ARG1:[0-9a-zA-Z]+]]: memref<256xf16>, %[[ARG2:[0-9a-zA-Z]+]]: memref<8x16xf32>) {
-// CHECK: %[[OFFSET:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>}
-// CHECK-SAME: dense<[0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240]> : vector<16xindex>
-// CHECK-NEXT: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<true> : vector<16xi1>
-// CHECK-NEXT: %{{.*}} = xegpu.load %arg1[%[[OFFSET]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>}> : memref<256xf16>, vector<16xindex>, vector<16xi1> -> vector<16x16xf16>
+// CHECK: %[[OFFSET:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16, 16], lane_layout = [16, 1], lane_data = [1, 2]>}
+// CHECK-SAME: dense<0> : vector<16x16xindex>
+// CHECK-NEXT: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16, 16], lane_layout = [16, 1], lane_data = [1, 2]>} dense<true> : vector<16x16xi1>
+// CHECK-NEXT: %{{.*}} = xegpu.load %arg1[%[[OFFSET]]], %[[MASK]] <{layout = #xegpu.layout<inst_data = [16, 16], lane_layout = [16, 1], lane_data = [1, 2]>}> : memref<256xf16>, vector<16x16xindex>, vector<16x16xi1> -> vector<16x16xf16>
func.func @load_gather_with_coalesce_chunksize(%arg0: memref<8x16xf16>, %arg1: memref<256xf16>, %arg2: memref<8x16xf32>) {
%c0 = arith.constant 0 : index
%0 = xegpu.create_nd_tdesc %arg0 : memref<8x16xf16> -> !xegpu.tensor_desc<8x16xf16>
>From d10c559f68b0d6516313dd047468c1c30b463a73 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 06:08:36 +0000
Subject: [PATCH 16/20] test: update case 12
(vector_shape_cast_expand_and_merge) check lines. layouts now carry full
inst_data + lane_layout + lane_data; lane_data = 2 reflects scatter-load's
per-lane chunk for f16.
---
.../Dialect/XeGPU/propagate-layout-inst-data.mlir | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
index b0b7af8e801eb..c8525eefde1e4 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
@@ -277,13 +277,13 @@ func.func @vector_2d_reduction_with_fractional_subgroup_size_1x4x1(%arg0: memref
// -----
gpu.module @test {
// CHECK-LABEL: func.func @vector_shape_cast_expand_and_merge(
-// CHECK: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [32]>} dense<true> : vector<256xi1>
-// CHECK: %[[STEP:.*]] = vector.step {layout_result_0 = #xegpu.layout<inst_data = [32]>} : vector<256xindex>
-// CHECK: %[[LOAD:.*]] = xegpu.load %arg0[%[[STEP]]], %[[CST]] <{layout = #xegpu.layout<inst_data = [32]>}> : memref<256xf16>, vector<256xindex>, vector<256xi1> -> vector<256xf16>
-// CHECK: %[[CAST_0:.*]] = vector.shape_cast %[[LOAD]] {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 32]>} : vector<256xf16> to vector<2x4x32xf16>
-// CHECK: %[[CAST_1:.*]] = vector.shape_cast %[[CAST_0]] {layout_result_0 = #xegpu.layout<inst_data = [1, 32]>} : vector<2x4x32xf16> to vector<1x256xf16>
-// CHECK: %[[CAST_2:.*]] = vector.shape_cast %[[CAST_1]] {layout_result_0 = #xegpu.layout<inst_data = [32]>} : vector<1x256xf16> to vector<256xf16>
-// CHECK: xegpu.store %[[CAST_2]], %arg1[%[[STEP]]], %[[CST]] <{layout = #xegpu.layout<inst_data = [32]>}> : vector<256xf16>, memref<256xf16>, vector<256xindex>, vector<256xi1>
+// CHECK: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [32], lane_layout = [16], lane_data = [2]>} dense<true> : vector<256xi1>
+// CHECK: %[[STEP:.*]] = vector.step {layout_result_0 = #xegpu.layout<inst_data = [32], lane_layout = [16], lane_data = [2]>} : vector<256xindex>
+// CHECK: %[[LOAD:.*]] = xegpu.load %arg0[%[[STEP]]], %[[CST]] <{layout = #xegpu.layout<inst_data = [32], lane_layout = [16], lane_data = [2]>}> : memref<256xf16>, vector<256xindex>, vector<256xi1> -> vector<256xf16>
+// CHECK: %[[CAST_0:.*]] = vector.shape_cast %[[LOAD]] {layout_result_0 = #xegpu.layout<inst_data = [1, 1, 32], lane_layout = [1, 1, 16], lane_data = [1, 1, 2]>} : vector<256xf16> to vector<2x4x32xf16>
+// CHECK: %[[CAST_1:.*]] = vector.shape_cast %[[CAST_0]] {layout_result_0 = #xegpu.layout<inst_data = [1, 32], lane_layout = [1, 16], lane_data = [1, 2]>} : vector<2x4x32xf16> to vector<1x256xf16>
+// CHECK: %[[CAST_2:.*]] = vector.shape_cast %[[CAST_1]] {layout_result_0 = #xegpu.layout<inst_data = [32], lane_layout = [16], lane_data = [2]>} : vector<1x256xf16> to vector<256xf16>
+// CHECK: xegpu.store %[[CAST_2]], %arg1[%[[STEP]]], %[[CST]] <{layout = #xegpu.layout<inst_data = [32], lane_layout = [16], lane_data = [2]>}> : vector<256xf16>, memref<256xf16>, vector<256xindex>, vector<256xi1>
func.func @vector_shape_cast_expand_and_merge(%arg0: memref<256xf16>, %arg1: memref<256xf16>) {
%cst = arith.constant dense<true> : vector<256xi1>
%0 = vector.step : vector<256xindex>
>From 3159743df131815e4cde47a405b2198a43d12484 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 06:12:33 +0000
Subject: [PATCH 17/20] test: update case 18 (dpas_mx_f8e5m2) check lines.
load_nd anchors carry full inst_data + lane info; load_nd inst_data may
exceed dpas_mx per-instruction tile (multiple-of relation).
---
.../XeGPU/propagate-layout-inst-data.mlir | 32 +++++++++----------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
index c8525eefde1e4..2d4e22f69dd11 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
@@ -410,24 +410,24 @@ func.func @bitcast_ui16_to_f4(%arg0: memref<256x16xui16>) {
// CHECK-LABEL: func.func @dpas_mx_f8e5m2
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<16x64xf8E5M2>, %[[ARG1:[0-9a-zA-Z]+]]: memref<64x32xf8E5M2>, %[[ARG2:[0-9a-zA-Z]+]]: memref<16x32xbf16>
// CHECK-SAME: %[[ARG3:[0-9a-zA-Z]+]]: memref<16x2xf8E8M0FNU>, %[[ARG4:[0-9a-zA-Z]+]]: memref<2x32xf8E8M0FNU>
-// CHECK: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} dense<0.000000e+00> : vector<16x32xbf16>
-// CHECK: %[[T0:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<16x64xf8E5M2> -> !xegpu.tensor_desc<16x64xf8E5M2, #xegpu.layout<inst_data = [8, 32]>>
-// CHECK: %[[T1:.*]] = xegpu.create_nd_tdesc %[[ARG1]] : memref<64x32xf8E5M2> -> !xegpu.tensor_desc<64x32xf8E5M2, #xegpu.layout<inst_data = [32, 16]>>
-// CHECK: %[[T2:.*]] = xegpu.load_nd %[[T0]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 32]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<16x64xf8E5M2, #xegpu.layout<inst_data = [8, 32]>> -> vector<16x64xf8E5M2>
-// CHECK: %[[T3:.*]] = xegpu.load_nd %[[T1]][0, 0] <{layout = #xegpu.layout<inst_data = [32, 16]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<64x32xf8E5M2, #xegpu.layout<inst_data = [32, 16]>> -> vector<64x32xf8E5M2>
-// CHECK: %[[T4:.*]] = xegpu.create_nd_tdesc %[[ARG3]] : memref<16x2xf8E8M0FNU> -> !xegpu.tensor_desc<16x2xf8E8M0FNU, #xegpu.layout<inst_data = [8, 1]>>
-// CHECK: %[[T5:.*]] = xegpu.load_nd %[[T4]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 1]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<16x2xf8E8M0FNU, #xegpu.layout<inst_data = [8, 1]>> -> vector<16x2xf8E8M0FNU>
-// CHECK: %[[T6:.*]] = xegpu.create_nd_tdesc %[[ARG4]] : memref<2x32xf8E8M0FNU> -> !xegpu.tensor_desc<2x32xf8E8M0FNU, #xegpu.layout<inst_data = [1, 16]>>
-// CHECK: %[[T7:.*]] = xegpu.load_nd %[[T6]][0, 0] <{layout = #xegpu.layout<inst_data = [1, 16]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<2x32xf8E8M0FNU, #xegpu.layout<inst_data = [1, 16]>> -> vector<2x32xf8E8M0FNU>
+// CHECK: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>} dense<0.000000e+00> : vector<16x32xbf16>
+// CHECK: %[[T0:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<16x64xf8E5M2> -> !xegpu.tensor_desc<16x64xf8E5M2, #xegpu.layout<inst_data = [8, 32], lane_layout = [1, 16], lane_data = [1, 2]>>
+// CHECK: %[[T1:.*]] = xegpu.create_nd_tdesc %[[ARG1]] : memref<64x32xf8E5M2> -> !xegpu.tensor_desc<64x32xf8E5M2, #xegpu.layout<inst_data = [32, 32], lane_layout = [1, 16], lane_data = [4, 1]>>
+// CHECK: %[[T2:.*]] = xegpu.load_nd %[[T0]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 32], lane_layout = [1, 16], lane_data = [1, 2]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<16x64xf8E5M2, #xegpu.layout<inst_data = [8, 32], lane_layout = [1, 16], lane_data = [1, 2]>> -> vector<16x64xf8E5M2>
+// CHECK: %[[T3:.*]] = xegpu.load_nd %[[T1]][0, 0] <{layout = #xegpu.layout<inst_data = [32, 32], lane_layout = [1, 16], lane_data = [4, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<64x32xf8E5M2, #xegpu.layout<inst_data = [32, 32], lane_layout = [1, 16], lane_data = [4, 1]>> -> vector<64x32xf8E5M2>
+// CHECK: %[[T4:.*]] = xegpu.create_nd_tdesc %[[ARG3]] : memref<16x2xf8E8M0FNU> -> !xegpu.tensor_desc<16x2xf8E8M0FNU, #xegpu.layout<inst_data = [16, 1], lane_layout = [8, 1], lane_data = [1, 1]>>
+// CHECK: %[[T5:.*]] = xegpu.load_nd %[[T4]][0, 0] <{layout = #xegpu.layout<inst_data = [16, 1], lane_layout = [8, 1], lane_data = [1, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<16x2xf8E8M0FNU, #xegpu.layout<inst_data = [16, 1], lane_layout = [8, 1], lane_data = [1, 1]>> -> vector<16x2xf8E8M0FNU>
+// CHECK: %[[T6:.*]] = xegpu.create_nd_tdesc %[[ARG4]] : memref<2x32xf8E8M0FNU> -> !xegpu.tensor_desc<2x32xf8E8M0FNU, #xegpu.layout<inst_data = [2, 32], lane_layout = [1, 16], lane_data = [1, 1]>>
+// CHECK: %[[T7:.*]] = xegpu.load_nd %[[T6]][0, 0] <{layout = #xegpu.layout<inst_data = [2, 32], lane_layout = [1, 16], lane_data = [1, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<2x32xf8E8M0FNU, #xegpu.layout<inst_data = [2, 32], lane_layout = [1, 16], lane_data = [1, 1]>> -> vector<2x32xf8E8M0FNU>
// CHECK: %[[T8:.*]] = xegpu.dpas_mx %[[T2]], %[[T3]], %[[CST]] scale_a = %[[T5]] scale_b = %[[T7]]
-// CHECK-SAME: {layout_a = #xegpu.layout<inst_data = [8, 32]>, layout_a_scale = #xegpu.layout<inst_data = [8, 1]>, layout_b = #xegpu.layout<inst_data = [32, 16]>, layout_b_scale = #xegpu.layout<inst_data = [1, 16]>, layout_cd = #xegpu.layout<inst_data = [8, 16]>} :
+// CHECK-SAME: {layout_a = #xegpu.layout<inst_data = [8, 32], lane_layout = [1, 16], lane_data = [1, 2]>, layout_a_scale = #xegpu.layout<inst_data = [8, 1], lane_layout = [8, 1], lane_data = [1, 1]>, layout_b = #xegpu.layout<inst_data = [32, 16], lane_layout = [1, 16], lane_data = [4, 1]>, layout_b_scale = #xegpu.layout<inst_data = [1, 16], lane_layout = [1, 16], lane_data = [1, 1]>, layout_cd = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>} :
// CHECK-SAME: (vector<16x64xf8E5M2>, vector<64x32xf8E5M2>, vector<16x32xbf16>, vector<16x2xf8E8M0FNU>, vector<2x32xf8E8M0FNU>) -> vector<16x32xbf16>
-// CHECK: %[[T9:.*]] = xegpu.create_nd_tdesc %[[ARG2]] : memref<16x32xbf16> -> !xegpu.tensor_desc<16x32xbf16, #xegpu.layout<inst_data = [8, 16]>>
-// CHECK: xegpu.store_nd %[[T8]], %[[T9]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16]>}> : vector<16x32xbf16>, !xegpu.tensor_desc<16x32xbf16, #xegpu.layout<inst_data = [8, 16]>>
+// CHECK: %[[T9:.*]] = xegpu.create_nd_tdesc %[[ARG2]] : memref<16x32xbf16> -> !xegpu.tensor_desc<16x32xbf16, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
+// CHECK: xegpu.store_nd %[[T8]], %[[T9]][0, 0] <{layout = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> : vector<16x32xbf16>, !xegpu.tensor_desc<16x32xbf16, #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
gpu.module @test {
func.func @dpas_mx_f8e5m2(%arg0: memref<16x64xf8E5M2>, %arg1: memref<64x32xf8E5M2>, %arg2: memref<16x32xbf16>,
%arg3: memref<16x2xf8E8M0FNU>, %arg4: memref<2x32xf8E8M0FNU>) {
>From a2098b60827a5f678467beb55146495b1a3db97c Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 06:23:47 +0000
Subject: [PATCH 18/20] setupGenericNdAnchorLayout: cap default lane_data and
lane_layout by innermost shape so the lane factorization always fits the
tensor (e.g., ui8 with shape innermost=16 used full packing factor 2 giving
lane_product=32 > 16 and produced an invalid layout). also update case 16
check lines.
---
.../Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp | 16 +++++++++++++---
.../XeGPU/propagate-layout-inst-data.mlir | 6 +++---
2 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index 0bc962bb868d1..d3244fb677c79 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -1667,13 +1667,23 @@ static xegpu::DistributeLayoutAttr setupGenericNdAnchorLayout(
int rank = dataShape.size();
assert(rank >= 1 && "Expected at least 1D shape for ND op");
- // Compute the default 2D block IO lane layout / lane data.
+ // Compute the default 2D block IO lane layout / lane data. Cap each by
+ // the innermost shape so the product `lane_layout * lane_data` doesn't
+ // exceed it (e.g. ui8 with shape innermost=16 cannot use the full
+ // packing factor of 2 because subgroupSize * 2 = 32 > 16).
unsigned bitwidth = elemTy.getIntOrFloatBitWidth();
int packingFactor = bitwidth < packingSize ? packingSize / bitwidth : 1;
SmallVector<int64_t> laneLayout(rank, 1);
SmallVector<int64_t> laneData(rank, 1);
- laneLayout.back() = uArch->getSubgroupSize();
- laneData.back() = packingFactor;
+ int64_t innermostShape = dataShape.back();
+ int64_t lanesOnInnermost =
+ std::min<int64_t>(uArch->getSubgroupSize(), innermostShape);
+ laneLayout.back() = lanesOnInnermost;
+ if (lanesOnInnermost > 0)
+ laneData.back() = std::min<int64_t>(packingFactor,
+ innermostShape / lanesOnInnermost);
+ else
+ laneData.back() = 1;
// Honor the consumer's lane info when valid. The consumer (e.g. dpas) may
// require VNNI-style packing on a non-innermost dim that the load's own
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
index 2d4e22f69dd11..ae7310bf6fadb 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout-inst-data.mlir
@@ -363,9 +363,9 @@ gpu.module @test_collapse_dims [#xevm.target<O = 3, chip = "pvc">] {
gpu.module @test {
// CHECK-LABEL: func.func @bitcast_ui8_to_f4(
// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<256x16xui8>) {
-// CHECK: %[[TDESC:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<256x16xui8> -> !xegpu.tensor_desc<256x16xui8, #xegpu.layout<inst_data = [32, 16]>>
-// CHECK: %[[LOAD:.*]] = xegpu.load_nd %[[TDESC]][0, 0] <{layout = #xegpu.layout<inst_data = [32, 16]>}> :
-// CHECK-SAME: !xegpu.tensor_desc<256x16xui8, #xegpu.layout<inst_data = [32, 16]>> -> vector<256x16xui8>
+// CHECK: %[[TDESC:.*]] = xegpu.create_nd_tdesc %[[ARG0]] : memref<256x16xui8> -> !xegpu.tensor_desc<256x16xui8, #xegpu.layout<inst_data = [32, 16], lane_layout = [1, 16], lane_data = [1, 1]>>
+// CHECK: %[[LOAD:.*]] = xegpu.load_nd %[[TDESC]][0, 0] <{layout = #xegpu.layout<inst_data = [32, 16], lane_layout = [1, 16], lane_data = [1, 1]>}> :
+// CHECK-SAME: !xegpu.tensor_desc<256x16xui8, #xegpu.layout<inst_data = [32, 16], lane_layout = [1, 16], lane_data = [1, 1]>> -> vector<256x16xui8>
// CHECK: %[[BC:.*]] = vector.bitcast %[[LOAD]] {layout_result_0 = #xegpu.layout<inst_data = [32, 32]>} : vector<256x16xui8> to vector<256x32xf4E2M1FN>
// CHECK: xegpu.convert_layout %[[BC]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [32, 32]>, target_layout = #xegpu.layout<inst_data = [32, 32]>}>
>From 349bd34b3c4c501c6e01f277f8341e9599ccbc82 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 06:31:29 +0000
Subject: [PATCH 19/20] test: fix case 6 (load_gather_with_coalesce_chunksize).
source memref to 1D <256xf16>; offset literal to dense<0> splat (matching 2D
type); CHECK lines updated for 2D mask/offset layouts.
---
mlir/test/Dialect/XeGPU/propagate-layout.mlir | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout.mlir b/mlir/test/Dialect/XeGPU/propagate-layout.mlir
index c04e949015368..48891079f85a7 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout.mlir
@@ -134,19 +134,18 @@ func.func @extf_truncf(%arg0: !xegpu.tensor_desc<8x16xf16>, %arg1: !xegpu.tensor
// -----
gpu.module @test {
// CHECK-LABEL: func.func @load_gather_with_coalesce_chunksize(
-// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x16xf16>, %[[ARG1:[0-9a-zA-Z]+]]: memref<16x16xf16>, %[[ARG2:[0-9a-zA-Z]+]]: memref<8x16xf32>) {
-// CHECK: %[[OFFSET:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>}
-// CHECK-SAME: dense<[0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240]> : vector<16xindex>
-// CHECK-NEXT: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<true> : vector<16xi1>
-// CHECK-NEXT: %{{.*}} = xegpu.load %arg1[%[[OFFSET]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>}> : memref<16x16xf16>, vector<16xindex>, vector<16xi1> -> vector<16x16xf16>
-func.func @load_gather_with_coalesce_chunksize(%arg0: memref<8x16xf16>, %arg1: memref<16x16xf16>, %arg2: memref<8x16xf32>) {
+// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<8x16xf16>, %[[ARG1:[0-9a-zA-Z]+]]: memref<256xf16>, %[[ARG2:[0-9a-zA-Z]+]]: memref<8x16xf32>) {
+// CHECK: %[[OFFSET:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>} dense<0> : vector<16x16xindex>
+// CHECK: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>} dense<true> : vector<16x16xi1>
+// CHECK: %{{.*}} = xegpu.load %arg1[%[[OFFSET]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 2]>}> : memref<256xf16>, vector<16x16xindex>, vector<16x16xi1> -> vector<16x16xf16>
+func.func @load_gather_with_coalesce_chunksize(%arg0: memref<8x16xf16>, %arg1: memref<256xf16>, %arg2: memref<8x16xf32>) {
%c0 = arith.constant 0 : index
%0 = xegpu.create_nd_tdesc %arg0 : memref<8x16xf16> -> !xegpu.tensor_desc<8x16xf16>
%1 = xegpu.load_nd %0[0, 0] : !xegpu.tensor_desc<8x16xf16> -> vector<8x16xf16>
- %offset = arith.constant dense<[0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240]> : vector<16x16xindex>
+ %offset = arith.constant dense<0> : vector<16x16xindex>
%mask = arith.constant dense<true> : vector<16x16xi1>
%3 = xegpu.load %arg1[%offset], %mask
- : memref<16x16xf16>, vector<16x16xindex>, vector<16x16xi1> -> vector<16x16xf16>
+ : memref<256xf16>, vector<16x16xindex>, vector<16x16xi1> -> vector<16x16xf16>
%4 = vector.transpose %3, [1, 0] : vector<16x16xf16> to vector<16x16xf16>
%5 = xegpu.dpas %1, %4 : vector<8x16xf16>, vector<16x16xf16> -> vector<8x16xf32>
%6 = xegpu.create_nd_tdesc %arg2 : memref<8x16xf32> -> !xegpu.tensor_desc<8x16xf32>
>From e8bbbf36f62a61054fd3f188a2980d401354d6ea Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Wed, 10 Jun 2026 06:34:12 +0000
Subject: [PATCH 20/20] test: fix case 7 (scatter_ops_coalesce_chunksize).
source memref to 1D <128xf16>; CHECK lines updated for [1, 8] / [1, 1] lane
factorization that propagation now produces.
---
mlir/test/Dialect/XeGPU/propagate-layout.mlir | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/mlir/test/Dialect/XeGPU/propagate-layout.mlir b/mlir/test/Dialect/XeGPU/propagate-layout.mlir
index 48891079f85a7..e3aad3a3fc965 100644
--- a/mlir/test/Dialect/XeGPU/propagate-layout.mlir
+++ b/mlir/test/Dialect/XeGPU/propagate-layout.mlir
@@ -157,19 +157,19 @@ func.func @load_gather_with_coalesce_chunksize(%arg0: memref<8x16xf16>, %arg1: m
// -----
gpu.module @test {
// CHECK-LABEL: func.func @scatter_ops_coalesce_chunksize(
-// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<16x8xf16>) {
-// CHECK: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<true> : vector<16x8xi1>
-// CHECK: %[[OFFSETS:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [16], lane_data = [1]>} dense<12> : vector<16x8xindex>
-// CHECK: %[[LOAD_VEC:.*]] = xegpu.load %[[ARG0]][%[[OFFSETS]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 8]>}>
-// CHECK-SAME: memref<16x8xf16>, vector<16x8xindex>, vector<16x8xi1> -> vector<16x8xf16>
-// CHECK: xegpu.store %[[LOAD_VEC]], %[[ARG0]][%[[OFFSETS]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 8]>}> : vector<16x8xf16>, memref<16x8xf16>, vector<16x8xindex>, vector<16x8xi1>
-func.func @scatter_ops_coalesce_chunksize(%src: memref<16x8xf16>) {
+// CHECK-SAME: %[[ARG0:[0-9a-zA-Z]+]]: memref<128xf16>) {
+// CHECK: %[[MASK:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [1, 8], lane_data = [1, 1]>} dense<true> : vector<16x8xi1>
+// CHECK: %[[OFFSETS:.*]] = arith.constant {layout_result_0 = #xegpu.layout<lane_layout = [1, 8], lane_data = [1, 1]>} dense<12> : vector<16x8xindex>
+// CHECK: %[[LOAD_VEC:.*]] = xegpu.load %[[ARG0]][%[[OFFSETS]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [1, 8], lane_data = [1, 1]>}>
+// CHECK-SAME: memref<128xf16>, vector<16x8xindex>, vector<16x8xi1> -> vector<16x8xf16>
+// CHECK: xegpu.store %[[LOAD_VEC]], %[[ARG0]][%[[OFFSETS]]], %[[MASK]] <{layout = #xegpu.layout<lane_layout = [1, 8], lane_data = [1, 1]>}> : vector<16x8xf16>, memref<128xf16>, vector<16x8xindex>, vector<16x8xi1>
+func.func @scatter_ops_coalesce_chunksize(%src: memref<128xf16>) {
%1 = arith.constant dense<1>: vector<16x8xi1>
%offset = arith.constant dense<12> : vector<16x8xindex>
%3 = xegpu.load %src[%offset], %1
- : memref<16x8xf16>, vector<16x8xindex>, vector<16x8xi1> -> vector<16x8xf16>
+ : memref<128xf16>, vector<16x8xindex>, vector<16x8xi1> -> vector<16x8xf16>
xegpu.store %3, %src[%offset], %1
- : vector<16x8xf16>, memref<16x8xf16>, vector<16x8xindex>, vector<16x8xi1>
+ : vector<16x8xf16>, memref<128xf16>, vector<16x8xindex>, vector<16x8xi1>
return
}
}
More information about the Mlir-commits
mailing list