diff --git a/src/core/include/openvino/op/col2im.hpp b/src/core/include/openvino/op/col2im.hpp new file mode 100644 index 00000000000000..48724a1d72500e --- /dev/null +++ b/src/core/include/openvino/op/col2im.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "openvino/op/op.hpp" + +namespace ov { +namespace op { +namespace v15 { +/// \brief Operator combining sliding blocks into an image tensor +/// \ingroup ov_ops_cpp_api +class OPENVINO_API Col2Im : public ov::op::Op { +public: + OPENVINO_OP("Col2Im", "opset15", ov::op::Op); + + Col2Im() = default; + /// \brief Constructs a Col2Im operation. + /// + /// \param data Input tensor with data + /// \param output_size Shape of the spatial dimensions of the output image + /// \param kernel_size Size of the sliding blocks + /// \param strides Stride in the sliding blocks in the input spatial dimensions + /// \param dilations Local stride of the elements + /// \param pads_begin Paddings at the beginning of each spatial axis, if undefined no padding is applied + /// \param pads_end Paddings at the end of each spatial axis, if undefined no padding is applied + Col2Im(const Output& data, + const Output& output_size, + const Output& kernel_size, + const Strides& strides = Strides{1, 1}, + const Strides& dilations = Strides{1, 1}, + const Shape& pads_begin = Shape{0, 0}, + const Shape& pads_end = Shape{0, 0}); + + bool visit_attributes(AttributeVisitor& visitor) override; + void validate_and_infer_types() override; + std::shared_ptr clone_with_new_inputs(const OutputVector& new_args) const override; + + const Strides& get_strides() const; + const Strides& get_dilations() const; + const Shape& get_pads_begin() const; + const Shape& get_pads_end() const; + +private: + Strides m_strides; + Strides m_dilations; + Shape m_pads_begin; + Shape m_pads_end; +}; + +} // namespace v15 +} // namespace op +} // namespace ov diff --git a/src/core/include/openvino/op/ops.hpp b/src/core/include/openvino/op/ops.hpp index 755bbc9470f44c..ebe5ac6bf32e06 100644 --- a/src/core/include/openvino/op/ops.hpp +++ b/src/core/include/openvino/op/ops.hpp @@ -29,6 +29,7 @@ #include "openvino/op/bucketize.hpp" #include "openvino/op/ceiling.hpp" #include "openvino/op/clamp.hpp" +#include "openvino/op/col2im.hpp" #include "openvino/op/concat.hpp" #include "openvino/op/constant.hpp" #include "openvino/op/convert.hpp" diff --git a/src/core/shape_inference/include/col2im_shape_inference.hpp b/src/core/shape_inference/include/col2im_shape_inference.hpp new file mode 100644 index 00000000000000..187c3c79820547 --- /dev/null +++ b/src/core/shape_inference/include/col2im_shape_inference.hpp @@ -0,0 +1,125 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include + +#include "openvino/op/col2im.hpp" +#include "utils.hpp" + +namespace ov { +namespace op { +namespace v15 { +template > +std::vector shape_infer(const Col2Im* op, + const std::vector& input_shapes, + const ITensorAccessor& tensor_accessor = make_tensor_accessor()) { + NODE_VALIDATION_CHECK(op, input_shapes.size() == 3); + + const auto& data_shape = input_shapes[0]; + const auto& output_size_shape = input_shapes[1]; + const auto& kernel_shape = input_shapes[2]; + + NODE_SHAPE_INFER_CHECK(op, + input_shapes, + ov::util::is_rank_compatible_any_of(data_shape.rank(), {2, 3}), + "input data must be an unbatched 2D or a batched 3D input. Got: ", + data_shape); + + const auto is_two_elem_1d = [](const TShape& shape) -> bool { + static const auto exp_shape = Shape{2}; + return shape.rank().is_dynamic() || shape.to_shape() == exp_shape; + }; + + if (output_size_shape.rank().is_static()) { + NODE_SHAPE_INFER_CHECK(op, + input_shapes, + is_two_elem_1d(output_size_shape), + "output_size must be a 1D input of shape [2]. Got: ", + output_size_shape); + } + + if (kernel_shape.rank().is_static()) { + NODE_SHAPE_INFER_CHECK(op, + input_shapes, + is_two_elem_1d(kernel_shape), + "kernel_size must be a 1D input of shape [2]."); + } + + if (data_shape.rank().is_static()) { + auto output_shapes = std::vector(1); + auto& output_shape = output_shapes[0]; + const bool is_batched = data_shape.rank() == 3; + output_shape.resize(is_batched ? 4 : 3); + size_t idx = 0; + + // output_shape: (N, C, H, W) + // ^ + if (is_batched) { + output_shape[idx] = data_shape[0]; + idx++; + } + + // output_shape: (N, C, H, W) + // ^ + const size_t C_idx = is_batched ? 1 : 0; + const auto kernel_val = ov::op::get_input_const_data_as(op, 2, tensor_accessor); + if (kernel_val && data_shape.rank().is_static() && data_shape[C_idx].is_static()) { + const auto& dividend = data_shape[C_idx].get_length(); + const auto divisor = ((*kernel_val)[0] * (*kernel_val)[1]); + output_shape[idx] = dividend / divisor; + + NODE_SHAPE_INFER_CHECK(op, + input_shapes, + dividend % divisor == 0, + "First non-batch dimension is not evenly divisible by Product(kernel_shape). Got: ", + data_shape[C_idx].get_length()); + } + + // output_shape: (N, C, H, W) + // ^ ^ + if (const auto output_size_val = + ov::op::get_input_const_data_as_shape(op, 1, tensor_accessor)) { + idx++; + output_shape[idx] = (*output_size_val)[0]; + idx++; + output_shape[idx] = (*output_size_val)[1]; + const size_t L_idx = is_batched ? 2 : 1; + if (data_shape.rank().is_static() && data_shape[L_idx].is_static()) { + constexpr size_t spatial_dims = 2; + + const auto& pads_begin = op->get_pads_begin(); + const auto& pads_end = op->get_pads_end(); + const auto& strides = op->get_strides(); + const auto& dilations = op->get_dilations(); + + if (kernel_val) { + using TVal = typename TShape::value_type::value_type; + TVal L_calculated = 1; + for (size_t d = 0; d < spatial_dims; ++d) { + L_calculated *= (((*output_size_val)[d].get_length() + pads_begin[d] + pads_end[d] - + dilations[d] * ((*kernel_val)[d] - 1) - 1) / + strides[d]) + + 1; + } + const auto L = data_shape[L_idx].get_length(); + NODE_SHAPE_INFER_CHECK( + op, + input_shapes, + L == L_calculated, + "For given inputs and parameters the total number of data blocks must be equal to " + + std::to_string(L_calculated) + ". Got: ", + L); + } + } + } + return output_shapes; + } else { + return {PartialShape::dynamic()}; + } +} +} // namespace v15 +} // namespace op +} // namespace ov diff --git a/src/core/src/op/col2im.cpp b/src/core/src/op/col2im.cpp new file mode 100644 index 00000000000000..1c317a00c78718 --- /dev/null +++ b/src/core/src/op/col2im.cpp @@ -0,0 +1,94 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/op/col2im.hpp" + +#include "col2im_shape_inference.hpp" +#include "itt.hpp" +#include "openvino/core/validation_util.hpp" +#include "openvino/op/op.hpp" + +namespace ov { +namespace op { +namespace v15 { + +Col2Im::Col2Im(const Output& data, + const Output& output_size, + const Output& kernel_size, + const Strides& strides, + const Strides& dilations, + const Shape& pads_begin, + const Shape& pads_end) + : Op({data, output_size, kernel_size}), + m_strides(strides), + m_dilations(dilations), + m_pads_begin(pads_begin), + m_pads_end(pads_end) { + constructor_validate_and_infer_types(); +} + +bool Col2Im::visit_attributes(ov::AttributeVisitor& visitor) { + OV_OP_SCOPE(v15_Col2Im_visit_attributes); + visitor.on_attribute("strides", m_strides); + visitor.on_attribute("dilations", m_dilations); + visitor.on_attribute("pads_begin", m_pads_begin); + visitor.on_attribute("pads_end", m_pads_end); + return true; +} + +void Col2Im::validate_and_infer_types() { + OV_OP_SCOPE(v15_Col2Im_validate_and_infer_types); + + const auto& data_element_type = get_input_element_type(0); + const auto& output_size_element_type = get_input_element_type(1); + const bool is_valid_output_size_type = + output_size_element_type == element::i32 || output_size_element_type == element::i64; + NODE_VALIDATION_CHECK(this, + is_valid_output_size_type, + "The element type of the output_size tensor must be i32 or i64 type. Got: ", + output_size_element_type); + + const auto& kernel_size_element_type = get_input_element_type(2); + const bool is_valid_kernel_size_type = + kernel_size_element_type == element::i32 || kernel_size_element_type == element::i64; + NODE_VALIDATION_CHECK(this, + is_valid_kernel_size_type, + "The element type of the kernel_size tensor must be i32 or i64 type. Got: ", + kernel_size_element_type); + + const auto output_shapes = shape_infer(this, ov::util::get_node_input_partial_shapes(*this)); + set_output_type(0, data_element_type, output_shapes[0]); +} + +std::shared_ptr Col2Im::clone_with_new_inputs(const ov::OutputVector& new_args) const { + OV_OP_SCOPE(v15_Col2Im_clone_with_new_inputs); + check_new_args_count(this, new_args); + return std::make_shared(new_args.at(0), + new_args.at(1), + new_args.at(2), + m_strides, + m_dilations, + m_pads_begin, + m_pads_end); +} + +const Strides& Col2Im::get_strides() const { + return m_strides; +} + +const Strides& Col2Im::get_dilations() const { + return m_dilations; +} + +const Shape& Col2Im::get_pads_begin() const { + return m_pads_begin; +} + +const Shape& Col2Im::get_pads_end() const { + return m_pads_end; +} + +} // namespace v15 +} // namespace op +} // namespace ov diff --git a/src/core/tests/type_prop/col2im.cpp b/src/core/tests/type_prop/col2im.cpp new file mode 100644 index 00000000000000..6f38e3a3943786 --- /dev/null +++ b/src/core/tests/type_prop/col2im.cpp @@ -0,0 +1,356 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/op/col2im.hpp" + +#include + +#include "common_test_utils/test_assertions.hpp" +#include "common_test_utils/type_prop.hpp" +#include "openvino/op/constant.hpp" +#include "openvino/op/shape_of.hpp" +#include "openvino/op/subtract.hpp" + +namespace ov { +namespace test { + +using ov::op::v0::Constant; +using ov::op::v0::Parameter; +using ov::op::v3::ShapeOf; +using testing::HasSubstr; + +class TypePropCol2ImTest : public TypePropOpTest {}; + +TEST_F(TypePropCol2ImTest, default_ctor) { + const auto data = std::make_shared(element::i32, PartialShape{3, 12, 225}); + const auto output_size = std::make_shared(element::i64, PartialShape{2}); + const auto kernel_size = std::make_shared(element::i64, PartialShape{2}); + + const auto op = make_op(data, output_size, kernel_size); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{1, 1})); + EXPECT_EQ(op->get_dilations(), (Strides{1, 1})); + EXPECT_EQ(op->get_pads_begin(), (Shape{0, 0})); + EXPECT_EQ(op->get_pads_end(), (Shape{0, 0})); + EXPECT_EQ(op->get_output_element_type(0), element::i32); + EXPECT_EQ(op->get_output_partial_shape(0), + (PartialShape{3, Dimension::dynamic(), Dimension::dynamic(), Dimension::dynamic()})); +} + +TEST_F(TypePropCol2ImTest, non_default_args) { + PartialShape data_shape{3, 12, 81}; + auto data_symbols = set_shape_symbols(data_shape); + const auto data = std::make_shared(element::i64, data_shape); + const auto output_size = std::make_shared(element::i64, Shape{2}); + const auto kernel_size = std::make_shared(element::i64, Shape{2}); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{2, 2}; + const auto pads_end = Shape{2, 2}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), + (PartialShape{3, Dimension::dynamic(), Dimension::dynamic(), Dimension::dynamic()})); + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{2, 2})); + EXPECT_EQ(op->get_pads_end(), (Shape{2, 2})); + EXPECT_THAT(get_shape_symbols(op->get_output_partial_shape(0)), + testing::ElementsAre(data_symbols[0], nullptr, nullptr, nullptr)); +} + +TEST_F(TypePropCol2ImTest, incorrect_types) { + const auto data = std::make_shared(element::i32, PartialShape{3, 12, 225}); + const auto output_size = std::make_shared(element::i64, PartialShape{2}); + const auto kernel_size = std::make_shared(element::i64, PartialShape{2}); + { + const auto output_size_i4 = std::make_shared(element::i4, PartialShape{16, 16}); + OV_EXPECT_THROW(std::ignore = make_op(data, output_size_i4, kernel_size), + ov::NodeValidationFailure, + HasSubstr("The element type of the output_size tensor must be i32 or i64 type")); + } + { + const auto kernel_size_u8 = std::make_shared(element::u8, PartialShape{2, 2}); + OV_EXPECT_THROW(std::ignore = make_op(data, output_size, kernel_size_u8), + ov::NodeValidationFailure, + HasSubstr("The element type of the kernel_size tensor must be i32 or i64 type")); + } +} + +TEST_F(TypePropCol2ImTest, batched_const_values) { + PartialShape data_shape{3, 12, 81}; + auto data_symbols = set_shape_symbols(data_shape); + const auto data = std::make_shared(element::i64, data_shape); + const auto output_size = + std::make_shared(element::i64, Shape{2}, std::vector{16, 16}); + const auto kernel_size = std::make_shared(element::i64, Shape{2}, std::vector{2, 2}); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{2, 2}; + const auto pads_end = Shape{2, 2}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{2, 2})); + EXPECT_EQ(op->get_pads_end(), (Shape{2, 2})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{3, 3, 16, 16})); + EXPECT_THAT(get_shape_symbols(op->get_output_partial_shape(0)), + testing::ElementsAre(data_symbols[0], nullptr, nullptr, nullptr)); +} + +TEST_F(TypePropCol2ImTest, unbatched_const_values) { + const auto data = std::make_shared(element::i64, Shape{12, 324}); + const auto output_size = + std::make_shared(element::i64, Shape{2}, std::vector{32, 32}); + const auto kernel_size = std::make_shared(element::i64, Shape{2}, std::vector{2, 2}); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{3, 32, 32})); +} + +TEST_F(TypePropCol2ImTest, kernel_size_and_output_size_from_shapeof) { + const auto data = std::make_shared(element::i64, Shape{12, 324}); + const auto output_size = std::make_shared(std::make_shared(element::i64, Shape{32, 32})); + const auto kernel_size = std::make_shared(std::make_shared(element::i64, Shape{2, 2})); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{3, 32, 32})); +} + +TEST_F(TypePropCol2ImTest, incorrect_L) { + const auto data = std::make_shared(element::i64, Shape{12, 325}); + const auto output_size = + std::make_shared(element::i64, Shape{2}, std::vector{32, 32}); + const auto kernel_size = std::make_shared(element::i64, Shape{2}, std::vector{2, 2}); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + OV_EXPECT_THROW(std::ignore = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end), + ov::NodeValidationFailure, + HasSubstr("For given inputs and parameters the total number of data blocks must be equal to 324")); +} + +TEST_F(TypePropCol2ImTest, incorrect_first_non_batch_dimension) { + const auto data = std::make_shared(element::i64, Shape{13, 324}); + const auto output_size = + std::make_shared(element::i64, Shape{2}, std::vector{32, 32}); + const auto kernel_size = std::make_shared(element::i64, Shape{2}, std::vector{2, 2}); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + OV_EXPECT_THROW(std::ignore = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end), + ov::NodeValidationFailure, + HasSubstr("First non-batch dimension is not evenly divisible by Product(kernel_shape)")); +} + +TEST_F(TypePropCol2ImTest, dynamic_output_size) { + const auto data = std::make_shared(element::i64, Shape{12, 324}); + const auto output_size = std::make_shared(element::i64, PartialShape::dynamic()); + const auto kernel_size = std::make_shared(element::i64, Shape{2}, std::vector{2, 2}); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{3, Dimension::dynamic(), Dimension::dynamic()})); +} + +TEST_F(TypePropCol2ImTest, dynamic_kernel_size) { + const auto data = std::make_shared(element::i64, Shape{12, 324}); + const auto output_size = std::make_shared(std::make_shared(element::i64, Shape{32, 32})); + const auto kernel_size = std::make_shared(element::i64, PartialShape::dynamic()); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{Dimension::dynamic(), 32, 32})); +} + +TEST_F(TypePropCol2ImTest, dynamic_batch_size) { + const auto data = std::make_shared(element::i64, PartialShape{Dimension::dynamic(), 12, 324}); + const auto output_size = std::make_shared(std::make_shared(element::i64, Shape{32, 32})); + const auto kernel_size = std::make_shared(element::i64, Shape{2}, std::vector{2, 2}); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{Dimension::dynamic(), 3, 32, 32})); +} + +TEST_F(TypePropCol2ImTest, interval_data_shape) { + const auto data = std::make_shared(element::i64, PartialShape{{4, 5}, {12, 16}, {324, 623}}); + const auto output_size = std::make_shared(std::make_shared(element::i64, Shape{32, 32})); + const auto kernel_size = std::make_shared(element::i64, Shape{2}, std::vector{2, 2}); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{{4, 5}, Dimension::dynamic(), 32, 32})); +} + +TEST_F(TypePropCol2ImTest, dynamic_input_shapes) { + const auto data = std::make_shared(element::i64, PartialShape::dynamic()); + const auto output_size = std::make_shared(element::i64, PartialShape::dynamic()); + const auto kernel_size = std::make_shared(element::i64, PartialShape::dynamic()); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape::dynamic())); +} + +TEST_F(TypePropCol2ImTest, static_batch) { + PartialShape data_shape{5, Dimension::dynamic(), Dimension::dynamic()}; + auto data_symbols = set_shape_symbols(data_shape); + const auto data = std::make_shared(element::i64, data_shape); + const auto output_size = std::make_shared(element::i64, PartialShape::dynamic()); + const auto kernel_size = std::make_shared(element::i64, PartialShape::dynamic()); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), + (PartialShape{5, Dimension::dynamic(), Dimension::dynamic(), Dimension::dynamic()})); + EXPECT_THAT(get_shape_symbols(op->get_output_partial_shape(0)), + testing::ElementsAre(data_symbols[0], nullptr, nullptr, nullptr)); +} + +TEST_F(TypePropCol2ImTest, 2D_dynamic_input) { + const auto data = + std::make_shared(element::i64, PartialShape{Dimension::dynamic(), Dimension::dynamic()}); + const auto output_size = std::make_shared(element::i64, PartialShape::dynamic()); + const auto kernel_size = std::make_shared(element::i64, PartialShape::dynamic()); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), + (PartialShape{Dimension::dynamic(), Dimension::dynamic(), Dimension::dynamic()})); +} + +TEST_F(TypePropCol2ImTest, interval_inputs_from_shapeof) { + PartialShape data_shape{{4, 5}, {12, 16}, {324, 623}}; + auto data_symbols = set_shape_symbols(data_shape); + const auto data = std::make_shared(element::i64, data_shape); + const auto kernel_size = + std::make_shared(std::make_shared(element::i64, PartialShape{{2, 32}, {2, 32}})); + PartialShape output_size_shape{{5, 16}, {5, 16}}; + auto output_size_symbols = set_shape_symbols(output_size_shape); + const auto output_size = std::make_shared(std::make_shared(element::i64, output_size_shape)); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{3, 3}; + const auto pads_end = Shape{3, 3}; + + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + op->validate_and_infer_types(); + + EXPECT_EQ(op->get_strides(), (Strides{2, 2})); + EXPECT_EQ(op->get_dilations(), (Strides{2, 2})); + EXPECT_EQ(op->get_pads_begin(), (Shape{3, 3})); + EXPECT_EQ(op->get_pads_end(), (Shape{3, 3})); + EXPECT_EQ(op->get_output_element_type(0), element::i64); + EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{{4, 5}, Dimension::dynamic(), {5, 16}, {5, 16}})); + EXPECT_THAT(get_shape_symbols(op->get_output_partial_shape(0)), + testing::ElementsAre(data_symbols[0], nullptr, output_size_symbols[0], output_size_symbols[1])); +} + +} // namespace test +} // namespace ov diff --git a/src/core/tests/visitors/op/col2im.cpp b/src/core/tests/visitors/op/col2im.cpp new file mode 100644 index 00000000000000..804918e0627296 --- /dev/null +++ b/src/core/tests/visitors/op/col2im.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/op/col2im.hpp" + +#include + +#include "visitors/visitors.hpp" + +using ov::PartialShape; +using ov::op::v0::Parameter; +using ov::op::v15::Col2Im; +using ov::test::NodeBuilder; + +TEST(attributes, col2im_v15_attr_comp_type_default) { + NodeBuilder::opset().insert(); + + const auto data = std::make_shared(ov::element::i32, PartialShape{3, 12, 81}); + const auto output_size = std::make_shared(ov::element::i64, ov::Shape{2}); + const auto kernel_size = std::make_shared(ov::element::i64, ov::Shape{2}); + + const auto op = std::make_shared(data, output_size, kernel_size); + + NodeBuilder builder(op, {data, output_size, kernel_size}); + auto g_op = ov::as_type_ptr(builder.create()); + + EXPECT_EQ(g_op->get_strides(), op->get_strides()); + EXPECT_EQ(g_op->get_dilations(), op->get_dilations()); + EXPECT_EQ(g_op->get_pads_begin(), op->get_pads_begin()); + EXPECT_EQ(g_op->get_pads_end(), op->get_pads_end()); +} + +TEST(attributes, col2im_v15_attr_comp_type_custom) { + NodeBuilder::opset().insert(); + + const auto data = std::make_shared(ov::element::i32, PartialShape{3, 12, 81}); + const auto output_size = std::make_shared(ov::element::i64, ov::Shape{2}); + const auto kernel_size = std::make_shared(ov::element::i64, ov::Shape{2}); + const auto strides = ov::Strides{2, 2}; + const auto dilations = ov::Strides{2, 2}; + const auto pads_begin = ov::Shape{2, 2}; + const auto pads_end = ov::Shape{2, 2}; + + const auto op = std::make_shared(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + + NodeBuilder builder(op, {data, output_size, kernel_size}); + auto g_op = ov::as_type_ptr(builder.create()); + + EXPECT_EQ(g_op->get_strides(), op->get_strides()); + EXPECT_EQ(g_op->get_dilations(), op->get_dilations()); + EXPECT_EQ(g_op->get_pads_begin(), op->get_pads_begin()); + EXPECT_EQ(g_op->get_pads_end(), op->get_pads_end()); +} diff --git a/src/plugins/intel_cpu/src/shape_inference/shape_inference.cpp b/src/plugins/intel_cpu/src/shape_inference/shape_inference.cpp index b3588f8bffbd47..35e02d87fbadab 100644 --- a/src/plugins/intel_cpu/src/shape_inference/shape_inference.cpp +++ b/src/plugins/intel_cpu/src/shape_inference/shape_inference.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include "augru_sequence_shape_inference.hpp" #include "avg_pool_shape_inference.hpp" #include "batch_to_space_shape_inference.hpp" +#include "col2im_shape_inference.hpp" #include "binary_convolution_shape_inference.hpp" #include "broadcast_shape_inference.hpp" #include "bucketize_shape_inference.hpp" @@ -399,6 +401,8 @@ using IStaticShapeInferFactory = // To use other version of operators, explicitly specify operator with opset version namespace. template <> const IStaticShapeInferFactory::TRegistry IStaticShapeInferFactory::registry{ + // opset15 + _OV_OP_SHAPE_INFER_MASK_REG(op::v15::Col2Im, ShapeInferTA, util::bit::mask(1, 2)), // opset14 _OV_OP_SHAPE_INFER_MASK_REG(op::v14::RMSNorm, ShapeInferTA, util::bit::mask(1)), _OV_OP_SHAPE_INFER_MASK_REG(opset14::Inverse, ShapeInferTA, util::bit::mask()), diff --git a/src/plugins/intel_cpu/tests/unit/shape_inference_test/col2im_shape_inference_test.cpp b/src/plugins/intel_cpu/tests/unit/shape_inference_test/col2im_shape_inference_test.cpp new file mode 100644 index 00000000000000..8e0fc06dde142c --- /dev/null +++ b/src/plugins/intel_cpu/tests/unit/shape_inference_test/col2im_shape_inference_test.cpp @@ -0,0 +1,133 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include "common_test_utils/test_assertions.hpp" +#include "utils.hpp" + +using namespace ov; +using namespace ov::intel_cpu; +using ov::op::v0::Constant; +using ov::op::v0::Parameter; +using testing::HasSubstr; + +class Col2ImStaticTestSuite : public ::testing::TestWithParam, // output_size values + std::vector, // kernel_size values + Strides, // strides + Strides, // dilations + ov::Shape, // pads_begin + ov::Shape, // pads_end + ov::Shape>> {}; // expected output shape + +class Col2ImStaticShapeInferenceTest: public OpStaticShapeInferenceTest {}; + +TEST_F(Col2ImStaticShapeInferenceTest, kernel_size_and_output_size_from_tensor_accessor) { + const auto data = std::make_shared(element::i64, ov::PartialShape::dynamic()); + const auto output_size = std::make_shared(element::i64, ov::PartialShape::dynamic()); + const auto kernel_size = std::make_shared(element::i64, ov::PartialShape::dynamic()); + const auto strides = Strides{2, 2}; + const auto dilations = Strides{2, 2}; + const auto pads_begin = Shape{2, 2}; + const auto pads_end = Shape{2, 2}; + const auto op = make_op(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + + int64_t output_size_val[] = {32, 32}; + int64_t kernel_size_val[] = {2, 2}; + auto const_inputs = std::unordered_map{{1, {element::i64, Shape{2}, output_size_val}}, + {2, {element::i64, Shape{2}, kernel_size_val}}}; + + const auto input_shapes = ShapeVector{Shape{3, 12, 289}, Shape{2}, Shape{2}}; + auto shape_infer = make_shape_inference(op); + const auto input_shape_refs = make_static_shape_refs(input_shapes); + const auto output_shapes = *shape_infer->infer(input_shape_refs, make_tensor_accessor(const_inputs)); + EXPECT_EQ(output_shapes.size(), 1); + EXPECT_EQ(output_shapes.front(), StaticShape({3, 3, 32, 32})); +} + +TEST_P(Col2ImStaticTestSuite, Col2ImStaticShapeInference) { + const auto& param = GetParam(); + const auto& data_shape = std::get<0>(param); + const auto& output_size_val = std::get<1>(param); + const auto& kernel_size_val = std::get<2>(param); + const auto& strides = std::get<3>(param); + const auto& dilations = std::get<4>(param); + const auto& pads_begin = std::get<5>(param); + const auto& pads_end = std::get<6>(param); + const auto& expected_output_shape = std::get<7>(param); + + const auto data = std::make_shared(element::i64, data_shape); + const auto output_size = std::make_shared(element::i64, Shape{2}, output_size_val); + const auto kernel_size = std::make_shared(element::i64, Shape{2}, kernel_size_val); + const auto op = std::make_shared(data, output_size, kernel_size, strides, dilations, pads_begin, pads_end); + const auto input_shapes = ShapeVector{data_shape, Shape{2}, Shape{2}}; + auto shape_infer = make_shape_inference(op); + const auto input_shape_refs = make_static_shape_refs(input_shapes); + const auto output_shapes = *shape_infer->infer(input_shape_refs, make_tensor_accessor()); + + EXPECT_EQ(output_shapes.size(), 1); + EXPECT_EQ(output_shapes.front(), StaticShape(expected_output_shape)); +} + +INSTANTIATE_TEST_SUITE_P( + Col2ImStaticShapeInferenceTests, + Col2ImStaticTestSuite, + ::testing::Values( + std::make_tuple( + Shape{3, 12, 81}, // data shape + std::vector{16, 16}, // output_size values + std::vector{2, 2}, // kernel_size values + Strides{2, 2}, // strides + Strides{2, 2}, // dilations + Shape{2, 2}, // pads_begin + Shape{2, 2}, // pads_end + Shape{3, 3, 16, 16}), // expected output shape + std::make_tuple( + Shape{12, 81}, // data shape + std::vector{16, 16}, // output_size values + std::vector{2, 2}, // kernel_size values + Strides{2, 2}, // strides + Strides{2, 2}, // dilations + Shape{2, 2}, // pads_begin + Shape{2, 2}, // pads_end + Shape{3, 16, 16}), // expected output shape + std::make_tuple( + Shape{3, 12, 225}, // data shape + std::vector{16, 16}, // output_size values + std::vector{2, 2}, // kernel_size values + Strides{1, 1}, // strides + Strides{1, 1}, // dilations + Shape{0, 0}, // pads_begin + Shape{0, 0}, // pads_end + Shape{3, 3, 16, 16}), // expected output shape + std::make_tuple( + Shape{1, 27, 49}, // data shape + std::vector{16, 16}, // output_size values + std::vector{3, 3}, // kernel_size values + Strides{2, 2}, // strides + Strides{2, 2}, // dilations + Shape{1, 1}, // pads_begin + Shape{1, 1}, // pads_end + Shape{1, 3, 16, 16}), // expected output shape + std::make_tuple( + Shape{1, 18, 104}, // data shape + std::vector{16, 16}, // output_size values + std::vector{2, 3}, // kernel_size values + Strides{2, 1}, // strides + Strides{2, 2}, // dilations + Shape{1, 0}, // pads_begin + Shape{0, 1}, // pads_end + Shape{1, 3, 16, 16}), // expected output shape + std::make_tuple( + Shape{12, 12, 324}, // data shape + std::vector{32, 32}, // output_size values + std::vector{2, 2}, // kernel_size values + Strides{2, 2}, // strides + Strides{2, 2}, // dilations + Shape{3, 3}, // pads_begin + Shape{3, 3}, // pads_end + Shape{12, 3, 32, 32}) // expected output shape + ) +);