[Core] StridedSlice improvements of bound evaluation and constant folding (#17505)
* StridedSlice improvements: -Bound evaluation for begin, end partial values when ignore mask set. - Custom constant fold implementation. * Improve const folding when all begin or end values are ignored
This commit is contained in:
@@ -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<int64_t>& mask) const;
|
||||
bool indicies_input_has_and_set_bounds(const size_t port, const std::vector<int64_t>& masks) const;
|
||||
|
||||
std::vector<int64_t> m_begin_mask;
|
||||
std::vector<int64_t> m_end_mask;
|
||||
|
||||
@@ -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::Tensor, ov::Tensor> ov::evaluate_both_bounds(const Output<Node>& 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]);
|
||||
}
|
||||
|
||||
@@ -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<Node> calculate_default_strides(const Output<Node>& begin, const Outp
|
||||
|
||||
return op::Constant::create(element::i64, ov::Shape{strides_length}, vector<int64_t>(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<Node>& 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<int64_t>& 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<TCast>();
|
||||
const auto lb = ov::get_tensor_data_as<TCast>(lb_t, i64_cast);
|
||||
const auto ub = ov::get_tensor_data_as<TCast>(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<Node> {
|
||||
return all_indicies_ignored(inputs_values[port].get_partial_shape(), mask)
|
||||
? std::make_shared<op::v0::Constant>(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;
|
||||
}
|
||||
|
||||
@@ -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<op::Constant>(element::i32,
|
||||
Shape{1, 1, 2, 4, 2},
|
||||
std::vector<int>{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<op::Parameter>(element::i64, begin_shape);
|
||||
const auto shape_of_begin = std::make_shared<op::ShapeOf>(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<op::Parameter>(element::i64, end_shape);
|
||||
const auto shape_of_end = std::make_shared<op::ShapeOf>(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<op::v1::StridedSlice>(constant,
|
||||
shape_of_begin,
|
||||
shape_of_end,
|
||||
stride,
|
||||
std::vector<int64_t>{0, 1, 0, 0, 0},
|
||||
std::vector<int64_t>{1, 1, 0, 0, 1});
|
||||
slice->set_friendly_name("test");
|
||||
|
||||
auto model = make_shared<ov::Model>(slice, ParameterVector{p_begin, p_end});
|
||||
|
||||
run_constant_folding(model);
|
||||
|
||||
ASSERT_EQ(count_ops_of_type<op::v1::StridedSlice>(model), 0);
|
||||
ASSERT_EQ(count_ops_of_type<op::Constant>(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<int>();
|
||||
|
||||
vector<int> 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<op::Constant>(element::i32,
|
||||
Shape{1, 1, 2, 4, 2},
|
||||
std::vector<int>{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<op::Parameter>(element::i64, begin_shape);
|
||||
const auto shape_of_begin = std::make_shared<op::ShapeOf>(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<op::Parameter>(element::i64, end_shape);
|
||||
const auto shape_of_end = std::make_shared<op::ShapeOf>(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<op::v1::StridedSlice>(constant,
|
||||
shape_of_begin,
|
||||
shape_of_end,
|
||||
stride,
|
||||
std::vector<int64_t>{1, 1, 1, 1, 1},
|
||||
std::vector<int64_t>{1, 1, 1, 1, 1});
|
||||
slice->set_friendly_name("test");
|
||||
|
||||
auto model = make_shared<ov::Model>(slice, ParameterVector{p_begin, p_end});
|
||||
|
||||
run_constant_folding(model);
|
||||
|
||||
ASSERT_EQ(count_ops_of_type<op::v1::StridedSlice>(model), 0);
|
||||
ASSERT_EQ(count_ops_of_type<op::Constant>(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<int>();
|
||||
|
||||
vector<int> 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<op::Constant>(element::i32,
|
||||
Shape{1, 1, 2, 4, 2},
|
||||
std::vector<int>{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<op::Parameter>(element::i64, begin_shape);
|
||||
p_begin->set_friendly_name("begin");
|
||||
|
||||
const auto end_shape = PartialShape{5};
|
||||
const auto p_end = std::make_shared<op::Parameter>(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<op::v1::StridedSlice>(constant,
|
||||
p_begin,
|
||||
p_end,
|
||||
stride,
|
||||
std::vector<int64_t>{1, 1, 1, 1, 1},
|
||||
std::vector<int64_t>{1, 1, 1, 1, 1});
|
||||
slice->set_friendly_name("test");
|
||||
|
||||
auto model = make_shared<ov::Model>(slice, ParameterVector{p_begin, p_end});
|
||||
|
||||
run_constant_folding(model);
|
||||
|
||||
ASSERT_EQ(count_ops_of_type<op::v1::StridedSlice>(model), 0);
|
||||
ASSERT_EQ(count_ops_of_type<op::Constant>(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<int>();
|
||||
|
||||
vector<int> 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<op::Constant>(element::i32,
|
||||
Shape{1, 1, 2, 4, 2},
|
||||
std::vector<int>{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<op::Parameter>(element::i64, begin_shape);
|
||||
p_begin->set_friendly_name("begin");
|
||||
|
||||
const auto end_shape = PartialShape{5};
|
||||
const auto p_end = std::make_shared<op::Parameter>(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<op::v1::StridedSlice>(constant,
|
||||
p_begin,
|
||||
p_end,
|
||||
stride,
|
||||
std::vector<int64_t>{1, 1, 1, 1},
|
||||
std::vector<int64_t>{1, 1, 1, 1});
|
||||
slice->set_friendly_name("test");
|
||||
|
||||
auto model = make_shared<ov::Model>(slice, ParameterVector{p_begin, p_end});
|
||||
|
||||
run_constant_folding(model);
|
||||
|
||||
ASSERT_EQ(count_ops_of_type<op::v1::StridedSlice>(model), 1);
|
||||
ASSERT_EQ(count_ops_of_type<op::Constant>(model), 2);
|
||||
}
|
||||
|
||||
TEST(constant_folding, strided_slice_not_ignored_dynamic_begin_from_shape_of) {
|
||||
const auto constant =
|
||||
make_shared<op::Constant>(element::i32,
|
||||
Shape{1, 1, 2, 4, 2},
|
||||
std::vector<int>{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<op::Parameter>(element::i64, begin_shape);
|
||||
const auto shape_of_begin = std::make_shared<op::ShapeOf>(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<op::Parameter>(element::i64, end_shape);
|
||||
const auto shape_of_end = std::make_shared<op::ShapeOf>(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<op::v1::StridedSlice>(constant,
|
||||
shape_of_begin,
|
||||
shape_of_end,
|
||||
stride,
|
||||
std::vector<int64_t>{0, 0, 0, 0, 0},
|
||||
std::vector<int64_t>{1, 1, 0, 0, 1});
|
||||
slice->set_friendly_name("test");
|
||||
|
||||
auto model = make_shared<ov::Model>(slice, ParameterVector{p_begin, p_end});
|
||||
|
||||
run_constant_folding(model);
|
||||
|
||||
ASSERT_EQ(count_ops_of_type<op::v1::StridedSlice>(model), 1);
|
||||
ASSERT_EQ(count_ops_of_type<op::Constant>(model), 2);
|
||||
}
|
||||
|
||||
TEST(constant_folding, constant_dyn_reshape) {
|
||||
Shape shape_in{2, 4};
|
||||
vector<float> values_in{0, 1, 2, 3, 4, 5, 6, 7};
|
||||
|
||||
Reference in New Issue
Block a user