[Mlir-commits] [mlir] [MLIR][XeGPU] Unroll dpas with N in the outer loop (PR #235095)
Md Abdullah Shahneous Bari
llvmlistbot at llvm.org
Fri Oct 23 13:53:32 PDT 2026
https://github.com/mshahneo created https://github.com/llvm/llvm-project/pull/235095
When `UnrollDpasOp` splits a `dpas` into instruction-sized blocks, it walked the M blocks in the outer loop and the N blocks in the inner loop. So consecutive `dpas` ops used a different B block (the `dpas` src1 operand) each time.
This change swaps the two loops: N in the outer loop, M in the inner loop. Consecutive `dpas` ops then use the same B block. IGC groups consecutive `dpas` that share src1 into one dpas macro.
Each result is still stored at the same C block index, so the unpacked result does not change. Only the order of the `dpas` ops changes.
- New test case in `xegpu-blocking.mlir`: checks the new order and where each result is stored.
- `UnrollDpasMxOp` uses the same loop order. It is not changed here.
Tested with the IMEX XeGPU integration tests on Intel GPU: same pass set with and without this change.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
>From 1a42fd54b984d44e9dccd48b42834c84bed3e27c Mon Sep 17 00:00:00 2001
From: "Shahneous Bari, Md Abdullah" <md.abdullah.shahneous.bari at intel.com>
Date: Fri, 23 Oct 2026 16:02:22 +0000
Subject: [PATCH] [MLIR][XeGPU] Unroll dpas with N in the outer loop
When the unroll pattern splits a dpas into instruction-sized blocks, it now
walks the N blocks in the outer loop and the M blocks in the inner loop.
Consecutive dpas ops then use the same B block (the dpas src1 operand).
IGC groups consecutive dpas that share src1 into one dpas macro. Before
this change, consecutive ops used a new B block each time.
Each result still goes to the same C block, so the unpacked result does not
change. Only the order of the dpas ops changes.
A new test in xegpu-blocking.mlir checks the order and where each result
is stored.
Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
.../Dialect/XeGPU/Transforms/XeGPUUnroll.cpp | 8 ++---
mlir/test/Dialect/XeGPU/xegpu-blocking.mlir | 36 +++++++++++++++++++
2 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
index aaee71bb861822..5991f1f15bd069 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
@@ -413,10 +413,10 @@ struct UnrollDpasOp : public UnrollPattern<xegpu::DpasOp> {
for (int64_t d = 0; d < batchRank; ++d)
batchIters *= aShape[d] / batchDims[d];
- SmallVector<Value> newOps;
+ SmallVector<Value> newOps(batchIters * mIters * nIters);
for (int64_t batch = 0; batch < batchIters; ++batch) {
- for (int64_t i = 0; i < mIters; ++i) {
- for (int64_t j = 0; j < nIters; ++j) {
+ for (int64_t j = 0; j < nIters; ++j) {
+ for (int64_t i = 0; i < mIters; ++i) {
Value tmpC;
if (c)
tmpC = cVals[batch * (mIters * nIters) + i * nIters + j];
@@ -435,7 +435,7 @@ struct UnrollDpasOp : public UnrollPattern<xegpu::DpasOp> {
xegpu::dropInstDataOnInherentAttrs(newDpasOp);
tmpC = newDpasOp.getResult();
}
- newOps.push_back(tmpC);
+ newOps[batch * (mIters * nIters) + i * nIters + j] = tmpC;
}
}
}
diff --git a/mlir/test/Dialect/XeGPU/xegpu-blocking.mlir b/mlir/test/Dialect/XeGPU/xegpu-blocking.mlir
index 70de8194ff1bc4..e2598ff9a58031 100644
--- a/mlir/test/Dialect/XeGPU/xegpu-blocking.mlir
+++ b/mlir/test/Dialect/XeGPU/xegpu-blocking.mlir
@@ -853,3 +853,39 @@ gpu.module @test_kernel {
gpu.return
}
}
+
+// -----
+// The unrolled dpas ops walk N in the outer loop and M in the inner loop, so
+// consecutive dpas ops use the same B block. Each result still goes to its own
+// C block.
+#a = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [8, 1]>
+#b = #xegpu.layout<inst_data = [16, 16], lane_layout = [1, 16], lane_data = [16, 1]>
+#c = #xegpu.layout<inst_data = [8, 16], lane_layout = [1, 16], lane_data = [8, 1]>
+gpu.module @test_kernel {
+ // CHECK-LABEL: gpu.func @dpas_unroll_order
+ // CHECK-DAG: %[[C0:.+]] = arith.constant 0 : index
+ // CHECK-DAG: %[[C8:.+]] = arith.constant 8 : index
+ // CHECK-DAG: %[[C16:.+]] = arith.constant 16 : index
+ // CHECK: %[[A0:.+]] = xegpu.load_nd %{{.+}}[%[[C0]], %[[C0]]] {{.*}} -> vector<8x16xf16>
+ // CHECK: %[[A1:.+]] = xegpu.load_nd %{{.+}}[%[[C8]], %[[C0]]] {{.*}} -> vector<8x16xf16>
+ // CHECK: %[[B0:.+]] = xegpu.load_nd %{{.+}}[%[[C0]], %[[C0]]] {{.*}} -> vector<16x16xf16>
+ // CHECK: %[[B1:.+]] = xegpu.load_nd %{{.+}}[%[[C0]], %[[C16]]] {{.*}} -> vector<16x16xf16>
+ // CHECK: %[[D00:.+]] = xegpu.dpas %[[A0]], %[[B0]]
+ // CHECK-NEXT: %[[D10:.+]] = xegpu.dpas %[[A1]], %[[B0]]
+ // CHECK-NEXT: %[[D01:.+]] = xegpu.dpas %[[A0]], %[[B1]]
+ // CHECK-NEXT: %[[D11:.+]] = xegpu.dpas %[[A1]], %[[B1]]
+ // CHECK: xegpu.store_nd %[[D00]], %{{.+}}[%[[C0]], %[[C0]]]
+ // CHECK: xegpu.store_nd %[[D01]], %{{.+}}[%[[C0]], %[[C16]]]
+ // CHECK: xegpu.store_nd %[[D10]], %{{.+}}[%[[C8]], %[[C0]]]
+ // CHECK: xegpu.store_nd %[[D11]], %{{.+}}[%[[C8]], %[[C16]]]
+ gpu.func @dpas_unroll_order(%A: memref<16x16xf16>, %B: memref<16x32xf16>, %C: memref<16x32xf32>) {
+ %a_tdesc = xegpu.create_nd_tdesc %A : memref<16x16xf16> -> !xegpu.tensor_desc<16x16xf16, #a>
+ %b_tdesc = xegpu.create_nd_tdesc %B : memref<16x32xf16> -> !xegpu.tensor_desc<16x32xf16, #b>
+ %c_tdesc = xegpu.create_nd_tdesc %C : memref<16x32xf32> -> !xegpu.tensor_desc<16x32xf32, #c>
+ %a = xegpu.load_nd %a_tdesc[0, 0] <{layout = #a}>: !xegpu.tensor_desc<16x16xf16, #a> -> vector<16x16xf16>
+ %b = xegpu.load_nd %b_tdesc[0, 0] <{layout = #b}>: !xegpu.tensor_desc<16x32xf16, #b> -> vector<16x32xf16>
+ %c = xegpu.dpas %a, %b <{layout_a = #a, layout_b = #b, layout_cd = #c}>: vector<16x16xf16>, vector<16x32xf16> -> vector<16x32xf32>
+ xegpu.store_nd %c, %c_tdesc[0, 0] <{layout = #c}>: vector<16x32xf32>, !xegpu.tensor_desc<16x32xf32, #c>
+ gpu.return
+ }
+}
More information about the Mlir-commits
mailing list