[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:
Pawel Raasz
2023-05-15 10:54:41 +02:00
committed by GitHub
parent 3394f654e7
commit 5df0a006df
4 changed files with 284 additions and 20 deletions

View File

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

View File

@@ -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]);
}

View File

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

View File

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