[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
This commit is contained in:
Katarzyna Mitrus 2024-06-03 11:59:07 +02:00 committed by GitHub
parent 56d184dbb1
commit 142114ccbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 55 additions and 78 deletions

View File

@ -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
</port>
</output>
</layer>

View File

@ -54,17 +54,15 @@ std::vector<TRShape> shape_infer(const Squeeze* op,
unique_axes.reset(new std::set<int64_t>(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<TRShape> 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;
}

View File

@ -20,10 +20,10 @@ using namespace testing;
TEST(type_prop, squeeze_axes_invalid_value) {
auto param = make_shared<ov::op::v0::Parameter>(element::f32, Shape{1, 2, 3, 4});
auto axes_node = make_shared<ov::op::v0::Constant>(element::u64, Shape{2}, vector<int64_t>{0, 2});
const auto squeeze = std::make_shared<op::v0::Squeeze>(param, axes_node);
OV_EXPECT_THROW(auto s = make_shared<op::v0::Squeeze>(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::op::v0::Parameter>(ov::element::f32, PartialShape{2, 2, 4});
const auto axes_node = std::make_shared<ov::op::v0::Parameter>(element::u64, PartialShape{1});
const auto squeeze = std::make_shared<op::v0::Squeeze>(param, axes_node);
OV_EXPECT_THROW(auto s = make_shared<op::v0::Squeeze>(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) {

View File

@ -83,30 +83,19 @@ Result SqueezeShapeInfer::infer(const std::vector<std::reference_wrapper<const V
ov::util::Cast<int64_t>());
std::vector<int64_t> originOutPattern = outPattern;
std::vector<bool> 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<int64_t>(inputShapeSize)) {
if (outPattern[i] >= 0 && outPattern[i] < static_cast<int64_t>(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

View File

@ -87,50 +87,30 @@ INSTANTIATE_TEST_SUITE_P(
make_tuple(unit_test::ShapeVector{{2, 6, 7, 8, 1}, {2}}, std::vector<int64_t>{-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<op::v0::Parameter>(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<size_t, ov::Tensor> 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<int64_t>{1}, StaticShape({})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {1}}, std::vector<int64_t>{2}, StaticShape({})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector<int64_t>{1, 2}, StaticShape({})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {1}}, std::vector<int64_t>{-1}, StaticShape({})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector<int64_t>{-1, -1}, StaticShape({})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector<int64_t>{-1, -2}, StaticShape({}))),
SqueezeCpuShapeInferenceThrowExceptionTest::getTestCaseName);
CpuShapeInfer_Squeeze_non_squeezable,
SqueezeCpuShapeInferenceTestNonSqueeezable,
Values(
make_tuple(unit_test::ShapeVector{{1, 2, 3, 1}, {1}}, std::vector<int64_t>{1}, StaticShape({1, 2, 3, 1})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 1}, {2}}, std::vector<int64_t>{0, 1}, StaticShape({2, 3, 1})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {1}}, std::vector<int64_t>{2}, StaticShape({1, 2, 3, 8})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector<int64_t>{1, 2}, StaticShape({1, 2, 3, 8})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {1}}, std::vector<int64_t>{-1}, StaticShape({1, 2, 3, 8})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector<int64_t>{-1, -1}, StaticShape({1, 2, 3, 8})),
make_tuple(unit_test::ShapeVector{{1, 2, 3, 8}, {2}}, std::vector<int64_t>{-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

View File

@ -2,13 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
//
#include "squeeze_shape_inference.hpp"
#include <gmock/gmock.h>
#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<int64_t>{-1}, StaticShape({})),
make_tuple(ShapeVector{{6}, {1}}, std::vector<int64_t>{-1}, StaticShape({6})),
make_tuple(ShapeVector{{1}, {1}}, std::vector<int64_t>{0}, StaticShape({}))),
PrintToStringParamName());
@ -77,6 +79,7 @@ INSTANTIATE_TEST_SUITE_P(
SqueezeStaticShapeInferenceTest,
Values(make_tuple(ShapeVector{{1, 2, 3, 1}, {2}}, std::vector<int64_t>{0, 3}, StaticShape({2, 3})),
make_tuple(ShapeVector{{2, 1, 1, 4}, {2}}, std::vector<int64_t>{2, 1}, StaticShape({2, 4})),
make_tuple(ShapeVector{{2, 1, 1, 4, 1}, {2}}, std::vector<int64_t>{0, 1, -2, -1}, StaticShape({2, 1, 4})),
make_tuple(ShapeVector{{1, 3, 1, 2, 1}, {3}}, std::vector<int64_t>{0, 2, 4}, StaticShape({3, 2})),
make_tuple(ShapeVector{{1, 3, 1, 2, 1}, {3}}, std::vector<int64_t>{4, 2, 0}, StaticShape({3, 2})),
make_tuple(ShapeVector{{1, 3, 1, 2, 1}, {3}}, std::vector<int64_t>{2, 0, 4}, StaticShape({3, 2})),

View File

@ -116,6 +116,15 @@ std::vector<SqueezeParams> generateParamsForSqueeze() {
using T2 = typename element_type_traits<Axes_T>::value_type;
std::vector<SqueezeParams> params{
SqueezeParams(Shape{1, 4, 1, 1, 2},
Shape{4, 1, 2},
IO_T,
IO_T,
std::vector<T1>{1, 2, 3, 4, 5, 6, 7, 8},
std::vector<T1>{1, 2, 3, 4, 5, 6, 7, 8},
Shape{4},
Axes_T,
std::vector<T2>{0, 1, 3, -1}),
SqueezeParams(Shape{1, 4, 1, 1, 2},
Shape{4, 1, 2},
IO_T,

View File

@ -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