[Codegen][CPU] Fold reshape-containing encoding relayouts to map_store. (#24533)
GPU's configuration pipeline folds the `pack`/`expand_shape`/`transpose`
relayout chain from encoding materialization into a single
`iree_linalg_ext.map_store` before tiling; CPU did not. For a non-row-
major encoding swizzle the intervening `tensor.expand_shape` (not a
`TilingInterface` op) blocks producer fusion and strands an untiled,
whole-tensor `pack` intermediate.
Mirror GPU on CPU:
- Add `RelayoutCombinationScope::DispatchReshape`: like `Dispatch` but
restricted to chains whose backward slice contains an
expand/collapse_shape. Pure pack/unpack/transpose chains tile fine and
are left alone.
- Run `CombineResultLayoutTransformation` with that scope in the CPU
configuration pipeline, after MaterializeDeviceEncoding.
- Skip `map_load`/`map_store` as `getRootOperation` candidates, so a
dispatch with a real compute op plus a `map_store` roots on the compute
op.
Inert for existing (row-major) CPU encodings.
Progress towards #24515.
Signed-off-by: Benoit Jacob <jacob.benoit.1@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
diff --git a/compiler/src/iree/compiler/Codegen/Common/CombineLayoutTransformation.cpp b/compiler/src/iree/compiler/Codegen/Common/CombineLayoutTransformation.cpp
index 0579662..6fbebb6 100644
--- a/compiler/src/iree/compiler/Codegen/Common/CombineLayoutTransformation.cpp
+++ b/compiler/src/iree/compiler/Codegen/Common/CombineLayoutTransformation.cpp
@@ -930,6 +930,29 @@
return isa<IREE::Codegen::StoreToBufferOp>(*leaf.getUsers().begin());
};
break;
+ // Control function for DispatchReshape scope. Like Dispatch, but additionally
+ // requires the backward relayout slice to contain a `tensor.expand_shape` or
+ // `tensor.collapse_shape` — the non-tileable reshape whose presence is what
+ // makes folding into a `map_store` necessary (see the enum doc comment).
+ case IREE::Codegen::RelayoutCombinationScope::DispatchReshape:
+ controlFn = [](OpResult leaf) {
+ if (leaf.getNumUses() != 1) {
+ return false;
+ }
+ if (!isa<IREE::Codegen::StoreToBufferOp>(*leaf.getUsers().begin())) {
+ return false;
+ }
+ llvm::SetVector<Operation *> slice;
+ BackwardSliceOptions options;
+ options.filter = isSupportedSingleInputRelayoutOpForResult;
+ options.inclusive = true;
+ if (failed(getBackwardSlice(leaf, &slice, options))) {
+ return false;
+ }
+ return llvm::any_of(
+ slice, llvm::IsaPred<tensor::CollapseShapeOp, tensor::ExpandShapeOp>);
+ };
+ break;
// Control function for Workgroup scope. Filters to only relayout ops with
// a single tensor.parallel_insert_slice user inside of a workgroup
// scf.forall op. Relayout chains of only reshapes are also filtered out,
diff --git a/compiler/src/iree/compiler/Codegen/Common/CombineLayoutTransformation.h b/compiler/src/iree/compiler/Codegen/Common/CombineLayoutTransformation.h
index f7fe9e4..b869402 100644
--- a/compiler/src/iree/compiler/Codegen/Common/CombineLayoutTransformation.h
+++ b/compiler/src/iree/compiler/Codegen/Common/CombineLayoutTransformation.h
@@ -44,10 +44,18 @@
/// Enum defining the scope of the CombineResultLayoutTransformationPass.
/// - The `Dispatch` scope will combine layout transformation chains that are
/// consumed by an `iree_codegen.store_to_buffer` op.
+/// - The `DispatchReshape` scope is like `Dispatch`, but additionally
+/// restricted to chains whose backward slice contains a
+/// `tensor.expand_shape` or `tensor.collapse_shape`. Such a reshape does not
+/// implement `TilingInterface`, so when it sits between two tileable
+/// relayout ops it blocks producer fusion and leaves an untiled,
+/// whole-tensor intermediate (iree-org/iree#24483). Pure
+/// pack/unpack/transpose/pad chains tile fine via tile-and-fuse and do not
+/// need a `map_store`.
/// - The `Workgroup` scope will combine layout transformation chains that are
/// consumed by a `tensor.parallel_insert_slice` op at the end of an
/// scf.forall with an `iree_codegen.workgroup_mapping` attribute.
-enum class RelayoutCombinationScope { Dispatch, Workgroup };
+enum class RelayoutCombinationScope { Dispatch, DispatchReshape, Workgroup };
} // namespace IREE::Codegen
/// Get the corresponding control function for the given scope. The control
diff --git a/compiler/src/iree/compiler/Codegen/Common/Passes.td b/compiler/src/iree/compiler/Codegen/Common/Passes.td
index 000df09..65c21c4 100644
--- a/compiler/src/iree/compiler/Codegen/Common/Passes.td
+++ b/compiler/src/iree/compiler/Codegen/Common/Passes.td
@@ -199,6 +199,8 @@
::llvm::cl::values(
clEnumValN(IREE::Codegen::RelayoutCombinationScope::Dispatch, "dispatch",
"Combine relayout ops starting from iree_codegen.store_to_buffer"),
+ clEnumValN(IREE::Codegen::RelayoutCombinationScope::DispatchReshape, "dispatch-reshape",
+ "Like dispatch, but only for chains containing an expand/collapse_shape"),
clEnumValN(IREE::Codegen::RelayoutCombinationScope::Workgroup, "workgroup",
"Combine relayout ops starting from workgroup forall terminator ops"))
}]>
diff --git a/compiler/src/iree/compiler/Codegen/Common/test/combine_result_layout_transformation.mlir b/compiler/src/iree/compiler/Codegen/Common/test/combine_result_layout_transformation.mlir
index 84b6cfe..3284f32 100644
--- a/compiler/src/iree/compiler/Codegen/Common/test/combine_result_layout_transformation.mlir
+++ b/compiler/src/iree/compiler/Codegen/Common/test/combine_result_layout_transformation.mlir
@@ -2,6 +2,8 @@
// RUN: -split-input-file %s | FileCheck %s --check-prefixes=DISPATCH-SCOPE
// RUN: iree-opt --pass-pipeline="builtin.module(func.func(iree-codegen-combine-result-layout-transformation{scope=workgroup},canonicalize,cse))" \
// RUN: -split-input-file %s | FileCheck %s --check-prefixes=WORKGROUP-SCOPE
+// RUN: iree-opt --pass-pipeline="builtin.module(func.func(iree-codegen-combine-result-layout-transformation{scope=dispatch-reshape},canonicalize,cse))" \
+// RUN: -split-input-file %s | FileCheck %s --check-prefixes=RESHAPE-SCOPE
func.func @fold_collapse_shape_op(%source : tensor<2x4x16xf32>, %result : memref<8x16xf32>) {
%collapse = tensor.collapse_shape %source [[0, 1], [2]] : tensor<2x4x16xf32> into tensor<8x16xf32>
@@ -555,3 +557,46 @@
// Verify no second padding loop (inner tile size 1 needs no padding)
// DISPATCH-SCOPE-NOT: scf.forall
// DISPATCH-SCOPE: return
+
+// -----
+
+// The `dispatch-reshape` scope folds a relayout chain only when its backward
+// slice contains a non-tileable `expand_shape`/`collapse_shape`. Here the
+// chain is `expand_shape` -> `transpose`, so it folds into a single map_store.
+func.func @reshape_chain_folds_under_dispatch_reshape(
+ %source : tensor<8x16xf32>, %result : memref<4x16x2xf32>) {
+ %expand = tensor.expand_shape %source [[0, 1], [2]] output_shape [2, 4, 16]
+ : tensor<8x16xf32> into tensor<2x4x16xf32>
+ %init = tensor.empty() : tensor<4x16x2xf32>
+ %transposed = linalg.transpose ins(%expand : tensor<2x4x16xf32>)
+ outs(%init : tensor<4x16x2xf32>) permutation = [1, 2, 0]
+ iree_codegen.store_to_buffer %transposed, %result
+ : tensor<4x16x2xf32> into memref<4x16x2xf32>
+ return
+}
+// RESHAPE-SCOPE-LABEL: @reshape_chain_folds_under_dispatch_reshape
+// RESHAPE-SCOPE-NOT: tensor.expand_shape
+// RESHAPE-SCOPE-NOT: linalg.transpose
+// RESHAPE-SCOPE: iree_linalg_ext.map_store
+
+// -----
+
+// A pure transpose chain carries no reshape, so the `dispatch-reshape` scope
+// leaves it untouched for tile-and-fuse. The plain `dispatch` scope, which is
+// not gated on a reshape, still folds it into a map_store -- this contrast is
+// the whole point of the `dispatch-reshape` scope.
+func.func @pure_transpose_skipped_under_dispatch_reshape(
+ %source : tensor<2x4x16xf32>, %result : memref<4x16x2xf32>) {
+ %init = tensor.empty() : tensor<4x16x2xf32>
+ %transposed = linalg.transpose ins(%source : tensor<2x4x16xf32>)
+ outs(%init : tensor<4x16x2xf32>) permutation = [1, 2, 0]
+ iree_codegen.store_to_buffer %transposed, %result
+ : tensor<4x16x2xf32> into memref<4x16x2xf32>
+ return
+}
+// DISPATCH-SCOPE-LABEL: @pure_transpose_skipped_under_dispatch_reshape
+// DISPATCH-SCOPE: iree_linalg_ext.map_store
+//
+// RESHAPE-SCOPE-LABEL: @pure_transpose_skipped_under_dispatch_reshape
+// RESHAPE-SCOPE: linalg.transpose
+// RESHAPE-SCOPE-NOT: iree_linalg_ext.map_store
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/Passes.cpp b/compiler/src/iree/compiler/Codegen/LLVMCPU/Passes.cpp
index 52e2b61..e9f4fb6 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMCPU/Passes.cpp
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/Passes.cpp
@@ -652,7 +652,25 @@
.addPass(createConvertAccGEMMToGEMMPass)
// TODO: Remove the following pass the plumb support for
// #hal.descriptor_type memory space through the stack.
- .addPass(createEraseHALDescriptorTypeFromMemRefPass);
+ .addPass(createEraseHALDescriptorTypeFromMemRefPass)
+ // Fold reshape-containing relayout chains (`pack` -> `expand_shape` ->
+ // `transpose`) emitted by encoding materialization for non-row-major
+ // swizzles into a single `iree_linalg_ext.map_store`, before the
+ // dispatch is tiled. This mirrors the GPU configuration pipeline.
+ // Without it, the intervening `tensor.expand_shape` (not a
+ // `TilingInterface` op) blocks producer fusion and leaves an untiled,
+ // whole-tensor `pack` intermediate whose dynamic `tensor.empty`
+ // bufferizes to a bogus unbounded allocation (iree-org/iree#24483);
+ // `map_store` is a scatter with no intermediate buffer. The
+ // `DispatchReshape` scope restricts this to reshape-containing chains so
+ // plain `pack` encodings (which tile fine) are left untouched.
+ .addPass(createBufferizeDispatchTensorLoadStorePass)
+ .addPass([] {
+ CombineResultLayoutTransformationPassOptions options;
+ options.scope =
+ IREE::Codegen::RelayoutCombinationScope::DispatchReshape;
+ return createCombineResultLayoutTransformationPass(options);
+ });
modulePassManager.addPass(createLLVMCPUSelectLoweringStrategyPass());
LLVM_DEBUG({
llvm::dbgs() << "LLVMCPU codegen configuration pass pipeline:\n";
diff --git a/compiler/src/iree/compiler/Codegen/Utils/CPUUtils.cpp b/compiler/src/iree/compiler/Codegen/Utils/CPUUtils.cpp
index 08b4281..9b6d3ef 100644
--- a/compiler/src/iree/compiler/Codegen/Utils/CPUUtils.cpp
+++ b/compiler/src/iree/compiler/Codegen/Utils/CPUUtils.cpp
@@ -6,6 +6,7 @@
#include "iree/compiler/Codegen/Utils/CPUUtils.h"
#include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenAttrs.h"
+#include "iree/compiler/Dialect/LinalgExt/IR/LinalgExtOps.h"
#include <numeric>
@@ -56,7 +57,8 @@
}
if (isa<TilingInterface>(op) &&
- !isa<tensor::PadOp, linalg::PackOp, linalg::UnPackOp>(op)) {
+ !isa<tensor::PadOp, linalg::PackOp, linalg::UnPackOp,
+ IREE::LinalgExt::MapLoadOp, IREE::LinalgExt::MapStoreOp>(op)) {
// All other operations that implement this interface are root ops.
rootOperation = op;
break;
@@ -74,9 +76,14 @@
}
if (!rootOperation) {
- // Check for pad/pack/unpack ops by themselves.
+ // Check for relayout ops (pad/pack/unpack and the map_load/map_store
+ // scatter/gather ops that encoding materialization folds into) by
+ // themselves. These are excluded from the sweeps above so that a real
+ // compute op in the same dispatch wins; a pure-relayout dispatch (e.g. a
+ // `set_encoding` dispatch) still picks one of them here.
for (auto op : llvm::reverse(computeOps)) {
- if (isa<tensor::PadOp, linalg::PackOp, linalg::UnPackOp>(op)) {
+ if (isa<tensor::PadOp, linalg::PackOp, linalg::UnPackOp,
+ IREE::LinalgExt::MapLoadOp, IREE::LinalgExt::MapStoreOp>(op)) {
rootOperation = op;
break;
}