Detect loops in topological_sort (#15688)

This commit is contained in:
Vitaliy Urusovskij authored and GitHub committed 2023-03-02 13:21:52 +04:00
1 parent 65a45a6232
commit 18763f66ac
2 files changed
+65

No files matched your search

@@ -224,6 +224,7 @@ template <typename T>
std::vector<std::shared_ptr<Node>> topological_sort(T root_nodes) {
std::stack<Node*, std::vector<Node*>> nodes_to_do;
std::unordered_set<Node*> nodes_done;
std::unordered_map<Node*, uint8_t /*is_visited*/> nodes_visited;
std::vector<std::shared_ptr<Node>> result;
for (auto& node : root_nodes) {
@@ -233,6 +234,13 @@ std::vector<std::shared_ptr<Node>> topological_sort(T root_nodes) {
Node* node = nodes_to_do.top();
if (nodes_done.count(node) == 0) {
bool can_add = true;
if (++nodes_visited[node] > 2)
// Node may be at the top of `nodes_to_do` not more than twice before it's added to `nodes_done` -
// when visited and placed in `nodes_to_do` and after the subtree traversal is finished.
// Otherwise it's a loop.
throw Exception("Loop detected during topological sort starting from '" + node->get_friendly_name() +
"' node.");
size_t arg_count = node->get_input_size();
for (size_t i = 0; i < arg_count; ++i) {
Node* dep = node->get_input_node_ptr(arg_count - i - 1);
+57
View File
@@ -1334,6 +1334,63 @@ bool all_ops_have_same_info(const std::shared_ptr<ov::Model>& f) {
}
} // namespace
TEST(model, topological_sort_throws_if_loop_with_one_node) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1});
auto relu1 = std::make_shared<ov::opset8::Relu>(arg0);
auto result = std::make_shared<ov::opset8::Result>(relu1);
// Loop relu2->relu2
auto relu2 = std::make_shared<ov::opset8::Relu>(relu1->output(0));
ov::replace_node(relu1, relu2);
ASSERT_THROW(std::ignore = std::make_shared<ov::Model>(ov::ResultVector{result}, ov::ParameterVector{arg0}),
ov::Exception);
}
TEST(model, topological_sort_throws_if_loop_with_several_nodes) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1});
auto relu1 = std::make_shared<ov::opset8::Relu>(arg0);
auto result = std::make_shared<ov::opset8::Result>(relu1);
// Loop relu2->relu3->relu2
auto relu2 = std::make_shared<ov::opset8::Relu>(relu1->output(0));
auto relu3 = std::make_shared<ov::opset8::Relu>(relu2);
ov::replace_node(relu1, relu3);
ASSERT_THROW(std::ignore = std::make_shared<ov::Model>(ov::ResultVector{result}, ov::ParameterVector{arg0}),
ov::Exception);
}
TEST(model, topological_sort_throws_if_loop_with_control_dependency) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1});
auto relu1 = std::make_shared<ov::opset8::Relu>(arg0);
auto relu2 = std::make_shared<ov::opset8::Relu>(relu1);
auto result = std::make_shared<ov::opset8::Result>(relu2);
// Loop relu1->relu2->relu1
relu1->add_control_dependency(relu2);
ASSERT_THROW(std::ignore = std::make_shared<ov::Model>(ov::ResultVector{result}, ov::ParameterVector{arg0}),
ov::Exception);
}
TEST(model, topological_sort_throws_if_loop_with_control_dependency_only) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1});
auto relu0 = std::make_shared<ov::opset8::Relu>(arg0);
auto result0 = std::make_shared<ov::opset8::Result>(relu0);
auto arg1 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1});
auto relu1 = std::make_shared<ov::opset8::Relu>(arg1);
auto result1 = std::make_shared<ov::opset8::Result>(relu1);
// Loop relu0->relu1->relu0
relu0->add_control_dependency(relu1);
relu1->add_control_dependency(relu0);
ASSERT_THROW(
std::ignore = std::make_shared<ov::Model>(ov::ResultVector{result0, result1}, ov::ParameterVector{arg0, arg1}),
ov::Exception);
}
TEST(model, topological_sort_caching_basic) {
auto arg0 = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::PartialShape{1});
auto relu1 = std::make_shared<ov::opset8::Relu>(arg0);