Skip to content

Commit a6e793a

Browse files
asraacopybara-github
authored andcommitted
fix: rewrite avg pool as 2-d nchw fchw convolution
This fixes the bug where before the avg pooling was split into per-channel 2d convolutions, but that was incorrect since that doesn't properly compute the average since linalg.conv_2d doesnt support striding. This temporarily disables lenet since that includes the pooling layer Fixes #2702 PiperOrigin-RevId: 888710468
1 parent 2094bc1 commit a6e793a

3 files changed

Lines changed: 40 additions & 67 deletions

File tree

lib/Transforms/LinalgCanonicalizations/LinalgCanonicalizations.cpp

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,9 @@ struct RewriteAvgPoolAsConv2D
653653
auto filterTy = cast<RankedTensorType>(poolOp.getInputs()[1].getType());
654654
auto outputTy = cast<RankedTensorType>(poolOp.getResultTypes()[0]);
655655

656-
auto kernelShape =
657-
SmallVector<int64_t>{filterTy.getDimSize(0), filterTy.getDimSize(1)};
656+
auto c = inputTy.getDimSize(1);
657+
auto kernelShape = SmallVector<int64_t>{c, c, filterTy.getDimSize(0),
658+
filterTy.getDimSize(1)};
658659
auto kernelTy =
659660
RankedTensorType::get(kernelShape, filterTy.getElementType());
660661
TypedAttr kernelVals = rewriter.getOneAttr(kernelTy);
@@ -681,55 +682,17 @@ struct RewriteAvgPoolAsConv2D
681682
}
682683
auto kernel =
683684
arith::ConstantOp::create(rewriter, poolOp.getLoc(), kernelVals);
684-
685-
// Rewrite the 2D avg pool output shape is N x C x H' x W'. Apply the kernel
686-
// to each channel separately, and insert into the output.
687-
auto outputVal = poolOp.getOutputs()[0];
688-
// The filter must ensure each output channel i is only the sum of values
689-
// from input channel i. So the filter uses an identity matrix when f ==
690-
// c and 0 otherwise.
691-
RankedTensorType twoDOutputType =
692-
RankedTensorType::get({outputTy.getDimSize(2), outputTy.getDimSize(3)},
693-
outputTy.getElementType());
694-
RankedTensorType twoDInputType =
695-
RankedTensorType::get({inputTy.getDimSize(2), inputTy.getDimSize(3)},
696-
inputTy.getElementType());
697-
Value convOutput = tensor::EmptyOp::create(rewriter, poolOp.getLoc(),
698-
twoDOutputType.getShape(),
699-
twoDOutputType.getElementType());
700-
for (int n = 0; n < inputTy.getDimSize(0); ++n) {
701-
for (int c = 0; c < inputTy.getDimSize(1); ++c) {
702-
// Compute the 2-D constant convolution.
703-
SmallVector<OpFoldResult> offsets = {
704-
rewriter.getIndexAttr(n), rewriter.getIndexAttr(c),
705-
rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
706-
SmallVector<OpFoldResult> inputSizes = {
707-
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1),
708-
rewriter.getIndexAttr(inputTy.getDimSize(2)),
709-
rewriter.getIndexAttr(inputTy.getDimSize(3))};
710-
SmallVector<OpFoldResult> strides(4, rewriter.getIndexAttr(1));
711-
auto extractInputOp = tensor::ExtractSliceOp::create(
712-
rewriter, poolOp.getLoc(), twoDInputType, poolOp.getInputs()[0],
713-
offsets, inputSizes, strides);
714-
715-
auto convOp = linalg::Conv2DOp::create(
716-
rewriter, poolOp.getLoc(), twoDOutputType,
717-
ValueRange{extractInputOp, kernel}, ValueRange{convOutput});
718-
// Insert into the outputVal.
719-
SmallVector<OpFoldResult> outputSizes = {
720-
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1),
721-
rewriter.getIndexAttr(outputTy.getDimSize(2)),
722-
rewriter.getIndexAttr(outputTy.getDimSize(3))};
723-
outputVal = tensor::InsertSliceOp::create(
724-
rewriter, poolOp.getLoc(), convOp.getResult(0), outputVal, offsets,
725-
outputSizes, strides);
726-
}
727-
}
685+
Value conv = linalg::Conv2DNchwFchwOp::create(
686+
rewriter, poolOp.getLoc(), outputTy,
687+
ValueRange{poolOp.getInputs()[0], kernel},
688+
ValueRange{poolOp.getOutputs()[0]}, poolOp.getStrides(),
689+
poolOp.getDilations())
690+
.getResult(0);
728691

729692
if (avgPoolOutput) {
730-
rewriter.replaceAllUsesWith(avgPoolOutput, outputVal);
693+
rewriter.replaceAllUsesWith(avgPoolOutput, conv);
731694
} else {
732-
rewriter.replaceOp(poolOp, outputVal);
695+
rewriter.replaceOp(poolOp, conv);
733696
}
734697
return success();
735698
}
@@ -746,6 +709,14 @@ struct LowerConv2DNchwFchw
746709

747710
LogicalResult matchAndRewrite(mlir::linalg::Conv2DNchwFchwOp convOp,
748711
PatternRewriter& rewriter) const override {
712+
// Fails is strides > 1.
713+
if (!llvm::all_of(convOp.getStrides(), [](const APInt& element) {
714+
return element.getSExtValue() == 1;
715+
})) {
716+
return rewriter.notifyMatchFailure(convOp,
717+
"expected all ones for strides");
718+
}
719+
749720
Location loc = convOp.getLoc();
750721
Value image = convOp.getInputs()[0];
751722
Value filter = convOp.getInputs()[1];

tests/Examples/openfhe/ckks/lenet/BUILD

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
load("@heir//tools:heir-opt.bzl", "heir_opt")
21
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
2+
# load("@heir//tools:heir-opt.bzl", "heir_opt")
3+
34
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
45
load("@rules_cc//cc:cc_library.bzl", "cc_library")
56
load("@rules_python//python:py_test.bzl", "py_test")
@@ -39,20 +40,21 @@ cc_binary(
3940
],
4041
)
4142

42-
heir_opt(
43-
name = "lenet_mlir_opt",
44-
src = "lenet.mlir",
45-
generated_filename = "lenet.openfhe.mlir",
46-
pass_flags = [
47-
"--annotate-module=backend=openfhe scheme=ckks",
48-
"--torch-linalg-to-ckks=ciphertext-degree=1024",
49-
"--scheme-to-openfhe",
50-
],
51-
tags = [
52-
"nofastbuild",
53-
"requires-mem:28g",
54-
],
55-
)
43+
# TODO(#2702): Re-enable once pooling is fully supported.
44+
# heir_opt(
45+
# name = "lenet_mlir_opt",
46+
# src = "lenet.mlir",
47+
# generated_filename = "lenet.openfhe.mlir",
48+
# pass_flags = [
49+
# "--annotate-module=backend=openfhe scheme=ckks",
50+
# "--torch-linalg-to-ckks=ciphertext-degree=1024",
51+
# "--scheme-to-openfhe",
52+
# ],
53+
# tags = [
54+
# "nofastbuild",
55+
# "requires-mem:28g",
56+
# ],
57+
# )
5658

5759
cc_library(
5860
name = "interpreter_shim",

tests/Transforms/linalg_canonicalizations/average_pooling.mlir

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
module {
77
// CHECK: func.func @main
88
// CHECK-SAME: (%[[arg0:.*]]: tensor<1x6x28x28xf32>)
9+
// CHECK-DAG: %[[out:.*]] = arith.constant dense<2.0
910
// CHECK-DAG: %[[divided_cst:.*]] = arith.constant dense<2.500000e-01>
10-
// CHECK-DAG: %[[out:.*]] = tensor.empty() : tensor<14x14xf32>
11-
// CHECK-DAG: %[[extracted:.*]] = tensor.extract_slice %[[arg0]]
12-
// CHECK: linalg.conv_2d ins(%[[extracted]], %[[divided_cst]] : tensor<28x28xf32>, tensor<2x2xf32>) outs(%[[out]] : tensor<14x14xf32>)
13-
// CHECK-COUNT-5: linalg.conv_2d
11+
// CHECK: linalg.conv_2d_nchw_fchw
12+
// CHECK-SAME: strides = dense<2> : vector<2xi64>
13+
// CHECK-SAME: ins(%[[arg0]], %[[divided_cst]] : tensor<1x6x28x28xf32>, tensor<6x6x2x2xf32>) outs(%[[out]] : tensor<1x6x14x14xf32>)
1414
// CHECK: return
1515
func.func @main(%arg0: tensor<1x6x28x28xf32>) -> tensor<1x6x14x14xf32> {
1616
%cst_0 = arith.constant 2.000000e+00 : f32

0 commit comments

Comments
 (0)