Review reverse class for shape inference aspects (#15426)
* Check partial shape and label propagation * Add template shape_infer implementation
This commit is contained in:
@@ -78,6 +78,23 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Compare if value is less to expected.
|
||||
*
|
||||
* \tparam T Value type to compare.
|
||||
*/
|
||||
template <class T>
|
||||
class Less {
|
||||
const T m_exp_value{};
|
||||
|
||||
public:
|
||||
constexpr Less(const T& exp_value) : m_exp_value{exp_value} {}
|
||||
|
||||
constexpr bool operator()(const T& value) const {
|
||||
return value < m_exp_value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Compare two values (a < b) in safe way against lossy integer conversion.
|
||||
*
|
||||
|
||||
106
src/core/shape_inference/include/reverse_shape_inference.hpp
Normal file
106
src/core/shape_inference/include/reverse_shape_inference.hpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright (C) 2018-2023 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <openvino/op/reverse.hpp>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace ov {
|
||||
namespace op {
|
||||
|
||||
namespace util {
|
||||
|
||||
/** \brief Clip if value type T is less than 0, otherwise cast to AxisSet::value_type. */
|
||||
struct ClipNegative {
|
||||
using value_type = typename AxisSet::value_type;
|
||||
|
||||
template <class T>
|
||||
constexpr value_type operator()(const T value) const {
|
||||
return (std::is_signed<T>::value && value < 0) ? 0 : static_cast<value_type>(value);
|
||||
}
|
||||
};
|
||||
} // namespace util
|
||||
|
||||
namespace v1 {
|
||||
|
||||
/**
|
||||
* \brief Reverse shape inference
|
||||
*
|
||||
* \tparam TShape Type of shape.
|
||||
*
|
||||
* \param op Pointer to Reverse operator.
|
||||
* \param input_shapes Input shapes of Reverse.
|
||||
* \param constant_data Map of constant data. Default empty.
|
||||
*
|
||||
* \return Vector of output shapes with one shape.
|
||||
*/
|
||||
template <class TShape>
|
||||
std::vector<TShape> shape_infer(const Reverse* op,
|
||||
const std::vector<TShape>& input_shapes,
|
||||
const std::map<size_t, std::reference_wrapper<const ov::Tensor>>& constant_data = {}) {
|
||||
NODE_VALIDATION_CHECK(op, input_shapes.size() == 2);
|
||||
using DimType = typename TShape::value_type;
|
||||
|
||||
const auto& data_shape = input_shapes[0];
|
||||
const auto& data_rank = data_shape.rank();
|
||||
const auto& axes_shape = input_shapes[1];
|
||||
const auto& axes_rank = axes_shape.rank();
|
||||
|
||||
NODE_VALIDATION_CHECK(op,
|
||||
axes_rank.compatible(1),
|
||||
"The reversed_axes input must be a 1D tensor (got ",
|
||||
axes_rank,
|
||||
").");
|
||||
|
||||
if (op->get_mode() == Reverse::Mode::MASK) {
|
||||
NODE_VALIDATION_CHECK(
|
||||
op,
|
||||
data_rank.is_dynamic() || axes_rank.is_dynamic() || axes_shape[0].compatible(data_shape.size()),
|
||||
"The number of elements in the reversed_axes tensor (",
|
||||
axes_shape[0],
|
||||
") must match the input data tensor rank (",
|
||||
data_rank,
|
||||
") in 'mask' mode.");
|
||||
} else if (data_rank.is_static()) {
|
||||
// mode index & data rank is static
|
||||
using TAxis = typename AxisSet::value_type;
|
||||
static_assert(std::is_same<TAxis, util::ClipNegative::value_type>(),
|
||||
"AxisSet::value_type != ClipNegative::value_type");
|
||||
|
||||
if (const auto axes =
|
||||
get_input_const_data_as<TShape, TAxis, AxisSet>(op, 1, constant_data, util::ClipNegative())) {
|
||||
NODE_VALIDATION_CHECK(op,
|
||||
all_of(axes->begin(), axes->end(), cmp::Less<TAxis>(data_rank.get_length())),
|
||||
"Some of the provided axes (",
|
||||
*axes,
|
||||
") are out of bounds (input rank: ",
|
||||
data_rank,
|
||||
").");
|
||||
}
|
||||
}
|
||||
|
||||
return {data_shape};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Reverse shape inference
|
||||
*
|
||||
* \tparam TShape Type of shape.
|
||||
*
|
||||
* \param op Pointer to Reverse operator.
|
||||
* \param input_shapes Input shapes of Reverse.
|
||||
* \param output_shapes Output shapes of Reverse
|
||||
* \param constant_data Map of constant data. Default empty.
|
||||
*/
|
||||
template <class TShape>
|
||||
void shape_infer(const Reverse* op,
|
||||
const std::vector<TShape>& input_shapes,
|
||||
std::vector<TShape>& output_shapes,
|
||||
const std::map<size_t, std::reference_wrapper<const ov::Tensor>>& constant_data = {}) {
|
||||
output_shapes = shape_infer(op, input_shapes);
|
||||
}
|
||||
} // namespace v1
|
||||
} // namespace op
|
||||
} // namespace ov
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <ngraph/validation_util.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#include "itt.hpp"
|
||||
@@ -15,6 +14,7 @@
|
||||
#include "ngraph/op/constant.hpp"
|
||||
#include "ngraph/op/util/op_types.hpp"
|
||||
#include "ngraph/runtime/reference/reverse.hpp"
|
||||
#include "reverse_shape_inference.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace ngraph;
|
||||
@@ -43,66 +43,15 @@ void op::v1::Reverse::validate_and_infer_types() {
|
||||
NODE_VALIDATION_CHECK(this,
|
||||
get_input_element_type(1) == element::boolean,
|
||||
"In 'mask' mode the second input must contain boolean values.");
|
||||
}
|
||||
|
||||
const auto input_shape = get_input_partial_shape(0);
|
||||
const auto input_rank = input_shape.rank();
|
||||
|
||||
const auto rev_axes_shape = get_input_partial_shape(1);
|
||||
const auto rev_axes_rank = rev_axes_shape.rank();
|
||||
|
||||
if (rev_axes_rank.is_static()) {
|
||||
} else {
|
||||
// Index mode
|
||||
NODE_VALIDATION_CHECK(this,
|
||||
rev_axes_rank.get_length() == 1,
|
||||
"The reversed_axes input must be a 1D tensor (got ",
|
||||
rev_axes_rank.get_length(),
|
||||
").");
|
||||
|
||||
if (m_mode == Mode::MASK) {
|
||||
if (input_rank.is_static() && rev_axes_shape[0].is_static()) {
|
||||
const auto rev_axes_mask_elems_count = rev_axes_shape[0].get_length();
|
||||
NODE_VALIDATION_CHECK(this,
|
||||
rev_axes_mask_elems_count == input_rank.get_length(),
|
||||
"The number of elements in the reversed_axes tensor (",
|
||||
rev_axes_mask_elems_count,
|
||||
") must match the input data tensor rank (",
|
||||
input_rank.get_length(),
|
||||
") in 'mask' mode.");
|
||||
}
|
||||
}
|
||||
get_input_element_type(1).is_integral_number(),
|
||||
"In 'index' mode the second input must contain integer values.");
|
||||
}
|
||||
|
||||
if (input_rank.is_static()) {
|
||||
const size_t rank = input_rank.get_length();
|
||||
|
||||
if (const auto& rev_axes_constant = get_constant_from_source(input_value(1))) {
|
||||
if (m_mode == Mode::INDEX) {
|
||||
const AxisSet rev_axes = rev_axes_constant->get_axis_set_val();
|
||||
|
||||
NODE_VALIDATION_CHECK(this,
|
||||
rev_axes.size() <= rank,
|
||||
"Too many axes(",
|
||||
rev_axes,
|
||||
") have been provided for given input shape(",
|
||||
input_shape,
|
||||
").");
|
||||
|
||||
bool all_axes_in_range = all_of(rev_axes.begin(), rev_axes.end(), [&rank](const size_t axis) {
|
||||
return axis < rank;
|
||||
});
|
||||
|
||||
NODE_VALIDATION_CHECK(this,
|
||||
all_axes_in_range,
|
||||
"Some of the provided axes (",
|
||||
rev_axes,
|
||||
") are out of bounds (input rank: ",
|
||||
input_rank.get_length(),
|
||||
").");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_output_type(0, get_input_element_type(0), input_shape);
|
||||
const auto output_shape = shape_infer(this, get_node_input_partial_shapes(*this)).front();
|
||||
set_output_type(0, get_input_element_type(0), output_shape);
|
||||
}
|
||||
|
||||
shared_ptr<Node> op::v1::Reverse::clone_with_new_inputs(const OutputVector& new_args) const {
|
||||
|
||||
@@ -2,14 +2,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "common_test_utils/test_assertions.hpp"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ngraph/ngraph.hpp"
|
||||
#include "openvino/opsets/opset10.hpp"
|
||||
#include "util/type_prop.hpp"
|
||||
|
||||
NGRAPH_SUPPRESS_DEPRECATED_START
|
||||
|
||||
using namespace std;
|
||||
using namespace ngraph;
|
||||
using namespace testing;
|
||||
|
||||
class TypePropReverseV1Test : public TypePropOpTest<op::v1::Reverse> {};
|
||||
|
||||
TEST(type_prop, reverse_1d_deduce) {
|
||||
// Deduce type
|
||||
@@ -155,39 +160,175 @@ TEST(type_prop, reverse_partial_rank_dynamic) {
|
||||
op::v1::Reverse::Mode::INDEX);
|
||||
|
||||
EXPECT_EQ(rev->get_element_type(), element::f32);
|
||||
EXPECT_TRUE(rev->get_output_partial_shape(0).rank().is_dynamic());
|
||||
EXPECT_EQ(rev->get_output_partial_shape(0), PartialShape::dynamic());
|
||||
}
|
||||
|
||||
using namespace ov::opset10;
|
||||
|
||||
//
|
||||
// If the input rank is static but the shape is dynamic, we should pass if the axis indices are
|
||||
// in bounds.
|
||||
//
|
||||
TEST(type_prop, reverse_partial_rank_static_dynamic_axes_ok) {
|
||||
PartialShape param_shape{Dimension::dynamic(), Dimension::dynamic(), 2, 3};
|
||||
auto param = make_shared<op::Parameter>(element::f32, param_shape);
|
||||
auto rev = make_shared<op::v1::Reverse>(param,
|
||||
op::Constant::create(element::i64, {2}, {0, 2}),
|
||||
op::v1::Reverse::Mode::INDEX);
|
||||
TEST_F(TypePropReverseV1Test, partial_rank_static_dynamic_axes_ok) {
|
||||
PartialShape param_shape{Dimension::dynamic(), {10, 300}, 2, 3};
|
||||
set_shape_labels(param_shape, 10);
|
||||
auto param = make_shared<Parameter>(element::f32, param_shape);
|
||||
auto rev = make_op(param, Constant::create(element::i64, {2}, {0, 2}), op::v1::Reverse::Mode::INDEX);
|
||||
|
||||
EXPECT_EQ(rev->get_element_type(), element::f32);
|
||||
EXPECT_TRUE(rev->get_output_partial_shape(0).same_scheme(param_shape));
|
||||
EXPECT_EQ(rev->get_output_partial_shape(0), param_shape);
|
||||
EXPECT_THAT(get_shape_labels(rev->get_output_partial_shape(0)), ElementsAre(10, 11, 12, 13));
|
||||
}
|
||||
|
||||
TEST(type_prop, reverse_partial_rank_static_dynamic_axes_oob) {
|
||||
TEST_F(TypePropReverseV1Test, axes_index_is_not_1d_tensor) {
|
||||
PartialShape param_shape{Dimension::dynamic(), Dimension::dynamic(), 2, 3};
|
||||
auto param = make_shared<op::Parameter>(element::f32, param_shape);
|
||||
try {
|
||||
auto rev = make_shared<op::v1::Reverse>(param,
|
||||
op::Constant::create(element::i64, {3}, {0, 4, 2}),
|
||||
op::v1::Reverse::Mode::INDEX);
|
||||
auto axes = make_shared<Parameter>(element::i64, PartialShape{2, 3});
|
||||
|
||||
// Should have thrown, so fail if it didn't
|
||||
FAIL() << "Axis out of bounds not detected";
|
||||
} catch (const NodeValidationFailure& error) {
|
||||
EXPECT_HAS_SUBSTRING(
|
||||
error.what(),
|
||||
std::string("Some of the provided axes (AxisSet{0, 2, 4}) are out of bounds (input rank: 4)."));
|
||||
} catch (...) {
|
||||
FAIL() << "Deduced type check failed for unexpected reason";
|
||||
}
|
||||
OV_EXPECT_THROW(auto op = make_op(param, axes, op::v1::Reverse::Mode::INDEX),
|
||||
NodeValidationFailure,
|
||||
HasSubstr("The reversed_axes input must be a 1D tensor"));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, axes_mask_is_not_1d_tensor) {
|
||||
PartialShape param_shape{Dimension::dynamic(), Dimension::dynamic(), 2, 3};
|
||||
auto param = make_shared<op::Parameter>(element::f32, param_shape);
|
||||
auto axes = make_shared<Parameter>(element::boolean, PartialShape{2, 3});
|
||||
|
||||
OV_EXPECT_THROW(auto op = make_op(param, axes, op::v1::Reverse::Mode::MASK),
|
||||
NodeValidationFailure,
|
||||
HasSubstr("The reversed_axes input must be a 1D tensor"));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, axes_mask_length_lt_input_rank) {
|
||||
PartialShape param_shape{Dimension::dynamic(), Dimension::dynamic(), 2, 3};
|
||||
auto param = make_shared<op::Parameter>(element::f32, param_shape);
|
||||
auto axes = make_shared<Parameter>(element::boolean, PartialShape{2});
|
||||
|
||||
OV_EXPECT_THROW(
|
||||
auto op = make_op(param, axes, op::v1::Reverse::Mode::MASK),
|
||||
NodeValidationFailure,
|
||||
HasSubstr("The number of elements in the reversed_axes tensor (2) must match the input data tensor rank"));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, axes_mask_length_gt_input_rank) {
|
||||
PartialShape param_shape{Dimension::dynamic(), Dimension::dynamic(), 2, 3};
|
||||
auto param = make_shared<op::Parameter>(element::f32, param_shape);
|
||||
auto axes = make_shared<Parameter>(element::boolean, PartialShape{5});
|
||||
|
||||
OV_EXPECT_THROW(
|
||||
auto op = make_op(param, axes, op::v1::Reverse::Mode::MASK),
|
||||
NodeValidationFailure,
|
||||
HasSubstr("The number of elements in the reversed_axes tensor (5) must match the input data tensor rank"));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, axes_index_is_scalar) {
|
||||
PartialShape param_shape{2, {2, 10}, 8};
|
||||
auto param = make_shared<Parameter>(element::f32, param_shape);
|
||||
auto axes = make_shared<Parameter>(element::i64, PartialShape{});
|
||||
|
||||
OV_EXPECT_THROW(auto op = make_op(param, axes, op::v1::Reverse::Mode::INDEX),
|
||||
NodeValidationFailure,
|
||||
HasSubstr("The reversed_axes input must be a 1D tensor"));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, axes_mask_is_scalar) {
|
||||
PartialShape param_shape{2, {2, 10}, 8};
|
||||
auto param = make_shared<Parameter>(element::f32, param_shape);
|
||||
auto axes = make_shared<Parameter>(element::boolean, PartialShape{});
|
||||
|
||||
OV_EXPECT_THROW(auto op = make_op(param, axes, op::v1::Reverse::Mode::MASK),
|
||||
NodeValidationFailure,
|
||||
HasSubstr("The reversed_axes input must be a 1D tensor"));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, axes_mask_not_boolean_type) {
|
||||
PartialShape param_shape{2, {2, 10}, 8};
|
||||
auto param = make_shared<Parameter>(element::f32, param_shape);
|
||||
auto axes = make_shared<Parameter>(element::i32, PartialShape{4});
|
||||
|
||||
OV_EXPECT_THROW(auto op = make_op(param, axes, op::v1::Reverse::Mode::MASK),
|
||||
NodeValidationFailure,
|
||||
HasSubstr("In 'mask' mode the second input must contain boolean values"));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, axes_index_not_integer_type) {
|
||||
PartialShape param_shape{2, {2, 10}, 8};
|
||||
auto param = make_shared<Parameter>(element::f32, param_shape);
|
||||
auto axes = make_shared<Parameter>(element::f32, PartialShape{4});
|
||||
|
||||
OV_EXPECT_THROW(auto op = make_op(param, axes, op::v1::Reverse::Mode::INDEX),
|
||||
NodeValidationFailure,
|
||||
HasSubstr("In 'index' mode the second input must contain integer values"));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, param_static_rank_partial_shape_axes_out_of_input_rank) {
|
||||
PartialShape param_shape{Dimension::dynamic(), Dimension::dynamic(), 2, 3};
|
||||
auto param = make_shared<op::Parameter>(element::f32, param_shape);
|
||||
|
||||
OV_EXPECT_THROW(
|
||||
auto op = make_op(param, Constant::create(element::i64, {3}, {0, 4, 2}), op::v1::Reverse::Mode::INDEX),
|
||||
NodeValidationFailure,
|
||||
HasSubstr("Some of the provided axes (AxisSet{0, 2, 4}) are out of bounds (input rank: 4)."));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, param_static_rank_partial_shape_axes_negatives) {
|
||||
PartialShape param_shape{-1, {2, -1}, {-1, 3}, 5};
|
||||
set_shape_labels(param_shape, 10);
|
||||
auto param = make_shared<op::Parameter>(element::f32, param_shape);
|
||||
|
||||
auto op = make_op(param, Constant::create(element::i64, {3}, {0, -1, 2}), op::v1::Reverse::Mode::INDEX);
|
||||
|
||||
EXPECT_EQ(op->get_element_type(), element::f32);
|
||||
EXPECT_EQ(op->get_output_partial_shape(0), param_shape);
|
||||
EXPECT_THAT(get_shape_labels(op->get_output_partial_shape(0)), ElementsAre(10, 11, 12, 13));
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, more_axes_index_than_input_rank) {
|
||||
PartialShape param_shape{-1, {2, -1}, {-1, 3}, 5};
|
||||
auto param = make_shared<op::Parameter>(element::f32, param_shape);
|
||||
|
||||
auto op = make_op(param, Constant::create(element::i64, {7}, {0, -1, 1, 2, 3, 3, 2}), op::v1::Reverse::Mode::INDEX);
|
||||
|
||||
EXPECT_EQ(op->get_element_type(), element::f32);
|
||||
EXPECT_EQ(op->get_output_partial_shape(0), param_shape);
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, axes_index_is_dynamic) {
|
||||
PartialShape param_shape{2, {2, 10}, 8};
|
||||
auto param = make_shared<Parameter>(element::f32, param_shape);
|
||||
auto axes = make_shared<Parameter>(element::i64, PartialShape::dynamic());
|
||||
|
||||
auto op = make_op(param, axes, op::v1::Reverse::Mode::INDEX);
|
||||
|
||||
EXPECT_EQ(op->get_element_type(), element::f32);
|
||||
EXPECT_EQ(op->get_output_partial_shape(0), param_shape);
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, axes_index_interval_1d_tensor) {
|
||||
PartialShape param_shape{2, {2, 10}, 8};
|
||||
auto param = make_shared<Parameter>(element::f32, param_shape);
|
||||
auto axes = make_shared<Parameter>(element::i64, PartialShape{{2, 4}});
|
||||
|
||||
auto op = make_op(param, axes, op::v1::Reverse::Mode::INDEX);
|
||||
|
||||
EXPECT_EQ(op->get_element_type(), element::f32);
|
||||
EXPECT_EQ(op->get_output_partial_shape(0), param_shape);
|
||||
}
|
||||
|
||||
TEST_F(TypePropReverseV1Test, default_ctor) {
|
||||
PartialShape param_shape{2, {2, 10}, 8};
|
||||
auto param = make_shared<Parameter>(element::f32, param_shape);
|
||||
auto axes = Constant::create(element::i64, Shape{3}, {2, 0, 1});
|
||||
|
||||
auto op = make_op();
|
||||
op->set_arguments(OutputVector{param, axes});
|
||||
op->set_mode(op::v1::Reverse::Mode::INDEX);
|
||||
op->validate_and_infer_types();
|
||||
|
||||
EXPECT_EQ(op->get_mode(), op::v1::Reverse::Mode::INDEX);
|
||||
EXPECT_EQ(op->get_input_size(), 2);
|
||||
EXPECT_EQ(op->get_output_size(), 1);
|
||||
EXPECT_EQ(op->get_element_type(), element::f32);
|
||||
EXPECT_EQ(op->get_output_partial_shape(0), param_shape);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user