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:
1 parent
8d2bc2b895
commit
59a001580f
4 files changed
+241
-92
No files matched your search
@@ -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*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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";
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user