Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/core/include/openvino/core/graph_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,23 @@ void replace_nodes(const std::shared_ptr<Model>& f,
parameter_replacement_map,
const std::unordered_map<std::shared_ptr<Node>, std::shared_ptr<Node>>& body_replacement_map);

/// Topological sort of nodes needed to compute root_nodes
/// \brief Performs a topological sort on a graph of nodes.
///
/// \tparam T A container type (e.g., std::vector, std::deque) of shared_ptr<Node> or Node*.
/// \param root_nodes The starting nodes from which to begin the topological sort traversal.
///
/// \return A vector of nodes sorted in topological order, where dependencies come before dependents.
///
/// \details
/// This function performs a depth-first traversal of the computation graph starting from the given root nodes.
/// It produces an ordering where each node appears after all of its input dependencies.
///
/// \note If a circular dependency is detected (a node is visited more than twice during traversal),
/// the function clears the offending node's arguments and control dependencies, then throws
/// an OPENVINO_THROW exception with details about the loop source.
///
/// \warning The caller is responsible for providing valid, acyclic graph structures. Circular
/// dependencies will result in an exception being thrown.
template <typename T>
std::vector<std::shared_ptr<Node>> topological_sort(T root_nodes) {
std::stack<Node*, std::vector<Node*>> nodes_to_do;
Expand All @@ -236,13 +252,17 @@ 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)
if (++nodes_visited[node] > 2) {
// break circular dependencies to release memory
node->set_arguments(OutputVector{});
node->clear_control_dependencies();
// 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.
OPENVINO_THROW("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) {
Expand Down
2 changes: 1 addition & 1 deletion src/core/src/descriptor/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void ov::descriptor::Input::replace_output(Output& new_output) {
}
new_output.add_input(this);
m_output = &new_output;
m_src_node = std::shared_ptr<ov::Node>(new_output.get_node());
m_src_node = new_output.get_node();

// Output replacement may change the topological order of nodes,
// so we have to reset cache by setting a flag into shared node info.
Expand Down
12 changes: 8 additions & 4 deletions src/core/tests/conditional_compilation/ov_cc_collect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,16 @@ TEST(conditional_compilation, collect_ops_in_opset) {
#define ov_opset_test_opset1_Abs 1
ov::OpSet opset("test_opset1");
INSERT_OP(test_opset1, Abs, ov::op::v0);
EXPECT_NE(opset.create("Abs"), nullptr);
EXPECT_NE(opset.create_insensitive("Abs"), nullptr);
auto node = std::shared_ptr<ov::Node>(opset.create("Abs"));
EXPECT_NE(node.get(), nullptr);
node = std::shared_ptr<ov::Node>(opset.create_insensitive("Abs"));
EXPECT_NE(node.get(), nullptr);

INSERT_OP(test_opset1, Constant, ov::op::v0);
EXPECT_NE(opset.create("Constant"), nullptr);
EXPECT_NE(opset.create_insensitive("Constant"), nullptr);
node = std::shared_ptr<ov::Node>(opset.create("Constant"));
EXPECT_NE(node.get(), nullptr);
node = std::shared_ptr<ov::Node>(opset.create_insensitive("Constant"));
EXPECT_NE(node.get(), nullptr);
#undef ov_opset_test_opset1_Abs
}

Expand Down
12 changes: 8 additions & 4 deletions src/core/tests/conditional_compilation/ov_cc_off.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ TEST(conditional_compilation, op_scope_with_disabled_cc) {
TEST(conditional_compilation, all_ops_enabled_in_opset) {
ov::OpSet opset("test_opset2");
INSERT_OP(test_opset2, Abs, ov::op::v0);
EXPECT_NE(opset.create("Abs"), nullptr);
EXPECT_NE(opset.create_insensitive("Abs"), nullptr);
auto node = std::shared_ptr<ov::Node>(opset.create("Abs"));
EXPECT_NE(node.get(), nullptr);
node = std::shared_ptr<ov::Node>(opset.create_insensitive("Abs"));
EXPECT_NE(node.get(), nullptr);

INSERT_OP(test_opset2, Constant, ov::op::v0);
EXPECT_NE(opset.create("Constant"), nullptr);
EXPECT_NE(opset.create_insensitive("Constant"), nullptr);
node = std::shared_ptr<ov::Node>(opset.create("Constant"));
EXPECT_NE(node.get(), nullptr);
node = std::shared_ptr<ov::Node>(opset.create_insensitive("Constant"));
EXPECT_NE(node.get(), nullptr);
}

namespace {
Expand Down
12 changes: 8 additions & 4 deletions src/core/tests/conditional_compilation/ov_cc_on.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,16 @@ TEST(conditional_compilation, disabled_Constant_in_opset) {
#define ov_opset_test_opset3_Abs 1
ov::OpSet opset("test_opset3");
INSERT_OP(test_opset3, Abs, ov::op::v0);
EXPECT_NE(opset.create("Abs"), nullptr);
EXPECT_NE(opset.create_insensitive("Abs"), nullptr);
auto node = std::shared_ptr<ov::Node>(opset.create("Abs"));
EXPECT_NE(node.get(), nullptr);
node = std::shared_ptr<ov::Node>(opset.create_insensitive("Abs"));
EXPECT_NE(node.get(), nullptr);

INSERT_OP(test_opset3, Constant, ov::op::v0);
EXPECT_EQ(opset.create("Constant"), nullptr);
EXPECT_EQ(opset.create_insensitive("Constant"), nullptr);
node = std::shared_ptr<ov::Node>(opset.create("Constant"));
EXPECT_EQ(node.get(), nullptr);
node = std::shared_ptr<ov::Node>(opset.create_insensitive("Constant"));
EXPECT_EQ(node.get(), nullptr);
#undef ov_opset_test_opset3_Abs
}

Expand Down
18 changes: 14 additions & 4 deletions src/core/tests/model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@
#include "openvino/op/subtract.hpp"
#include "shared_node_info.hpp"

using ov::op::util::Variable;
using ov::op::util::VariableInfo;
using ov::op::v0::Parameter;
using ov::op::v0::Result;
using ov::op::util::Variable, ov::op::util::VariableInfo;
using ov::op::v0::Parameter, ov::op::v0::Result, ov::op::v0::Relu;

TEST(model, get_input_by_tensor_name) {
auto arg0 = std::make_shared<ov::op::v0::Parameter>(ov::element::f32, ov::PartialShape{1});
Expand Down Expand Up @@ -1540,6 +1538,18 @@ TEST(model, topological_sort_throws_if_loop_with_one_node) {
ov::Exception);
}

TEST(model, topological_sort_throws_if_loop_with_two_node) {
auto arg0 = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{1});
auto relu1 = std::make_shared<Relu>(arg0);
auto relu2 = std::make_shared<Relu>(relu1);
// Loop relu1->relu2->relu1
relu1->input(0).replace_source_output(relu2->output(0));

auto result = std::make_shared<Result>(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::op::v0::Parameter>(ov::element::f32, ov::PartialShape{1});
auto relu1 = std::make_shared<ov::op::v0::Relu>(arg0);
Expand Down
11 changes: 9 additions & 2 deletions src/core/tests/node_input_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <string>
#include <vector>

#include "common_test_utils/test_assertions.hpp"
#include "openvino/core/graph_util.hpp"
#include "openvino/op/add.hpp"
#include "openvino/op/convert.hpp"
Expand All @@ -18,9 +19,11 @@
#include "openvino/op/relu.hpp"
#include "transformations/utils/utils.hpp"

using namespace ov;
using namespace std;
namespace ov::test {

using ov::op::util::disconnect_output_from_consumers;
using std::make_shared;
using testing::_;

TEST(node_input_output, input_create) {
auto x = make_shared<ov::op::v0::Parameter>(element::f32, Shape{1, 2, 3, 4});
Expand Down Expand Up @@ -430,6 +433,9 @@ TEST(node_input_output, output_replace_bidirectional_connection) {
EXPECT_EQ(mul_count, 2) << "mul should have exactly 2 connections from add2";

EXPECT_EQ(add1->output(0).get_target_inputs().size(), 0) << "add1 should have no targets";

// create model from incorrect net (cycles) to release nodes without leaking memory
OV_EXPECT_THROW(ov::Model(OutputVector{mul}, ParameterVector{param}), ov::Exception, _);
}

TEST(node_input_output, output_replace_empty_targets) {
Expand Down Expand Up @@ -475,3 +481,4 @@ TEST(node_input_output, output_replace_cascade) {
EXPECT_EQ(add2->output(0).get_target_inputs().size(), 0);
EXPECT_EQ(add3->output(0).get_target_inputs().size(), 0);
}
} // namespace ov::test
4 changes: 3 additions & 1 deletion src/core/tests/ov_default_allocator_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ TEST_F(OVDefaultAllocatorTest, canAllocateAndDeallocate) {

TEST_F(OVDefaultAllocatorTest, alignedAllocationNotThrow) {
ov::Allocator allocator;
OV_ASSERT_NO_THROW(allocator.allocate(64, 64));
void* ptr = nullptr;
OV_ASSERT_NO_THROW(ptr = allocator.allocate(64, 64));
OV_ASSERT_NO_THROW(allocator.deallocate(ptr, 64, 64));
}

TEST_F(OVDefaultAllocatorTest, sizedAndAlignedDeallocationNotThrow) {
Expand Down
Loading