Fix MakeStateful transformation: use tensor names instead of friendly names (#8997)

* Use tensor names instead of friendly names, handle one output tensor to several Result ops case

* fix python tests

* fix python test

* fix incorrect merge

* remove redundant files

* fix variable names generation, fix python test

* Apply review comments

* fix python test
This commit is contained in:
Ivan Tikhonov
2022-02-16 09:26:31 +03:00
committed by GitHub
parent e71f23fc7e
commit 06eb74b77f
3 changed files with 214 additions and 86 deletions

View File

@@ -13,8 +13,10 @@ import openvino.runtime as ov
def get_test_function():
param = ov.opset8.parameter(PartialShape([1, 3, 22, 22]), name="parameter")
param.get_output_tensor(0).set_names({"parameter"})
relu = ov.opset8.relu(param)
res = ov.opset8.result(relu, name="result")
res.get_output_tensor(0).set_names({"result"})
return Model([res], [param], "test")

View File

@@ -15,48 +15,91 @@ using namespace opset8;
using namespace op::util;
namespace {
string generate_variable_name(const shared_ptr<Parameter>& param, const shared_ptr<Result>& res) {
return param->get_friendly_name() + res->get_friendly_name();
}
ov::pass::MakeStateful::ParamResPairs find_param_results_by_names(
std::tuple<ov::pass::MakeStateful::ParamResPairs, std::vector<std::string>> find_param_results_by_names(
const shared_ptr<ngraph::Function>& func,
const std::map<std::string, std::string>& param_res_names) {
ov::pass::MakeStateful::ParamResPairs pairs_to_replace;
std::vector<std::string> variable_names;
const auto& params = func->get_parameters();
const auto& results = func->get_results();
std::set<Node*> uniq_params;
std::set<Node*> uniq_res;
// find corresponding param and result by name and add to the list
for (const auto& param_res : param_res_names) {
const auto& param_name = param_res.first;
const auto& res_name = param_res.second;
auto param = std::find_if(params.begin(), params.end(), [&](const std::shared_ptr<ngraph::Node>& node) {
return node->get_friendly_name() == param_name;
const auto& possible_names = node->output(0).get_names();
return possible_names.find(param_name) != possible_names.end();
});
NGRAPH_CHECK(param != params.end(), "Parameter node with name = ", param_name, "doesn't exist in the function");
NGRAPH_CHECK(param != params.end(),
"The tensor name ",
param_name,
" is not associated with any of "
"Parameters in the network.");
uniq_params.insert(param->get());
auto res = std::find_if(results.begin(), results.end(), [&](const std::shared_ptr<ngraph::Node>& node) {
return node->get_friendly_name() == res_name;
const auto& possible_names = node->output(0).get_names();
return possible_names.find(res_name) != possible_names.end();
});
NGRAPH_CHECK(res != results.end(), "Result node with name = ", res_name, " doesn't exist in the function");
pairs_to_replace.emplace_back(*param, *res);
NGRAPH_CHECK(res != results.end(),
"The tensor name ",
res_name,
" is not associated with any of "
"Results in the network.");
// In case of several Results connected to one output tensor,
// We can't determine what result we need to take exactly.
// But we can take first unused, the order is not important, data is the same.
opset8::Result* unused_res = nullptr;
for (const auto& target_in : (*res)->input_value(0).get_target_inputs()) {
auto is_target_res = ov::as_type<opset8::Result>(target_in.get_node());
if (!is_target_res) {
continue;
}
if (uniq_res.find(is_target_res) == uniq_res.end()) {
unused_res = is_target_res;
break;
}
}
NGRAPH_CHECK(unused_res != nullptr,
"All Result operations associated with the tensor ",
res_name,
" are already involved in the transformation.");
uniq_res.insert(unused_res);
if (auto casted = std::dynamic_pointer_cast<opset8::Result>(unused_res->shared_from_this()))
pairs_to_replace.emplace_back(*param, casted);
variable_names.push_back(param_name + res_name);
}
return pairs_to_replace;
return std::make_tuple(pairs_to_replace, variable_names);
}
} // namespace
bool ov::pass::MakeStateful::run_on_model(const std::shared_ptr<ov::Model>& f) {
// in case of user passes the tensor names to find Parameter/Result nodes, we use these tensor names
// to generate variable names. In case of user passes Parameter/Result nodes directly, we use friendly
// names of these nodes to generate variable names.
std::vector<std::string> variable_names;
if (m_param_res_pairs.empty()) {
m_param_res_pairs = find_param_results_by_names(f, m_param_res_names);
std::tie(m_param_res_pairs, variable_names) = find_param_results_by_names(f, m_param_res_names);
} else {
for (const auto& pair : m_param_res_pairs) {
variable_names.push_back(pair.first->get_friendly_name() + pair.second->get_friendly_name());
}
}
VariableVector variables;
SinkVector sinks;
for (const auto& pair : m_param_res_pairs) {
const auto& param = pair.first;
const auto& res = pair.second;
for (size_t i = 0; i < m_param_res_pairs.size(); ++i) {
const auto& param = m_param_res_pairs[i].first;
const auto& res = m_param_res_pairs[i].second;
NGRAPH_CHECK(param->get_partial_shape().is_static(),
"Shape of Parameter ",
@@ -64,7 +107,7 @@ bool ov::pass::MakeStateful::run_on_model(const std::shared_ptr<ov::Model>& f) {
" must be static. MakeStateful transformation doesn't support dynamic shapes.");
// Create Variable
std::string var_name = generate_variable_name(param, res);
std::string var_name = variable_names[i];
auto variable =
std::make_shared<Variable>(VariableInfo{param->get_shape(), param->get_element_type(), var_name});
variables.push_back(variable);

View File

@@ -22,100 +22,135 @@ using namespace ngraph;
using namespace opset8;
using namespace std;
TEST(TransformationTests, make_stateful_by_name) {
std::shared_ptr<ngraph::Function> f(nullptr), f_ref(nullptr);
{
auto X = make_shared<Parameter>(element::f32, Shape{32, 1, 10});
auto Y = make_shared<Parameter>(element::f32, Shape{32, 1, 10});
std::shared_ptr<ov::Model> get_test_model(bool insert_squeeze, bool use_friendly_names) {
std::shared_ptr<ov::Model> model;
auto X = make_shared<Parameter>(element::f32, Shape{32, 1, 10});
auto Y = make_shared<Parameter>(element::f32, Shape{32, 1, 10});
if (!use_friendly_names) {
X->get_output_tensor(0).add_names({"x"});
Y->get_output_tensor(0).add_names({"y"});
} else {
X->set_friendly_name("x");
Y->set_friendly_name("y");
}
auto add = make_shared<Add>(X, Y);
auto result0 = make_shared<Result>(add);
auto result1 = make_shared<Result>(add);
// -> Add -> Squeeze -> Result
// -> Result
// or
// -> Add -> Result
// -> Result
std::shared_ptr<Node> node;
node = make_shared<Add>(X, Y);
auto result0 = make_shared<Result>(node);
if (insert_squeeze)
node = make_shared<Squeeze>(node);
auto result1 = make_shared<Result>(node);
if (!use_friendly_names) {
result0->get_input_tensor(0).add_names({"res0"});
result1->get_input_tensor(0).add_names({"res1"});
} else {
result0->set_friendly_name("res0");
result1->set_friendly_name("res1");
}
f = make_shared<Function>(ResultVector{result0, result1}, ParameterVector{X, Y});
std::map<std::string, std::string> pair_names = {{"x", "res0"}, {"y", "res1"}};
f->validate_nodes_and_infer_types();
model = make_shared<Function>(ResultVector{result0, result1}, ParameterVector{X, Y});
model->validate_nodes_and_infer_types();
return model;
}
std::shared_ptr<ov::Model> get_ref_model(bool insert_squeeze, bool use_friendly_names) {
std::shared_ptr<ov::Model> model;
// create ReadValue for X
auto variable_x = std::make_shared<Variable>(VariableInfo{PartialShape::dynamic(), element::dynamic, "xres0"});
auto const_zero_x = make_shared<Constant>(element::f32, Shape{32, 1, 10}, 0);
auto read_val_x = make_shared<ReadValue>(const_zero_x, variable_x);
// create ReadValue for Y
auto variable_y = std::make_shared<Variable>(VariableInfo{PartialShape::dynamic(), element::dynamic, "yres1"});
auto const_zero_y = make_shared<Constant>(element::f32, Shape{32, 1, 10}, 0);
auto read_val_y = make_shared<ReadValue>(const_zero_y, variable_y);
if (!use_friendly_names) {
read_val_x->get_output_tensor(0).add_names({"x"});
read_val_y->get_output_tensor(0).add_names({"y"});
} else {
read_val_x->set_friendly_name("x");
read_val_y->set_friendly_name("y");
}
// -> Add -> Squeeze -> Assign
// -> Assign
// or
// -> Add -> Assign
// -> Assign
shared_ptr<ov::Node> node;
node = make_shared<Add>(read_val_x, read_val_y);
auto assign_x = make_shared<Assign>(node, variable_x);
if (!use_friendly_names) {
node->get_output_tensor(0).add_names({"res0"});
} else {
node->set_friendly_name("res0");
}
if (insert_squeeze) {
node = make_shared<Squeeze>(node);
}
auto assign_y = make_shared<Assign>(node, variable_y);
if (!use_friendly_names) {
node->get_output_tensor(0).add_names({"res1"});
} else {
node->set_friendly_name("res1");
}
assign_x->add_control_dependency(read_val_x);
assign_y->add_control_dependency(read_val_y);
model = make_shared<Function>(ResultVector{}, SinkVector{assign_x, assign_y}, ParameterVector{});
model->validate_nodes_and_infer_types();
return model;
}
TEST(TransformationTests, make_stateful_by_tensor_name) {
std::shared_ptr<ngraph::Function> f(nullptr), f_ref(nullptr);
{
f = get_test_model(true, false);
std::map<std::string, std::string> tensor_names = {{"x", "res0"}, {"y", "res1"}};
ngraph::pass::Manager manager;
manager.register_pass<ngraph::pass::InitNodeInfo>();
manager.register_pass<ov::pass::MakeStateful>(pair_names);
manager.register_pass<ov::pass::MakeStateful>(tensor_names);
manager.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
{
// create ReadValue for X
auto variable_x = std::make_shared<Variable>(VariableInfo{PartialShape::dynamic(), element::dynamic, "xres0"});
auto const_zero_x = make_shared<Constant>(element::f32, Shape{32, 1, 10}, 0);
auto read_val_x = make_shared<ReadValue>(const_zero_x, variable_x);
// create ReadValue for Y
auto variable_y = std::make_shared<Variable>(VariableInfo{PartialShape::dynamic(), element::dynamic, "yres1"});
auto const_zero_y = make_shared<Constant>(element::f32, Shape{32, 1, 10}, 0);
auto read_val_y = make_shared<ReadValue>(const_zero_y, variable_y);
auto add = make_shared<Add>(read_val_x, read_val_y);
auto assign_x = make_shared<Assign>(add, variable_x);
assign_x->add_control_dependency(read_val_x);
auto assign_y = make_shared<Assign>(add, variable_y);
assign_y->add_control_dependency(read_val_y);
f_ref = make_shared<Function>(ResultVector{}, SinkVector{assign_x, assign_y}, ParameterVector{});
f_ref->validate_nodes_and_infer_types();
f_ref = get_ref_model(true, false);
}
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
EXPECT_TRUE(res.first) << res.second;
}
TEST(TransformationTests, make_stateful_by_param_res) {
std::shared_ptr<ngraph::Function> f(nullptr), f_ref(nullptr);
{
auto X = make_shared<Parameter>(element::f32, Shape{32, 1, 10});
auto Y = make_shared<Parameter>(element::f32, Shape{32, 1, 10});
X->set_friendly_name("x");
Y->set_friendly_name("y");
auto add = make_shared<Add>(X, Y);
auto result0 = make_shared<Result>(add);
auto result1 = make_shared<Result>(add);
result0->set_friendly_name("res0");
result1->set_friendly_name("res1");
f = make_shared<Function>(ResultVector{result0, result1}, ParameterVector{X, Y});
std::vector<std::pair<std::string, std::string>> pair_names = {{"x", "res0"}, {"y", "res1"}};
f->validate_nodes_and_infer_types();
f = get_test_model(true, true);
auto pairs = ov::pass::MakeStateful::ParamResPairs{{f->get_parameters()[0], f->get_results()[0]},
{f->get_parameters()[1], f->get_results()[1]}};
ngraph::pass::Manager manager;
manager.register_pass<ngraph::pass::InitNodeInfo>();
manager.register_pass<ov::pass::MakeStateful>(ov::pass::MakeStateful::ParamResPairs{{X, result0}, {Y, result1}});
manager.register_pass<ov::pass::MakeStateful>(pairs);
manager.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
{
// create ReadValue for X
auto variable_x = std::make_shared<Variable>(VariableInfo{PartialShape::dynamic(), element::dynamic, "xres0"});
auto const_zero_x = make_shared<Constant>(element::f32, Shape{32, 1, 10}, 0);
auto read_val_x = make_shared<ReadValue>(const_zero_x, variable_x);
// create ReadValue for Y
auto variable_y = std::make_shared<Variable>(VariableInfo{PartialShape::dynamic(), element::dynamic, "yres1"});
auto const_zero_y = make_shared<Constant>(element::f32, Shape{32, 1, 10}, 0);
auto read_val_y = make_shared<ReadValue>(const_zero_y, variable_y);
auto add = make_shared<Add>(read_val_x, read_val_y);
auto assign_x = make_shared<Assign>(add, variable_x);
assign_x->add_control_dependency(read_val_x);
auto assign_y = make_shared<Assign>(add, variable_y);
assign_y->add_control_dependency(read_val_y);
f_ref = make_shared<Function>(ResultVector{}, SinkVector{assign_x, assign_y}, ParameterVector{});
f_ref->validate_nodes_and_infer_types();
f_ref = get_ref_model(true, true);
}
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
@@ -124,16 +159,17 @@ TEST(TransformationTests, make_stateful_by_param_res) {
TEST(TransformationTests, make_stateful_dynamic_shapes) {
std::shared_ptr<ngraph::Function> f(nullptr);
{
// dynamic shapes are not supported
auto X = make_shared<Parameter>(element::f32, PartialShape::dynamic());
auto Y = make_shared<Parameter>(element::f32, PartialShape::dynamic());
X->set_friendly_name("x");
Y->set_friendly_name("y");
X->get_output_tensor(0).add_names({"x"});
Y->get_output_tensor(0).add_names({"y"});
auto add = make_shared<Add>(X, Y);
auto result0 = make_shared<Result>(add);
auto result1 = make_shared<Result>(add);
result0->set_friendly_name("res0");
result1->set_friendly_name("res1");
result0->get_input_tensor(0).add_names({"res0"});
result1->get_input_tensor(0).add_names({"res1"});
f = make_shared<Function>(ResultVector{result0, result1}, ParameterVector{X, Y});
map<std::string, std::string> pair_names = {{"x", "res0"}, {"y", "res1"}};
@@ -143,7 +179,54 @@ TEST(TransformationTests, make_stateful_dynamic_shapes) {
manager.register_pass<ngraph::pass::InitNodeInfo>();
manager.register_pass<ov::pass::MakeStateful>(pair_names);
EXPECT_THROW(manager.run_passes(f), ::ov::AssertFailure);
ASSERT_NO_THROW(check_rt_info(f));
try {
manager.run_passes(f);
} catch (::ov::AssertFailure ex) {
EXPECT_STR_CONTAINS(ex.what(), "MakeStateful transformation doesn't support dynamic shapes.");
} catch (...) {
FAIL() << "Expected ::ov::AssertFailure";
}
}
}
TEST(TransformationTests, make_stateful_one_out_to_several_results_by_tensor_names) {
std::shared_ptr<ngraph::Function> f(nullptr), f_ref(nullptr);
{
f = get_test_model(false, false);
std::map<std::string, std::string> tensor_names = {{"x", "res0"}, {"y", "res1"}};
ngraph::pass::Manager manager;
manager.register_pass<ngraph::pass::InitNodeInfo>();
manager.register_pass<ov::pass::MakeStateful>(tensor_names);
manager.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
{
f_ref = get_ref_model(false, false);
}
auto res = compare_functions(f, f_ref);
EXPECT_TRUE(res.first) << res.second;
}
TEST(TransformationTests, make_stateful_one_out_to_several_results_by_param_res) {
std::shared_ptr<ngraph::Function> f(nullptr), f_ref(nullptr);
{
f = get_test_model(false, true);
auto pairs = ov::pass::MakeStateful::ParamResPairs{{f->get_parameters()[0], f->get_results()[0]},
{f->get_parameters()[1], f->get_results()[1]}};
ngraph::pass::Manager manager;
manager.register_pass<ngraph::pass::InitNodeInfo>();
manager.register_pass<ov::pass::MakeStateful>(pairs);
manager.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
{
f_ref = get_ref_model(false, true);
}
auto res = compare_functions(f, f_ref);
EXPECT_TRUE(res.first) << res.second;
}