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:
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user