Skip to content

Commit

Permalink
Enable ResolveNamesCollisions transformation in MOC #2 (#18772)
Browse files Browse the repository at this point in the history
* ResolveNamesCollisions transformation refactoring; enable it in MOC

* fix the description

* call ResolveNamesCollisions transformation in the frontends; resolve review comments

* Resolve review comments

* fix EliminateUnsqueezeGather and AlignMixedTypes transformations
  • Loading branch information
itikhono authored Jul 29, 2023
1 parent 5113900 commit 5fadde6
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,30 @@ ov::pass::EliminateUnsqueezeGather::EliminateUnsqueezeGather() {

ov::pass::EliminateGatherUnsqueeze::EliminateGatherUnsqueeze() {
MATCHER_SCOPE(EliminateGatherUnsqueeze);

const auto are_all_outputs_unsqueezes = [](const Output<Node>& out) -> bool {
const auto& target_inputs = out.get_target_inputs();
bool res = out.get_partial_shape().rank() == 0 && !target_inputs.empty();
for (const auto& target_input : target_inputs) {
if (!res) {
break;
}
auto unsqueeze = ov::as_type<ov::op::v0::Unsqueeze>(target_input.get_node());
res = unsqueeze != nullptr && unsqueeze->output(0).get_partial_shape().rank() == 1;
}
return res;
};
const auto gather_indices_label = ngraph::pattern::wrap_type<ov::op::v0::Constant>(pattern::rank_equals(0));
const auto gather_axis_label = ngraph::pattern::wrap_type<ov::op::v0::Constant>();
const auto gather_label = ngraph::pattern::wrap_type<op::util::GatherBase>(
{pass::pattern::any_input(), gather_indices_label, gather_axis_label},
pattern::rank_equals(0));

const auto unsqueeze_label =
ngraph::pattern::wrap_type<ov::op::v0::Unsqueeze>({gather_label, pass::pattern::any_input()},
pattern::rank_equals(1));
are_all_outputs_unsqueezes);

ov::matcher_pass_callback callback = [=](ngraph::pattern::Matcher& m) {
auto pattern_nodes = m.get_pattern_map();

auto& gather_indices = pattern_nodes.at(gather_indices_label);
auto& gather = pattern_nodes.at(gather_label);
auto& unsqueeze = pattern_nodes.at(unsqueeze_label);
const auto& target_unsqueezes = gather->output(0).get_target_inputs();

auto new_indices =
ov::op::util::make_try_fold<ov::op::v1::Reshape>(gather_indices,
Expand All @@ -90,11 +97,13 @@ ov::pass::EliminateGatherUnsqueeze::EliminateGatherUnsqueeze() {
auto new_gather = gather->clone_with_new_inputs({gather->input_value(0), new_indices, gather->input_value(2)});

new_gather->set_friendly_name(gather->get_friendly_name());
ngraph::copy_runtime_info({unsqueeze, gather}, {new_gather, new_indices});
ngraph::replace_node(unsqueeze, new_gather);
ngraph::copy_runtime_info({gather}, {new_gather, new_indices});
for (const auto& unsqueeze : target_unsqueezes) {
unsqueeze.get_node()->output(0).replace(new_gather);
}
return true;
};

auto m = std::make_shared<ngraph::pattern::Matcher>(unsqueeze_label, matcher_name);
auto m = std::make_shared<ngraph::pattern::Matcher>(gather_label, matcher_name);
register_matcher(m, callback);
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,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);
Expand Down Expand Up @@ -246,6 +247,7 @@ bool ov::pass::MOCTransformations::run_on_model(const std::shared_ptr<ngraph::Fu
REGISTER_PASS(manager, AlignEltwiseInputRanks)
REGISTER_PASS(manager, SharedOpOptimization)
REGISTER_PASS(manager, ConstantFolding)
REGISTER_PASS(manager, ResolveNameCollisions)
manager.run_passes(f);

if (!m_use_shapes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ using namespace ov;

bool ov::pass::AlignMixedFP32FP16Types::run_on_model(const std::shared_ptr<ov::Model>& model) {
RUN_ON_MODEL_SCOPE(AlignMixedFP32FP16Types);
std::unordered_set<std::string> new_friendly_names;

auto generate_uniq_name = [&new_friendly_names](const std::string& initial_name) {
int idx = 0;
auto cur_name = initial_name;
while (new_friendly_names.find(cur_name) != new_friendly_names.end()) {
cur_name = initial_name + ":" + std::to_string(idx++);
}
new_friendly_names.insert(cur_name);
return cur_name;
};

std::function<bool(const std::shared_ptr<Node>&)> insert_converts_before_if_needed =
[&](const std::shared_ptr<Node>& node) {
Expand All @@ -32,7 +43,8 @@ bool ov::pass::AlignMixedFP32FP16Types::run_on_model(const std::shared_ptr<ov::M

auto convert =
std::make_shared<ov::op::v0::Convert>(incoming_output, incoming_output.get_element_type());
convert->set_friendly_name(incoming_node->get_friendly_name() + "_decompressed_to_f32");
auto init_name = incoming_node->get_friendly_name() + "_decompressed_to_f32";
convert->set_friendly_name(generate_uniq_name(init_name));
copy_runtime_info(incoming_node, convert);
input.replace_source_output(convert);
disable_fp16_compression(convert);
Expand Down Expand Up @@ -61,7 +73,8 @@ bool ov::pass::AlignMixedFP32FP16Types::run_on_model(const std::shared_ptr<ov::M
// ConvertPrecision(f32 -> f16). It's kept here f32 to keep ov::Model validatable
auto convert = std::make_shared<ov::op::v0::Convert>(output, out_inputs.get_element_type());
copy_runtime_info(node, convert);
convert->set_friendly_name(node->get_friendly_name() + "_compressed_to_f16");
auto init_name = node->get_friendly_name() + "_compressed_to_f16";
convert->set_friendly_name(generate_uniq_name(init_name));
out_inputs.replace_source_output(convert);
pass::disable_constant_folding(convert);
is_changed = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
121 changes: 88 additions & 33 deletions src/common/transformations/tests/resolve_names_collisions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_";
Expand All @@ -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});

Expand All @@ -38,50 +41,102 @@ 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 concat1 = std::make_shared<Concat>(ov::NodeVector{arg0, arg1}, 1);
concat1->set_friendly_name("concat");
auto concat = std::make_shared<Concat>(ov::NodeVector{concat1, arg1}, 1);
concat->set_friendly_name("concat");

auto result1 = std::make_shared<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");
EXPECT_EQ(concat->get_friendly_name(), concat1->get_friendly_name());

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);
concat1->set_friendly_name("concat");
auto concat = std::make_shared<ov::opset8::Concat>(ov::NodeVector{concat1, arg1}, 1);
concat->set_friendly_name("concat");
auto result1 = std::make_shared<ov::opset8::Result>(concat);

auto model = std::make_shared<ov::Model>(ov::ResultVector{result1}, ov::ParameterVector{arg0, arg1});

// 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_NE(concat->get_friendly_name(), concat1->get_friendly_name());

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());
}
Loading

0 comments on commit 5fadde6

Please sign in to comment.