From 142114ccbc0e192ec6a7f257be2b09aa5c57356d Mon Sep 17 00:00:00 2001 From: Katarzyna Mitrus Date: Mon, 3 Jun 2024 11:59:07 +0200 Subject: [PATCH] [Op] Squeeze update - no throw on non-squeezable dims (#24715) ### Details: - Changes needed to enable Squeeze (and models) with axes pointing to non-squeezable dims (currently ov throws an error for such case) - considered as backward compatible change - If agreed to update functionality within the same version of the op, actual changes are needed only in shape infer related functions to cover **Template, CPU and GPU plugins**: - src/core/shape_inference/include/squeeze_shape_inference.hpp - src/plugins/intel_cpu/src/shape_inference/custom/reshape.cpp - The rest of the changes are test related. - Pytorch FE Squeeze test enabled - passed ### Tickets: - 142387 --- .../operation-specs/shape/squeeze-1.rst | 6 +- .../include/squeeze_shape_inference.hpp | 28 ++++----- src/core/tests/type_prop/squeeze.cpp | 12 ++-- .../src/shape_inference/custom/reshape.cpp | 14 +---- .../custom_shape_infer/squeeze.cpp | 58 ++++++------------- .../squeeze_shape_inference_test.cpp | 5 +- .../tests/functional/op_reference/squeeze.cpp | 9 +++ .../layer_tests/pytorch_tests/test_squeeze.py | 1 - 8 files changed, 55 insertions(+), 78 deletions(-) diff --git a/docs/articles_en/documentation/openvino-ir-format/operation-sets/operation-specs/shape/squeeze-1.rst b/docs/articles_en/documentation/openvino-ir-format/operation-sets/operation-specs/shape/squeeze-1.rst index 9d426f88e5f..1ccce710ee0 100644 --- a/docs/articles_en/documentation/openvino-ir-format/operation-sets/operation-specs/shape/squeeze-1.rst +++ b/docs/articles_en/documentation/openvino-ir-format/operation-sets/operation-specs/shape/squeeze-1.rst @@ -17,7 +17,10 @@ Squeeze **Detailed description**: *Squeeze* can be used with or without the second input tensor. * If only the first input is provided, every dimension that is equal to 1 will be removed from it. -* With the second input provided, each value is an index of a dimension from the first tensor that is to be removed. Specified dimension has to be equal to 1, otherwise an error will be raised. Dimension indices can be specified directly, or by negative indices (counting dimensions from the end). +* With the second input provided, each value is an index of a dimension from the first tensor that is to be removed. Specified dimension should be equal to 1, otherwise it will be ignored and copied as is. +Dimension indices can be specified directly, or by negative indices (counting dimensions from the end). + +Note: Updated behavior since 2024.3, request of squeezing dimension not equal to 1 is expected to be ignored instead of causing an error. **Attributes**: *Squeeze* operation doesn't have attributes. @@ -87,4 +90,3 @@ Squeeze - diff --git a/src/core/shape_inference/include/squeeze_shape_inference.hpp b/src/core/shape_inference/include/squeeze_shape_inference.hpp index cd3dda050fb..fabaf2ba68a 100644 --- a/src/core/shape_inference/include/squeeze_shape_inference.hpp +++ b/src/core/shape_inference/include/squeeze_shape_inference.hpp @@ -54,17 +54,15 @@ std::vector shape_infer(const Squeeze* op, unique_axes.reset(new std::set(axes->cbegin(), axes->cend())); } else if (arg_rank.get_length() > 0 && shape_size(axes_shape.to_shape()) == 1) { // The `axes` input is a single element tensor which is unique by definition, deducing output rank - NODE_VALIDATION_CHECK(op, - std::any_of(arg_shape.cbegin(), - arg_shape.cend(), - [](const DimType& dim) { - return dim.compatible(1); - }), - "Data input shape ", - arg_shape, - " doesn't contain squeezable dimension," - " but axes input is expected to have one element."); - output_shape = PartialShape::dynamic(arg_rank.get_length() - 1); + const auto has_squeezable_dim = + std::any_of(arg_shape.cbegin(), arg_shape.cend(), [](const DimType& dim) { + return dim.compatible(1); + }); + if (has_squeezable_dim) { + output_shape = PartialShape::dynamic(arg_rank.get_length() - 1); + } else { + output_shape = arg_shape; + } return output_shapes; } } @@ -97,13 +95,11 @@ std::vector shape_infer(const Squeeze* op, auto rm_axis_end = unique_axes->cend(); // Returns true if dimension not squeezable on axis from input axes. - const auto not_squeezable_at_axis = [&op, &rm_axis_iter, &rm_axis_end, &idx](const DimType& dim) { + const auto not_squeezable_at_axis = [&rm_axis_iter, &rm_axis_end, &idx](const DimType& dim) { if ((rm_axis_iter != rm_axis_end) && (*rm_axis_iter == idx++)) { - NODE_VALIDATION_CHECK(op, - dim.compatible(1), - "provided axis value is invalid. Only axes of size 1 may be removed."); ++rm_axis_iter; - return false; + // Ignore: Pointed by axis, but not squeezable + return !dim.compatible(1); } else { return true; } diff --git a/src/core/tests/type_prop/squeeze.cpp b/src/core/tests/type_prop/squeeze.cpp index a9ade7b52e6..eeb872bad97 100644 --- a/src/core/tests/type_prop/squeeze.cpp +++ b/src/core/tests/type_prop/squeeze.cpp @@ -20,10 +20,10 @@ using namespace testing; TEST(type_prop, squeeze_axes_invalid_value) { auto param = make_shared(element::f32, Shape{1, 2, 3, 4}); auto axes_node = make_shared(element::u64, Shape{2}, vector{0, 2}); + const auto squeeze = std::make_shared(param, axes_node); - OV_EXPECT_THROW(auto s = make_shared(param, axes_node), - NodeValidationFailure, - HasSubstr("provided axis value is invalid. Only axes of size 1 may be removed.")); + EXPECT_EQ(squeeze->get_element_type(), element::f32); + EXPECT_EQ(squeeze->get_output_partial_shape(0), (PartialShape{2, 3, 4})); } TEST(type_prop, squeeze_single_input) { @@ -53,10 +53,10 @@ TEST(type_prop, squeeze_incorrect_negative_axes) { TEST(type_prop, squeeze_data_static_param_axes_1D_single_elem_static_shape_no_squeezable_dims) { auto param = std::make_shared(ov::element::f32, PartialShape{2, 2, 4}); const auto axes_node = std::make_shared(element::u64, PartialShape{1}); + const auto squeeze = std::make_shared(param, axes_node); - OV_EXPECT_THROW(auto s = make_shared(param, axes_node), - NodeValidationFailure, - HasSubstr("doesn't contain squeezable dimension")); + EXPECT_EQ(squeeze->get_element_type(), element::f32); + EXPECT_EQ(squeeze->get_output_partial_shape(0), (PartialShape{2, 2, 4})); } TEST(type_prop, squeeze_data_static_param_axes_1D_two_elem_static_shape_squeezable_dims_two) { diff --git a/src/plugins/intel_cpu/src/shape_inference/custom/reshape.cpp b/src/plugins/intel_cpu/src/shape_inference/custom/reshape.cpp index ace1dc80b0d..e6db58637de 100644 --- a/src/plugins/intel_cpu/src/shape_inference/custom/reshape.cpp +++ b/src/plugins/intel_cpu/src/shape_inference/custom/reshape.cpp @@ -83,30 +83,19 @@ Result SqueezeShapeInfer::infer(const std::vector()); std::vector originOutPattern = outPattern; std::vector removeMask(inputShapeSize, false); - bool existError = false; for (size_t i = 0; i < outputPatternSize; i++) { if (outPattern[i] < 0) { outPattern[i] = inputShapeSize + outPattern[i]; } - if (outPattern[i] >= 0 && outPattern[i] < static_cast(inputShapeSize)) { + if (outPattern[i] >= 0 && outPattern[i] < static_cast(inputShapeSize) && inputShape[outPattern[i]] == 1) { removeMask[outPattern[i]] = true; - } else { - existError = true; - break; } } for (size_t i = 0; i < inputShapeSize; i++) { if (!removeMask[i]) { outputShape.push_back(inputShape[i]); - } else if (inputShape[i] != 1) { - existError = true; - break; } } - if (existError) { - OPENVINO_THROW("[cpu]squeeze: the shape of input data ", ov::intel_cpu::vec2str(inputShape), - " conflicts with the squeeze pattern ", ov::intel_cpu::vec2str(originOutPattern)); - } } else { for (size_t i = 0; i < inputShapeSize; i++) { if (inputShape[i] != 1) { @@ -189,4 +178,3 @@ ShapeInferPtr ReshapeShapeInferFactory::makeShapeInfer() const { } // namespace node } // namespace intel_cpu } // namespace ov - diff --git a/src/plugins/intel_cpu/tests/unit/shape_inference_test/custom_shape_infer/squeeze.cpp b/src/plugins/intel_cpu/tests/unit/shape_inference_test/custom_shape_infer/squeeze.cpp index 7de0e1a67ed..a059833428e 100644 --- a/src/plugins/intel_cpu/tests/unit/shape_inference_test/custom_shape_infer/squeeze.cpp +++ b/src/plugins/intel_cpu/tests/unit/shape_inference_test/custom_shape_infer/squeeze.cpp @@ -87,50 +87,30 @@ INSTANTIATE_TEST_SUITE_P( make_tuple(unit_test::ShapeVector{{2, 6, 7, 8, 1}, {2}}, std::vector{-1, -1}, StaticShape({2, 6, 7, 8}))), SqueezeCpuShapeInferenceTest::getTestCaseName); -using SqueezeCpuShapeInferenceThrowExceptionTest = SqueezeCpuShapeInferenceTest; -TEST_P(SqueezeCpuShapeInferenceThrowExceptionTest, wrong_pattern) { +// Tests with non-squeezable dims pointed by axes (no throw, ignore) +class SqueezeCpuShapeInferenceTestNonSqueeezable : public SqueezeCpuShapeInferenceTest {}; +TEST_P(SqueezeCpuShapeInferenceTestNonSqueeezable, shape_inference_non_squeezable_with_const_map) { const auto axes_node = std::make_shared(element::i64, PartialShape::dynamic()); const auto op = make_op(arg, axes_node); - const auto axes_tensor = ov::Tensor(element::i64, ov::Shape{axes.size()}, axes.data()); const std::unordered_map constant_data = {{1, axes_tensor}}; - std::ostringstream os; - os << "[cpu]squeeze: the shape of input data "; - os << "("; - for (size_t i = 0; i < input_shapes[0].size(); i++) { - os << input_shapes[0][i]; - if (i < input_shapes[0].size() - 1) { - os << "."; - } - } - os << ")"; - os << " conflicts with the squeeze pattern "; - os << "("; - for (size_t i = 0; i < axes.size(); i++) { - os << axes[i]; - if (i < axes.size() - 1) { - os << "."; - } - } - os << ")"; - - OV_EXPECT_THROW(unit_test::cpu_test_shape_infer(op.get(), input_shapes, output_shapes, constant_data), - ov::Exception, - HasSubstr(os.str())); + unit_test::cpu_test_shape_infer(op.get(), input_shapes, output_shapes, constant_data); } INSTANTIATE_TEST_SUITE_P( - CpuShapeInfer, - SqueezeCpuShapeInferenceThrowExceptionTest, - Values(make_tuple(unit_test::ShapeVector{{1, 2, 3, 1}, {1}}, std::vector{1}, StaticShape({})), - make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {1}}, std::vector{2}, StaticShape({})), - make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector{1, 2}, StaticShape({})), - make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {1}}, std::vector{-1}, StaticShape({})), - make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector{-1, -1}, StaticShape({})), - make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector{-1, -2}, StaticShape({}))), - SqueezeCpuShapeInferenceThrowExceptionTest::getTestCaseName); + CpuShapeInfer_Squeeze_non_squeezable, + SqueezeCpuShapeInferenceTestNonSqueeezable, + Values( + make_tuple(unit_test::ShapeVector{{1, 2, 3, 1}, {1}}, std::vector{1}, StaticShape({1, 2, 3, 1})), + make_tuple(unit_test::ShapeVector{{1, 2, 3, 1}, {2}}, std::vector{0, 1}, StaticShape({2, 3, 1})), + make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {1}}, std::vector{2}, StaticShape({1, 2, 3, 8})), + make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector{1, 2}, StaticShape({1, 2, 3, 8})), + make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {1}}, std::vector{-1}, StaticShape({1, 2, 3, 8})), + make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector{-1, -1}, StaticShape({1, 2, 3, 8})), + make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector{-1, -2}, StaticShape({1, 2, 3, 8}))), + SqueezeCpuShapeInferenceTest::getTestCaseName); -} // namespace cpu_shape_infer -} // namespace unit_test -} // namespace intel_cpu -} // namespace ov +} // namespace cpu_shape_infer +} // namespace unit_test +} // namespace intel_cpu +} // namespace ov diff --git a/src/plugins/intel_cpu/tests/unit/shape_inference_test/squeeze_shape_inference_test.cpp b/src/plugins/intel_cpu/tests/unit/shape_inference_test/squeeze_shape_inference_test.cpp index bd42a19aa70..69da74b10a2 100644 --- a/src/plugins/intel_cpu/tests/unit/shape_inference_test/squeeze_shape_inference_test.cpp +++ b/src/plugins/intel_cpu/tests/unit/shape_inference_test/squeeze_shape_inference_test.cpp @@ -2,13 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 // +#include "squeeze_shape_inference.hpp" + #include #include "common_test_utils/test_assertions.hpp" #include "openvino/op/constant.hpp" #include "openvino/op/parameter.hpp" #include "openvino/op/squeeze.hpp" -#include "squeeze_shape_inference.hpp" #include "utils.hpp" using namespace ov; @@ -69,6 +70,7 @@ protected: INSTANTIATE_TEST_SUITE_P(1d_shapes, SqueezeStaticShapeInferenceTest, Values(make_tuple(ShapeVector{{1}, {1}}, std::vector{-1}, StaticShape({})), + make_tuple(ShapeVector{{6}, {1}}, std::vector{-1}, StaticShape({6})), make_tuple(ShapeVector{{1}, {1}}, std::vector{0}, StaticShape({}))), PrintToStringParamName()); @@ -77,6 +79,7 @@ INSTANTIATE_TEST_SUITE_P( SqueezeStaticShapeInferenceTest, Values(make_tuple(ShapeVector{{1, 2, 3, 1}, {2}}, std::vector{0, 3}, StaticShape({2, 3})), make_tuple(ShapeVector{{2, 1, 1, 4}, {2}}, std::vector{2, 1}, StaticShape({2, 4})), + make_tuple(ShapeVector{{2, 1, 1, 4, 1}, {2}}, std::vector{0, 1, -2, -1}, StaticShape({2, 1, 4})), make_tuple(ShapeVector{{1, 3, 1, 2, 1}, {3}}, std::vector{0, 2, 4}, StaticShape({3, 2})), make_tuple(ShapeVector{{1, 3, 1, 2, 1}, {3}}, std::vector{4, 2, 0}, StaticShape({3, 2})), make_tuple(ShapeVector{{1, 3, 1, 2, 1}, {3}}, std::vector{2, 0, 4}, StaticShape({3, 2})), diff --git a/src/plugins/template/tests/functional/op_reference/squeeze.cpp b/src/plugins/template/tests/functional/op_reference/squeeze.cpp index 96b74cf3547..8bf1902c403 100644 --- a/src/plugins/template/tests/functional/op_reference/squeeze.cpp +++ b/src/plugins/template/tests/functional/op_reference/squeeze.cpp @@ -116,6 +116,15 @@ std::vector generateParamsForSqueeze() { using T2 = typename element_type_traits::value_type; std::vector params{ + SqueezeParams(Shape{1, 4, 1, 1, 2}, + Shape{4, 1, 2}, + IO_T, + IO_T, + std::vector{1, 2, 3, 4, 5, 6, 7, 8}, + std::vector{1, 2, 3, 4, 5, 6, 7, 8}, + Shape{4}, + Axes_T, + std::vector{0, 1, 3, -1}), SqueezeParams(Shape{1, 4, 1, 1, 2}, Shape{4, 1, 2}, IO_T, diff --git a/tests/layer_tests/pytorch_tests/test_squeeze.py b/tests/layer_tests/pytorch_tests/test_squeeze.py index ffc37583734..80f28ae4142 100644 --- a/tests/layer_tests/pytorch_tests/test_squeeze.py +++ b/tests/layer_tests/pytorch_tests/test_squeeze.py @@ -39,7 +39,6 @@ class TestSqueeze(PytorchLayerTest): pytest.xfail(reason="export fails if dim is not provided") self._test(*self.create_model(dim), ie_device, precision, ir_version, dynamic_shapes=dynamic_shapes) - @pytest.mark.xfail(reason='OpenVINO squeeze does not support dimension is not equal to 1.') @pytest.mark.parametrize("dim", [-1, 2]) @pytest.mark.nightly @pytest.mark.precommit