diff --git a/src/core/include/openvino/op/strided_slice.hpp b/src/core/include/openvino/op/strided_slice.hpp index 5535e292580..c4b0394fa26 100644 --- a/src/core/include/openvino/op/strided_slice.hpp +++ b/src/core/include/openvino/op/strided_slice.hpp @@ -112,14 +112,14 @@ public: bool evaluate(const HostTensorVector& output_values, const HostTensorVector& input_values) const override; OPENVINO_SUPPRESS_DEPRECATED_END bool has_evaluate() const override; - OPENVINO_SUPPRESS_DEPRECATED_START - OPENVINO_SUPPRESS_DEPRECATED_END bool evaluate_lower(TensorVector& outputs) const override; bool evaluate_upper(TensorVector& outputs) const override; bool evaluate_label(TensorLabelVector& output_labels) const override; + bool constant_fold(OutputVector& output_values, const OutputVector& inputs_values) override; private: AxisSet convert_mask_to_axis_set(const std::vector& mask) const; + bool indicies_input_has_and_set_bounds(const size_t port, const std::vector& masks) const; std::vector m_begin_mask; std::vector m_end_mask; diff --git a/src/core/src/bound_evaluate.cpp b/src/core/src/bound_evaluate.cpp index 2aeeea2b2ab..fb07ddaed06 100644 --- a/src/core/src/bound_evaluate.cpp +++ b/src/core/src/bound_evaluate.cpp @@ -49,7 +49,7 @@ bool are_same_tensor(const ov::Tensor& lhs, const ov::Tensor& rhs) { (lhs.data() == rhs.data()); } -bool are_equal(const ov::Tensor& lhs, const ov::Tensor& rhs, size_t element_limit = 10) { +bool are_equal(const ov::Tensor& lhs, const ov::Tensor& rhs) { if (!lhs || !rhs) { return false; } @@ -59,7 +59,7 @@ bool are_equal(const ov::Tensor& lhs, const ov::Tensor& rhs, size_t element_limi const auto& lhs_et = lhs.get_element_type(); const auto& rhs_et = rhs.get_element_type(); - auto are_eq = (lhs_et == rhs_et) && (lhs_shape == rhs_shape) && shape_size(lhs_shape) <= element_limit; + auto are_eq = (lhs_et == rhs_et) && (lhs_shape == rhs_shape); if (are_eq) { are_eq = memcmp(lhs.data(), rhs.data(), lhs.get_byte_size()) == 0; @@ -317,10 +317,14 @@ std::pair ov::evaluate_both_bounds(const Output& o bool labels_evaluated = node->evaluate_label(output_labels); for (size_t i = 0; i < node->get_output_size(); ++i) { auto& out_tensor = node->get_output_tensor(i); + out_tensor.set_lower_value(outputs_lower[i]); - out_tensor.set_upper_value(outputs_upper[i]); - if (same_inputs || are_equal(outputs_lower[i], outputs_upper[i])) + if (same_inputs || are_equal(outputs_lower[i], outputs_upper[i])) { out_tensor.set_upper_value(outputs_lower[i]); + } else { + out_tensor.set_upper_value(outputs_upper[i]); + } + if (labels_evaluated) node->get_output_tensor(i).set_value_label(output_labels[i]); } diff --git a/src/core/src/op/strided_slice.cpp b/src/core/src/op/strided_slice.cpp index 1ba48d885df..97981412aa5 100644 --- a/src/core/src/op/strided_slice.cpp +++ b/src/core/src/op/strided_slice.cpp @@ -18,6 +18,7 @@ #include "ngraph/slice_plan.hpp" #include "ngraph/type/element_type_traits.hpp" #include "ngraph/util.hpp" +#include "openvino/core/validation_util.hpp" #include "openvino/op/util/precision_sensitive_attribute.hpp" #include "strided_slice_shape_inference.hpp" @@ -65,6 +66,17 @@ shared_ptr calculate_default_strides(const Output& begin, const Outp return op::Constant::create(element::i64, ov::Shape{strides_length}, vector(strides_length, 1)); } + +/** + * @brief Check if all indicies in 1-D input shape are ignored by masks. + * + * @param shape Indicies shape (assume compatible 1-D shape). + * @param ignored_mask Axis set of ignored indicies. + * @return True if all ignored other wise false. + */ +bool all_indicies_ignored(const ov::PartialShape& shape, const AxisSet& ignore_mask) { + return shape.rank().is_static() && ov::cmp::le(shape[0].get_interval().get_max_val(), ignore_mask.size()); +} } // namespace op::v1::StridedSlice::StridedSlice(const Output& data, @@ -237,27 +249,75 @@ bool op::v1::StridedSlice::has_evaluate() const { return get_input_size() == 4; } -namespace { -bool strided_slice_input_check(const ov::Node* node) { - if (!node->get_input_tensor(1).has_and_set_bound() || !node->get_input_tensor(2).has_and_set_bound() || - !node->get_input_tensor(3).has_and_set_bound()) - return false; - return true; +bool op::v1::StridedSlice::indicies_input_has_and_set_bounds(const size_t port, + const std::vector& masks) const { + const auto& lb_t = get_input_tensor(port).get_lower_value(); + const auto& ub_t = get_input_tensor(port).get_upper_value(); + + const auto mask_set = convert_mask_to_axis_set(masks); + bool valid_bounds = all_indicies_ignored(get_input_partial_shape(port), mask_set); + + if (!valid_bounds && lb_t && ub_t) { + using TCast = int64_t; + constexpr auto i64_cast = ov::util::Cast(); + const auto lb = ov::get_tensor_data_as(lb_t, i64_cast); + const auto ub = ov::get_tensor_data_as(ub_t, i64_cast); + + size_t axis = 0; + valid_bounds = + std::equal(lb.cbegin(), lb.cend(), ub.cbegin(), [&axis, &mask_set](TCast lhs, TCast rhs) -> bool { + return mask_set.count(axis++) || lhs == rhs; + }); + } + + return valid_bounds; } -} // namespace bool op::v1::StridedSlice::evaluate_lower(ov::TensorVector& output_values) const { - return strided_slice_input_check(this) && default_lower_bound_evaluator(this, output_values); + return indicies_input_has_and_set_bounds(1, get_begin_mask()) && + indicies_input_has_and_set_bounds(2, get_end_mask()) && get_input_tensor(3).has_and_set_bound() && + default_lower_bound_evaluator(this, output_values); } bool op::v1::StridedSlice::evaluate_upper(ov::TensorVector& output_values) const { - return strided_slice_input_check(this) && default_upper_bound_evaluator(this, output_values); + return indicies_input_has_and_set_bounds(1, get_begin_mask()) && + indicies_input_has_and_set_bounds(2, get_end_mask()) && get_input_tensor(3).has_and_set_bound() && + default_upper_bound_evaluator(this, output_values); } bool op::v1::StridedSlice::evaluate_label(TensorLabelVector& output_labels) const { - if (!strided_slice_input_check(this)) - return false; - OPENVINO_SUPPRESS_DEPRECATED_START - return default_label_evaluator(this, output_labels); - OPENVINO_SUPPRESS_DEPRECATED_END + return indicies_input_has_and_set_bounds(1, get_begin_mask()) && + indicies_input_has_and_set_bounds(2, get_end_mask()) && get_input_tensor(3).has_and_set_bound() && + default_label_evaluator(this, {0}, output_labels); +} + +bool op::v1::StridedSlice::constant_fold(OutputVector& output_values, const OutputVector& inputs_values) { + auto is_folded = Node::constant_fold(output_values, inputs_values); + if (!is_folded) { + // If all ignore mask are set for all begin or end then replace this input by dummy constant + // to avoid return false from `could_propagate` during bound evaluation (value of const will be ignore). + auto get_indicies_input = [&inputs_values](size_t port, AxisSet&& mask) -> Output { + return all_indicies_ignored(inputs_values[port].get_partial_shape(), mask) + ? std::make_shared(inputs_values[port].get_element_type(), + Shape{mask.size()}, + 0) + : inputs_values[port]; + }; + + const auto& begin = get_indicies_input(1, convert_mask_to_axis_set(get_begin_mask())); + const auto& end = get_indicies_input(2, convert_mask_to_axis_set(get_end_mask())); + + const auto& output = + ((&begin != &inputs_values[1]) || (&end != &inputs_values[2])) + ? clone_with_new_inputs(OutputVector{inputs_values[0], begin, end, inputs_values[3]})->output(0) + : this->output(0); + + OPENVINO_SUPPRESS_DEPRECATED_START + if (const auto c = ov::get_constant_from_source(output)) { + OPENVINO_SUPPRESS_DEPRECATED_END + output_values[0] = c; + is_folded = true; + } + } + return is_folded; } diff --git a/src/core/tests/constant_folding.cpp b/src/core/tests/constant_folding.cpp index d5ee167dc25..71c1fc52ba9 100644 --- a/src/core/tests/constant_folding.cpp +++ b/src/core/tests/constant_folding.cpp @@ -2224,6 +2224,206 @@ TEST(constant_folding, const_strided_slice) { ASSERT_EQ(sliced_values, values_out); } +TEST(constant_folding, strided_slice_ignored_dynamic_begin_end_values_from_shape_of) { + const auto constant = + make_shared(element::i32, + Shape{1, 1, 2, 4, 2}, + std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); + constant->set_friendly_name("constant"); + + const auto begin_shape = PartialShape{0, -1, 0, 0, 0}; + const auto p_begin = std::make_shared(element::i64, begin_shape); + const auto shape_of_begin = std::make_shared(p_begin); + shape_of_begin->set_friendly_name("begin"); + + const auto end_shape = PartialShape{-1, 512, 2, 2, 16}; + const auto p_end = std::make_shared(element::i64, end_shape); + const auto shape_of_end = std::make_shared(p_end); + shape_of_end->set_friendly_name("end"); + + const auto stride = op::Constant::create(element::i64, {5}, {1, 1, 1, 1, 1}); + stride->set_friendly_name("stride"); + + const auto slice = make_shared(constant, + shape_of_begin, + shape_of_end, + stride, + std::vector{0, 1, 0, 0, 0}, + std::vector{1, 1, 0, 0, 1}); + slice->set_friendly_name("test"); + + auto model = make_shared(slice, ParameterVector{p_begin, p_end}); + + run_constant_folding(model); + + ASSERT_EQ(count_ops_of_type(model), 0); + ASSERT_EQ(count_ops_of_type(model), 1); + + const auto new_const = get_result_constant(model); + ASSERT_TRUE(new_const); + check_names(new_const, {"constant", "begin", "end", "stride", "test"}); + const auto values_out = new_const->get_vector(); + + vector sliced_values{1, 2, 3, 4, 9, 10, 11, 12}; + ASSERT_EQ(sliced_values, values_out); +} + +TEST(constant_folding, strided_slice_all_ignore_mask_set_for_non_parameter_begin_end) { + const auto constant = + make_shared(element::i32, + Shape{1, 1, 2, 4, 2}, + std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); + constant->set_friendly_name("constant"); + + const auto begin_shape = PartialShape{{2, 5}, -1, 10, 1, {0, 200}}; + const auto p_begin = std::make_shared(element::i64, begin_shape); + const auto shape_of_begin = std::make_shared(p_begin); + shape_of_begin->set_friendly_name("begin"); + + const auto end_shape = PartialShape{-1, 1, {2, 3}, 0, 0}; + const auto p_end = std::make_shared(element::i64, end_shape); + const auto shape_of_end = std::make_shared(p_end); + shape_of_end->set_friendly_name("end"); + + const auto stride = op::Constant::create(element::i64, {5}, {1, 1, 1, 2, 2}); + stride->set_friendly_name("stride"); + + const auto slice = make_shared(constant, + shape_of_begin, + shape_of_end, + stride, + std::vector{1, 1, 1, 1, 1}, + std::vector{1, 1, 1, 1, 1}); + slice->set_friendly_name("test"); + + auto model = make_shared(slice, ParameterVector{p_begin, p_end}); + + run_constant_folding(model); + + ASSERT_EQ(count_ops_of_type(model), 0); + ASSERT_EQ(count_ops_of_type(model), 1); + + const auto new_const = get_result_constant(model); + ASSERT_TRUE(new_const); + check_names(new_const, {"constant", "begin", "end", "stride", "test"}); + const auto values_out = new_const->get_vector(); + + vector sliced_values{1, 5, 9, 13}; + ASSERT_EQ(sliced_values, values_out); +} + +TEST(constant_folding, strided_slice_all_ignore_mask_set_for_parameter_begin_end) { + const auto constant = + make_shared(element::i32, + Shape{1, 1, 2, 4, 2}, + std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); + constant->set_friendly_name("constant"); + + const auto begin_shape = PartialShape{{1, 5}}; + const auto p_begin = std::make_shared(element::i64, begin_shape); + p_begin->set_friendly_name("begin"); + + const auto end_shape = PartialShape{5}; + const auto p_end = std::make_shared(element::i64, end_shape); + p_end->set_friendly_name("end"); + + const auto stride = op::Constant::create(element::i64, {5}, {1, 1, 1, 2, 2}); + stride->set_friendly_name("stride"); + + const auto slice = make_shared(constant, + p_begin, + p_end, + stride, + std::vector{1, 1, 1, 1, 1}, + std::vector{1, 1, 1, 1, 1}); + slice->set_friendly_name("test"); + + auto model = make_shared(slice, ParameterVector{p_begin, p_end}); + + run_constant_folding(model); + + ASSERT_EQ(count_ops_of_type(model), 0); + ASSERT_EQ(count_ops_of_type(model), 1); + + const auto new_const = get_result_constant(model); + ASSERT_TRUE(new_const); + check_names(new_const, {"constant", "begin", "end", "stride", "test"}); + const auto values_out = new_const->get_vector(); + + vector sliced_values{1, 5, 9, 13}; + ASSERT_EQ(sliced_values, values_out); +} + +TEST(constant_folding, strided_slice_not_all_ignore_mask_set_for_parameter_begin_end) { + const auto constant = + make_shared(element::i32, + Shape{1, 1, 2, 4, 2}, + std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); + constant->set_friendly_name("constant"); + + const auto begin_shape = PartialShape::dynamic(); + const auto p_begin = std::make_shared(element::i64, begin_shape); + p_begin->set_friendly_name("begin"); + + const auto end_shape = PartialShape{5}; + const auto p_end = std::make_shared(element::i64, end_shape); + p_end->set_friendly_name("end"); + + const auto stride = op::Constant::create(element::i64, {5}, {1, 1, 1, 2, 2}); + stride->set_friendly_name("stride"); + + const auto slice = make_shared(constant, + p_begin, + p_end, + stride, + std::vector{1, 1, 1, 1}, + std::vector{1, 1, 1, 1}); + slice->set_friendly_name("test"); + + auto model = make_shared(slice, ParameterVector{p_begin, p_end}); + + run_constant_folding(model); + + ASSERT_EQ(count_ops_of_type(model), 1); + ASSERT_EQ(count_ops_of_type(model), 2); +} + +TEST(constant_folding, strided_slice_not_ignored_dynamic_begin_from_shape_of) { + const auto constant = + make_shared(element::i32, + Shape{1, 1, 2, 4, 2}, + std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); + constant->set_friendly_name("constant"); + + const auto begin_shape = PartialShape{0, -1, 0, 0, 0}; + const auto p_begin = std::make_shared(element::i64, begin_shape); + const auto shape_of_begin = std::make_shared(p_begin); + shape_of_begin->set_friendly_name("begin"); + + const auto end_shape = PartialShape{-1, 512, 2, 2, 16}; + const auto p_end = std::make_shared(element::i64, end_shape); + const auto shape_of_end = std::make_shared(p_end); + shape_of_end->set_friendly_name("end"); + + const auto stride = op::Constant::create(element::i64, {5}, {1, 1, 1, 1, 1}); + stride->set_friendly_name("stride"); + + const auto slice = make_shared(constant, + shape_of_begin, + shape_of_end, + stride, + std::vector{0, 0, 0, 0, 0}, + std::vector{1, 1, 0, 0, 1}); + slice->set_friendly_name("test"); + + auto model = make_shared(slice, ParameterVector{p_begin, p_end}); + + run_constant_folding(model); + + ASSERT_EQ(count_ops_of_type(model), 1); + ASSERT_EQ(count_ops_of_type(model), 2); +} + TEST(constant_folding, constant_dyn_reshape) { Shape shape_in{2, 4}; vector values_in{0, 1, 2, 3, 4, 5, 6, 7};