Enable ResolveNamesCollisions transformation in MOC (#18052)

* ResolveNamesCollisions transformation refactoring; enable it in MOC

* fix the description

* call ResolveNamesCollisions transformation in the frontends; resolve review comments

* Resolve review comments
This commit is contained in:
Ivan Tikhonov 2023-06-23 11:56:26 +04:00 committed by GitHub
parent 48b47e754f
commit 9630766e03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 183 additions and 97 deletions

View File

@ -13,7 +13,7 @@ namespace pass {
/**
* @ingroup ie_transformation_common_api
* @brief ResolveNameCollisions transformation helps to fix names collisions
* if some internal nodes or nodes with autogenerated names have conflicts with other nodes from the original graph
* if nodes with autogenerated names have conflicts with other node names.
*
* Every transformation call can change the graph structure and create some additional operations,
* autogenerated name is used if new operation doesn't have friendly name.

View File

@ -83,6 +83,7 @@
#include <transformations/smart_reshape/reshape_sinking.hpp>
#include "itt.hpp"
#include "transformations/resolve_names_collisions.hpp"
bool ov::pass::MOCTransformations::run_on_model(const std::shared_ptr<ngraph::Function>& f) {
RUN_ON_FUNCTION_SCOPE(MOCTransformations);
@ -244,6 +245,7 @@ bool ov::pass::MOCTransformations::run_on_model(const std::shared_ptr<ngraph::Fu
REGISTER_PASS(manager, ReverseInputChannelsFusion)
REGISTER_PASS(manager, AlignEltwiseInputRanks)
REGISTER_PASS(manager, ConstantFolding)
REGISTER_PASS(manager, ResolveNameCollisions)
manager.run_passes(f);
if (!m_use_shapes) {

View File

@ -6,12 +6,27 @@
#include <algorithm>
#include <memory>
#include <numeric>
#include "itt.hpp"
#include "openvino/op/parameter.hpp"
#include "openvino/op/result.hpp"
#include "openvino/op/sink.hpp"
#include "openvino/op/util/multi_subgraph_base.hpp"
namespace {
void collect_name_collisions_map(const std::shared_ptr<ov::Model>& model,
std::unordered_map<std::string, std::list<ov::Node*>>& name_collisions_map) {
for (const auto& node : model->get_ordered_ops()) {
// Collect a names collision map for all nodes in the graph
const auto& friendly_name = node->get_friendly_name();
name_collisions_map[friendly_name].emplace_back(node.get());
if (auto msn = ov::as_type_ptr<ov::op::util::MultiSubGraphOp>(node)) {
for (const auto& body : msn->get_functions()) {
collect_name_collisions_map(body, name_collisions_map);
}
}
}
}
} // namespace
bool ov::pass::ResolveNameCollisions::run_on_model(const std::shared_ptr<ov::Model>& model) {
// Next containers are used to fix collisions in autogenerated names
@ -20,63 +35,28 @@ bool ov::pass::ResolveNameCollisions::run_on_model(const std::shared_ptr<ov::Mod
std::vector<Node*> nodes_with_conflicts;
std::unordered_map<std::string, std::list<Node*>> visited_nodes;
for (const auto& node : model->get_ordered_ops()) {
// Detect names collisions only for nodes with autogenerated names
const auto& friendly_name = node->get_friendly_name();
visited_nodes[friendly_name].emplace_back(node.get());
}
collect_name_collisions_map(model, visited_nodes);
for (const auto& l_nodes : visited_nodes) {
if (l_nodes.second.size() == 1)
for (const auto& it : visited_nodes) {
const auto& same_named_ops = it.second;
if (same_named_ops.size() < 2) {
continue;
const size_t nodes_size = l_nodes.second.size();
bool has_public_node = false; // Parameter, Result ans Sinks
size_t i(0);
for (auto* node : l_nodes.second) {
i++;
// Skip the last node if we don't have public nodes with collisions
if (i == nodes_size && !has_public_node)
break;
if (dynamic_cast<const ov::op::v0::Result*>(node)) {
// Result is a service node
}
int64_t cnt = 2;
for (const auto& op : same_named_ops) {
// check if op has OV autogenerated friendly name. unique and friendly names have to be equal.
bool is_autogenerated = op->m_friendly_name.empty();
if (!is_autogenerated) {
continue;
}
if (dynamic_cast<const ov::op::Sink*>(node) || dynamic_cast<const ov::op::v0::Parameter*>(node)) {
// Resolve names for public ops with autogenerated name
if (node->m_friendly_name.empty())
nodes_with_conflicts.emplace_back(node);
has_public_node = true;
continue;
} else {
// For result we need to avoid changes in previous operation
bool is_public = false;
for (const auto& output : node->outputs()) {
for (const auto input : output.get_target_inputs()) {
if (dynamic_cast<const ov::op::v0::Result*>(input.get_node())) {
has_public_node = true;
is_public = true;
break;
}
}
if (is_public)
break;
}
if (is_public)
continue;
// add a prefix "_counter" to the autogenerated name to make it unique.
auto new_name = op->get_friendly_name() + "_" + std::to_string(cnt++);
while (visited_nodes.find(new_name) != visited_nodes.end()) {
new_name = op->get_friendly_name() + "_" + std::to_string(cnt++);
}
nodes_with_conflicts.emplace_back(node);
op->set_friendly_name(new_name);
}
}
// Resolve names collisions
for (auto* node : nodes_with_conflicts) {
size_t idx = 2;
const auto friendly_name = node->get_friendly_name();
while (visited_nodes.find(friendly_name + "_" + std::to_string(idx)) != visited_nodes.end())
idx++;
const auto new_friendly_name = friendly_name + "_" + std::to_string(idx);
node->set_friendly_name(new_friendly_name);
visited_nodes[new_friendly_name].emplace_back(node);
}
return true;
return false;
}

View File

@ -8,8 +8,11 @@
#include "openvino/opsets/opset8.hpp"
#include "openvino/pass/manager.hpp"
using namespace ov;
using namespace ov::opset8;
TEST(ResolveNameCollisionsTest, FixGeneratedNames) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 3, 3, 3});
auto arg0 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 3, 3, 3});
const auto gen_friendly_name = arg0->get_friendly_name();
std::string name = "Parameter_";
@ -19,10 +22,10 @@ TEST(ResolveNameCollisionsTest, FixGeneratedNames) {
arg0->set_friendly_name(name);
auto arg1 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 2, 3, 3});
auto arg1 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 2, 3, 3});
auto concat = std::make_shared<ov::opset8::Concat>(ov::NodeVector{arg0, arg1}, 1);
auto result1 = std::make_shared<ov::opset8::Result>(concat);
auto concat = std::make_shared<Concat>(ov::NodeVector{arg0, arg1}, 1);
auto result1 = std::make_shared<Result>(concat);
auto model = std::make_shared<ov::Model>(ov::ResultVector{result1}, ov::ParameterVector{arg0, arg1});
@ -38,43 +41,20 @@ TEST(ResolveNameCollisionsTest, FixGeneratedNames) {
EXPECT_EQ(arg1->get_friendly_name(), arg0->get_friendly_name() + "_2");
}
TEST(ResolveNameCollisionsTest, DoNotFixFriendlyNamesForParameters) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 3, 3, 3});
TEST(ResolveNameCollisionsTest, FixFriendlyNamesForAutogeneratedNames) {
auto arg0 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 3, 3, 3});
const auto gen_friendly_name = arg0->get_friendly_name();
arg0->set_friendly_name(gen_friendly_name);
auto arg1 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 2, 3, 3});
auto arg1 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1, 2, 3, 3});
// set the same name as for the first Parameter
arg1->set_friendly_name(gen_friendly_name);
auto concat = std::make_shared<ov::opset8::Concat>(ov::NodeVector{arg0, arg1}, 1);
auto result1 = std::make_shared<ov::opset8::Result>(concat);
auto model = std::make_shared<ov::Model>(ov::ResultVector{result1}, ov::ParameterVector{arg0, arg1});
EXPECT_EQ(gen_friendly_name, arg0->get_friendly_name());
EXPECT_EQ(arg1->get_friendly_name(), arg0->get_friendly_name());
EXPECT_NE(arg1->get_friendly_name(), arg0->get_friendly_name() + "_2");
ov::pass::Manager pass_manager;
pass_manager.register_pass<ov::pass::ResolveNameCollisions>();
pass_manager.run_passes(model);
EXPECT_EQ(gen_friendly_name, arg0->get_friendly_name());
EXPECT_EQ(arg1->get_friendly_name(), arg0->get_friendly_name());
EXPECT_NE(arg1->get_friendly_name(), arg0->get_friendly_name() + "_2");
}
TEST(ResolveNameCollisionsTest, FixFriendlyNamesForInternalOperations) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 3, 3, 3});
const auto gen_friendly_name = arg0->get_friendly_name();
auto arg1 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1, 2, 3, 3});
auto concat1 = std::make_shared<ov::opset8::Concat>(ov::NodeVector{arg0, arg1}, 1);
auto concat1 = std::make_shared<Concat>(ov::NodeVector{arg0, arg1}, 1);
concat1->set_friendly_name("concat");
auto concat = std::make_shared<ov::opset8::Concat>(ov::NodeVector{concat1, arg1}, 1);
auto concat = std::make_shared<Concat>(ov::NodeVector{concat1, arg1}, 1);
concat->set_friendly_name("concat");
auto result1 = std::make_shared<ov::opset8::Result>(concat);
auto result1 = std::make_shared<Result>(concat);
auto model = std::make_shared<ov::Model>(ov::ResultVector{result1}, ov::ParameterVector{arg0, arg1});
@ -83,5 +63,80 @@ TEST(ResolveNameCollisionsTest, FixFriendlyNamesForInternalOperations) {
ov::pass::Manager pass_manager;
pass_manager.register_pass<ov::pass::ResolveNameCollisions>();
pass_manager.run_passes(model);
EXPECT_NE(concat->get_friendly_name(), concat1->get_friendly_name());
// these names weren't set automatically, and have to remain the same.
EXPECT_EQ(concat->get_friendly_name(), concat1->get_friendly_name());
// arg0's name was set automatically and matches with another name in the graph,
// so it have to be changed.
EXPECT_NE(arg0->get_friendly_name(), arg1->get_friendly_name());
EXPECT_EQ(arg0->get_friendly_name(), arg1->get_friendly_name() + "_2");
}
TEST(ResolveNameCollisionsTest, FixFriendlyNamesForAutogeneratedNamesMultiSubgraphOp) {
// external params
auto X = std::make_shared<Parameter>(element::f32, Shape{4});
auto Y = std::make_shared<Parameter>(element::f32, Shape{4});
auto Z = std::make_shared<Parameter>(element::f32, Shape{8});
auto axis = std::make_shared<Constant>(element::i32, Shape{}, 0);
auto external_split = std::make_shared<Split>(X, axis, 2);
// internal params
auto Xt = std::make_shared<Parameter>(element::f32, PartialShape::dynamic());
Xt->set_friendly_name(X->get_friendly_name());
auto Yt = std::make_shared<Parameter>(element::f32, PartialShape::dynamic());
Yt->set_friendly_name(Y->get_friendly_name());
auto Ze = std::make_shared<Parameter>(element::f32, PartialShape::dynamic());
// then body
auto cond = std::make_shared<Constant>(element::boolean, Shape{1}, true);
auto axis_then = std::make_shared<Constant>(element::i32, Shape{}, 0);
auto split_y = std::make_shared<Split>(Yt, axis_then, 2);
split_y->set_friendly_name(external_split->get_friendly_name());
auto then_op = std::make_shared<Subtract>(Xt, split_y->output(0));
auto res0 = std::make_shared<Result>(then_op);
// else body
auto axis_else = std::make_shared<Constant>(element::i32, Shape{}, 0);
auto split_z = std::make_shared<Split>(Ze, axis_else, 4);
split_z->set_friendly_name(external_split->get_friendly_name());
auto else_op = std::make_shared<Relu>(split_z);
else_op->set_friendly_name(then_op->get_friendly_name());
auto res1 = std::make_shared<Result>(else_op);
// If set up
auto then_body = std::make_shared<ov::Model>(OutputVector{res0}, ParameterVector{Yt, Xt}, "then_body");
auto else_body = std::make_shared<ov::Model>(OutputVector{res1}, ParameterVector{Ze}, "else_body");
auto if_op = std::make_shared<op::v8::If>(cond);
if_op->set_then_body(then_body);
if_op->set_else_body(else_body);
if_op->set_input(external_split->output(0), Xt, nullptr);
if_op->set_input(Y, Yt, nullptr);
if_op->set_input(Z, nullptr, Ze);
auto result = if_op->set_output(res0, res1);
auto res = std::make_shared<Result>(result);
auto model = std::make_shared<Model>(OutputVector{res}, ParameterVector{X, Y, Z});
EXPECT_EQ(external_split->get_friendly_name(), split_y->get_friendly_name());
EXPECT_EQ(external_split->get_friendly_name(), split_z->get_friendly_name());
EXPECT_EQ(X->get_friendly_name(), Xt->get_friendly_name());
EXPECT_EQ(Y->get_friendly_name(), Yt->get_friendly_name());
EXPECT_EQ(then_op->get_friendly_name(), else_op->get_friendly_name());
ov::pass::Manager pass_manager;
pass_manager.register_pass<ov::pass::ResolveNameCollisions>();
pass_manager.run_passes(model);
EXPECT_EQ(external_split->get_friendly_name(), split_y->get_friendly_name() + "_2");
EXPECT_EQ(X->get_friendly_name(), Xt->get_friendly_name() + "_2");
EXPECT_EQ(Y->get_friendly_name(), Yt->get_friendly_name() + "_2");
EXPECT_EQ(then_op->get_friendly_name(), else_op->get_friendly_name() + "_2");
// remain the same, because they were set via "set_friendly_name" method
// and are not autogenerated.
EXPECT_EQ(split_y->get_friendly_name(), split_z->get_friendly_name());
}

View File

@ -198,6 +198,13 @@ public:
virtual const std::shared_ptr<Model>& get_function(size_t index) const {
return m_bodies[index];
};
/// \brief Gets internal sub-graphs
/// \return a vector of pointers to sub-graph Models
virtual const std::vector<std::shared_ptr<Model>>& get_functions() const {
return m_bodies;
};
/// \brief Adds sub-graph to MultiSubGraphOp
///
/// \param index index of new sub-graph

View File

@ -33,6 +33,10 @@ public:
/// \param extension base extension
void add_extension(const ov::Extension::Ptr& extension) override;
/// \brief Runs normalization passes on Model that was loaded with partial conversion
/// \param Model partially converted OV Model
void normalize(const std::shared_ptr<ov::Model>& model) const override;
protected:
/// \brief Check if FrontEndIR can recognize model from given parts
/// \param params Can be path to the model file or std::istream

View File

@ -14,6 +14,7 @@
#include "openvino/core/any.hpp"
#include "openvino/util/file_util.hpp"
#include "so_extension.hpp"
#include "transformations/resolve_names_collisions.hpp"
#include "xml_parse_utils.h"
using namespace ov;
@ -234,13 +235,21 @@ InputModel::Ptr FrontEnd::load_impl(const std::vector<ov::Any>& variants) const
std::shared_ptr<ov::Model> FrontEnd::convert(const InputModel::Ptr& model) const {
auto ir_model = std::dynamic_pointer_cast<InputModel>(model);
OPENVINO_ASSERT(ir_model != nullptr);
return ir_model->convert();
const auto& converted_model = ir_model->convert();
normalize(converted_model);
return converted_model;
}
std::string FrontEnd::get_name() const {
return "ir";
}
void FrontEnd::normalize(const std::shared_ptr<ov::Model>& model) const {
ov::pass::Manager manager;
manager.register_pass<pass::ResolveNameCollisions>();
manager.run_passes(model);
}
} // namespace ir
} // namespace frontend
} // namespace ov

View File

@ -24,6 +24,7 @@ public:
std::string get_name() const override;
bool supported_impl(const std::vector<ov::Any>& variants) const override;
void add_extension(const std::shared_ptr<ov::Extension>& extension) override;
void normalize(const std::shared_ptr<ov::Model>& model) const override;
protected:
InputModel::Ptr load_impl(const std::vector<ov::Any>& params) const override;

View File

@ -21,6 +21,7 @@
#include "openvino/frontend/extension/telemetry.hpp"
#include "ops_bridge.hpp"
#include "so_extension.hpp"
#include "transformations/resolve_names_collisions.hpp"
using namespace ov;
using namespace ov::frontend::onnx;
@ -89,11 +90,14 @@ std::shared_ptr<ngraph::Function> FrontEnd::convert(const InputModel::Ptr& model
return function;
}
return model_onnx->convert();
const auto& converted_model = model_onnx->convert();
normalize(converted_model);
return converted_model;
}
void FrontEnd::convert(const std::shared_ptr<ov::Model>& partially_converted) const {
ngraph::onnx_import::detail::convert_decoded_function(partially_converted);
normalize(partially_converted);
}
std::shared_ptr<ngraph::Function> FrontEnd::decode(const InputModel::Ptr& model) const {
@ -179,3 +183,9 @@ void FrontEnd::add_extension(const std::shared_ptr<ov::Extension>& extension) {
});
}
}
void FrontEnd::normalize(const std::shared_ptr<ov::Model>& model) const {
ov::pass::Manager manager;
manager.register_pass<pass::ResolveNameCollisions>();
manager.run_passes(model);
}

View File

@ -58,6 +58,10 @@ public:
void add_extension(const std::shared_ptr<ov::Extension>& extension) override;
/// \brief Runs normalization passes on Model that was loaded with partial conversion
/// \param Model partially converted OV Model
void normalize(const std::shared_ptr<ov::Model>& model) const override;
protected:
/// \brief Check if FrontEnd can recognize model from given parts
/// \param params Can be path to folder which contains __model__ file or path to

View File

@ -27,6 +27,7 @@
#include "paddle_utils.hpp"
#include "place.hpp"
#include "so_extension.hpp"
#include "transformations/resolve_names_collisions.hpp"
using namespace ov::frontend::paddle::op::default_opset;
using namespace ov;
@ -449,6 +450,7 @@ std::shared_ptr<ov::Model> FrontEnd::convert(const InputModel::Ptr& model) const
fuse_fakequantize_ops(f);
try_remove_internal_ops(f);
normalize(f[0]);
return f[0];
}
@ -464,6 +466,7 @@ void FrontEnd::convert(const std::shared_ptr<ov::Model>& partiallyConverted) con
fuse_fakequantize_ops({partiallyConverted});
try_remove_internal_ops({partiallyConverted});
normalize(partiallyConverted);
}
std::shared_ptr<ov::Model> FrontEnd::convert_partially(const InputModel::Ptr& model) const {
@ -496,7 +499,7 @@ std::shared_ptr<ov::Model> FrontEnd::convert_partially(const InputModel::Ptr& mo
fuse_fakequantize_ops(f);
try_remove_internal_ops(f);
normalize(f[0]);
return f[0];
}
@ -534,6 +537,12 @@ void FrontEnd::add_extension(const std::shared_ptr<ov::Extension>& extension) {
}
}
void FrontEnd::normalize(const std::shared_ptr<ov::Model>& model) const {
ov::pass::Manager manager;
manager.register_pass<ov::pass::ResolveNameCollisions>();
manager.run_passes(model);
}
} // namespace paddle
} // namespace frontend
} // namespace ov

View File

@ -18,6 +18,7 @@
#include "transformations/control_flow/unroll_if.hpp"
#include "transformations/low_precision/mark_dequantization_subgraph.hpp"
#include "transformations/op_conversions/convert_convertlike.hpp"
#include "transformations/resolve_names_collisions.hpp"
#include "transforms.hpp"
#include "transforms/append_list_unpack_replacer.hpp"
#include "transforms/aten_cat_replacer.hpp"
@ -182,7 +183,7 @@ void FrontEnd::normalize(const std::shared_ptr<ov::Model>& model) const {
manager.register_pass<ov::frontend::pytorch::pass::IndexLoopGetitemReplacer>();
manager.register_pass<ov::pass::RemoveMultiSubGraphOpDanglingParamsResults>();
manager.register_pass<ov::pass::ReverseShapeAndTypeInfer>();
manager.register_pass<ov::pass::ResolveNameCollisions>();
manager.run_passes(model);
apply_pytorch_conversion_transforms(model);

View File

@ -27,6 +27,7 @@
#include "tf_framework_node.hpp"
#include "transformations/common_optimizations/remove_concat_zero_dim_input.hpp"
#include "transformations/common_optimizations/reverse_shape_and_type_infer.hpp"
#include "transformations/resolve_names_collisions.hpp"
#include "transformations/transpose_sinking/ts_general.hpp"
#include "translate_session.hpp"
#include "utils.hpp"
@ -472,6 +473,7 @@ void FrontEnd::normalize(const std::shared_ptr<ov::Model>& model) const {
manager.register_pass<ov::pass::RemoveConcatZeroDimInput>();
manager.register_pass<ov::pass::TransposeSinkingGeneral>();
manager.register_pass<ov::pass::ReverseShapeAndTypeInfer>();
manager.register_pass<ov::pass::ResolveNameCollisions>();
manager.run_passes(model);
}

View File

@ -16,6 +16,7 @@
#include "tflite_transformations/rfft2d_complex_abs.h"
#include "tflite_transformations/tflite_quantize_resolver.hpp"
#include "transformations/common_optimizations/transpose_sinking.hpp"
#include "transformations/resolve_names_collisions.hpp"
#include "transformations/transpose_sinking/ts_general.hpp"
using namespace ov;
@ -291,6 +292,7 @@ void FrontEnd::normalize(const std::shared_ptr<ov::Model>& function) const {
manager.register_pass<ov::frontend::tensorflow_lite::pass::Rfft2dSimplifier>();
manager.register_pass<ov::pass::TransposeSinking>();
manager.register_pass<ov::pass::TransposeSinkingGeneral>();
manager.register_pass<ov::pass::ResolveNameCollisions>();
manager.run_passes(function);
}