Revise Split operation reference implementation (#5174)

* Review Split operation class

   * Added node validation checks
   * Added type_prop unit tests with invalid cases and negative axis value

* Add ngraph check for host tensor inputs and outputs

* Enable CPU backend tests

* Add serialization single layer tests

* Add more precisions, axis and num of splits in single layer tests

* Add op to script of trusted operations

* Fix check macro for inputs and outputs in evaluate method

* Fix comparison of integer expressions of different signedness compilation error

* Add validation check for upper bound dimension for dynamic shapes

* Add node validation check for num_splits attribute greater than zero

* Align spec with num_splits attribute range of values
This commit is contained in:
Gabriele Galiero Casay authored and GitHub committed 2021-04-23 15:58:08 +03:00
1 parent 8d2bc2b895
commit 59a001580f
4 files changed
+241 -92

No files matched your search

+1 -1
View File
@@ -22,7 +22,7 @@ Where D is the rank of input tensor `data`. The axis being split must be evenly
* *num_splits*
* **Description**: number of outputs into which the input tensor `data` will be split along `axis` dimension. The dimension of `data` shape along `axis` must be evenly divisible by *num_splits*
* **Range of values**: a positive integer less than or equal to the value of `axis` dimension being split over
* **Range of values**: an integer within the range `[1, data.shape[axis]]`
* **Type**: `int`
* **Default value**: none
* **Required**: *yes*
+39 -18
View File
@@ -36,28 +36,29 @@ bool ngraph::op::v1::Split::visit_attributes(AttributeVisitor& visitor)
void op::v1::Split::validate_and_infer_types()
{
NGRAPH_OP_SCOPE(v1_Split_validate_and_infer_types);
const auto data_ps = input_value(0).get_partial_shape();
const auto axis_ps = input_value(1).get_partial_shape();
const auto axis_et = input_value(1).get_element_type();
if (axis_ps.rank().is_static())
{
NODE_VALIDATION_CHECK(this,
axis_ps.rank().get_length() == 0,
"The 'axis' input is expected to be a scalar. Got: ",
axis_ps);
}
const PartialShape& data_ps = get_input_partial_shape(0);
const PartialShape& axis_ps = get_input_partial_shape(1);
const element::Type& axis_et = get_input_element_type(1);
NODE_VALIDATION_CHECK(
this, axis_et.is_integral(), "The 'axis' input only accepts integral types");
this, axis_ps.rank().compatible(0), "'axis' input must be a scalar. Got: ", axis_ps);
NODE_VALIDATION_CHECK(this,
axis_et.is_integral_number(),
"Element type of 'axis' input must be integer. Got: ",
axis_et);
NODE_VALIDATION_CHECK(this,
m_num_splits > 0,
"Attribute 'num_splits' must be greater than zero. Got: ",
m_num_splits);
PartialShape each_output_shape{data_ps};
const Rank data_rank = data_ps.rank();
const auto axis_input = get_constant_from_source(input_value(1));
if (axis_input && data_ps.rank().is_static())
if (axis_input && data_rank.is_static())
{
auto axis = axis_input->cast_vector<int64_t>()[0];
const auto data_rank = get_input_partial_shape(0).rank();
axis = ngraph::normalize_axis(this, axis, data_rank);
if (data_ps[axis].is_static())
@@ -66,16 +67,34 @@ void op::v1::Split::validate_and_infer_types()
NODE_VALIDATION_CHECK(this,
dimension_at_axis % m_num_splits == 0,
"The input tensor's dimension pointed by the 'axis' parameter: ",
"Dimension of data input shape along 'axis': ",
dimension_at_axis,
" has to be a multiple of the 'num_splits' attribute value: ",
" must be evenly divisible by 'num_splits' attribute value: ",
m_num_splits);
each_output_shape[axis] = dimension_at_axis / m_num_splits;
}
else
{
each_output_shape[axis] = Dimension::dynamic();
const auto dim_interval_at_axis = data_ps[axis].get_interval();
NODE_VALIDATION_CHECK(
this,
dim_interval_at_axis.get_max_val() >= static_cast<int64_t>(m_num_splits),
"The interval maximum of the dimension for data input shape along 'axis' must be "
"greater or equal to 'num_splits' attribute. Got: ",
dim_interval_at_axis,
" and ",
m_num_splits);
auto dim_interval_at_axis_min =
static_cast<int64_t>(dim_interval_at_axis.get_min_val() * (1.0f / m_num_splits));
auto dim_interval_at_axis_max = dim_interval_at_axis.get_max_val();
if (dim_interval_at_axis.has_upper_bound())
{
dim_interval_at_axis_max =
static_cast<int64_t>(dim_interval_at_axis_max * (1.0f / m_num_splits));
}
each_output_shape[axis] = Dimension(dim_interval_at_axis_min, dim_interval_at_axis_max);
}
}
else
@@ -142,6 +161,8 @@ namespace split
bool op::v1::Split::evaluate(const HostTensorVector& outputs, const HostTensorVector& inputs) const
{
NGRAPH_OP_SCOPE(v1_Split_evaluate);
NGRAPH_CHECK(validate_host_tensor_vector(outputs, m_num_splits) &&
validate_host_tensor_vector(inputs, 2));
const auto& data = inputs[0];
const auto& axis = inputs[1];
return split::evaluate_split(data, axis, outputs, m_num_splits, this);
-10
View File
@@ -69,16 +69,6 @@ onnx_model_split_variable_parts_2d
onnx_top_k_opset_10_const_k
onnx_top_k_opset_11_const_k_smallest
onnx_top_k_opset_11_const_k_smallest_negative_axis
split_1d
split_2d_axis_0
split_2d_axis_1
split_3d_axis_0
split_3d_axis_1
split_3d_axis_2
split_4d_axis_0
split_4d_axis_1
split_4d_axis_2
split_4d_axis_3
# [NOT_IMPLEMENTED] Input image format BOOL is not supported yet...
bool_input_or
+201 -63
View File
@@ -11,66 +11,7 @@ NGRAPH_SUPPRESS_DEPRECATED_START
using namespace std;
using namespace ngraph;
TEST(type_prop, split)
{
const auto data = make_shared<op::Parameter>(element::i32, Shape{2, 6});
try
{
const auto axis = op::Constant::create(element::i64, Shape{}, {1});
const auto split = make_shared<op::v1::Split>(data, axis, 7);
FAIL() << "Split node was created with incorrect data.";
}
catch (const NodeValidationFailure& error)
{
EXPECT_HAS_SUBSTRING(
error.what(),
std::string("The input tensor's dimension pointed by the 'axis' parameter: 6 has to be "
"a multiple of the 'num_splits' attribute value: 7"));
}
try
{
const auto axis = op::Constant::create(element::i64, Shape{}, {-5});
const auto split = make_shared<op::v1::Split>(data, axis, 4); // invalid axis
FAIL() << "Split node was created with incorrect data.";
}
catch (const ngraph_error& error)
{
EXPECT_HAS_SUBSTRING(error.what(), std::string("Parameter axis -5 out of the tensor rank"));
}
const auto axis = op::Constant::create(element::i64, Shape{}, {1});
const auto split = make_shared<op::v1::Split>(data, axis, 2);
EXPECT_EQ(split->outputs().size(), 2);
EXPECT_EQ(split->get_output_shape(0), (Shape{2, 3}));
EXPECT_EQ(split->get_output_shape(1), (Shape{2, 3}));
EXPECT_EQ(split->get_output_element_type(0), element::i32);
EXPECT_EQ(split->get_output_element_type(1), element::i32);
}
TEST(type_prop, split_axis_must_be_scalar)
{
const auto data = make_shared<op::Parameter>(element::i32, Shape{2, 6});
const auto axis = op::Constant::create(element::i64, Shape{2}, {0, 1});
try
{
const auto split = make_shared<op::v1::Split>(data, axis, 1);
FAIL() << "Incorrect axis of Split not detected.";
}
catch (const NodeValidationFailure& error)
{
EXPECT_HAS_SUBSTRING(error.what(),
std::string("The 'axis' input is expected to be a scalar"));
}
catch (...)
{
FAIL() << "Deduced type check failed for unexpected reason.";
}
}
TEST(type_prop, split_v1)
TEST(type_prop, split_v1_axis_const_positive)
{
const auto data = make_shared<op::Parameter>(element::f16, Shape{2, 3, 4});
const auto axis = op::Constant::create(element::i64, {}, {1});
@@ -85,6 +26,21 @@ TEST(type_prop, split_v1)
}
}
TEST(type_prop, split_v1_axis_const_negative)
{
const auto data = make_shared<op::Parameter>(element::i32, Shape{2, 6});
const auto axis = op::Constant::create(element::i64, {}, {-2});
const size_t num_splits = 2;
const auto split = make_shared<op::v1::Split>(data, axis, num_splits);
EXPECT_EQ(split->outputs().size(), num_splits);
for (size_t i = 0; i < num_splits; ++i)
{
EXPECT_EQ(split->get_output_element_type(i), element::i32);
EXPECT_EQ(split->get_output_shape(i), (Shape{1, 6}));
}
}
TEST(type_prop, split_v1_axis_const_data_axis_dim_known)
{
const auto data =
@@ -132,6 +88,61 @@ TEST(type_prop, split_v1_axis_const_data_axis_dim_unknown)
}
}
TEST(type_prop, split_v1_axis_const_data_axis_dim_interval_known_divisible)
{
const auto data =
make_shared<op::Parameter>(element::f32, PartialShape{4, Dimension(3, 6), 3, 5});
const auto axis = op::Constant::create(element::i8, {}, {1});
const size_t num_splits = 3;
const auto split = make_shared<op::v1::Split>(data, axis, num_splits);
EXPECT_EQ(split->outputs().size(), num_splits);
for (size_t i = 0; i < num_splits; ++i)
{
EXPECT_EQ(split->get_output_partial_shape(i),
(PartialShape{4, Dimension(1, 2), 3, 5}));
}
}
TEST(type_prop, split_v1_axis_const_data_axis_dim_interval_known_upper_bound_divisible)
{
const auto data =
make_shared<op::Parameter>(element::f32, PartialShape{4, Dimension(2, 4), 3, 5});
const auto axis = op::Constant::create(element::i8, {}, {1});
const size_t num_splits = 3;
const auto split = make_shared<op::v1::Split>(data, axis, num_splits);
EXPECT_EQ(split->outputs().size(), num_splits);
for (size_t i = 0; i < num_splits; ++i)
{
EXPECT_EQ(split->get_output_partial_shape(i),
(PartialShape{4, Dimension(0, 1), 3, 5}));
}
}
TEST(type_prop, split_v1_axis_const_invalid_data_axis_dim_interval_known)
{
const auto data =
make_shared<op::Parameter>(element::f32, PartialShape{4, Dimension(1, 2), 3, 5});
const auto axis = op::Constant::create(element::i8, {}, {1});
const size_t num_splits = 3;
try
{
const auto split = make_shared<op::v1::Split>(data, axis, num_splits);
FAIL() << "Invalid dimension of data input along axis not detected";
}
catch(const NodeValidationFailure& error)
{
EXPECT_HAS_SUBSTRING(error.what(),
"The interval maximum of the dimension for data input shape along 'axis' must be "
"greater or equal to 'num_splits' attribute.");
}
catch (...)
{
FAIL() << "Invalid dimension of data input along axis validation check failed for unexpected reason";
}
}
TEST(type_prop, split_v1_axis_const_only_data_rank_known)
{
const auto data = make_shared<op::Parameter>(element::f32, PartialShape::dynamic(4));
@@ -146,7 +157,7 @@ TEST(type_prop, split_v1_axis_const_only_data_rank_known)
}
}
TEST(type_prop, split_v1_axis_not_const_only_data_rank_known)
TEST(type_prop, split_v1_axis_param_only_data_rank_known)
{
const auto data = make_shared<op::Parameter>(element::f32, PartialShape::dynamic(4));
const auto axis = make_shared<op::Parameter>(element::u32, PartialShape{});
@@ -174,7 +185,7 @@ TEST(type_prop, split_v1_axis_const_data_rank_unknown)
}
}
TEST(type_prop, split_v1_axis_not_const_data_rank_unknown)
TEST(type_prop, split_v1_axis_param_data_rank_unknown)
{
const auto data = make_shared<op::Parameter>(element::f32, PartialShape::dynamic());
const auto axis = make_shared<op::Parameter>(element::u8, PartialShape{});
@@ -188,7 +199,7 @@ TEST(type_prop, split_v1_axis_not_const_data_rank_unknown)
}
}
TEST(type_prop, split_v1_axis_dynamic_rank)
TEST(type_prop, split_v1_axis_param_dynamic_ranks)
{
const auto data = make_shared<op::Parameter>(element::f32, PartialShape::dynamic());
const auto axis = make_shared<op::Parameter>(element::u8, PartialShape::dynamic());
@@ -201,3 +212,130 @@ TEST(type_prop, split_v1_axis_dynamic_rank)
EXPECT_EQ(split->get_output_partial_shape(i), PartialShape::dynamic());
}
}
TEST(type_prop, split_v1_invalid_axis_et)
{
auto data = make_shared<op::Parameter>(element::f32, Shape{2, 6});
try
{
auto axis = op::Constant::create(element::f32, Shape{}, {1});
auto split = make_shared<op::v1::Split>(data, axis, 2);
// axis input element type is floating-point
FAIL() << "Invalid floating-point element type of 'axis' input not detected";
}
catch (NodeValidationFailure& error)
{
EXPECT_HAS_SUBSTRING(error.what(), "Element type of 'axis' input must be integer.");
}
catch(...)
{
FAIL() << "Element type of 'axis' input validation check failed for unexpected reason";
}
try
{
auto axis = op::Constant::create(element::boolean, Shape{}, {1});
auto split = make_shared<op::v1::Split>(data, axis, 2);
// axis input element type is boolean
FAIL() << "Invalid boolean element type of axis input not detected";
}
catch (const NodeValidationFailure& error)
{
EXPECT_HAS_SUBSTRING(error.what(), "Element type of 'axis' input must be integer.");
}
catch(...)
{
FAIL() << "Element type of 'axis' input validation check failed for unexpected reason";
}
}
TEST(type_prop, split_v1_invalid_axis_not_a_scalar)
{
auto data = make_shared<op::Parameter>(element::i32, Shape{2, 6});
auto axis = op::Constant::create(element::i64, Shape{2}, {0, 1});
try
{
auto split = make_shared<op::v1::Split>(data, axis, 1);
// axis has rank 1, not a scalar
FAIL() << "Invalid shape of axis input not detected";
}
catch (const NodeValidationFailure& error)
{
EXPECT_HAS_SUBSTRING(error.what(),
std::string("'axis' input must be a scalar."));
}
catch (...)
{
FAIL() << "Scalar 'axis' input validation check failed for unexpected reason";
}
}
TEST(type_prop, split_v1_invalid_num_splits)
{
auto data = make_shared<op::Parameter>(element::i32, Shape{2, 6});
auto axis = op::Constant::create(element::i64, Shape{}, {1});
const size_t num_splits = 0;
try
{
auto split = make_shared<op::v1::Split>(data, axis, num_splits);
// num_splits value is zero
FAIL() << "Invalid 'num_splits' attribute value not detected.";
}
catch (const ngraph_error& error)
{
EXPECT_HAS_SUBSTRING(error.what(), "Attribute 'num_splits' must be greater than zero");
}
catch(...)
{
FAIL() << "Attribute 'num_splits' validation check failed for unexpected reason";
}
}
TEST(type_prop, split_v1_invalid_axis_value)
{
auto data = make_shared<op::Parameter>(element::i32, Shape{2, 6});
auto axis = op::Constant::create(element::i64, Shape{}, {-5});
const size_t num_splits = 4;
try
{
auto split = make_shared<op::v1::Split>(data, axis, num_splits);
// axis value not in the range [-2, 1]
FAIL() << "Invalid 'axis' value not detected.";
}
catch (const ngraph_error& error)
{
EXPECT_HAS_SUBSTRING(error.what(), "Parameter axis -5 out of the tensor rank range");
}
catch(...)
{
FAIL() << "'axis' value validation check failed for unexpected reason";
}
}
TEST(type_prop, split_v1_incompatible_data_shape_with_num_splits)
{
auto data = make_shared<op::Parameter>(element::i32, Shape{2, 6});
auto axis = op::Constant::create(element::i64, Shape{}, {1});
const size_t num_splits = 4;
try
{
auto split = make_shared<op::v1::Split>(data, axis, num_splits);
FAIL() << "Incompatible shape of data input along 'axis' and 'num_splits' attribute "
"not detected.";
}
catch (const NodeValidationFailure& error)
{
EXPECT_HAS_SUBSTRING(
error.what(),
std::string("Dimension of data input shape along 'axis': 6 must be evenly "
"divisible by 'num_splits' attribute value: 4"));
}
catch(...)
{
FAIL() << "Data input shape along 'axis' dimension validation check with "
"'num_splits' attribute, failed for unexpected reason";
}
}