[Codegen] Add canonicalization pattern for reshape into interface stores (#12471)
This pattern is similar to the existing one for loads except for stores. This is needed for implicit gemm where a reshape ends up at the bottom of the kernel.This pattern is similar to the existing one for loads except for stores. This is needed for implicit gemm where a reshape ends up at the bottom of the kernel.
diff --git a/compiler/src/iree/compiler/Codegen/Common/test/canonicalize_interface_load_store.mlir b/compiler/src/iree/compiler/Codegen/Common/test/canonicalize_interface_load_store.mlir
index aeec7aa..deae76f 100644
--- a/compiler/src/iree/compiler/Codegen/Common/test/canonicalize_interface_load_store.mlir
+++ b/compiler/src/iree/compiler/Codegen/Common/test/canonicalize_interface_load_store.mlir
@@ -1,9 +1,10 @@
// RUN: iree-opt --split-input-file --iree-codegen-cleanup-buffer-alloc-view %s | FileCheck %s
-// CHECK-LABEL: func.func @fold_reshape()
-func.func @fold_reshape() {
+// CHECK-LABEL: func.func @fold_reshape_load()
+func.func @fold_reshape_load() {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
+ %cst = arith.constant 0.0 : f32
// CHECK: %[[ARG:.+]] = hal.interface.binding.subspan set(0) binding(0) type(storage_buffer) : !flow.dispatch.tensor<readonly:tensor<3x3x96xf32>>
%1 = hal.interface.binding.subspan set(0) binding(0) type(storage_buffer) : !flow.dispatch.tensor<readonly:tensor<3x3x1x96xf32>>
%2 = hal.interface.binding.subspan set(0) binding(0) type(storage_buffer) : !flow.dispatch.tensor<writeonly:tensor<3x3x96xf32>>
@@ -11,8 +12,31 @@
%3 = flow.dispatch.tensor.load %1, offsets=[0, 0, 0, 0], sizes =[3, 3, 1, 96], strides=[1, 1, 1, 1] : !flow.dispatch.tensor<readonly:tensor<3x3x1x96xf32>> -> tensor<3x3x1x96xf32>
%4 = tensor.collapse_shape %3 [[0, 1, 2, 3]] : tensor<3x3x1x96xf32> into tensor<864xf32>
%5 = tensor.expand_shape %4 [[0, 1, 2]] : tensor<864xf32> into tensor<3x3x96xf32>
- // CHECK: flow.dispatch.tensor.store %[[LOAD]], {{.*}}
- flow.dispatch.tensor.store %5, %2, offsets = [%c0, %c0, %c0], sizes = [%c1, %c1, %c1], strides = [%c1, %c1, %c1] : tensor<3x3x96xf32> -> !flow.dispatch.tensor<writeonly:tensor<3x3x96xf32>>
+ // CHECK: %[[FILL:.+]] = linalg.fill ins(%{{.+}}) outs(%[[LOAD]] : tensor<3x3x96xf32>)
+ %6 = linalg.fill ins(%cst : f32) outs(%5 : tensor<3x3x96xf32>) -> tensor<3x3x96xf32>
+ // CHECK: flow.dispatch.tensor.store %[[FILL]], {{.*}}
+ flow.dispatch.tensor.store %6, %2, offsets = [%c0, %c0, %c0], sizes = [%c1, %c1, %c1], strides = [%c1, %c1, %c1] : tensor<3x3x96xf32> -> !flow.dispatch.tensor<writeonly:tensor<3x3x96xf32>>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @fold_reshape_store()
+func.func @fold_reshape_store() {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %cst = arith.constant 0.0 : f32
+ %1 = hal.interface.binding.subspan set(0) binding(0) type(storage_buffer) : !flow.dispatch.tensor<readonly:tensor<3x3x1x96xf32>>
+ %2 = hal.interface.binding.subspan set(0) binding(0) type(storage_buffer) : !flow.dispatch.tensor<writeonly:tensor<3x3x96xf32>>
+ // CHECK: %[[OUT:.+]] = hal.interface.binding.subspan set(0) binding(0) type(storage_buffer) : !flow.dispatch.tensor<writeonly:tensor<3x3x1x96xf32>>
+ // CHECK: %[[LOAD:.+]] = flow.dispatch.tensor.load %{{.*}}, {{.*}}
+ %3 = flow.dispatch.tensor.load %1, offsets=[0, 0, 0, 0], sizes =[3, 3, 1, 96], strides=[1, 1, 1, 1] : !flow.dispatch.tensor<readonly:tensor<3x3x1x96xf32>> -> tensor<3x3x1x96xf32>
+ // CHECK: %[[FILL:.+]] = linalg.fill ins(%{{.+}}) outs(%[[LOAD]] : tensor<3x3x1x96xf32>)
+ %4 = linalg.fill ins(%cst : f32) outs(%3 : tensor<3x3x1x96xf32>) -> tensor<3x3x1x96xf32>
+ %5 = tensor.collapse_shape %4 [[0, 1, 2, 3]] : tensor<3x3x1x96xf32> into tensor<864xf32>
+ %6 = tensor.expand_shape %5 [[0, 1, 2]] : tensor<864xf32> into tensor<3x3x96xf32>
+ // CHECK: flow.dispatch.tensor.store %[[FILL]], %[[OUT]], {{.+}} : tensor<3x3x1x96xf32> -> !flow.dispatch.tensor<writeonly:tensor<3x3x1x96xf32>>
+ flow.dispatch.tensor.store %6, %2, offsets = [0, 0, 0], sizes = [3, 3, 96], strides = [1, 1, 1] : tensor<3x3x96xf32> -> !flow.dispatch.tensor<writeonly:tensor<3x3x96xf32>>
return
}
diff --git a/compiler/src/iree/compiler/Codegen/Transforms/Transforms.cpp b/compiler/src/iree/compiler/Codegen/Transforms/Transforms.cpp
index f8a1221..18a8089 100644
--- a/compiler/src/iree/compiler/Codegen/Transforms/Transforms.cpp
+++ b/compiler/src/iree/compiler/Codegen/Transforms/Transforms.cpp
@@ -254,6 +254,17 @@
namespace {
+// TODO(antigainst): enable dynamic shape support once they are needed.
+template <typename TensorReshapeOp>
+static Optional<Value> getStaticReshapeOpSrc(TensorReshapeOp reshapeOp) {
+ auto reshapeSrcType =
+ reshapeOp.getSrc().getType().template cast<ShapedType>();
+ auto reshapeDstType = reshapeOp.getType().template cast<ShapedType>();
+ if (!reshapeSrcType.hasStaticShape() || !reshapeDstType.hasStaticShape())
+ return std::nullopt;
+ return reshapeOp.getSrc();
+}
+
/// Folds tensor.expand/collapse_shape into the source
/// hal.interface.binding.subspan.
///
@@ -280,17 +291,12 @@
LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
PatternRewriter &rewriter) const override {
- // TODO(antigainst): enable dynamic shape support once they are needed.
- auto reshapeSrcType =
- reshapeOp.getSrc().getType().template cast<ShapedType>();
- auto reshapeDstType = reshapeOp.getType().template cast<ShapedType>();
- if (!reshapeSrcType.hasStaticShape() || !reshapeDstType.hasStaticShape()) {
- return failure();
- }
+ Optional<Value> reshapeSrc =
+ getStaticReshapeOpSrc<TensorReshapeOp>(reshapeOp);
+ if (!reshapeSrc) return failure();
auto loadOp =
- reshapeOp.getSrc()
- .template getDefiningOp<IREE::Flow::DispatchTensorLoadOp>();
+ reshapeSrc->template getDefiningOp<IREE::Flow::DispatchTensorLoadOp>();
if (!loadOp) return failure();
// Make sure we are loading the full incoming subspan. Otherwise we cannot
@@ -324,12 +330,88 @@
return success();
}
};
+
+/// Folds tensor.expand/collapse_shape into the source
+/// hal.interface.binding.subspan.
+///
+/// For example, this matches the following pattern:
+///
+/// %subspan = hal.interface.binding.subspan ... :
+/// !flow.dispatch.tensor<writeonly:tensor<3x3x1x96xf32>>
+/// %0 = linalg.tensor_reshape %tensor [
+/// affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
+/// ] : tensor<864xf32> into tensor<3x3x1x96xf32>
+/// %tensor = flow.dispatch.tensor.store %0, %subspan :
+/// !flow.dispatch.tensor<writeonly:tensor<3x3x1x96xf32>> ->
+/// tensor<3x3x1x96xf32>
+///
+/// And turns it into:
+///
+/// %subspan = hal.interface.binding.subspan ... :
+/// !flow.dispatch.tensor<writeonly:tensor<864xf32>>
+/// %0 = flow.dispatch.tensor.store %tensor, %subspan :
+/// !flow.dispatch.tensor<writeonly:tensor<864xf32>> -> tensor<864xf32>
+struct FoldReshapeIntoInterfaceTensorStore
+ : OpRewritePattern<IREE::Flow::DispatchTensorStoreOp> {
+ using OpRewritePattern<IREE::Flow::DispatchTensorStoreOp>::OpRewritePattern;
+
+ LogicalResult matchAndRewrite(IREE::Flow::DispatchTensorStoreOp storeOp,
+ PatternRewriter &rewriter) const override {
+ // Make sure we are storing the full incoming subspan. Otherwise we cannot
+ // simply adjust the subspan's resultant type later.
+ if (!storeOp.offsets().empty() || !storeOp.sizes().empty() ||
+ !storeOp.strides().empty())
+ return failure();
+
+ auto reshapeOp = storeOp.getValue().getDefiningOp();
+ if (!isa<tensor::CollapseShapeOp, tensor::ExpandShapeOp>(reshapeOp))
+ return failure();
+
+ // Dynamic shapes are currently unsupported.
+ Optional<Value> reshapeSrc =
+ isa<tensor::CollapseShapeOp>(reshapeOp)
+ ? getStaticReshapeOpSrc<tensor::CollapseShapeOp>(
+ cast<tensor::CollapseShapeOp>(reshapeOp))
+ : getStaticReshapeOpSrc<tensor::ExpandShapeOp>(
+ cast<tensor::ExpandShapeOp>(reshapeOp));
+ if (!reshapeSrc) return failure();
+
+ auto subspanOp =
+ storeOp.getTarget()
+ .template getDefiningOp<IREE::HAL::InterfaceBindingSubspanOp>();
+ if (!subspanOp) return failure();
+ assert(subspanOp.getDynamicDims().empty());
+
+ auto tensorAccess = subspanOp.getType()
+ .template cast<IREE::Flow::DispatchTensorType>()
+ .getAccess();
+ auto newSubspanType = IREE::Flow::DispatchTensorType::get(
+ tensorAccess, reshapeSrc->getType());
+
+ Value newSubspanOp;
+ {
+ OpBuilder::InsertionGuard guard(rewriter);
+ rewriter.setInsertionPointAfter(subspanOp);
+ newSubspanOp = rewriter.create<IREE::HAL::InterfaceBindingSubspanOp>(
+ subspanOp.getLoc(), newSubspanType, subspanOp.getSet(),
+ subspanOp.getBinding(), subspanOp.getDescriptorType(),
+ subspanOp.getByteOffset(), subspanOp.getDynamicDims(),
+ subspanOp.getAlignmentAttr(), subspanOp.getDescriptorFlagsAttr());
+ }
+
+ rewriter.replaceOpWithNewOp<IREE::Flow::DispatchTensorStoreOp>(
+ storeOp, *reshapeSrc, newSubspanOp, storeOp.getTargetDims());
+
+ return success();
+ }
+};
} // namespace
void populateReshapeToInterfaceTensorPatterns(RewritePatternSet &patterns) {
patterns.insert<FoldReshapeIntoInterfaceTensorLoad<tensor::CollapseShapeOp>,
FoldReshapeIntoInterfaceTensorLoad<tensor::ExpandShapeOp>>(
patterns.getContext());
+ patterns.insert<FoldReshapeIntoInterfaceTensorStore>(patterns.getContext());
}
//===--------------------------------------------------------------------====//