From 57eddaa239bd9c73d8aaac44d22e20db0029296b Mon Sep 17 00:00:00 2001 From: Roman Kazantsev Date: Mon, 7 Nov 2022 01:24:23 +0300 Subject: [PATCH] [TF FE][Common] Use opset Unique in TF FE and move Unique decomposition to common (#13818) * [TF FE][Common] Move TF FE to opset Unique and add Unique decomposing to common Signed-off-by: Kazantsev, Roman * Add Unique decomposition transformation into the common part * Add transformation tests for UniqueDecomposition * Fix unique_decomposition_test * Simplify condition of transformation applicability * Clarify the comment when the transformation is applicable * Fix transformation test * Fix transformation test * Add support of opset10 in IR Reader * Mark the single element case as xfail * Fix tensor names in the transformation Signed-off-by: Kazantsev, Roman --- .../op_conversions/unique_decomposition.hpp | 26 ++++++++ .../common_optimizations.cpp | 2 + .../op_conversions/unique_decomposition.cpp} | 61 ++++++++++++++----- src/frontends/ir/src/input_model.cpp | 1 + src/frontends/tensorflow/src/CMakeLists.txt | 1 - src/frontends/tensorflow/src/frontend.cpp | 2 - .../tensorflow/src/helper_ops/unique.hpp | 48 --------------- .../embedding_segments_feature_fusing.cpp | 9 ++- .../src/helper_transforms/unique_replacer.hpp | 29 --------- .../tensorflow/src/op/sparse_reshape.cpp | 1 - src/frontends/tensorflow/src/op/unique.cpp | 21 ++++--- .../unique_decomposition_test.cpp} | 36 +++++------ .../tensorflow_tests/test_tf_Unique.py | 3 +- 13 files changed, 113 insertions(+), 127 deletions(-) create mode 100644 src/common/transformations/include/transformations/op_conversions/unique_decomposition.hpp rename src/{frontends/tensorflow/src/helper_transforms/unique_replacer.cpp => common/transformations/src/transformations/op_conversions/unique_decomposition.cpp} (75%) delete mode 100644 src/frontends/tensorflow/src/helper_ops/unique.hpp delete mode 100644 src/frontends/tensorflow/src/helper_transforms/unique_replacer.hpp rename src/{frontends/tensorflow/tests/unique_replacer.cpp => tests/functional/inference_engine/transformations/op_conversions/unique_decomposition_test.cpp} (87%) diff --git a/src/common/transformations/include/transformations/op_conversions/unique_decomposition.hpp b/src/common/transformations/include/transformations/op_conversions/unique_decomposition.hpp new file mode 100644 index 00000000000..88a782d645f --- /dev/null +++ b/src/common/transformations/include/transformations/op_conversions/unique_decomposition.hpp @@ -0,0 +1,26 @@ +// Copyright (C) 2018-2022 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include + +#include "openvino/pass/graph_rewrite.hpp" +#include "transformations_visibility.hpp" + +namespace ov { +namespace pass { + +class TRANSFORMATIONS_API UniqueDecomposition; + +} // namespace pass +} // namespace ov + +// This transformation expresses Unique with a sub-graph of OpenVINO operations +class ov::pass::UniqueDecomposition : public ov::pass::MatcherPass { +public: + OPENVINO_RTTI("UniqueDecomposition", "0"); + UniqueDecomposition(); +}; diff --git a/src/common/transformations/src/transformations/common_optimizations/common_optimizations.cpp b/src/common/transformations/src/transformations/common_optimizations/common_optimizations.cpp index e00c55376e8..58920e7314f 100644 --- a/src/common/transformations/src/transformations/common_optimizations/common_optimizations.cpp +++ b/src/common/transformations/src/transformations/common_optimizations/common_optimizations.cpp @@ -105,6 +105,7 @@ #include "transformations/op_conversions/reduce_l1_decomposition.hpp" #include "transformations/op_conversions/reduce_l2_decomposition.hpp" #include "transformations/op_conversions/simplify_ctc_greedy_decoder_seq_len.hpp" +#include "transformations/op_conversions/unique_decomposition.hpp" bool ngraph::pass::CommonOptimizations::run_on_model(const std::shared_ptr& f) { RUN_ON_FUNCTION_SCOPE(CommonOptimizations); @@ -168,6 +169,7 @@ bool ngraph::pass::CommonOptimizations::run_on_model(const std::shared_ptradd_matcher(); decomp->add_matcher(); decomp->add_matcher(); + decomp->add_matcher(); decomp->set_name("ngraph::pass::CommonDecompositions"); // CF is required after all decompositions diff --git a/src/frontends/tensorflow/src/helper_transforms/unique_replacer.cpp b/src/common/transformations/src/transformations/op_conversions/unique_decomposition.cpp similarity index 75% rename from src/frontends/tensorflow/src/helper_transforms/unique_replacer.cpp rename to src/common/transformations/src/transformations/op_conversions/unique_decomposition.cpp index e6b30e8c910..5a851c04f51 100644 --- a/src/frontends/tensorflow/src/helper_transforms/unique_replacer.cpp +++ b/src/common/transformations/src/transformations/op_conversions/unique_decomposition.cpp @@ -2,14 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 // -#include "helper_transforms/unique_replacer.hpp" +#include "transformations/op_conversions/unique_decomposition.hpp" #include #include -#include "helper_ops/unique.hpp" +#include "itt.hpp" #include "openvino/core/rt_info.hpp" -#include "openvino/opsets/opset9.hpp" +#include "openvino/opsets/opset10.hpp" #include "openvino/pass/graph_rewrite.hpp" #include "openvino/pass/pattern/matcher.hpp" #include "openvino/pass/pattern/op/wrap_type.hpp" @@ -18,10 +18,22 @@ using namespace std; using namespace ov; using namespace ov::pass; -using namespace ov::opset9; -using namespace ov::frontend::tensorflow; +using namespace ov::opset10; + +namespace { +Output get_elements_number_1d(const ov::Output& output, + ov::element::Type output_type, + ov::pass::NodeRegistry& rg) { + auto output_rank = output.get_partial_shape().rank(); + auto shape = rg.make(output, output_type); + auto num_elements = rg.make(shape); + return num_elements; +} +} // namespace + +ov::pass::UniqueDecomposition::UniqueDecomposition() { + MATCHER_SCOPE(UniqueDecomposition); -ov::frontend::tensorflow::pass::UniqueReplacer::UniqueReplacer() { auto unique = pattern::wrap_type(); matcher_pass_callback callback = [=](pattern::Matcher& matcher) { @@ -32,9 +44,27 @@ ov::frontend::tensorflow::pass::UniqueReplacer::UniqueReplacer() { return false; } - auto x = unique_node->input_value(0); - auto output_indices_type = unique_node->get_output_indices_type(); - auto x_type = x.get_element_type(); + // currently, the transformation supports only the first and the third outputs + if (!unique_node->get_output_target_inputs(1).empty() || !unique_node->get_output_target_inputs(3).empty()) { + return false; + } + + // the second input to Unique is optional and this an axis parameter. When it has the single parameter we can + // decompose. Otherwise, we need to check a rank of the input tensor. Currently, the transformation supports + // only searching for unique elements among scalar elements, not vectors. + auto input_rank = unique_node->get_input_partial_shape(0).rank(); + if (unique_node->get_input_size() > 1 && (input_rank.is_dynamic() || input_rank.get_length() > 1)) { + return false; + } + + auto x_unflatten = unique_node->input_value(0); + + // make input tensor flatten + auto minus_one_const = rg.make(element::i32, Shape{1}, -1); + auto x = rg.make(x_unflatten, minus_one_const, false); + + auto output_indices_type = unique_node->get_index_element_type(); + auto x_type = x_unflatten.get_element_type(); if (!x_type.is_real() && !x_type.is_integral_number()) { return false; } @@ -46,7 +76,6 @@ ov::frontend::tensorflow::pass::UniqueReplacer::UniqueReplacer() { auto zero_const = rg.make(element::i32, Shape{1}, 0); auto one_const = rg.make(element::i32, Shape{1}, 1); auto one_const_scalar = rg.make(element::i32, Shape{}, 1); - auto minus_one_const = rg.make(element::i32, Shape{1}, -1); auto true_const = rg.make(element::boolean, Shape{1}, true); auto one_const_out_idx = rg.make(output_indices_type, Shape{1}, 1); auto zero_const_out_idx = rg.make(output_indices_type, Shape{1}, 0); @@ -96,11 +125,11 @@ ov::frontend::tensorflow::pass::UniqueReplacer::UniqueReplacer() { auto output_unique_elements = rg.make(x, sorted_minumum_indices->output(0), zero_const); if (!unique_node->get_output_target_inputs(0).empty()) { - output_unique_elements->set_friendly_name(unique_node->get_friendly_name() + ":0"); + output_unique_elements->set_friendly_name(unique_node->get_friendly_name() + ".0"); unique_node->output(0).replace(output_unique_elements->output(0)); } - if (!unique_node->get_output_target_inputs(1).empty()) { + if (!unique_node->get_output_target_inputs(2).empty()) { // compute the second output // indices of elements of x in the vector of unique elements // 1. compute a mask for unique elements in the original order @@ -116,8 +145,8 @@ ov::frontend::tensorflow::pass::UniqueReplacer::UniqueReplacer() { auto output_idx_plus1 = rg.make(unique_vs_x_ind_orig, zero_const); auto output_idx = rg.make(output_idx_plus1, one_const_out_idx); - output_idx->set_friendly_name(unique_node->get_friendly_name() + ":1"); - unique_node->output(1).replace(output_idx->output(0)); + output_idx->set_friendly_name(unique_node->get_friendly_name() + ".2"); + unique_node->output(2).replace(output_idx->output(0)); } copy_runtime_info(unique_node, rg.get()); @@ -125,6 +154,6 @@ ov::frontend::tensorflow::pass::UniqueReplacer::UniqueReplacer() { return true; }; - auto m = make_shared(unique, "ov::frontend::tensorflow::pass::UniqueReplacer"); - register_matcher(m, callback); + auto m = make_shared(unique, matcher_name); + this->register_matcher(m, callback); } diff --git a/src/frontends/ir/src/input_model.cpp b/src/frontends/ir/src/input_model.cpp index 776f7140454..98f89487955 100644 --- a/src/frontends/ir/src/input_model.cpp +++ b/src/frontends/ir/src/input_model.cpp @@ -215,6 +215,7 @@ public: m_opsets["opset7"] = ngraph::get_opset7(); m_opsets["opset8"] = ngraph::get_opset8(); m_opsets["opset9"] = ngraph::get_opset9(); + m_opsets["opset10"] = ngraph::get_opset10(); } std::shared_ptr convert(); diff --git a/src/frontends/tensorflow/src/CMakeLists.txt b/src/frontends/tensorflow/src/CMakeLists.txt index dfa37723f51..3c5facd7de8 100644 --- a/src/frontends/tensorflow/src/CMakeLists.txt +++ b/src/frontends/tensorflow/src/CMakeLists.txt @@ -31,7 +31,6 @@ add_library(openvino_tensorflow_frontend_static_tests STATIC EXCLUDE_FROM_ALL helper_transforms/block_lstm_replacer.cpp helper_transforms/embedding_segments_feature_fusing.cpp helper_transforms/gru_block_cell_replacer.cpp - helper_transforms/unique_replacer.cpp pass/transpose_sinking.cpp exception.cpp openvino_conversions.cpp diff --git a/src/frontends/tensorflow/src/frontend.cpp b/src/frontends/tensorflow/src/frontend.cpp index c03f9edb79e..ba7d5ce0ff2 100644 --- a/src/frontends/tensorflow/src/frontend.cpp +++ b/src/frontends/tensorflow/src/frontend.cpp @@ -8,7 +8,6 @@ #include "helper_transforms/block_lstm_replacer.hpp" #include "helper_transforms/embedding_segments_feature_fusing.hpp" #include "helper_transforms/gru_block_cell_replacer.hpp" -#include "helper_transforms/unique_replacer.hpp" #include "input_model.hpp" #include "op_table.hpp" #include "openvino/frontend/tensorflow/extension/conversion.hpp" @@ -433,7 +432,6 @@ void FrontEnd::normalize(const std::shared_ptr& function) const { manager.register_pass(); manager.register_pass(); manager.register_pass(); - manager.register_pass(); // TODO: reimplement TransposeSinking that does not corrupt filters for Convolution // and preserve tensor names in case of sinking diff --git a/src/frontends/tensorflow/src/helper_ops/unique.hpp b/src/frontends/tensorflow/src/helper_ops/unique.hpp deleted file mode 100644 index 341c3020c21..00000000000 --- a/src/frontends/tensorflow/src/helper_ops/unique.hpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2018-2022 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// - -#pragma once - -#include -#include - -#include "helper_ops/internal_operation.hpp" - -namespace ov { -namespace frontend { -namespace tensorflow { - -class Unique : public ov::frontend::tensorflow::InternalOperation { -public: - OPENVINO_OP("Unique", "ov::frontend::tensorflow::util", ov::frontend::tensorflow::InternalOperation); - - Unique(const Output& input_values, - ov::element::Type output_indices_type, - const std::shared_ptr& decoder = nullptr) - : ov::frontend::tensorflow::InternalOperation(decoder, OutputVector{input_values}, 2), - m_output_indices_type(output_indices_type) { - validate_and_infer_types(); - } - - void validate_and_infer_types() override { - // Unique finds unique elements in a 1-D tensor. - // Inputs: - // 0) 1D tensor of values - // Outputs: - // 0) 1D tensor of unique elements - // 1) 1D tensor of indices of the unique elements in the input - set_output_type(0, get_input_element_type(0), ov::PartialShape({ov::Dimension::dynamic()})); - set_output_type(1, m_output_indices_type, ov::PartialShape({ov::Dimension::dynamic()})); - } - - ov::element::Type get_output_indices_type() const { - return m_output_indices_type; - } - -private: - ov::element::Type m_output_indices_type; -}; -} // namespace tensorflow -} // namespace frontend -} // namespace ov diff --git a/src/frontends/tensorflow/src/helper_transforms/embedding_segments_feature_fusing.cpp b/src/frontends/tensorflow/src/helper_transforms/embedding_segments_feature_fusing.cpp index 6658df29559..ef88745fb99 100644 --- a/src/frontends/tensorflow/src/helper_transforms/embedding_segments_feature_fusing.cpp +++ b/src/frontends/tensorflow/src/helper_transforms/embedding_segments_feature_fusing.cpp @@ -9,8 +9,7 @@ #include "helper_ops/sparse_fill_empty_rows.hpp" #include "helper_ops/sparse_segment_ops.hpp" -#include "helper_ops/unique.hpp" -#include "openvino/opsets/opset8.hpp" +#include "openvino/opsets/opset10.hpp" #include "openvino/pass/pattern/matcher.hpp" #include "openvino/pass/pattern/op/or.hpp" #include "openvino/pass/pattern/op/wrap_type.hpp" @@ -18,7 +17,7 @@ using namespace std; using namespace ov::pass; -using namespace ov::opset8; +using namespace ov::opset10; ov::frontend::tensorflow::pass::EmbeddingSegmentSingleFeatureFusion::EmbeddingSegmentSingleFeatureFusion() { // The transformation looks for pattern (sub-graph) that performs extraction of embedding vectors from the @@ -60,13 +59,13 @@ ov::frontend::tensorflow::pass::EmbeddingSegmentSingleFeatureFusion::EmbeddingSe std::vector{1}); auto cast = make_shared(strided_slice, ov::element::i64); - auto unique = make_shared(sparse_fill_empty_rows->output(1), ov::element::i32); + auto unique = make_shared(sparse_fill_empty_rows->output(1), false, ov::element::i32); auto gather = make_shared(embedding_table_pattern, unique->output(0), make_shared(element::i64, Shape{}, vector{0})); // SparseSegmentSum sums-up extracted embedding vectors by indices for each segment - auto sparse_segment_op = make_shared(gather->output(0), unique->output(1), cast->output(0)); + auto sparse_segment_op = make_shared(gather->output(0), unique->output(2), cast->output(0)); auto shape = make_shared(sparse_segment_op, ov::element::i32); auto strided_slice_for_shape_begin = make_shared(element::i32, Shape{1}, vector{1}); diff --git a/src/frontends/tensorflow/src/helper_transforms/unique_replacer.hpp b/src/frontends/tensorflow/src/helper_transforms/unique_replacer.hpp deleted file mode 100644 index 35171bc1f77..00000000000 --- a/src/frontends/tensorflow/src/helper_transforms/unique_replacer.hpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2018-2022 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// - -#pragma once - -#include -#include - -#include "openvino/frontend/tensorflow/visibility.hpp" -#include "openvino/pass/graph_rewrite.hpp" -#include "openvino/pass/pass.hpp" - -namespace ov { -namespace frontend { -namespace tensorflow { -namespace pass { - -// This transformation expresses Unique with a sub-graph of OpenVINO operations -class UniqueReplacer : public ov::pass::MatcherPass { -public: - OPENVINO_RTTI("ov::frontend::tensorflow::pass::UniqueReplacer"); - UniqueReplacer(); -}; - -} // namespace pass -} // namespace tensorflow -} // namespace frontend -} // namespace ov diff --git a/src/frontends/tensorflow/src/op/sparse_reshape.cpp b/src/frontends/tensorflow/src/op/sparse_reshape.cpp index f32e1382fb2..fb61406c3ce 100644 --- a/src/frontends/tensorflow/src/op/sparse_reshape.cpp +++ b/src/frontends/tensorflow/src/op/sparse_reshape.cpp @@ -4,7 +4,6 @@ #include "helper_ops/sparse_fill_empty_rows.hpp" #include "helper_ops/sparse_segment_ops.hpp" -#include "helper_ops/unique.hpp" #include "ngraph/validation_util.hpp" #include "op_table.hpp" #include "openvino/core/validation_util.hpp" diff --git a/src/frontends/tensorflow/src/op/unique.cpp b/src/frontends/tensorflow/src/op/unique.cpp index 92fcfb87af7..0fa7352332c 100644 --- a/src/frontends/tensorflow/src/op/unique.cpp +++ b/src/frontends/tensorflow/src/op/unique.cpp @@ -2,26 +2,33 @@ // SPDX-License-Identifier: Apache-2.0 // -#include "helper_ops/unique.hpp" - #include "op_table.hpp" +#include "openvino/opsets/opset10.hpp" using namespace std; +using namespace ov; namespace ov { namespace frontend { namespace tensorflow { namespace op { OutputVector translate_unique_op(const NodeContext& node) { + // This operation returns a tensor y containing all of the unique elements of x sorted in the same order that they + // occur in x. This operation also returns a tensor idx the same size as x that contains the index of each value of + // x in the unique output y. default_op_checks(node, 1, {"Unique"}); + auto node_name = node.get_name(); auto input_values = node.get_input(0); - - // retrieve attribute auto output_indices_type = node.get_attribute("out_idx", ov::element::i32); + auto unique = make_shared(input_values, false, output_indices_type); - auto unique = make_shared(input_values, output_indices_type, node.get_decoder()); - set_node_name(node.get_name(), unique); - return unique->outputs(); + // set up new Unique node name and tensor names manually + // because the second and fourth outputs of OpenVINO Unique are not needed + unique->set_friendly_name(node.get_name()); + set_out_name(node_name + ":0", unique->output(0)); + set_out_name(node_name + ":1", unique->output(2)); + + return {unique->output(0), unique->output(2)}; } } // namespace op diff --git a/src/frontends/tensorflow/tests/unique_replacer.cpp b/src/tests/functional/inference_engine/transformations/op_conversions/unique_decomposition_test.cpp similarity index 87% rename from src/frontends/tensorflow/tests/unique_replacer.cpp rename to src/tests/functional/inference_engine/transformations/op_conversions/unique_decomposition_test.cpp index 348a2f23f71..50d53ae8d9d 100644 --- a/src/frontends/tensorflow/tests/unique_replacer.cpp +++ b/src/tests/functional/inference_engine/transformations/op_conversions/unique_decomposition_test.cpp @@ -2,24 +2,23 @@ // SPDX-License-Identifier: Apache-2.0 // -#include "helper_transforms/unique_replacer.hpp" +#include "transformations/op_conversions/unique_decomposition.hpp" #include -#include -#include +#include +#include #include +#include #include "common_test_utils/ngraph_test_utils.hpp" #include "gtest/gtest.h" -#include "helper_ops/unique.hpp" +#include "openvino/core/model.hpp" using namespace std; using namespace ov; -using namespace opset9; +using namespace opset10; using namespace element; -using namespace frontend::tensorflow; -using namespace frontend::tensorflow::pass; namespace { Output get_elements_number_1d(const Output& output, element::Type output_type) { @@ -30,13 +29,17 @@ Output get_elements_number_1d(const Output& output, element::Type ou shared_ptr gen_model(PartialShape input_shape, element::Type out_idx) { auto x = make_shared(f32, input_shape); - auto unique = make_shared(x, out_idx); + auto unique = make_shared(x, false, out_idx); - return make_shared(OutputVector{unique->output(0), unique->output(1)}, ParameterVector{x}); + return make_shared(OutputVector{unique->output(0), unique->output(2)}, ParameterVector{x}); } shared_ptr gen_model_ref(PartialShape input_shape, element::Type out_idx) { - auto x = make_shared(f32, input_shape); + auto x_unflatten = make_shared(f32, input_shape); + + auto minus_one_const = make_shared(element::i32, Shape{1}, -1); + // flatten input + auto x = make_shared(x_unflatten, minus_one_const, false); // denote a number of elements in x as n auto n = get_elements_number_1d(x, element::i32); @@ -45,7 +48,6 @@ shared_ptr gen_model_ref(PartialShape input_shape, element::Type out_idx) auto zero_const = make_shared(element::i32, Shape{1}, 0); auto one_const = make_shared(element::i32, Shape{1}, 1); auto one_const_scalar = make_shared(element::i32, Shape{}, 1); - auto minus_one_const = make_shared(element::i32, Shape{1}, -1); auto true_const = make_shared(element::boolean, Shape{1}, true); auto one_const_out_idx = make_shared(out_idx, Shape{1}, 1); auto zero_const_out_idx = make_shared(out_idx, Shape{1}, 0); @@ -109,25 +111,25 @@ shared_ptr gen_model_ref(PartialShape input_shape, element::Type out_idx) auto output_idx_plus1 = make_shared(unique_vs_x_ind_orig, zero_const); auto output_idx = make_shared(output_idx_plus1, one_const_out_idx); - return make_shared(OutputVector{output_idx->output(0), output_unique_elements->output(0)}, - ParameterVector{x}); + return make_shared(OutputVector{output_unique_elements->output(0), output_idx->output(0)}, + ParameterVector{x_unflatten}); } } // namespace -TEST_F(TransformationTestsF, UniqueReplacerInt32) { +TEST_F(TransformationTestsF, UniqueDecompositionInt32) { { function = gen_model(PartialShape{10}, element::i32); - manager.register_pass(); + manager.register_pass(); } { function_ref = gen_model_ref(PartialShape{10}, element::i32); } comparator.enable(FunctionsComparator::CmpValues::ACCURACY); } -TEST_F(TransformationTestsF, UniqueReplacerInt64) { +TEST_F(TransformationTestsF, UniqueDecompositionInt64) { { function = gen_model(PartialShape{42}, element::i64); - manager.register_pass(); + manager.register_pass(); } { function_ref = gen_model_ref(PartialShape{42}, element::i64); } comparator.enable(FunctionsComparator::CmpValues::ACCURACY); diff --git a/tests/layer_tests/tensorflow_tests/test_tf_Unique.py b/tests/layer_tests/tensorflow_tests/test_tf_Unique.py index 4e93741a0ff..fc55cd0dcaa 100644 --- a/tests/layer_tests/tensorflow_tests/test_tf_Unique.py +++ b/tests/layer_tests/tensorflow_tests/test_tf_Unique.py @@ -30,7 +30,8 @@ class TestUnique(CommonTFLayerTest): return tf_net, None test_data_basic = [ - dict(x_shape=[1], data_type=tf.float32, out_idx=tf.int32), + pytest.param(dict(x_shape=[1], data_type=tf.float32, out_idx=tf.int32), + marks=pytest.mark.xfail(reason="95824")), dict(x_shape=[50], data_type=tf.float32, out_idx=tf.int32), dict(x_shape=[100], data_type=tf.float32, out_idx=tf.int64), ]