From e6e7f5106e29589d0953216ed316965bcc4183ff Mon Sep 17 00:00:00 2001 From: =?utf8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=91=D0=B0=D1=80?= =?utf8?q?=D0=B0=D0=BD=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2/AI=20Tools=20Lab=20/S?= =?utf8?q?RR/Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Wed, 5 Dec 2018 20:02:39 +0300 Subject: [PATCH] [nnc] Support for asymmetric strides in Pool, Conv2D, DepthwiseConv2D operations (#2460) * Add support for asymmetric strides to Pool, Conv2D, DepthwiseConv2D operations; * Refactor convolution and pooling parameter computation in Caffe importer. Signed-off-by: Sergei Barannikov --- contrib/nnc/core/modelIR/IrDotDumper.cpp | 15 +- contrib/nnc/core/modelIR/operations/Conv2DOp.cpp | 52 ++-- contrib/nnc/core/modelIR/operations/DeConv2DOp.cpp | 42 ++-- .../core/modelIR/operations/DepthwiseConv2DOp.cpp | 51 ++-- contrib/nnc/core/modelIR/operations/PoolOp.cpp | 70 +++--- .../nnc/include/core/modelIR/operations/Conv2DOp.h | 30 +-- .../include/core/modelIR/operations/Deconv2DOp.h | 13 +- .../core/modelIR/operations/DepthwiseConv2DOp.h | 30 +-- .../nnc/include/core/modelIR/operations/PoolOp.h | 46 ++-- .../passes/acl_soft_backend/AclCppOpGenerator.cpp | 61 +---- .../nnc/passes/caffe_frontend/caffe_op_creator.cpp | 261 ++++++++++----------- contrib/nnc/passes/interpreter/ops/DeConv2D.cpp | 21 +- .../passes/interpreter/ops/Depthwise_conv_2D.cpp | 10 +- .../nnc/passes/interpreter/ops/Depthwise_conv_2D.h | 1 - contrib/nnc/passes/interpreter/ops/Pool.cpp | 9 +- contrib/nnc/passes/interpreter/ops/conv_2D.cpp | 13 +- contrib/nnc/passes/interpreter/ops/conv_2D.h | 1 - contrib/nnc/passes/interpreter/ops/conv_FFT.cpp | 4 +- contrib/nnc/passes/interpreter/ops/conv_FFT.h | 1 - contrib/nnc/passes/onnx_frontend/ONNXOpCreator.cpp | 17 +- contrib/nnc/passes/soft_backend/SBSerializer.cpp | 11 +- .../soft_backend/code_snippets/cpp_operations.def | 23 +- .../passes/tflite_frontend/tflite_op_creator.cpp | 84 ++++++- contrib/nnc/tests/interpreter/graph_creator.cpp | 10 +- contrib/nnc/tests/interpreter/op_info_util.cpp | 13 - contrib/nnc/tests/interpreter/op_info_util.h | 1 - .../nnc/unittests/soft_backend/CPPOperations.cpp | 37 +-- 27 files changed, 417 insertions(+), 510 deletions(-) diff --git a/contrib/nnc/core/modelIR/IrDotDumper.cpp b/contrib/nnc/core/modelIR/IrDotDumper.cpp index 5e09f69..b43961c 100644 --- a/contrib/nnc/core/modelIR/IrDotDumper.cpp +++ b/contrib/nnc/core/modelIR/IrDotDumper.cpp @@ -72,8 +72,9 @@ void IrDotDumper::visit(ops::Conv2DOp& op) { .withInShapes(getInputShapes(op)) .withOutShapes(getOutputShapes(op)) .withKernelShape(op.getKernel().getShape()) - .withPadType(op.getPaddingType()) - .withStride(op.getStrides()); + .withStride(op.getStrides()) + .withShape("Padding before", Shape(op.getPaddingBefore())) + .withShape("Padding after", Shape(op.getPaddingAfter())); dotBuilder.updateWithOp(&op, nodeInfo); } @@ -83,8 +84,9 @@ void IrDotDumper::visit(ops::DepthwiseConv2DOp& op) { .withInShapes(getInputShapes(op)) .withOutShapes(getOutputShapes(op)) .withKernelShape(op.getKernel().getShape()) - .withPadType(op.getPaddingType()) - .withStride(op.getStrides()); + .withStride(op.getStrides()) + .withShape("Padding before", Shape(op.getPaddingBefore())) + .withShape("Padding after", Shape(op.getPaddingAfter())); dotBuilder.updateWithOp(&op, nodeInfo); } @@ -112,9 +114,10 @@ void IrDotDumper::visit(ops::PoolOp& op) { .withInShapes(getInputShapes(op)) .withOutShapes(getOutputShapes(op)) .withShape("PoolWindow", op.getWindowShape()) - .withPadType(op.getPaddingType()) .withPoolType(op.getPoolingType()) - .withStride(op.getStrides()); + .withStride(op.getStrides()) + .withShape("Padding before", Shape(op.getPaddingBefore())) + .withShape("Padding after", Shape(op.getPaddingAfter())); dotBuilder.updateWithOp(&op, nodeInfo); } diff --git a/contrib/nnc/core/modelIR/operations/Conv2DOp.cpp b/contrib/nnc/core/modelIR/operations/Conv2DOp.cpp index aea018d..5f71eef 100644 --- a/contrib/nnc/core/modelIR/operations/Conv2DOp.cpp +++ b/contrib/nnc/core/modelIR/operations/Conv2DOp.cpp @@ -22,46 +22,30 @@ namespace ops { void Conv2DOp::inferOutputShapes() { auto& input_shape = getInputShape(0); - auto& kernel_shape = getKernel().getShape(); - auto& strides = getStrides(); - auto input_rank = input_shape.rank(); + auto& kernel_shape = _kernel.getShape(); - assert(input_rank == 4); + assert(input_shape.rank() == 4); + assert(kernel_shape.rank() == 4); + assert(_strides.rank() == 2); + assert(_paddingBefore.size() == 2); + assert(_paddingAfter.size() == 2); Shape output_shape; - output_shape.resize(input_rank); - - switch (getPaddingType()) { - case ops::PaddingType::Same: { - output_shape.dim(1) = (input_shape.dim(1) - 1) / strides.dim(0) + 1; - output_shape.dim(2) = (input_shape.dim(2) - 1) / strides.dim(1) + 1; - int32_t pad_along_height = (input_shape.dim(1) % strides.dim(0) == 0) - ? std::max(kernel_shape.dim(0) - strides.dim(0), 0) - : std::max((output_shape.dim(1) - 1) * strides.dim(0) + kernel_shape.dim(0) - input_shape.dim(1), 0); - int32_t pad_along_width = (input_shape.dim(2) % strides.dim(0) == 0) - ? std::max(kernel_shape.dim(1) - strides.dim(1), 0) - : std::max((output_shape.dim(2) - 1) * strides.dim(1) + kernel_shape.dim(1) - input_shape.dim(2), 0); - _paddings.at(0) = pad_along_height / 2; - _paddings.at(1) = pad_along_width / 2; - break; - } - case ops::PaddingType::Valid: - _paddings.at(0) = 0; - _paddings.at(1) = 0; - // FALLTHROUGH - case ops::PaddingType::Custom: - output_shape.dim(1) = (input_shape.dim(1) + 2 * getPadding(0) - kernel_shape.dim(0)) / strides.dim(0) + 1; - output_shape.dim(2) = (input_shape.dim(2) + 2 * getPadding(1) - kernel_shape.dim(1)) / strides.dim(1) + 1; - break; - default: - assert(false && "invalid padding type"); - break; - } - // For now padding for channels is not supported, initialize it with zero - _paddings.at(2) = 0; + output_shape.resize(4); + // Batch size and number of channels. output_shape.dim(0) = input_shape.dim(0); output_shape.dim(3) = kernel_shape.dim(3); + + // Height and width. + for (int i = 0; i < 2; i++) { + auto padded_input = input_shape.dim(1 + i) + _paddingBefore[i] + _paddingAfter[i]; + // out_size = ceil((in_size - kernel_size + 1) / stride) = + // (in_size - kernel_size + 1 + stride - 1) / stride = + // (in_size - kernel_size) / stride + 1 + output_shape.dim(1 + i) = (padded_input - kernel_shape.dim(i)) / _strides.dim(i) + 1; + } + setOutputShape(0, output_shape); } diff --git a/contrib/nnc/core/modelIR/operations/DeConv2DOp.cpp b/contrib/nnc/core/modelIR/operations/DeConv2DOp.cpp index dda82d9..4d429f3 100644 --- a/contrib/nnc/core/modelIR/operations/DeConv2DOp.cpp +++ b/contrib/nnc/core/modelIR/operations/DeConv2DOp.cpp @@ -24,42 +24,44 @@ namespace ops { // VALID: output = input * stride + filter - stride // SAME: output = input * stride - stride + 1 void DeConv2DOp::inferOutputShapes() { + // Input shape: [N, Hi, Wi, Ci] + // Kernel shape: [Hk, Wk, Co, Ci] + // Output shape: [N, Ho, Wo, Co] auto& input_shape = getInputShape(0); - auto& kernel_shape = getKernel().getShape(); - auto& strides = getStrides(); - auto input_rank = input_shape.rank(); + auto& kernel_shape = _kernel.getShape(); - assert(input_rank == 4); + assert(input_shape.rank() == 4); assert(kernel_shape.rank() == 4); assert(kernel_shape.dim(3) == input_shape.dim(3)); Shape output_shape; - output_shape.resize(input_rank); + output_shape.resize(4); - // Assumes no batch strides. + // Batch size and number of channels. + output_shape.dim(0) = input_shape.dim(0); + output_shape.dim(3) = kernel_shape.dim(2); + + // Height and width. switch (_paddingType) { case ops::PaddingType::Same: - for (int d = 1; d < 3; d++) - output_shape.dim(d) = input_shape.dim(d) * strides.dim(d - 1) - strides.dim(d - 1) + 1; + for (int d = 0; d < 2; d++) + output_shape.dim(1 + d) = input_shape.dim(1 + d) * _strides.dim(d) - _strides.dim(d) + 1; break; case ops::PaddingType::Valid: - for (int d = 1; d < 3; d++) { - output_shape.dim(d) = input_shape.dim(d) * strides.dim(d - 1) + - kernel_shape.dim(d - 1) - strides.dim(d - 1); - } + for (int d = 0; d < 2; d++) + output_shape.dim(1 + d) = + input_shape.dim(1 + d) * _strides.dim(d) + kernel_shape.dim(d) - _strides.dim(d); break; case ops::PaddingType::Custom: - for (int d = 1; d < 3; d++) { - output_shape.dim(d) = input_shape.dim(d) * strides.dim(d - 1) + - kernel_shape.dim(d - 1) - strides.dim(d - 1) - 2 * getPadding(0); - } + for (int d = 0; d < 2; d++) + output_shape.dim(1 + d) = + input_shape.dim(1 + d) * _strides.dim(d) + kernel_shape.dim(d) - _strides.dim(d) - + (_paddingBefore.at(d) + _paddingAfter.at(d)); break; - default: { + default: assert(false && "invalid padding type"); - } } - output_shape.dim(0) = input_shape.dim(0); - output_shape.dim(-1) = kernel_shape.dim(-2); + setOutputShape(0, output_shape); } diff --git a/contrib/nnc/core/modelIR/operations/DepthwiseConv2DOp.cpp b/contrib/nnc/core/modelIR/operations/DepthwiseConv2DOp.cpp index 3129713..878a421 100644 --- a/contrib/nnc/core/modelIR/operations/DepthwiseConv2DOp.cpp +++ b/contrib/nnc/core/modelIR/operations/DepthwiseConv2DOp.cpp @@ -23,47 +23,30 @@ namespace ops { void DepthwiseConv2DOp::inferOutputShapes() { auto& input_shape = getInputShape(0); auto& kernel_shape = getKernel().getShape(); - auto& strides = getStrides(); - auto input_rank = input_shape.rank(); - // Assuming input tensor is 3-dimensional. Will support more general cases when needed. - assert(input_rank == 4); + assert(input_shape.rank() == 4); + assert(kernel_shape.rank() == 4); assert(input_shape.dim(3) == kernel_shape.dim(2)); + assert(_strides.rank() == 2); + assert(_paddingBefore.size() == 2); + assert(_paddingAfter.size() == 2); Shape output_shape; - output_shape.resize(input_rank); - - switch (getPaddingType()) { - case ops::PaddingType::Same: { - output_shape.dim(1) = (input_shape.dim(1) - 1) / strides.dim(0) + 1; - output_shape.dim(2) = (input_shape.dim(2) - 1) / strides.dim(1) + 1; - int32_t pad_along_height = (input_shape.dim(1) % strides.dim(0) == 0) - ? std::max(kernel_shape.dim(0) - strides.dim(0), 0) - : std::max((output_shape.dim(1) - 1) * strides.dim(0) + kernel_shape.dim(0) - input_shape.dim(1), 0); - int32_t pad_along_width = (input_shape.dim(2) % strides.dim(1) == 0) - ? std::max(kernel_shape.dim(1) - strides.dim(1), 0) - : std::max((output_shape.dim(2) - 1) * strides.dim(1) + kernel_shape.dim(1) - input_shape.dim(2), 0); - _paddings.at(0) = pad_along_height / 2; - _paddings.at(1) = pad_along_width / 2; - break; - } - case ops::PaddingType::Valid: - _paddings.at(0) = 0; - _paddings.at(1) = 0; - // FALLTHROUGH - case ops::PaddingType::Custom: - output_shape.dim(1) = (input_shape.dim(1) + 2 * getPadding(0) - kernel_shape.dim(0)) / strides.dim(0) + 1; - output_shape.dim(2) = (input_shape.dim(2) + 2 * getPadding(1) - kernel_shape.dim(1)) / strides.dim(1) + 1; - break; - default: - assert(false && "invalid padding type"); - break; - } - // For now padding for channels is not supported, initialize it with zero - _paddings.at(2) = 0; + output_shape.resize(4); + // Batch size and number of channels. output_shape.dim(0) = input_shape.dim(0); output_shape.dim(3) = input_shape.dim(3) * kernel_shape.dim(3); + + // Height and width. + for (int i = 0; i < 2; i++) { + auto padded_input = input_shape.dim(1 + i) + _paddingBefore[i] + _paddingAfter[i]; + // out_size = ceil((in_size - kernel_size + 1) / stride) = + // (in_size - kernel_size + 1 + stride - 1) / stride = + // (in_size - kernel_size) / stride + 1 + output_shape.dim(1 + i) = (padded_input - kernel_shape.dim(i)) /_strides.dim(i) + 1; + } + setOutputShape(0, output_shape); } diff --git a/contrib/nnc/core/modelIR/operations/PoolOp.cpp b/contrib/nnc/core/modelIR/operations/PoolOp.cpp index a49e1da..122c4bc 100644 --- a/contrib/nnc/core/modelIR/operations/PoolOp.cpp +++ b/contrib/nnc/core/modelIR/operations/PoolOp.cpp @@ -22,56 +22,46 @@ namespace ops { void PoolOp::inferOutputShapes() { auto& input_shape = getInputShape(0); - auto& window_shape = getWindowShape(); - auto& strides = getStrides(); - auto input_rank = input_shape.rank(); - assert(input_rank == 4); + assert(input_shape.rank() == 4); + assert(_windowShape.rank() == 2); + assert(_strides.rank() == 2); + assert(_paddingBefore.size() == 2); + assert(_paddingAfter.size() == 2); Shape output_shape; - output_shape.resize(input_rank); + output_shape.resize(4); - switch (getPaddingType()) { - case ops::PaddingType::Same: { - output_shape.dim(1) = (input_shape.dim(1) - 1) / strides.dim(0) + 1; - output_shape.dim(2) = (input_shape.dim(2) - 1) / strides.dim(1) + 1; - int32_t pad_along_height = (input_shape.dim(1) % strides.dim(0) == 0) - ? std::max(window_shape.dim(0) - strides.dim(0), 0) - : std::max((output_shape.dim(1) - 1) * strides.dim(0) + window_shape.dim(0) - input_shape.dim(1), 0); - int32_t pad_along_width = (input_shape.dim(1) % strides.dim(0) == 0) - ? std::max(window_shape.dim(1) - strides.dim(1), 0) - : std::max((output_shape.dim(2) - 1) * strides.dim(1) + window_shape.dim(1) - input_shape.dim(2), 0); - _paddings.at(0) = pad_along_height / 2; - _paddings.at(1) = pad_along_width / 2; + // Batch size and number of channels. + output_shape.dim(0) = input_shape.dim(0); + output_shape.dim(3) = input_shape.dim(3); + + // Height and width. + switch (_roundMode) { + case RoundMode::ceil: + for (int i = 0; i < 2; i++) { + auto padded_input = input_shape.dim(1 + i) + _paddingBefore.at(i) + _paddingAfter.at(i); + // Incorrect formula taken from Caffe leading to out-of-bound accesses. + // out_size = ceil((in_size - window_size) / stride) + 1 = + // = (in_size - window_size + stride - 1) / stride + 1 = + // = ceil((in_size - window_size + stride) / stride) + output_shape.dim(1 + i) = + (padded_input - _windowShape.dim(i) + _strides.dim(i) - 1) / _strides.dim(i) + 1; + } break; - } - case ops::PaddingType::Valid: - _paddings.at(0) = 0; - _paddings.at(1) = 0; - // FALLTHROUGH - case ops::PaddingType::Custom: - switch (_roundMode) { - case PoolOp::RoundMode::ceil: - output_shape.dim(1) = static_cast(ceil(static_cast( - input_shape.dim(1) + 2 * getPadding(0) - window_shape.dim(0)) / strides.dim(0))) + 1; - output_shape.dim(2) = static_cast(ceil(static_cast( - input_shape.dim(2) + 2 * getPadding(1) - window_shape.dim(1)) / strides.dim(1))) + 1; - break; - case PoolOp::RoundMode::floor: - output_shape.dim(1) = (input_shape.dim(1) + 2 * getPadding(0) - window_shape.dim(0)) / strides.dim(0) + 1; - output_shape.dim(2) = (input_shape.dim(2) + 2 * getPadding(1) - window_shape.dim(1)) / strides.dim(1) + 1; - break; + case RoundMode::floor: + for (int i = 0; i < 2; i++) { + auto padded_input = input_shape.dim(1 + i) + _paddingBefore.at(i) + _paddingAfter.at(i); + // out_size = ceil((in_size - window_size + 1) / stride) = + // (in_size - window_size + 1 + stride - 1) / stride = + // (in_size - window_size) / stride + 1 + output_shape.dim(1 + i) = (padded_input - _windowShape.dim(i)) / _strides.dim(i) + 1; } break; default: - assert(false && "invalid padding type"); - break; + assert(false); } - // For now padding for channels is not supported, initialize it with zero - _paddings.at(2) = 0; - output_shape.dim(0) = input_shape.dim(0); - output_shape.dim(3) = input_shape.dim(3); setOutputShape(0, output_shape); } diff --git a/contrib/nnc/include/core/modelIR/operations/Conv2DOp.h b/contrib/nnc/include/core/modelIR/operations/Conv2DOp.h index 536d7a7..6fa821a 100644 --- a/contrib/nnc/include/core/modelIR/operations/Conv2DOp.h +++ b/contrib/nnc/include/core/modelIR/operations/Conv2DOp.h @@ -32,25 +32,13 @@ public: Conv2DOp(const IODescriptor& arg, const TensorVariant& kernel, const Shape& strides, - const std::vector& paddings) + const std::vector& padding_before, + const std::vector& padding_after) : Operation(Type::conv2D, {arg}), _kernel(kernel), _strides(strides), - _paddingType(PaddingType::Custom), - _paddings(paddings) { - inferOutputShapes(); - } - - Conv2DOp(const IODescriptor& arg, - const TensorVariant& kernel, - const Shape& strides, - PaddingType padding_type) - : Operation(Type::conv2D, {arg}), - _kernel(kernel), - _strides(strides), - _paddingType(padding_type), - _paddings(3) { - assert(_paddingType != PaddingType::Custom); + _paddingBefore(padding_before), + _paddingAfter(padding_after) { inferOutputShapes(); } @@ -58,17 +46,17 @@ public: const Shape& getStrides() const { return _strides; } - PaddingType getPaddingType() const { return _paddingType; } + const std::vector& getPaddingBefore() const { return _paddingBefore; } - int32_t getPadding(int32_t dim) const { return _paddings[dim]; } + const std::vector& getPaddingAfter() const { return _paddingAfter; } private: void inferOutputShapes(); - const TensorVariant _kernel; + TensorVariant _kernel; Shape _strides; - PaddingType _paddingType; - std::vector _paddings; + std::vector _paddingBefore; + std::vector _paddingAfter; }; } // namespace ops diff --git a/contrib/nnc/include/core/modelIR/operations/Deconv2DOp.h b/contrib/nnc/include/core/modelIR/operations/Deconv2DOp.h index 3816256..0be89fd 100644 --- a/contrib/nnc/include/core/modelIR/operations/Deconv2DOp.h +++ b/contrib/nnc/include/core/modelIR/operations/Deconv2DOp.h @@ -35,7 +35,8 @@ public: _kernel(kernel), _strides(strides), _paddingType(PaddingType::Custom), - _paddings(paddings) { + _paddingBefore(paddings), + _paddingAfter(paddings) { inferOutputShapes(); } @@ -47,7 +48,8 @@ public: _kernel(kernel), _strides(strides), _paddingType(padding_type), - _paddings(3) { + _paddingBefore(2), + _paddingAfter(2) { assert(_paddingType != PaddingType::Custom); inferOutputShapes(); } @@ -58,7 +60,9 @@ public: PaddingType getPaddingType() const { return _paddingType; } - int getPadding(int32_t dim) const { return _paddings[dim]; } + const std::vector& getPaddingBefore() const { return _paddingBefore; } + + const std::vector& getPaddingAfter() const { return _paddingAfter; } private: void inferOutputShapes(); @@ -66,7 +70,8 @@ private: const TensorVariant _kernel; Shape _strides; PaddingType _paddingType; - std::vector _paddings; + std::vector _paddingBefore; + std::vector _paddingAfter; }; } // namespace ops diff --git a/contrib/nnc/include/core/modelIR/operations/DepthwiseConv2DOp.h b/contrib/nnc/include/core/modelIR/operations/DepthwiseConv2DOp.h index bf062ef..eb0f078 100644 --- a/contrib/nnc/include/core/modelIR/operations/DepthwiseConv2DOp.h +++ b/contrib/nnc/include/core/modelIR/operations/DepthwiseConv2DOp.h @@ -32,25 +32,13 @@ public: DepthwiseConv2DOp(const IODescriptor& arg, const TensorVariant& kernel, const Shape& strides, - const std::vector& paddings) + const std::vector& padding_before, + const std::vector& padding_after) : Operation(Type::depthwiseConv, {arg}), _kernel(kernel), _strides(strides), - _paddingType(PaddingType::Custom), - _paddings(paddings) { - inferOutputShapes(); - } - - DepthwiseConv2DOp(const IODescriptor& arg, - const TensorVariant& kernel, - const Shape& strides, - PaddingType padding_type) - : Operation(Type::depthwiseConv, {arg}), - _kernel(kernel), - _strides(strides), - _paddingType(padding_type), - _paddings(_kernel.getShape().rank()) { - assert(_paddingType != PaddingType::Custom); + _paddingBefore(padding_before), + _paddingAfter(padding_after) { inferOutputShapes(); } @@ -58,17 +46,17 @@ public: const Shape& getStrides() const { return _strides; } - PaddingType getPaddingType() const { return _paddingType; } + const std::vector& getPaddingBefore() const { return _paddingBefore; } - int32_t getPadding(int32_t dim) const { return _paddings[dim]; } + const std::vector& getPaddingAfter() const { return _paddingAfter; } private: void inferOutputShapes(); - const TensorVariant _kernel; + TensorVariant _kernel; Shape _strides; - PaddingType _paddingType; - std::vector _paddings; + std::vector _paddingBefore; + std::vector _paddingAfter; }; } // namespace ops diff --git a/contrib/nnc/include/core/modelIR/operations/PoolOp.h b/contrib/nnc/include/core/modelIR/operations/PoolOp.h index df411c3..b8c5c7f 100644 --- a/contrib/nnc/include/core/modelIR/operations/PoolOp.h +++ b/contrib/nnc/include/core/modelIR/operations/PoolOp.h @@ -39,48 +39,30 @@ public: EMPTY // Consider that there are no elements outside of input shape }; + // Rounding mode to use when calculating the output size. enum class RoundMode { - ceil, // used in caffe - floor // used in tflite + ceil, + floor }; PoolOp(const IODescriptor& arg, - const Shape& window_shape, - const Shape& strides, PoolingType pooling_type, - const std::vector& paddings, - BorderType border_type, - RoundMode round_mode) - : Operation(Type::pool, {arg}), - _windowShape(window_shape), - _strides(strides), - _poolingType(pooling_type), - _paddingType(PaddingType::Custom), - _paddings(paddings), - _borderType(border_type), - _roundMode(round_mode) { - inferOutputShapes(); - } - - PoolOp(const IODescriptor& arg, const Shape& window_shape, const Shape& strides, - PoolingType pooling_type, - PaddingType padding_type, + const std::vector& padding_before, + const std::vector& padding_after, BorderType border_type, RoundMode round_mode) : Operation(Type::pool, {arg}), + _poolingType(pooling_type), _windowShape(window_shape), _strides(strides), - _poolingType(pooling_type), - _paddingType(padding_type), - _paddings(window_shape.rank()), + _paddingBefore(padding_before), + _paddingAfter(padding_after), _borderType(border_type), _roundMode(round_mode) { - assert(_paddingType != PaddingType::Custom); inferOutputShapes(); } - PaddingType getPaddingType() const { return _paddingType; } BorderType getBorderType() const { return _borderType; } @@ -90,18 +72,20 @@ public: const Shape& getStrides() const { return _strides; } - int32_t getPadding(int32_t dim) const { return _paddings[dim]; } + const std::vector& getPaddingBefore() const { return _paddingBefore; } + + const std::vector& getPaddingAfter() const { return _paddingAfter; } private: void inferOutputShapes(); - PaddingType _paddingType; PoolingType _poolingType; - BorderType _borderType; - RoundMode _roundMode; Shape _windowShape; Shape _strides; - std::vector _paddings; + std::vector _paddingBefore; + std::vector _paddingAfter; + BorderType _borderType; + RoundMode _roundMode; }; } // namespace ops diff --git a/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.cpp b/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.cpp index 3546c32..37a72d2 100644 --- a/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.cpp +++ b/contrib/nnc/passes/acl_soft_backend/AclCppOpGenerator.cpp @@ -182,18 +182,6 @@ void AclCppOpGenerator::visit(ops::SoftmaxOp& op) { } } -template -static Shape getWindowShape(const Oper& op) { - const Shape& kshape = op.getKernel().getShape(); - return Shape{1, kshape.dim(0), kshape.dim(1), kshape.dim(2)}; -} - -template<> -Shape getWindowShape(const ops::PoolOp& op) { - const Shape& wshape = op.getWindowShape(); - return Shape{1, wshape.dim(-3), wshape.dim(-2), wshape.dim(-1)}; -} - /** * @brief Generate DOM for PadStrideInfo object * @tparam Oper Class of operation with pad and stride properties @@ -207,43 +195,10 @@ static shared_ptr genPadStrideInfo(const Oper& op, const string& prefix, ArtifactBlock* block) { using AF = ArtifactFactory; - const Shape& strides = transposeShape<1, 0, 2>(op.getStrides()); - assert(strides.rank() == 3 && strides.dim(2) == 1); - - // array of paddings - // order is following: [(top, bottom), (left, right)] - std::array, 2> pads; - - // TODO this is workaround to overcome limitations of Model IR - // replace when left and right paddings introduced - if (op.getPaddingType() == mir::ops::PaddingType::Same) { - // If padding type is Same we need to recompute paddigns, - // to cover case with unequal "left" and "right" paddings - const int32_t h_axis = -3; - const int32_t w_axis = -2; - // This is offset to work correctly with bare padding and strides arrays - const int32_t axis_offset = 3; - - const Shape window_shape = getWindowShape(op); - - for (int32_t axis : {h_axis, w_axis}) { - int32_t full_pad; - const int32_t n = op.getInputShape(0).dim(axis); - const int32_t k = window_shape.dim(axis); - const int32_t s = op.getStrides().dim(axis); - if (n % s == 0) - full_pad = max(k - s, 0); - else - full_pad = max(k - n % s, 0); - pads[axis + axis_offset].first = full_pad / 2; - pads[axis + axis_offset].second = full_pad - pads[axis + axis_offset].first; - } - } else { - pads[0].first = pads[0].second = op.getPadding(0); - pads[1].first = pads[1].second = op.getPadding(1); - } - - assert(op.getPadding(2) == 0); + const Shape& strides = transposeShape<1, 0>(op.getStrides()); + assert(strides.rank() == 2); + auto& padding_before = op.getPaddingBefore(); + auto& padding_after = op.getPaddingAfter(); string type_name = "arm_compute::PadStrideInfo"; @@ -252,10 +207,10 @@ static shared_ptr list> var_init_params = {AF::lit(to_string(op.getStrides().dim(1))), AF::lit(to_string(op.getStrides().dim(0))), - AF::lit(to_string(pads[1].first)), - AF::lit(to_string(pads[1].second)), - AF::lit(to_string(pads[0].first)), - AF::lit(to_string(pads[0].second)), + AF::lit(to_string(padding_before.at(1))), + AF::lit(to_string(padding_after.at(1))), + AF::lit(to_string(padding_before.at(0))), + AF::lit(to_string(padding_after.at(0))), AF::lit("arm_compute::DimensionRoundingType::FLOOR")}; auto pad_stride_info_var = block->var(type_name, var_name, {}, var_init_params); diff --git a/contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp b/contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp index feaef45..c0cb96a 100644 --- a/contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp +++ b/contrib/nnc/passes/caffe_frontend/caffe_op_creator.cpp @@ -52,85 +52,6 @@ namespace nnc { using namespace mir; using namespace ::caffe; -template -static inline bool has2DStride(const OptsType& opts) { - if (opts.has_stride_h() != opts.has_stride_w()) - throw PassException("Conv or Pool layer has only 1 out of 2 2D strides, investigate"); - // We already checked that both 2D strides are both present or both are not - return opts.has_stride_h(); -} - -static inline Shape getStrideFromOneValue(bool has_stride, uint32_t stride) { - if (has_stride) - return Shape{static_cast(stride), static_cast(stride), 1}; - else - return Shape{1, 1, 1}; -} - -static inline Shape getStrideFromTwoValues(uint32_t stride1, uint32_t stride2) { - return Shape{static_cast(stride1), static_cast(stride2), 1}; -} - -template -static inline Shape getStride(const OptsType& opts) { - // stride might have size 0, then it defaults to 1 - if (opts.stride_size() == 0) - return Shape{1, 1, 1}; - else if (opts.stride_size() == 1) - return getStrideFromOneValue(true, opts.stride(0)); - else - return getStrideFromTwoValues(opts.stride(0), opts.stride(1)); -} - -/** - * @brief Determines stride values from Caffe layer options. - * Only 2D convolutions/pools are supported currently. - * @todo Currently, stride_h and stride_w options take precedence if they are present, - * but maybe it is not correct logic. Check how it really is done. - */ -static Shape getConvStride(const ConvolutionParameter& opts) { - if (has2DStride(opts)) - return getStrideFromTwoValues(opts.stride_h(), opts.stride_w()); - else - return getStride(opts); -} - -static Shape getPoolStride(const PoolingParameter& opts) { - if (has2DStride(opts)) - return getStrideFromTwoValues(opts.stride_h(), opts.stride_w()); - else - return getStrideFromOneValue(opts.has_stride(), opts.stride()); -} - -static Shape getPoolWindowShape(const PoolingParameter& opts) { - if (opts.has_kernel_h() != opts.has_kernel_w()) - throw PassException("Pool layer has only 1 out of 2 kernel dimensions, investigate"); - - if (opts.has_kernel_h()) { - return Shape{static_cast(opts.kernel_h()), - static_cast(opts.kernel_w()), - 1}; - } else if (opts.has_kernel_size()) { - return Shape{static_cast(opts.kernel_size()), - static_cast(opts.kernel_size()), - 1}; - } else { - throw PassException("Pooling layer doesn't have kernel size data, investigate"); - } -} - -static ops::PoolOp::PoolingType getPoolingType(const PoolingParameter& opts) { - using PoolingType = ops::PoolOp::PoolingType; - - if (opts.pool() == PoolingParameter::MAX) - return PoolingType::MAX; - else if (opts.pool() == PoolingParameter::AVE) - return PoolingType::AVG; - else - throw PassException("Unsupported pooling type: " + - PoolingParameter::PoolMethod_Name(opts.pool())); -} - /** Convert kernel for grouped 2d convolution in kernel for ordinary 2d convolution * * Grouped convolution breaks input and kernel channels into selected number of groups and applies convolution in every group of channels independently. @@ -236,6 +157,47 @@ CaffeOpCreator::convertInput(const LayerParameter& layer) { return descriptors; } +static void convertConvolutionParam(const ConvolutionParameter& conv_param, Shape& strides, + std::vector& padding) { + int32_t stride_h, stride_w; + if (conv_param.has_stride_h() || conv_param.has_stride_w()) { + // If stride_h or stride_w are set, they take precedence. + stride_h = conv_param.stride_h(); + stride_w = conv_param.stride_w(); + } else if (conv_param.stride_size() == 0) { + // If no strides specified, they defaults to 1. + stride_h = stride_w = 1; + } else if (conv_param.stride_size() == 1) { + // If only one stride specified, all strides take the same value. + stride_h = stride_w = conv_param.stride(0); + } else { + // Otherwise, there must be a stride for each dimension. + assert(conv_param.stride_size() == 2); + stride_h = conv_param.stride(0); + stride_w = conv_param.stride(1); + } + strides = {stride_h, stride_w}; + + int32_t pad_h, pad_w; + if (conv_param.has_pad_h() || conv_param.has_pad_w()) { + // If pad_h or pad_w are set, they take precedence. + pad_h = conv_param.pad_h(); + pad_w = conv_param.pad_w(); + } else if (conv_param.pad_size() == 0) { + // If no pads specified, they defaults to 0. + pad_h = pad_w = 0; + } else if (conv_param.pad_size() == 1) { + // If only one pad specified, all pads take the same value. + pad_h = pad_w = conv_param.pad(0); + } else { + // Otherwise, there must be a pad for each dimension. + assert(conv_param.pad_size() == 2); + pad_h = conv_param.pad(0); + pad_w = conv_param.pad(1); + } + padding = {pad_h, pad_w}; +} + void CaffeOpCreator::checkConvolution(const ConvolutionParameter& opts, std::set& problems_op_set) { assert(opts.stride_size() <= 2); @@ -252,14 +214,10 @@ CaffeOpCreator::convertConvolution(const caffe::LayerParameter& layer, const std::vector& inputs, const std::vector>& params) { auto& opts = layer.convolution_param(); - Shape strides = getConvStride(opts); + Shape strides; + std::vector padding; - // Set pads - int pad_h = opts.has_pad_h() ? opts.pad_h() : 0; - int pad_w = opts.has_pad_w() ? opts.pad_w() : 0; - if (opts.pad_size() == 1) - pad_h = pad_w = opts.pad(0); - std::vector paddings{pad_h, pad_w, 0}; + convertConvolutionParam(opts, strides, padding); std::shared_ptr unfolded_tensor = params[0]; Operation* conv2d; @@ -272,14 +230,14 @@ CaffeOpCreator::convertConvolution(const caffe::LayerParameter& layer, // TODO handle properly kernel with layer multiplier std::shared_ptr transposed_tensor = mir::transposeTensor<0, 1, 3, 2>(params[0]); conv2d = createOp(layer.name(), convertCaffeToMIR(inputs[0]), - *transposed_tensor, strides, paddings); + *transposed_tensor, strides, padding, padding); } else { if (num_groups != 1) { // first we need to convert kernel of grouped convolution to appropriate ordinary kernel unfolded_tensor = fixGroupedKernel(opts.group(), params[0]); } conv2d = createOp(layer.name(), convertCaffeToMIR(inputs[0]), *unfolded_tensor, - strides, paddings); + strides, padding, padding); } // bias_term is optional (so might not be present) and defaults to true @@ -292,6 +250,34 @@ CaffeOpCreator::convertConvolution(const caffe::LayerParameter& layer, } } +std::vector +CaffeOpCreator::convertDeconvolution(const caffe::LayerParameter& layer, + const std::vector& inputs, + const std::vector>& params) { + auto& opts = layer.convolution_param(); + Shape strides; + std::vector padding; + + convertConvolutionParam(opts, strides, padding); + + std::shared_ptr unfolded_tensor = params[0]; + if (opts.group() != 1) { + // first we need to convert kernel of grouped convolution to appropriate ordinary kernel + unfolded_tensor = fixGroupedKernel(opts.group(), params[0]); + } + auto deconv2d = createOp(layer.name(), convertCaffeToMIR(inputs[0]), + *unfolded_tensor, strides, padding); + + // bias_term is optional (so might not be present) and defaults to true + if (!opts.has_bias_term() || opts.bias_term()) { + auto bias_add = createOp(layer.name() + ".bias", deconv2d->getOutput(0), + *params[1]); + return {convertMIRToCaffe(bias_add->getOutput(0))}; + } else { + return {convertMIRToCaffe(deconv2d->getOutput(0))}; + } +} + void CaffeOpCreator::checkInnerProduct(const InnerProductParameter& opts, std::set& problemsOpSet) { if (opts.has_axis() && opts.axis() != 1) @@ -349,6 +335,50 @@ CaffeOpCreator::convertConcat(const caffe::LayerParameter& layer, } } +static ops::PoolOp::PoolingType getPoolingType(const PoolingParameter& pool_param) { + using PoolingType = ops::PoolOp::PoolingType; + + if (pool_param.pool() == PoolingParameter::MAX) + return PoolingType::MAX; + else if (pool_param.pool() == PoolingParameter::AVE) + return PoolingType::AVG; + else + throw PassException("Unsupported pooling type: " + + PoolingParameter::PoolMethod_Name(pool_param.pool())); +} + +static void convertPoolingParam(const caffe::PoolingParameter& pool_param, + Shape& window_shape, Shape& strides, + std::vector& padding) { + int32_t kernel_h, kernel_w; + assert(!pool_param.global_pooling()); + if (pool_param.has_kernel_size()) { + kernel_h = kernel_w = pool_param.kernel_size(); + } else { + kernel_h = pool_param.kernel_h(); + kernel_w = pool_param.kernel_w(); + } + window_shape = {kernel_h, kernel_w}; + + int32_t stride_h, stride_w; + if (pool_param.has_stride_h() || pool_param.has_stride_w()) { + stride_h = pool_param.stride_h(); + stride_w = pool_param.stride_w(); + } else { + stride_h = stride_w = pool_param.stride(); + } + strides = {stride_h, stride_w}; + + int32_t pad_h, pad_w; + if (pool_param.has_pad_h() || pool_param.has_pad_w()) { + pad_h = pool_param.pad_h(); + pad_w = pool_param.pad_w(); + } else { + pad_h = pad_w = pool_param.pad(); + } + padding = {pad_h, pad_w}; +} + void CaffeOpCreator::checkPooling(const PoolingParameter& opts, std::set& problemsOpSet) { if (opts.has_global_pooling() && opts.global_pooling()) @@ -366,17 +396,13 @@ std::vector CaffeOpCreator::convertPooling(const caffe::LayerParameter& layer, const std::vector& inputs) { auto& opts = layer.pooling_param(); - Shape window_shape = getPoolWindowShape(opts); + Shape window_shape; + Shape strides; + std::vector padding; - // Set pads - int pad_h = opts.has_pad_h() ? opts.pad_h() : 0; - int pad_w = opts.has_pad_w() ? opts.pad_w() : 0; - if (opts.has_pad()) - pad_h = pad_w = opts.pad(); - std::vector paddings{pad_h, pad_w, 1}; + convertPoolingParam(opts, window_shape, strides, padding); ops::PoolOp::PoolingType pool_type = getPoolingType(opts); - Shape strides = getPoolStride(opts); ops::PoolOp::BorderType border_type; switch (pool_type) { case ops::PoolOp::PoolingType::AVG: @@ -386,12 +412,11 @@ CaffeOpCreator::convertPooling(const caffe::LayerParameter& layer, border_type = ops::PoolOp::BorderType::EMPTY; break; default: - // This check performed in checkPooling() assert(false); } - auto pooling = createOp(layer.name(), convertCaffeToMIR(inputs[0]), window_shape, - strides, pool_type, paddings, border_type, + auto pooling = createOp(layer.name(), convertCaffeToMIR(inputs[0]), pool_type, + window_shape, strides, padding, padding, border_type, ops::PoolOp::RoundMode::ceil); return {convertMIRToCaffe(pooling->getOutput(0))}; } @@ -537,38 +562,6 @@ CaffeOpCreator::convertDropout(const caffe::LayerParameter& layer, } std::vector -CaffeOpCreator::convertDeconvolution(const caffe::LayerParameter& layer, - const std::vector& inputs, - const std::vector>& params) { - auto& opts = layer.convolution_param(); - Shape strides = getStride(opts); - - // Set pads - int pad_h = opts.has_pad_h() ? opts.pad_h() : 0; - int pad_w = opts.has_pad_w() ? opts.pad_w() : 0; - if (opts.pad_size()) - pad_h = pad_w = opts.pad(0); - std::vector paddings{pad_h, pad_w, 0}; - - std::shared_ptr unfolded_tensor = params[0]; - if (opts.group() != 1) { - // first we need to convert kernel of grouped convolution to appropriate ordinary kernel - unfolded_tensor = fixGroupedKernel(opts.group(), params[0]); - } - auto deconv2d = createOp(layer.name(), convertCaffeToMIR(inputs[0]), - *unfolded_tensor, strides, paddings); - - // bias_term is optional (so might not be present) and defaults to true - if (!opts.has_bias_term() || opts.bias_term()) { - auto bias_add = createOp(layer.name() + ".bias", deconv2d->getOutput(0), - *params[1]); - return {convertMIRToCaffe(bias_add->getOutput(0))}; - } else { - return {convertMIRToCaffe(deconv2d->getOutput(0))}; - } -} - -std::vector CaffeOpCreator::convertELU(const caffe::LayerParameter& layer, const std::vector& inputs, const std::vector>& params) { @@ -577,9 +570,9 @@ CaffeOpCreator::convertELU(const caffe::LayerParameter& layer, return {elu->getOutput(0)}; } -std::vector +std::vector CaffeOpCreator::convertTanH(const caffe::LayerParameter& layer, - const std::vector& inputs) { + const std::vector& inputs) { auto tanh = createOp(layer.name(), inputs[0]); return {tanh->getOutput(0)}; } @@ -610,7 +603,7 @@ CaffeOpCreator::convertEltwise(const caffe::LayerParameter& layer, std::vector CaffeOpCreator::convertSplit(const caffe::LayerParameter& layer, const std::vector& inputs) { - std::vector outputs(layer.top_size(), inputs.at(0)); + std::vector outputs(layer.top_size(), inputs.at(0)); return outputs; } diff --git a/contrib/nnc/passes/interpreter/ops/DeConv2D.cpp b/contrib/nnc/passes/interpreter/ops/DeConv2D.cpp index ff23d62..b33d32f 100644 --- a/contrib/nnc/passes/interpreter/ops/DeConv2D.cpp +++ b/contrib/nnc/passes/interpreter/ops/DeConv2D.cpp @@ -32,7 +32,7 @@ std::vector nnc::DeConv2D::operator()() { Shape out_shape = _out_shape; auto res = allocate_tensor(out_shape); Tensor res_accesor(res); - Index pads({_op.getPadding(0), _op.getPadding(1), 0}); + Index pads({_op.getPaddingBefore().at(0), _op.getPaddingBefore().at(1), 0}); out_shape.dim(3) = 1; ShapeRange out_range(out_shape); @@ -98,19 +98,20 @@ std::vector nnc::DeConv2D::operator()() { return {res}; } -DeConv2D::DeConv2D(const TensorVariant &input, const DeConv2DOp &op) - : _input(input), _kernel(op.getKernel()), _strides(op.getStrides()), - _padding(op.getPaddingType()), _out_shape(op.getOutputShape(0)), _op(op) { - +DeConv2D::DeConv2D(const TensorVariant& input, const DeConv2DOp& op) + : _input(input), _kernel(op.getKernel()), _strides(op.getStrides()), + _padding(op.getPaddingType()), _out_shape(op.getOutputShape(0)), _op(op) { + // Input shape: [N, Hi, Wi, Ci] + // Kernel shape: [Hk, Wk, Co, Ci] assert(_op.getInputShape(0).rank() == 4); + const auto& input_shape = _input.getShape(); const auto& kernel_shape = _kernel.getShape(); - const auto& inp_shape = input.getShape(); - assert(inp_shape.rank() == 4); + assert(input_shape.rank() == 4); assert(kernel_shape.rank() == 4); + assert(kernel_shape.dim(3) == input_shape.dim(3)); assert(_strides.dim(2) == 1); - assert(_op.getPadding(2) == 0); - // kernel shape inp_shape [hw"oc""ic"]; input's - ["batch"hw"ic"] - assert(kernel_shape.dim(3) == inp_shape.dim(3)); + assert(_op.getPaddingBefore().size() == 2); + assert(_op.getPaddingAfter().size() == 2); } } // namespace nnc diff --git a/contrib/nnc/passes/interpreter/ops/Depthwise_conv_2D.cpp b/contrib/nnc/passes/interpreter/ops/Depthwise_conv_2D.cpp index e58b20f..0110d8d 100644 --- a/contrib/nnc/passes/interpreter/ops/Depthwise_conv_2D.cpp +++ b/contrib/nnc/passes/interpreter/ops/Depthwise_conv_2D.cpp @@ -30,8 +30,8 @@ std::vector DepthwiseConv2D::operator()() TensorVariant res = allocate_tensor(_out_shape); Tensor resAccessor(res); - Shape strides({_strides.dim(0), _strides.dim(1), _strides.dim(2)}); - Index pads({_op.getPadding(0), _op.getPadding(1), 0u}); + Shape strides({_strides.dim(0), _strides.dim(1), 1}); + Index pads({_op.getPaddingBefore().at(0), _op.getPaddingBefore().at(1), 0}); Shape outShape = res.getShape(); // Assume batch size == 1 and strip it off. @@ -82,14 +82,12 @@ std::vector DepthwiseConv2D::operator()() DepthwiseConv2D::DepthwiseConv2D(const TensorVariant &input, const DepthwiseConv2DOp &op) : _input(input), _kernel(op.getKernel()), _strides(op.getStrides()), - _padding(op.getPaddingType()), _out_shape(op.getOutputShape(0)), _op(op) + _out_shape(op.getOutputShape(0)), _op(op) { assert(_op.getInputShape(0).rank() == 4); - assert(input.getShape().rank() == 4); + assert(_input.getShape().rank() == 4); assert(_kernel.getShape().rank() == 4); - assert(_strides.dim(2) == 1); - assert(_op.getPadding(2) == 0); assert(_kernel.getShape().dim(2) == _input.getShape().dim(3)); } diff --git a/contrib/nnc/passes/interpreter/ops/Depthwise_conv_2D.h b/contrib/nnc/passes/interpreter/ops/Depthwise_conv_2D.h index ae1c2a1..e4e7707 100644 --- a/contrib/nnc/passes/interpreter/ops/Depthwise_conv_2D.h +++ b/contrib/nnc/passes/interpreter/ops/Depthwise_conv_2D.h @@ -35,7 +35,6 @@ private: const mir::Tensor _input; const mir::Tensor _kernel; const mir::Shape _strides; - const mir::ops::PaddingType _padding; const mir::Shape &_out_shape; const mir::ops::DepthwiseConv2DOp &_op; }; diff --git a/contrib/nnc/passes/interpreter/ops/Pool.cpp b/contrib/nnc/passes/interpreter/ops/Pool.cpp index 0948a79..48efc29 100644 --- a/contrib/nnc/passes/interpreter/ops/Pool.cpp +++ b/contrib/nnc/passes/interpreter/ops/Pool.cpp @@ -31,7 +31,10 @@ using namespace mir::ops; Pool::Pool(const TensorVariant &_input, const PoolOp &op) : _op(op), _input(_input) { assert(_input.getShape().rank() == 4); - assert(op.getWindowShape().rank() == 3); + assert(op.getWindowShape().rank() == 2); + assert(op.getStrides().rank() == 2); + assert(op.getPaddingBefore().size() == 2); + assert(op.getPaddingAfter().size() == 2); } std::vector Pool::operator()() @@ -41,14 +44,14 @@ std::vector Pool::operator()() Shape window_shape{1, _op.getWindowShape().dim(0), _op.getWindowShape().dim(1), 1}; Shape strides{1, _op.getStrides().dim(0), _op.getStrides().dim(1), 1}; - Index pads{0, _op.getPadding(0), _op.getPadding(1), 0}; + Index pads{0, _op.getPaddingBefore().at(0), _op.getPaddingBefore().at(1), 0}; const Shape &outShape = resAccessor.getShape(); ShapeRange outRange(outShape); ShapeRange inRange(_input.getShape()); - float initialValue = 0.0f; + float initialValue; switch (_op.getPoolingType()) { case PoolOp::PoolingType::MAX: diff --git a/contrib/nnc/passes/interpreter/ops/conv_2D.cpp b/contrib/nnc/passes/interpreter/ops/conv_2D.cpp index 51dc70c..b1281ed 100644 --- a/contrib/nnc/passes/interpreter/ops/conv_2D.cpp +++ b/contrib/nnc/passes/interpreter/ops/conv_2D.cpp @@ -42,7 +42,8 @@ std::vector Conv2D::operator()() { auto res = allocate_tensor(_out_shape); Tensor resAccesor(res); - Index pads({_op.getPadding(0), _op.getPadding(1), 0u}); + Shape strides{_op.getStrides().dim(0), _op.getStrides().dim(1), 1}; + Index pads{_op.getPaddingBefore().at(0), _op.getPaddingBefore().at(1), 0}; Shape outShape = resAccesor.getShape(); // Assume batch size == 1 and strip it off. @@ -74,7 +75,7 @@ std::vector Conv2D::operator()() for (auto& kernelIdx : kernelRange) { - translate(inputIdx, outIdx, kernelIdx, _strides, pads); + translate(inputIdx, outIdx, kernelIdx, strides, pads); if (inRange.contains(inputIdx)) { auto kernelRegion = _kernel.getRegion(kernelIdx); @@ -100,14 +101,10 @@ std::vector Conv2D::operator()() Conv2D::Conv2D(const TensorVariant &input, const Conv2DOp &op) : _input(input), _kernel(op.getKernel()), _strides(op.getStrides()), - _padding(op.getPaddingType()), _out_shape(op.getOutputShape(0)), _op(op) -{ - + _out_shape(op.getOutputShape(0)), _op(op) { assert(_op.getInputShape(0).rank() == 4); - assert(input.getShape().rank() == 4); + assert(_input.getShape().rank() == 4); assert(_kernel.getShape().rank() == 4); - assert(_strides.dim(2) == 1); - assert(_op.getPadding(2) == 0); } } // namespace nnc diff --git a/contrib/nnc/passes/interpreter/ops/conv_2D.h b/contrib/nnc/passes/interpreter/ops/conv_2D.h index 2bf7e7d..79d3015 100644 --- a/contrib/nnc/passes/interpreter/ops/conv_2D.h +++ b/contrib/nnc/passes/interpreter/ops/conv_2D.h @@ -33,7 +33,6 @@ private: const mir::Tensor _input; mir::Tensor _kernel; const mir::Shape _strides; - const mir::ops::PaddingType _padding; const mir::Shape &_out_shape; const mir::ops::Conv2DOp &_op; }; diff --git a/contrib/nnc/passes/interpreter/ops/conv_FFT.cpp b/contrib/nnc/passes/interpreter/ops/conv_FFT.cpp index 2effd08..9737346 100644 --- a/contrib/nnc/passes/interpreter/ops/conv_FFT.cpp +++ b/contrib/nnc/passes/interpreter/ops/conv_FFT.cpp @@ -33,7 +33,7 @@ using namespace mir::ops; // Refer to https://www.tensorflow.org/api_docs/python/tf/nn/conv2d for info std::vector Conv2D_FFT::operator()() { - Index pads({_op.getPadding(0), _op.getPadding(1), 0u}); + Index pads({_op.getPaddingBefore().at(0), _op.getPaddingBefore().at(1)}); // // 1. Pad input (currently only with clamp to zero, maybe clamp to edge and wrap later) auto inputPadded = pad_input(pads); @@ -78,7 +78,6 @@ std::vector Conv2D_FFT::operator()() Conv2D_FFT::Conv2D_FFT(const TensorVariant &input, const Conv2DOp &op) : _input(input), _kernel(op.getKernel()), _strides(op.getStrides()), - _padding(op.getPaddingType()), _out_shape(op.getOutputShape(0)), _op(op) { // Same assertions as in Conv2D @@ -86,7 +85,6 @@ Conv2D_FFT::Conv2D_FFT(const TensorVariant &input, const Conv2DOp &op) assert(input.getShape().rank() == 3); assert(_kernel.getShape().rank() == 4); assert(_strides.dim(2) == 1); - assert(_op.getPadding(2) == 0); } std::vector Conv2D_FFT::pad_input(const Index &pads) diff --git a/contrib/nnc/passes/interpreter/ops/conv_FFT.h b/contrib/nnc/passes/interpreter/ops/conv_FFT.h index 463ed1b..2ebe0c8 100644 --- a/contrib/nnc/passes/interpreter/ops/conv_FFT.h +++ b/contrib/nnc/passes/interpreter/ops/conv_FFT.h @@ -103,7 +103,6 @@ private: const mir::Tensor _input; mir::Tensor _kernel; const mir::Shape _strides; - const mir::ops::PaddingType _padding; const mir::Shape &_out_shape; const mir::ops::Conv2DOp &_op; }; diff --git a/contrib/nnc/passes/onnx_frontend/ONNXOpCreator.cpp b/contrib/nnc/passes/onnx_frontend/ONNXOpCreator.cpp index 9d90c35..d2cfb94 100644 --- a/contrib/nnc/passes/onnx_frontend/ONNXOpCreator.cpp +++ b/contrib/nnc/passes/onnx_frontend/ONNXOpCreator.cpp @@ -103,14 +103,16 @@ std::vector ONNXOpCreator::convertConv2D(InputOps& inputs, assert(in_weights); auto in_weights_tensor = in_weights->getValue(); // TODO: we don't support padding at the moment - auto pad_type = ops::PaddingType::Valid; + std::vector padding_before{0, 0}; + std::vector padding_after{0, 0}; Operation* input_bias; if (inputs.size() > 2) input_bias = inputs[2]; inputs.resize(1); std::vector outputs; - outputs = createOp(inputs[0]->getOutput(0), in_weights_tensor, strides_shape, pad_type); + outputs = createOp(inputs[0]->getOutput(0), in_weights_tensor, strides_shape, + padding_before, padding_after); // TODO: there could be bias tensor as inputs[2]. return outputs; } @@ -139,7 +141,6 @@ std::vector ONNXOpCreator::convertPool(InputOps& inputs, ONNXOpCode ops::PoolOp::BorderType border_type; ops::PoolOp::PoolingType pool_type; - ops::PaddingType pad_type = ops::PaddingType::Custom; switch (op_code) { case ONNXOpCode::opAveragePool: @@ -155,12 +156,14 @@ std::vector ONNXOpCreator::convertPool(InputOps& inputs, ONNXOpCode } // FIXME: it's a hack to be compatible with current implementation of PoolOp - we expect // 3 dims for 2D picture - Shape window_shape ({onnx_kernel_shape.dim(0), onnx_kernel_shape.dim(1), 1}); + Shape window_shape{onnx_kernel_shape.dim(0), onnx_kernel_shape.dim(1)}; // FIXME: it's another hack identical to the above one - Shape strides_shape ({onnx_strides_shape.dim(0), onnx_strides_shape.dim(1), 1}); + Shape strides_shape{onnx_strides_shape.dim(0), onnx_strides_shape.dim(1)}; + std::vector padding_before{0, 0}; + std::vector padding_after{0, 0}; // TODO: ONNX has more parameters for pooling. We should use them. - auto pooling = createOp(inputs[0]->getOutput(0), window_shape, strides_shape, pool_type, - pad_type, border_type); + auto pooling = createOp(inputs[0]->getOutput(0), pool_type, window_shape, + strides_shape, padding_before, padding_after, border_type); return pooling; } diff --git a/contrib/nnc/passes/soft_backend/SBSerializer.cpp b/contrib/nnc/passes/soft_backend/SBSerializer.cpp index d167924..fe7b9e4 100644 --- a/contrib/nnc/passes/soft_backend/SBSerializer.cpp +++ b/contrib/nnc/passes/soft_backend/SBSerializer.cpp @@ -137,19 +137,15 @@ void Serializer::serializeTensor(const TensorVariant &t) template void Serializer::serializePads(const Op& op, int32_t padsRank) { - // serialize padding type - assert(etoi(op.getPaddingType()) < MAX_ENUM_VAL); - serializeT(etoi(op.getPaddingType())); - // serialize pads assert(padsRank <= MAX_DIMS); serializeT(padsRank); for (int i = 0; i < static_cast(padsRank); ++i) { - auto pad = op.getPadding(i); + auto pad = op.getPaddingBefore().at(i); assert(pad <= MAX_DIM_SIZE); assert(pad >= 0); UNUSED(pad); - serializeT(op.getPadding(i)); + serializeT(op.getPaddingBefore().at(i)); } } @@ -201,8 +197,7 @@ void Serializer::visit(ops::SoftmaxOp& op) { void Serializer::visit(ops::PoolOp& op) { _curOp->_paramStartOffset = _buffer.size(); // serialize window shape - const Shape &windowShape = op.getWindowShape(); - serializeShape(windowShape); + serializeShape(op.getWindowShape()); // serialize strindes serializeShape(op.getStrides()); // serialize pads diff --git a/contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def b/contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def index e0b1b6c..c381001 100644 --- a/contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def +++ b/contrib/nnc/passes/soft_backend/code_snippets/cpp_operations.def @@ -230,9 +230,6 @@ void conv2d(Tensor &out, const char *params, const Tensor &in) Dims<4> input_d = shapeToDims(in.getShape()); Kernel kernel = deserializeKernel(params); Shape strides = deserializeShape(params); - // pads type. unused for now - int32_t pt = deserializeT(params); - UNUSED(pt); Shape pads = deserializeShape(params); Shape out_s = deserializeShape(params); @@ -252,9 +249,10 @@ void conv2d(Tensor &out, const char *params, const Tensor &in) stride *= im2col_d.sizes[i]; } + assert(strides.getDims() == 2); const int stride_w = strides[1]; const int stride_h = strides[0]; - assert(strides[2] == 1); + assert(pads.getDims() == 2); const int pad_w = pads[1]; const int pad_h = pads[0]; @@ -277,9 +275,6 @@ void convTransposed2d(Tensor &out, const char *params, const Tensor &in) { RuntimeShape input_shape = shapeToRuntimeShape(in.getShape()); KernelRT kernel = deserializeKernelRT(params); Shape strides = deserializeShape(params); - // pads type. unused for now - int32_t pt = deserializeT(params); - UNUSED(pt); Shape pads = deserializeShape(params); Shape out_s = deserializeShape(params); @@ -323,15 +318,13 @@ void depthwiseConv2d(Tensor &out, const char *params, const Tensor &in) Dims<4> input_d = shapeToDims(in.getShape()); Kernel kernel = deserializeKernel(params); Shape strides = deserializeShape(params); - // pads type. unused for now - int32_t pt = deserializeT(params); - UNUSED(pt); Shape pads = deserializeShape(params); Shape out_s = deserializeShape(params); + assert(strides.getDims() == 2); const int stride_w = strides[1]; const int stride_h = strides[0]; - assert(strides[2] == 1); + assert(pads.getDims() == 2); const int pad_w = pads[1]; const int pad_h = pads[0]; @@ -379,19 +372,17 @@ static inline void genericPool(Executor executor, Tensor &out, Dims<4> input_d = shapeToDims(in.getShape()); Shape window = deserializeShape(params); Shape strides = deserializeShape(params); - // pads type. unused for now - int32_t pt = deserializeT(params); - UNUSED(pt); Shape pads = deserializeShape(params); PoolBorderType borderType = static_cast(deserializeT(params)); Shape out_s = deserializeShape(params); + assert(window.getDims() == 2); const int window_w = window[1]; const int window_h = window[0]; - assert(window[2] == 1); + assert(strides.getDims() == 2); const int stride_w = strides[1]; const int stride_h = strides[0]; - assert(strides[2] == 1); + assert(pads.getDims() == 2); const int pad_w = pads[1]; const int pad_h = pads[0]; diff --git a/contrib/nnc/passes/tflite_frontend/tflite_op_creator.cpp b/contrib/nnc/passes/tflite_frontend/tflite_op_creator.cpp index 1828fda..757b241 100644 --- a/contrib/nnc/passes/tflite_frontend/tflite_op_creator.cpp +++ b/contrib/nnc/passes/tflite_frontend/tflite_op_creator.cpp @@ -45,8 +45,37 @@ using namespace nnc::mir; using namespace ::tflite; + namespace nnc { +static void calculatePadding(tflite::Padding padding, + const Shape& input_shape, + const Shape& window_shape, + const Shape& strides, + std::vector& padding_before, + std::vector& padding_after) { + switch (padding) { + case tflite::Padding_SAME: + for (int i = 0; i < 2; ++i) { + int32_t padding; + padding = (input_shape.dim(1 + i) % strides.dim(i) == 0) + ? std::max(0, window_shape.dim(i) - strides.dim(i)) + : std::max(0, window_shape.dim(i) - input_shape.dim(1 + i) % strides.dim(i)); + padding_before[i] = padding / 2; + padding_after[i] = (padding + 1) / 2; + } + break; + case tflite::Padding_VALID: + for (int i = 0; i < 2; ++i) { + padding_before[i] = 0; + padding_after[i] = 0; + } + break; + default: + assert(false); + } +} + void TFLiteOpCreator::checkConv2D(const Conv2DOptions* opts, std::set& problems_op_set) { checkActivationType(opts->fused_activation_function(), problems_op_set); @@ -54,9 +83,17 @@ void TFLiteOpCreator::checkConv2D(const Conv2DOptions* opts, std::vector TFLiteOpCreator::convertConv2D(InputOps inputs, InputParams params, const Conv2DOptions* opts) { - Shape strides{opts->stride_h(), opts->stride_w(), 1}; + auto& input_shape = inputs[0]->getOutputShape(0); + auto& kernel_shape = params[0]->getShape(); + Shape strides{opts->stride_h(), opts->stride_w()}; + std::vector padding_before(2); + std::vector padding_after(2); + + calculatePadding(opts->padding(), input_shape, kernel_shape, strides, padding_before, + padding_after); + auto outputs = createOp(ActivationFunctionType_NONE, inputs[0]->getOutput(0), - *params[0], strides, paddingMap[opts->padding()]); + *params[0], strides, padding_before, padding_after); return createOp(opts->fused_activation_function(), outputs[0]->getOutput(0), *params[1]); } @@ -69,10 +106,18 @@ void TFLiteOpCreator::checkDepthwiseConv2D(const DepthwiseConv2DOptions* opts, std::vector TFLiteOpCreator::convertDepthwiseConv2D(InputOps inputs, InputParams params, const DepthwiseConv2DOptions* opts) { - Shape strides{opts->stride_h(), opts->stride_w(), 1}; + auto& input_shape = inputs[0]->getOutputShape(0); + auto& kernel_shape = params[0]->getShape(); + Shape strides{opts->stride_h(), opts->stride_w()}; + std::vector padding_before(2); + std::vector padding_after(2); + + calculatePadding(opts->padding(), input_shape, kernel_shape, strides, padding_before, + padding_after); + auto outputs = createOp(ActivationFunctionType_NONE, inputs[0]->getOutput(0), *params[0], strides, - paddingMap[opts->padding()]); + padding_before, padding_after); return createOp(opts->fused_activation_function(), outputs[0]->getOutput(0), *params[1]); } @@ -99,22 +144,36 @@ void TFLiteOpCreator::checkPool2D(const Pool2DOptions* opts, std::vector TFLiteOpCreator::convertMaxPool2D(InputOps inputs, InputParams params, const Pool2DOptions* opts) { - Shape window_shape{opts->filter_height(), opts->filter_width(), 1}; - Shape strides{opts->stride_h(), opts->stride_w(), 1}; + auto& input_shape = inputs[0]->getOutputShape(0); + Shape window_shape{opts->filter_height(), opts->filter_width()}; + Shape strides{opts->stride_h(), opts->stride_w()}; + std::vector padding_before(2); + std::vector padding_after(2); + + calculatePadding(opts->padding(), input_shape, window_shape, strides, padding_before, + padding_after); + return createOp(opts->fused_activation_function(), inputs[0]->getOutput(0), - window_shape, strides, ops::PoolOp::PoolingType::MAX, - paddingMap[opts->padding()], ops::PoolOp::BorderType::EMPTY, + ops::PoolOp::PoolingType::MAX, window_shape, strides, padding_before, + padding_after, ops::PoolOp::BorderType::EMPTY, ops::PoolOp::RoundMode::floor); } std::vector TFLiteOpCreator::convertAveragePool2D(InputOps inputs, InputParams params, const Pool2DOptions* opts) { - Shape window_shape{opts->filter_height(), opts->filter_width(), 1}; - Shape strides{opts->stride_h(), opts->stride_w(), 1}; + auto& input_shape = inputs[0]->getOutputShape(0); + Shape window_shape{opts->filter_height(), opts->filter_width()}; + Shape strides{opts->stride_h(), opts->stride_w()}; + std::vector padding_before(2); + std::vector padding_after(2); + + calculatePadding(opts->padding(), input_shape, window_shape, strides, padding_before, + padding_after); + return createOp(opts->fused_activation_function(), inputs[0]->getOutput(0), - window_shape, strides, ops::PoolOp::PoolingType::AVG, - paddingMap[opts->padding()], ops::PoolOp::BorderType::EMPTY, + ops::PoolOp::PoolingType::AVG, window_shape, strides, padding_before, + padding_after, ops::PoolOp::BorderType::EMPTY, ops::PoolOp::RoundMode::floor); } @@ -136,7 +195,6 @@ std::vector TFLiteOpCreator::convertReshape(InputOps inputs, In return outputs; } - std::vector TFLiteOpCreator::createTransposeConv(InputOps& inputs, InputParams& params, const ::tflite::TransposeConvOptions* opts) { diff --git a/contrib/nnc/tests/interpreter/graph_creator.cpp b/contrib/nnc/tests/interpreter/graph_creator.cpp index f870a72..a23ac4e 100644 --- a/contrib/nnc/tests/interpreter/graph_creator.cpp +++ b/contrib/nnc/tests/interpreter/graph_creator.cpp @@ -48,21 +48,23 @@ static Operation* createConv2D(std::unique_ptr& g, const std::vector& inputs, const opinfo::OperatorInfo* opInfo) { return g->create("y", inputs[0], *getKernel(opInfo), getShapeParam(opInfo, 0), - getPaddingType(opInfo)); + std::vector{0, 0}, std::vector{0, 0}); } static Operation* createDepthwiseConv2D(std::unique_ptr& g, const std::vector& inputs, const opinfo::OperatorInfo* opInfo) { return g->create("y", inputs[0], *getKernel(opInfo), - getShapeParam(opInfo, 0), getPaddingType(opInfo)); + getShapeParam(opInfo, 0), std::vector{0, 0}, + std::vector{0, 0}); } static Operation* createPool(std::unique_ptr& g, const std::vector& inputs, const opinfo::OperatorInfo* opInfo) { - return g->create("y", inputs[0], getShapeParam(opInfo, 0), getShapeParam(opInfo, 1), - getPoolingType(opInfo), getPaddingType(opInfo), + return g->create("y", inputs[0], getPoolingType(opInfo), + getShapeParam(opInfo, 0), getShapeParam(opInfo, 1), + std::vector{0, 0}, std::vector{0, 0}, ops::PoolOp::BorderType::ZEROFILLED, ops::PoolOp::RoundMode::floor); } diff --git a/contrib/nnc/tests/interpreter/op_info_util.cpp b/contrib/nnc/tests/interpreter/op_info_util.cpp index f633933..f662f0e 100644 --- a/contrib/nnc/tests/interpreter/op_info_util.cpp +++ b/contrib/nnc/tests/interpreter/op_info_util.cpp @@ -46,19 +46,6 @@ std::shared_ptr getKernel(const opinfo::OperatorInfo* opInfo) return getTensor(opInfo->kernels()->Get(0)); } -ops::PaddingType getPaddingType(const opinfo::OperatorInfo* opInfo) -{ - switch (opInfo->padType()) - { - case opinfo::PadType_VALID: - return ops::PaddingType::Valid; - case opinfo::PadType_SAME: - return ops::PaddingType::Same; - default: - assert(false); - } -} - ops::PoolOp::PoolingType getPoolingType(const opinfo::OperatorInfo* opInfo) { switch (opInfo->poolType()) diff --git a/contrib/nnc/tests/interpreter/op_info_util.h b/contrib/nnc/tests/interpreter/op_info_util.h index e79f3bb..c7e1ec3 100644 --- a/contrib/nnc/tests/interpreter/op_info_util.h +++ b/contrib/nnc/tests/interpreter/op_info_util.h @@ -31,7 +31,6 @@ std::shared_ptr getTensor(const opinfo::Tensor* t); std::shared_ptr getKernel(const opinfo::OperatorInfo* opInfo); -nnc::mir::ops::PaddingType getPaddingType(const opinfo::OperatorInfo* opInfo); nnc::mir::ops::PoolOp::PoolingType getPoolingType(const opinfo::OperatorInfo* opInfo); nnc::mir::Shape getShapeParam(const opinfo::OperatorInfo* opInfo, unsigned int n); int getAxis(const opinfo::OperatorInfo* opInfo); diff --git a/contrib/nnc/unittests/soft_backend/CPPOperations.cpp b/contrib/nnc/unittests/soft_backend/CPPOperations.cpp index c975e99..e7dd519 100644 --- a/contrib/nnc/unittests/soft_backend/CPPOperations.cpp +++ b/contrib/nnc/unittests/soft_backend/CPPOperations.cpp @@ -494,15 +494,16 @@ TEST(cpp_operations_test, conv2d) { vector inputShapeData{1, 5, 7, static_cast(inputC)}; // NHWC mir::Shape kernelShape{kernelH, kernelW, inputC, outputC}; // HWCN - mir::Shape strides{strideH, strideW, 1}; + mir::Shape strides{strideH, strideW}; vector> inputNTensors(1); Tensor aInputTensor; fillTensors(inputNTensors[0], aInputTensor, inputShapeData, 1.0f); - auto padT = mir::ops::PaddingType::Same; mir::TensorVariant kernel = createNTensor(kernelShape, 1.0f); - auto opGenerator = [kernel, strides, padT](mir::Graph& g, - const std::vector& inputs) { - return g.create("y", inputs[0], kernel, strides, padT); + auto opGenerator = [kernel, strides](mir::Graph& g, + const std::vector& inputs) { + std::vector padding{0, 0}; + return g.create("y", inputs[0], kernel, strides, padding, + padding); }; createAndRunTestGraph(opGenerator, conv2d, inputNTensors, aInputTensor); @@ -526,15 +527,16 @@ TEST(cpp_operations_tests, depthwise_conv) { vector inputShapeData{1, 5, 7, static_cast(channels)}; // NHWC mir::Shape kernelShape{kernelH, kernelW, channels, multiplier}; // HWCN - mir::Shape strides{strideH, strideW, 1}; + mir::Shape strides{strideH, strideW}; vector> inputNTensors(1); Tensor aInputTensor; fillTensors(inputNTensors[0], aInputTensor, inputShapeData, 1.0f); - auto padT = mir::ops::PaddingType::Same; mir::TensorVariant kernel = createNTensor(kernelShape, 1.0f); - auto opGenerator = [kernel, strides, padT](mir::Graph& g, - const std::vector& inputs) { - return g.create("y", inputs[0], kernel, strides, padT); + auto opGenerator = [kernel, strides](mir::Graph& g, + const std::vector& inputs) { + std::vector padding{0, 0}; + return g.create("y", inputs[0], kernel, strides, + padding, padding); }; createAndRunTestGraph(opGenerator, depthwiseConv2d, inputNTensors, aInputTensor); @@ -571,19 +573,20 @@ static void genericPoolTest(Func testFunc, const vector shapeData{1, 5, 7, static_cast(channels)}; - mir::Shape windowShape{windowH, windowW, 1}; - mir::Shape strides{strideH, strideW, 1}; - auto padT = irOps::PaddingType::Same; + mir::Shape windowShape{windowH, windowW}; + mir::Shape strides{strideH, strideW}; Tensor aInputTensor; vector> inputNTensors(1); fillTensors(inputNTensors[0], aInputTensor, shapeData, 1.0f); for (auto border: borders) { - auto opGenerator = [windowShape, strides, padT, border](mir::Graph& g, - const std::vector& inputs) { - return g.create("y", inputs[0], windowShape, strides, poolT, padT, - border, irOps::PoolOp::RoundMode::floor); + auto opGenerator = [windowShape, strides, border](mir::Graph& g, + const std::vector& inputs) { + std::vector padding{0, 0}; + return g.create("y", inputs[0], poolT, windowShape, strides, + padding, padding, border, + irOps::PoolOp::RoundMode::floor); }; createAndRunTestGraph(opGenerator, testFunc, inputNTensors, aInputTensor); -- 2.7.4