Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TF FE] Support mutable tensors produced by variables and resources #22270

Merged
merged 21 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
10b06d8
[TF FE] Support mutable tensors produced by variables and resources
rkazants Jan 19, 2024
da7b3ad
Update documentation
rkazants Jan 19, 2024
3c3eaa5
Fix build and move variable.hpp to public
rkazants Jan 21, 2024
63ef715
Add missing variable.hpp header file
rkazants Jan 21, 2024
06ae4e2
Add layer tests for Assign operations
rkazants Jan 22, 2024
e502838
Add translators for AssignVariable operations
rkazants Jan 22, 2024
d32cdc1
Document new operations
rkazants Jan 22, 2024
3b87f04
Add layer tests for AssignVariableOps
rkazants Jan 23, 2024
5dd1f71
Fix build
rkazants Jan 23, 2024
284523f
Fix code-style issue
rkazants Jan 23, 2024
b1e16f5
Update src/frontends/tensorflow/include/openvino/frontend/tensorflow/…
rkazants Jan 23, 2024
92d4096
Update src/frontends/tensorflow/include/openvino/frontend/tensorflow/…
rkazants Jan 23, 2024
c64854d
Update src/frontends/tensorflow/include/openvino/frontend/tensorflow/…
rkazants Jan 23, 2024
f6d978b
Update src/frontends/tensorflow/src/op/assign.cpp
rkazants Jan 23, 2024
e32c75a
Update src/frontends/tensorflow/src/op/assign_add.cpp
rkazants Jan 23, 2024
6c6fa28
Update src/frontends/tensorflow/src/op/assign_sub.cpp
rkazants Jan 23, 2024
a768969
Do not change node name for read_variable_op
rkazants Jan 23, 2024
1e0a376
Merge remote-tracking branch 'rkazants/rkazants/mutable_tensors' into…
rkazants Jan 23, 2024
df88e6a
Make assign_variable_op backward-compatible
rkazants Jan 23, 2024
902aeb9
Fix layer tests to run them in parallel
rkazants Jan 24, 2024
006d75b
Fix testing model
rkazants Jan 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/frontends/tensorflow/docs/supported_ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ A "supported operation" is one that TensorFlow Frontend can convert to the OpenV
| AssertNextDataset | NO | |
| AssertPrevDataset | NO | |
| Assign | YES | |
| AssignAdd | NO | |
| AssignAddVariableOp | NO | |
| AssignSub | NO | |
| AssignSubVariableOp | NO | |
| AssignAdd | YES | |
| AssignAddVariableOp | YES | |
| AssignSub | YES | |
| AssignSubVariableOp | YES | |
| AssignVariableOp | YES | |
| AssignVariableXlaConcatND | NO | |
| Atan | YES | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "exception.hpp"
#include "openvino/core/any.hpp"
#include "openvino/frontend/node_context.hpp"
#include "variable.hpp"

namespace ov {
namespace frontend {
Expand All @@ -21,19 +22,50 @@ class NodeContext : public ov::frontend::NodeContext {
using Ptr = std::shared_ptr<NodeContext>;
NodeContext(const std::shared_ptr<DecoderBase>& decoder,
const OutputVector& inputs,
const VariableMap::Ptr& ov_variable_state_map = nullptr,
TranslateSession* translate_session = nullptr)
: ov::frontend::NodeContext(decoder->get_op_type()),
m_decoder(decoder),
m_translate_session(translate_session),
m_inputs(inputs) {}
m_inputs(inputs),
m_ov_variable_state_map(ov_variable_state_map) {}

/// Detects if there is at least one input attached with a given name
bool has_input(const size_t& port_index) const {
return port_index < m_inputs.size();
}

/// Retrieve the input by reference. It is needed for operations working with reference inputs
Output<Node> get_input_by_reference(int port_index) const {
auto input = m_inputs.at(port_index);
auto ref_node = ov::as_type_ptr<Variable>(input.get_node_shared_ptr());
if (ref_node) {
auto ref_node_name = ref_node->get_name();
Variable::Ptr found_var = nullptr;
// get the latest state of Variable with the required name
if (m_ov_variable_state_map->get_variable_state(get_name(), ref_node_name, found_var)) {
// no need to extract variable value and return variable output
return found_var->output(0);
}
}
return input;
}

/// Retrieve the input by value. If variable comes to the input, it tries to extracts its value at this moment
Output<Node> get_input(int port_index) const override {
return m_inputs.at(port_index);
auto input = m_inputs.at(port_index);
auto ref_node = ov::as_type_ptr<Variable>(input.get_node_shared_ptr());
if (ref_node) {
auto ref_node_name = ref_node->get_name();
Variable::Ptr found_var = nullptr;
// get the latest state of Variable with the required name
if (m_ov_variable_state_map->get_variable_state(get_name(), ref_node_name, found_var)) {
// resolve Variable node by extracting its value from variable state map
return found_var->get_value();
}
}

return input;
}

size_t get_input_size() const override {
Expand All @@ -60,12 +92,19 @@ class NodeContext : public ov::frontend::NodeContext {
return m_translate_session;
}

VariableMap::Ptr get_variable_state_map() const {
return m_ov_variable_state_map;
}

private:
ov::Any apply_additional_conversion_rules(const ov::Any& data, const std::type_info& type_info) const override;

std::shared_ptr<DecoderBase> m_decoder;
TranslateSession* m_translate_session;
const OutputVector& m_inputs;

// it contains variables states at each node of the graph
VariableMap::Ptr m_ov_variable_state_map;
};

using CreatorFunctionIndexed = std::function<ov::OutputVector(const ov::frontend::tensorflow::NodeContext&)>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include "openvino/op/util/framework_node.hpp"

namespace ov {
namespace frontend {
namespace tensorflow {

class Variable : public ov::op::util::FrameworkNode {
public:
using Ptr = std::shared_ptr<Variable>;
OPENVINO_OP("TFVariable", "ov::frontend::tensorflow", ::ov::op::util::FrameworkNode);

Variable(const std::string& name,
const ov::Shape& shape,
const ov::element::Type& type,
const std::shared_ptr<DecoderBase>& decoder)
: ov::op::util::FrameworkNode(ov::OutputVector{}, 1),
m_name(name),
m_shape(shape),
m_type(type),
m_decoder(decoder),
m_is_initialized(false),
m_init_counter(0) {
validate_and_infer_types();
}

Variable(const std::string& name,
const ov::Shape& shape,
const ov::element::Type& type,
const ov::Output<ov::Node>& value,
const std::shared_ptr<DecoderBase>& decoder)
: Variable(name, shape, type, decoder) {
m_value = value;
// reset names of tensor corresponding to variable value
// that is because variable can have multiple values during inference
m_value.set_names({});
m_is_initialized = true;
++m_init_counter;
}

Variable(const Variable& other, const ov::Output<ov::Node>& value) : Variable(other) {
m_value = value;
// reset names of tensor corresponding to variable value
// that is because variable can have multiple values during inference
m_value.set_names({});
m_is_initialized = true;
++m_init_counter;
}

void validate_and_infer_types() override {
set_output_type(0, m_type, m_shape);
}

bool is_initialized() const {
return m_is_initialized;
}

ov::Output<ov::Node> get_value() const {
FRONT_END_GENERAL_CHECK(
m_is_initialized,
"[TensorFlow Frontend] internal error: get_value() is called for uninitialized variable");
return m_value;
}

std::string get_name() const {
return m_name;
}

uint64_t get_init_counter() const {
return m_init_counter;
}

private:
std::string m_name;
ov::Shape m_shape;
ov::element::Type m_type;
std::shared_ptr<DecoderBase> m_decoder;
bool m_is_initialized;
ov::Output<ov::Node> m_value;
// this member is used to select the latest state of Variable
uint64_t m_init_counter;
};

// a container of Variables state for each operation node in a graph
class VariableMap {
public:
using Ptr = std::shared_ptr<VariableMap>;
bool get_variable_state(const std::string& node_name,
const std::string& variable_name,
Variable::Ptr& found_variable) const {
if (m_variables_state.count(node_name) > 0) {
for (const auto& variable : m_variables_state.at(node_name)) {
if (variable && variable->get_name() == variable_name && variable->is_initialized()) {
found_variable = variable;
return true;
}
}
} else {
return false;
}
return false;
}

void initialize_variable_state_map_for_node(const std::vector<std::string>& control_dependencies,
const std::vector<std::string>& data_dependencies,
const std::string& node_name) {
m_variables_state[node_name] = {};
for (const auto& dependency : control_dependencies) {
for (const auto& dependency_variable : m_variables_state[dependency]) {
update_variable_state_map_for_node(node_name, dependency_variable);
}
}

for (const auto& dependency : data_dependencies) {
for (const auto& dependency_variable : m_variables_state[dependency]) {
update_variable_state_map_for_node(node_name, dependency_variable);
}
}
}

void update_variable_state_map_for_node(const std::string& node_name, const Variable::Ptr& update_variable) {
FRONT_END_GENERAL_CHECK(
update_variable && update_variable->is_initialized(),
"[TensorFlow Frontend] internal error: variable maps must be updated with initialized variable");
auto variable_name = update_variable->get_name();

size_t remove_ind = 0;
bool remove_old_variable = false;
bool found_better = false;
// remove old variable state if exists
for (size_t ind = 0; ind < m_variables_state[node_name].size(); ++ind) {
auto checked_variable = m_variables_state[node_name][ind];
if (checked_variable->get_name() == variable_name && checked_variable->is_initialized() &&
checked_variable->get_init_counter() < update_variable->get_init_counter()) {
remove_ind = ind;
remove_old_variable = true;
break;
} else if (checked_variable->get_name() == variable_name && checked_variable->is_initialized() &&
checked_variable->get_init_counter() >= update_variable->get_init_counter()) {
found_better = true;
}
}

if (remove_old_variable) {
// update the variable map with new variable
m_variables_state[node_name].erase(m_variables_state[node_name].begin() + remove_ind);
}

if (!found_better) {
m_variables_state[node_name].push_back(update_variable);
}
}

private:
std::unordered_map<std::string, std::vector<Variable::Ptr>> m_variables_state;
};

} // namespace tensorflow
} // namespace frontend
} // namespace ov
44 changes: 44 additions & 0 deletions src/frontends/tensorflow/src/op/assign.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "common_op_table.hpp"
#include "openvino/frontend/tensorflow/node_context.hpp"
#include "openvino/frontend/tensorflow/variable.hpp"

using namespace std;
using namespace ov;
using namespace ov::op;
using namespace ov::frontend::tensorflow;

namespace ov {
namespace frontend {
namespace tensorflow {
namespace op {
OutputVector translate_assign_op(const NodeContext& node) {
default_op_checks(node, 2, {"Assign"});
auto ref = as_type_ptr<Variable>(node.get_input_by_reference(0).get_node_shared_ptr());
TENSORFLOW_OP_VALIDATION(
node,
ref,
"[TensorFlow Frontend] internal error: Assign operation expects variable by the first input");

// create new variable operation node that will represent the same variable but it will be initialized with value
auto value = node.get_input(1);
auto new_ref = make_shared<Variable>(*ref, value);
rkazants marked this conversation as resolved.
Show resolved Hide resolved

// since this operation produces new state of the variable
// it needs to update variables map
auto variables_state_map = node.get_variable_state_map();
TENSORFLOW_OP_VALIDATION(node,
variables_state_map,
"[TensorFlow Frontend] internal error: variable state map is nullptr");
variables_state_map->update_variable_state_map_for_node(node.get_name(), new_ref);

return {new_ref};
}

} // namespace op
} // namespace tensorflow
} // namespace frontend
} // namespace ov
47 changes: 47 additions & 0 deletions src/frontends/tensorflow/src/op/assign_add.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "common_op_table.hpp"
#include "openvino/frontend/tensorflow/node_context.hpp"
#include "openvino/frontend/tensorflow/variable.hpp"
#include "openvino/op/add.hpp"

using namespace std;
using namespace ov;
using namespace ov::op;
using namespace ov::frontend::tensorflow;

namespace ov {
namespace frontend {
namespace tensorflow {
namespace op {

OutputVector translate_assign_add_op(const NodeContext& node) {
default_op_checks(node, 2, {"AssignAdd"});
auto ref = as_type_ptr<Variable>(node.get_input_by_reference(0).get_node_shared_ptr());
TENSORFLOW_OP_VALIDATION(
node,
ref,
"[TensorFlow Frontend] internal error: AssignAdd operation expects variable by the first input");

// create new variable operation node that will represent the same variable but it will be initialized with value
auto value_to_add = node.get_input(1);
auto current_value = ref->get_value();
auto new_value = make_shared<v1::Add>(current_value, value_to_add);
auto new_ref = make_shared<Variable>(*ref, new_value);

// since this operation produces new state of the variable
// it needs to update variables map
auto variables_state_map = node.get_variable_state_map();
TENSORFLOW_OP_VALIDATION(node,
variables_state_map,
"[TensorFlow Frontend] internal error: variable state map is nullptr");
variables_state_map->update_variable_state_map_for_node(node.get_name(), new_ref);
return {new_ref};
}

} // namespace op
} // namespace tensorflow
} // namespace frontend
} // namespace ov