[ONNX] Add support for Upsample opset1 (#4678)

This commit is contained in:
Tomasz Jankowski 2021-03-16 10:30:41 +01:00 committed by GitHub
parent 7343fcc94e
commit 063a296842
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 364 additions and 153 deletions

View File

@ -29,14 +29,20 @@ namespace ngraph
{
namespace
{
bool check_mode_support(const onnx_import::Node& node, const std::string& mode)
{
const std::unordered_set<std::string> supported_modes = {"nearest", "linear"};
bool is_mode_supported =
(std::find(supported_modes.begin(), supported_modes.end(), mode) !=
supported_modes.end());
constexpr unsigned version_1{1};
constexpr unsigned version_7{7};
constexpr unsigned version_9{9};
if (!is_mode_supported)
void check_mode_support(const onnx_import::Node& node,
const std::string& mode,
const unsigned op_version)
{
const std::unordered_set<std::string> modes_v1 = {"nearest", "bilinear"};
const std::unordered_set<std::string> modes_v7 = {"nearest", "linear"};
const auto& supported_modes = op_version < version_7 ? modes_v1 : modes_v7;
if (std::find(supported_modes.cbegin(), supported_modes.cend(), mode) ==
supported_modes.cend())
{
std::string supported_modes_str = "";
for (const auto& mode_name : supported_modes)
@ -44,165 +50,123 @@ namespace ngraph
supported_modes_str += (mode_name + ", ");
}
CHECK_VALID_NODE(node,
is_mode_supported,
false,
mode,
" - this type of interpolation mode is not supported."
" Choose one of the following modes: ",
supported_modes_str);
}
return is_mode_supported;
}
using Transform_mode = ngraph::op::v4::Interpolate::CoordinateTransformMode;
using Nearest_mode = ngraph::op::v4::Interpolate::NearestMode;
using InterpolateMode = ngraph::op::v4::Interpolate::InterpolateMode;
using ShapeCalcMode = ngraph::op::v4::Interpolate::ShapeCalcMode;
InterpolateMode convert_mode(const std::string& mode_str)
default_opset::Interpolate::InterpolateAttrs get_attributes(std::string mode)
{
InterpolateMode result = InterpolateMode::nearest;
if (mode_str == "linear")
{
result = InterpolateMode::linear_onnx;
}
return result;
using InterpolateMode = default_opset::Interpolate::InterpolateMode;
using Transform_mode = default_opset::Interpolate::CoordinateTransformMode;
using ShapeCalcMode = default_opset::Interpolate::ShapeCalcMode;
const auto interpolate_mode =
(mode == "linear" || mode == "bilinear" ? InterpolateMode::linear_onnx
: InterpolateMode::nearest);
std::vector<size_t> pad{0};
auto attrs = default_opset::Interpolate::InterpolateAttrs(
interpolate_mode, ShapeCalcMode::scales, pad, pad);
if (attrs.mode == InterpolateMode::linear_onnx)
attrs.coordinate_transformation_mode = Transform_mode::asymmetric;
return attrs;
}
}
OutputVector create_upsample_subgraph(const Output<ngraph::Node>& data,
const Output<ngraph::Node>& scales,
const std::string& mode)
{
const auto shape_of_data = std::make_shared<default_opset::Convert>(
std::make_shared<default_opset::ShapeOf>(data), ngraph::element::f32);
const auto multiply =
std::make_shared<default_opset::Multiply>(shape_of_data, scales);
const auto output_shape = std::make_shared<default_opset::Convert>(
std::make_shared<default_opset::Floor>(multiply), ngraph::element::i64);
return {std::make_shared<default_opset::Interpolate>(
data, output_shape, scales, get_attributes(mode))};
}
} // namespace
namespace set_1
{
OutputVector upsample(const onnx_import::Node& node)
{
const auto inputs = node.get_ng_inputs();
const auto data = inputs.at(0);
const auto data_shape = data.get_partial_shape();
const auto scales = node.get_attribute_value<std::vector<float>>("scales");
const auto height_scale = node.get_attribute_value<float>("height_scale");
const auto width_scale = node.get_attribute_value<float>("width_scale");
const auto mode = node.get_attribute_value<std::string>("mode", "nearest");
check_mode_support(node, mode);
check_mode_support(node, mode, version_1);
auto attrs = ngraph::op::v4::Interpolate::InterpolateAttrs();
attrs.mode = convert_mode(mode);
attrs.shape_calculation_mode = ShapeCalcMode::scales;
attrs.coordinate_transformation_mode = Transform_mode::half_pixel;
attrs.nearest_mode = Nearest_mode::round_prefer_floor;
attrs.antialias = false;
attrs.cube_coeff = -0.75;
const auto data = node.get_ng_inputs().at(0);
if (attrs.mode == InterpolateMode::linear_onnx)
{
attrs.coordinate_transformation_mode = Transform_mode::asymmetric;
}
static const std::string expectation{"Input tensor is required to be 4D."};
const auto rank = data.get_partial_shape().rank();
CHECK_VALID_NODE(node, rank.is_static(), expectation);
const auto rank_size = rank.get_length();
CHECK_VALID_NODE(node, rank_size == 4, expectation);
auto zero_pad = std::vector<size_t>(1, 0);
attrs.pads_begin = zero_pad;
attrs.pads_end = zero_pad;
if (data_shape.is_static())
{
auto data_static_shape = data_shape.to_shape();
std::vector<int64_t> output_shape;
for (size_t i = 0; i < data_static_shape.size(); ++i)
{
output_shape.push_back(
std::floor(data_static_shape.at(i) * scales.at(i)));
}
auto output_shape_const = default_opset::Constant::create(
element::u64, Shape({output_shape.size()}), output_shape);
const auto scales_const = default_opset::Constant::create(
ngraph::element::f32, Shape({scales.size()}), scales);
return {std::make_shared<default_opset::Interpolate>(
data, output_shape_const, scales_const, attrs)};
}
std::vector<float> scales(rank_size, 1.f);
scales[rank_size - 1] = width_scale;
scales[rank_size - 2] = height_scale;
const auto scales_const = default_opset::Constant::create(
ngraph::element::f32, Shape({scales.size()}), scales);
auto shape_of_data = std::make_shared<default_opset::Convert>(
std::make_shared<default_opset::ShapeOf>(data), ngraph::element::f32);
auto multiply =
std::make_shared<default_opset::Multiply>(shape_of_data, scales_const);
auto output_shape = std::make_shared<default_opset::Convert>(
std::make_shared<default_opset::Floor>(multiply), ngraph::element::i64);
return {std::make_shared<default_opset::Interpolate>(
data, output_shape, scales_const, attrs)};
return create_upsample_subgraph(data, scales_const, mode);
}
} // namespace set_1
namespace set_7
{
OutputVector upsample(const onnx_import::Node& node)
{
const auto scales = node.get_attribute_value<std::vector<float>>("scales");
const auto mode = node.get_attribute_value<std::string>("mode", "nearest");
check_mode_support(node, mode, version_7);
const auto data = node.get_ng_inputs().at(0);
const auto rank = data.get_partial_shape().rank();
CHECK_VALID_NODE(node,
rank.is_static() && scales.size() == rank.get_length(),
"Input tensor's rank is required to be the same as number of "
"elements of 'scales' attribute.");
const auto scales_const = default_opset::Constant::create(
ngraph::element::f32, Shape({scales.size()}), scales);
return create_upsample_subgraph(data, scales_const, mode);
}
} // namespace set_7
namespace set_9
{
OutputVector upsample(const onnx_import::Node& node)
{
const auto inputs = node.get_ng_inputs();
const auto data = inputs.at(0);
const auto scales = inputs.at(1);
const auto data_shape = data.get_partial_shape();
const auto scales_shape = scales.get_partial_shape();
const auto mode = node.get_attribute_value<std::string>("mode", "nearest");
check_mode_support(node, mode);
check_mode_support(node, mode, version_9);
const auto inputs = node.get_ng_inputs();
const auto& data = inputs.at(0);
const auto& scales = inputs.at(1);
const auto& data_shape = data.get_partial_shape();
const auto& scales_shape = scales.get_partial_shape();
CHECK_VALID_NODE(
node,
(scales_shape.is_static() || data_shape.rank().is_static()),
" Data rank or shape of Scales input is required to be static.");
auto attrs = ngraph::op::v4::Interpolate::InterpolateAttrs();
attrs.mode = convert_mode(mode);
attrs.shape_calculation_mode = ShapeCalcMode::scales;
attrs.coordinate_transformation_mode = Transform_mode::half_pixel;
attrs.nearest_mode = Nearest_mode::round_prefer_floor;
attrs.antialias = false;
attrs.cube_coeff = -0.75;
if (attrs.mode == InterpolateMode::linear_onnx)
{
attrs.coordinate_transformation_mode = Transform_mode::asymmetric;
}
auto zero_pad = std::vector<size_t>(1, 0);
attrs.pads_begin = zero_pad;
attrs.pads_end = zero_pad;
if (ngraph::op::is_constant(scales.get_node()) && data_shape.is_static())
{
const auto scales_const =
as_type_ptr<default_opset::Constant>(scales.get_node_shared_ptr());
auto scales_vector = scales_const->cast_vector<float>();
auto data_static_shape = data_shape.to_shape();
std::vector<int64_t> output_shape;
for (size_t i = 0; i < data_static_shape.size(); ++i)
{
output_shape.push_back(
std::floor(data_static_shape.at(i) * scales_vector.at(i)));
}
auto output_shape_const = default_opset::Constant::create(
element::u64, Shape({output_shape.size()}), output_shape);
return {std::make_shared<default_opset::Interpolate>(
data, output_shape_const, scales, attrs)};
}
auto shape_of_data = std::make_shared<default_opset::Convert>(
std::make_shared<default_opset::ShapeOf>(data), ngraph::element::f32);
auto multiply =
std::make_shared<default_opset::Multiply>(shape_of_data, scales);
auto output_shape = std::make_shared<default_opset::Convert>(
std::make_shared<default_opset::Floor>(multiply), ngraph::element::i64);
return {std::make_shared<default_opset::Interpolate>(
data, output_shape, scales, attrs)};
return create_upsample_subgraph(data, scales, mode);
}
} // namespace set_9

View File

@ -31,6 +31,12 @@ namespace ngraph
} // namespace set_1
namespace set_7
{
OutputVector upsample(const Node& node);
} // namespace set_7
namespace set_9
{
OutputVector upsample(const Node& node);

View File

@ -467,6 +467,7 @@ namespace ngraph
REGISTER_OPERATOR("Unsqueeze", 1, unsqueeze);
REGISTER_OPERATOR("Unsqueeze", 13, unsqueeze);
REGISTER_OPERATOR("Upsample", 1, upsample);
REGISTER_OPERATOR("Upsample", 7, upsample);
REGISTER_OPERATOR("Upsample", 9, upsample);
REGISTER_OPERATOR("Where", 1, where);
REGISTER_OPERATOR("Xor", 1, logical_xor);

View File

@ -0,0 +1,61 @@
ir_version: 3
producer_name: "onnx-importer-test"
graph {
node {
input: "X"
output: "Y"
op_type: "Upsample"
attribute {
name: "mode"
s: "bilinear"
type: STRING
}
attribute {
name: "height_scale"
f: 2.0
type: FLOAT
}
attribute {
name: "width_scale"
f: 3.0
type: FLOAT
}
}
name: "test-model"
input {
name: "X"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 1
}
dim {
dim_value: 1
}
dim {
dim_value: 2
}
dim {
dim_value: 2
}
}
}
}
}
output {
name: "Y"
type {
tensor_type {
elem_type: 1
shape {
}
}
}
}
}
opset_import {
domain: ""
version: 6
}

View File

@ -0,0 +1,71 @@
ir_version: 3
producer_name: "onnx-importer-test"
graph {
node {
input: "X"
input: "S"
output: "R"
op_type: "Reshape"
}
node {
input: "R"
output: "Y"
op_type: "Upsample"
attribute {
name: "mode"
s: "nearest"
type: STRING
}
attribute {
name: "height_scale"
f: 1.5
type: FLOAT
}
attribute {
name: "width_scale"
f: 2.5
type: FLOAT
}
}
name: "test-model"
input {
name: "X"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 4
}
}
}
}
}
input {
name: "S"
type {
tensor_type {
elem_type: 7
shape {
dim {
dim_value: 4
}
}
}
}
}
output {
name: "Y"
type {
tensor_type {
elem_type: 1
shape {
}
}
}
}
}
opset_import {
domain: ""
version: 6
}

View File

@ -0,0 +1,61 @@
ir_version: 3
producer_name: "onnx-importer-test"
graph {
node {
input: "X"
output: "Y"
op_type: "Upsample"
attribute {
name: "mode"
s: "nearest"
type: STRING
}
attribute {
name: "height_scale"
f: 2.0
type: FLOAT
}
attribute {
name: "width_scale"
f: 3.0
type: FLOAT
}
}
name: "test-model"
input {
name: "X"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 1
}
dim {
dim_value: 1
}
dim {
dim_value: 2
}
dim {
dim_value: 2
}
}
}
}
}
output {
name: "Y"
type {
tensor_type {
elem_type: 1
shape {
}
}
}
}
}
opset_import {
domain: ""
version: 6
}

View File

@ -3262,19 +3262,77 @@ NGRAPH_TEST(${BACKEND_NAME}, onnx_model_scatter_elements_import_only)
EXPECT_EQ(count_ops_of_type<op::v0::Constant>(scatter_fn), 4);
}
NGRAPH_TEST(${BACKEND_NAME}, onnx_upsample8_import_only)
NGRAPH_TEST(${BACKEND_NAME}, onnx_upsample6_nearest_infer)
{
// clang-format off
const auto function = onnx_import::import_onnx_model(
file_util::path_join(SERIALIZED_ZOO, "onnx/upsample8_nearest.prototxt"));
// Input data shape (1, 1, 2, 2)
// Scales attribute values {1.0, 1.0, 2.0, 3.0}
file_util::path_join(SERIALIZED_ZOO, "onnx/upsample6_nearest.prototxt"));
// height_scale: 2.0
// width_scale: 3.0
// mode: nearest
const Shape input_shape {1, 1, 2, 2};
const Shape expected_output_shape{1, 1, 4, 6};
EXPECT_EQ(function->get_output_size(), 1);
EXPECT_EQ(function->get_output_shape(0), expected_output_shape);
EXPECT_EQ(count_ops_of_type<onnx_import::default_opset::Interpolate>(function), 1);
EXPECT_EQ(count_ops_of_type<onnx_import::default_opset::Constant>(function), 2);
auto test_case = test::TestCase<TestEngine>(function);
test_case.add_input<float>(input_shape,
{ 1.f, 2.f,
3.f, 4.f });
test_case.add_expected_output<float>(expected_output_shape,
{ 1.f, 1.f, 1.f, 2.f, 2.f, 2.f,
1.f, 1.f, 1.f, 2.f, 2.f, 2.f,
3.f, 3.f, 3.f, 4.f, 4.f, 4.f,
3.f, 3.f, 3.f, 4.f, 4.f, 4.f });
test_case.run();
// clang-format on
}
NGRAPH_TEST(${BACKEND_NAME}, onnx_upsample6_bilinear_infer)
{
// clang-format off
const auto function = onnx_import::import_onnx_model(
file_util::path_join(SERIALIZED_ZOO, "onnx/upsample6_bilinear.prototxt"));
// height_scale: 2.0
// width_scale: 3.0
// mode: bilinear
const Shape input_shape {1, 1, 2, 2};
const Shape expected_output_shape{1, 1, 4, 6};
auto test_case = test::TestCase<TestEngine>(function);
test_case.add_input<float>(input_shape,
{ 1.f, 2.f,
3.f, 4.f });
test_case.add_expected_output<float>(expected_output_shape,
{ 1.f, 4.f/3, 5.f/3, 2.f, 2.f, 2.f,
2.f, 7.f/3, 8.f/3, 3.f, 3.f, 3.f,
3.f, 10.f/3, 11.f/3, 4.f, 4.f, 4.f,
3.f, 10.f/3, 11.f/3, 4.f, 4.f, 4.f });
test_case.run();
// clang-format on
}
NGRAPH_TEST(${BACKEND_NAME}, onnx_upsample6_dynamic)
{
// clang-format off
const auto function = onnx_import::import_onnx_model(
file_util::path_join(SERIALIZED_ZOO, "onnx/upsample6_dynamic.prototxt"));
// height_scale: 1.5
// width_scale: 2.5
// mode: nearest
//
// X ───╤══> Reshape ──R──> Upsample ──> Y
// S ───┘
auto test_case = test::TestCase<TestEngine, test::TestCaseType::DYNAMIC>(function);
test_case.add_input<float>(Shape {4}, // X
{ 1.f, 2.f, 3.f, 4.f });
test_case.add_input<int64_t>(Shape {4}, {1, 1, 2, 2}); // S
test_case.add_expected_output<float>(Shape {1, 1, 3, 5}, // Y
{ 1.f, 1.f, 1.f, 2.f, 2.f,
1.f, 1.f, 1.f, 2.f, 2.f,
3.f, 3.f, 3.f, 4.f, 4.f });
test_case.run();
// clang-format on
}
NGRAPH_TEST(${BACKEND_NAME}, onnx_upsample8_nearest_infer)
@ -3313,20 +3371,6 @@ NGRAPH_TEST(${BACKEND_NAME}, onnx_upsample8_linear_infer)
test_case.run();
}
NGRAPH_TEST(${BACKEND_NAME}, onnx_upsample9_scales_const_import_only)
{
const auto function = onnx_import::import_onnx_model(
file_util::path_join(SERIALIZED_ZOO, "onnx/upsample9_scales_const_nearest.prototxt"));
// Input data shape (1, 1, 2, 2)
// Input const scales values {1.0, 1.0, 2.0, 3.0}
const Shape expected_output_shape{1, 1, 4, 6};
EXPECT_EQ(function->get_output_size(), 1);
EXPECT_EQ(function->get_output_shape(0), expected_output_shape);
EXPECT_EQ(count_ops_of_type<onnx_import::default_opset::Interpolate>(function), 1);
EXPECT_EQ(count_ops_of_type<onnx_import::default_opset::Constant>(function), 2);
}
NGRAPH_TEST(${BACKEND_NAME}, onnx_upsample9_scales_const_nearest_infer)
{
const auto function = onnx_import::import_onnx_model(

View File

@ -1600,3 +1600,6 @@ bin_convolution_2D_1batch_1channel_strides_dilation_padding_pad_val_0
bin_convolution_2D_1batch_1channel_strides_dilation_padding_pad_val_1
bin_convolution_2D_1batch_2channel
bin_convolution_2D_2batch_1channel
# RuntimeError: Unsupported dynamic ops: v4::Interpolate - Ticket: 50691
onnx_upsample6_dynamic