[TF FE] Fix body graph injection, CumSum and SparseFillEmptyRows (#20680)
* [TF FE] Fix body graph injection, CumSum and SparseFillEmptyRows Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com> * Do not handle non-parameters in body * Update layer test to cover default parameter and attribute values * Fix layer tests --------- Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>
This commit is contained in:
@@ -149,7 +149,44 @@ void InputModel::InputModelTFImpl::load_places() {
|
||||
all_op_names.insert(op_name);
|
||||
m_op_places.push_back(op_place);
|
||||
m_op_places_map[op_name] = op_place;
|
||||
|
||||
// compute non-terminating nodes in the graph
|
||||
// and put such nodes into op_names_with_consumers
|
||||
for (size_t input_port_idx = 0; input_port_idx < node_decoder->get_input_size(); ++input_port_idx) {
|
||||
std::string producer_op_name;
|
||||
std::string producer_output_port_name;
|
||||
size_t producer_output_port_idx;
|
||||
try {
|
||||
node_decoder->get_input_node(input_port_idx,
|
||||
producer_op_name,
|
||||
producer_output_port_name,
|
||||
producer_output_port_idx);
|
||||
if (is_conditional_edge(producer_op_name)) {
|
||||
// exclude "^" mark indicating (execution) conditional dependency
|
||||
// for example, "^sub_op" means dependency on a producer node with a name "sub_op"
|
||||
// if a node has dependent operation nodes and has no data consumers,
|
||||
// this node is not terminating and will not output to the Result node
|
||||
producer_op_name = producer_op_name.substr(1);
|
||||
}
|
||||
|
||||
op_names_with_consumers.insert(producer_op_name);
|
||||
} catch (const std::exception&) {
|
||||
FRONT_END_THROW("[ ERROR ] Exception happened when preparing input " + std::to_string(input_port_idx) +
|
||||
" for op '" + node_decoder->get_op_name() + "', expected input name: '" +
|
||||
producer_op_name +
|
||||
"', expected input port index: " + std::to_string(producer_output_port_idx));
|
||||
}
|
||||
}
|
||||
|
||||
// put places for all inputs of a model into m_inputs
|
||||
if (op_type == "Placeholder" || op_type == "PlaceholderWithDefault") {
|
||||
if (m_input_names.size() > 0 &&
|
||||
std::find(m_input_names.begin(), m_input_names.end(), op_name) == m_input_names.end()) {
|
||||
// this is a body graph since it contains non-empty m_input_names
|
||||
// such node not included into m_input_names should be skipped
|
||||
continue;
|
||||
}
|
||||
|
||||
// in case Placeholder we put created TensorPlace to both m_tensor_places container and m_inputs
|
||||
// since they can be used if user does not override them
|
||||
// in case PlaceholderWithDefault we put created TensorPlace only to m_tensor_places container
|
||||
@@ -199,6 +236,13 @@ void InputModel::InputModelTFImpl::load_places() {
|
||||
m_inputs.push_back(tensor_place);
|
||||
}
|
||||
} else if (op_type == "input_arg") {
|
||||
if (m_input_names.size() > 0 &&
|
||||
std::find(m_input_names.begin(), m_input_names.end(), op_name) == m_input_names.end()) {
|
||||
// this is a body graph since it contains non-empty m_input_names
|
||||
// such node not included into m_input_names should be skipped
|
||||
continue;
|
||||
}
|
||||
|
||||
// create a tensor place for the body graph parameter node and save it in the m_inputs
|
||||
// it allows to set shapes for the body graph InputModel for its more optimal conversion
|
||||
auto param_type = node_decoder->get_attribute("type");
|
||||
@@ -212,31 +256,6 @@ void InputModel::InputModelTFImpl::load_places() {
|
||||
std::vector<std::string>{op_name});
|
||||
m_inputs.push_back(tensor_place);
|
||||
}
|
||||
for (size_t input_port_idx = 0; input_port_idx < node_decoder->get_input_size(); ++input_port_idx) {
|
||||
std::string producer_op_name;
|
||||
std::string producer_output_port_name;
|
||||
size_t producer_output_port_idx;
|
||||
try {
|
||||
node_decoder->get_input_node(input_port_idx,
|
||||
producer_op_name,
|
||||
producer_output_port_name,
|
||||
producer_output_port_idx);
|
||||
if (is_conditional_edge(producer_op_name)) {
|
||||
// exclude "^" mark indicating (execution) conditional dependency
|
||||
// for example, "^sub_op" means dependency on a producer node with a name "sub_op"
|
||||
// if a node has dependent operation nodes and has no data consumers,
|
||||
// this node is not terminating and will not output to the Result node
|
||||
producer_op_name = producer_op_name.substr(1);
|
||||
}
|
||||
|
||||
op_names_with_consumers.insert(producer_op_name);
|
||||
} catch (const std::exception&) {
|
||||
FRONT_END_THROW("[ ERROR ] Exception happened when preparing input " + std::to_string(input_port_idx) +
|
||||
" for op '" + node_decoder->get_op_name() + "', expected input name: '" +
|
||||
producer_op_name +
|
||||
"', expected input port index: " + std::to_string(producer_output_port_idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_telemetry) {
|
||||
|
||||
@@ -70,20 +70,29 @@ OutputVector translate_sparse_reshape_op(const ov::frontend::tensorflow::NodeCon
|
||||
return {input_indices, input_shape};
|
||||
}
|
||||
|
||||
OutputVector translate_sparse_fill_empty_rows_op(const ov::frontend::tensorflow::NodeContext& node) {
|
||||
NamedOutputVector translate_sparse_fill_empty_rows_op(const ov::frontend::tensorflow::NodeContext& node) {
|
||||
default_op_checks(node, 3, {"SparseFillEmptyRows"});
|
||||
auto input_indices = node.get_input(0);
|
||||
auto input_values = node.get_input(1);
|
||||
auto dense_shape = node.get_input(2);
|
||||
auto default_value = node.get_input(3);
|
||||
auto node_name = node.get_name();
|
||||
|
||||
auto sparse_fill_empty_rows = make_shared<ov::frontend::tensorflow::SparseFillEmptyRows>(input_indices,
|
||||
input_values,
|
||||
dense_shape,
|
||||
default_value,
|
||||
node.get_decoder());
|
||||
set_node_name(node.get_name(), sparse_fill_empty_rows);
|
||||
return sparse_fill_empty_rows->outputs();
|
||||
sparse_fill_empty_rows->set_friendly_name(node_name);
|
||||
set_out_name(node_name + ":0", sparse_fill_empty_rows->output(0));
|
||||
set_out_name(node_name + ":1", sparse_fill_empty_rows->output(1));
|
||||
set_out_name(node_name + ":2", sparse_fill_empty_rows->output(2));
|
||||
set_out_name(node_name + ":3", sparse_fill_empty_rows->output(3));
|
||||
|
||||
return {{"output_indices", sparse_fill_empty_rows->output(0)},
|
||||
{"output_values", sparse_fill_empty_rows->output(1)},
|
||||
{"empty_row_indicator", sparse_fill_empty_rows->output(2)},
|
||||
{"reverse_index_map", sparse_fill_empty_rows->output(3)}};
|
||||
}
|
||||
|
||||
OutputVector translate_sparse_segment_sum_op(const ov::frontend::tensorflow::NodeContext& node) {
|
||||
|
||||
@@ -18,7 +18,8 @@ namespace frontend {
|
||||
namespace tensorflow {
|
||||
namespace op {
|
||||
|
||||
#define TF_OP_CONVERTER(op) OutputVector op(const ov::frontend::tensorflow::NodeContext& node)
|
||||
#define TF_OP_CONVERTER(op) OutputVector op(const ov::frontend::tensorflow::NodeContext& node)
|
||||
#define TF_OP_CONVERTER_NAMED(op) NamedOutputVector op(const ov::frontend::tensorflow::NodeContext& node)
|
||||
|
||||
TF_OP_CONVERTER(translate_assignvariable_op);
|
||||
TF_OP_CONVERTER(translate_block_lstm_op);
|
||||
@@ -40,7 +41,7 @@ TF_OP_CONVERTER(translate_queue_dequeue_op);
|
||||
TF_OP_CONVERTER(translate_queue_dequeue_many_op);
|
||||
TF_OP_CONVERTER(translate_readvariable_op);
|
||||
TF_OP_CONVERTER(translate_restorev2_op);
|
||||
TF_OP_CONVERTER(translate_sparse_fill_empty_rows_op);
|
||||
TF_OP_CONVERTER_NAMED(translate_sparse_fill_empty_rows_op);
|
||||
TF_OP_CONVERTER(translate_sparse_reshape_op);
|
||||
TF_OP_CONVERTER(translate_sparse_segment_sum_op);
|
||||
TF_OP_CONVERTER(translate_staticregexfullmatch_op);
|
||||
|
||||
@@ -337,6 +337,7 @@ void TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& inpu
|
||||
const auto& model_frozen_inputs = model_tf->get_tensor_values();
|
||||
const auto& saved_model_inputs = model_tf->get_saved_model_input_names();
|
||||
const auto& saved_model_outputs = model_tf->get_saved_model_output_names();
|
||||
bool is_body_graph = (model_tf->get_input_names().size() > 0);
|
||||
|
||||
// fill ng_op_map with Constant outputs for frozen inputs
|
||||
for (const auto& frozen_input : model_frozen_inputs) {
|
||||
@@ -532,7 +533,7 @@ void TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& inpu
|
||||
} else {
|
||||
auto param = as_type_ptr<ov::opset8::Parameter>(output.port.get_node_shared_ptr());
|
||||
// avoid duplicating Parameter nodes if they are already in the Parameters vector
|
||||
if (param && std::find(params.begin(), params.end(), param) == params.end()) {
|
||||
if (param && std::find(params.begin(), params.end(), param) == params.end() && !is_body_graph) {
|
||||
params.push_back(param);
|
||||
}
|
||||
ng_op_map[operation_name].push_back(output);
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
//
|
||||
|
||||
#include "common_op_table.hpp"
|
||||
#include "openvino/opsets/opset8.hpp"
|
||||
#include "openvino/op/cum_sum.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace ov::opset8;
|
||||
using namespace ov::op;
|
||||
|
||||
namespace ov {
|
||||
namespace frontend {
|
||||
@@ -14,14 +14,15 @@ namespace tensorflow {
|
||||
namespace op {
|
||||
|
||||
OutputVector translate_cumsum_op(const NodeContext& node) {
|
||||
auto ng_x = node.get_input(0);
|
||||
auto ng_axis = node.get_input(1);
|
||||
auto exclusive = node.get_attribute<bool>("exclusive");
|
||||
auto reverse = node.get_attribute<bool>("reverse");
|
||||
default_op_checks(node, 2, {"Cumsum"});
|
||||
auto x = node.get_input(0);
|
||||
auto axis = node.get_input(1);
|
||||
auto exclusive = node.get_attribute<bool>("exclusive", false);
|
||||
auto reverse = node.get_attribute<bool>("reverse", false);
|
||||
|
||||
auto res = make_shared<CumSum>(ng_x, ng_axis, exclusive, reverse);
|
||||
set_node_name(node.get_name(), res);
|
||||
return res->outputs();
|
||||
auto cum_sum = make_shared<v0::CumSum>(x, axis, exclusive, reverse);
|
||||
set_node_name(node.get_name(), cum_sum);
|
||||
return cum_sum->outputs();
|
||||
}
|
||||
} // namespace op
|
||||
} // namespace tensorflow
|
||||
|
||||
@@ -6,24 +6,16 @@ import pytest
|
||||
|
||||
from common.tf_layer_test_class import CommonTFLayerTest
|
||||
|
||||
|
||||
# Testing Cumsum operation
|
||||
# Documentation: https://www.tensorflow.org/api_docs/python/tf/raw_ops/Cumsum
|
||||
|
||||
class TestCumsumOps(CommonTFLayerTest):
|
||||
class TestCumsum(CommonTFLayerTest):
|
||||
# input_shape - should be an array
|
||||
# axis - array which points on axis for the operation
|
||||
# exclusive - enables exclusive Cumsum
|
||||
# reverse - enables reverse order of Cumsum
|
||||
# ir_version - common parameter
|
||||
# use_new_frontend - common parameter
|
||||
def create_cumsum_ops_placeholder_const_net(self, input_shape, axis, exclusive, reverse, ir_version, use_new_frontend):
|
||||
"""
|
||||
Tensorflow net IR net
|
||||
|
||||
Placeholder->Cumsum => Placeholder->Cumsum
|
||||
|
||||
"""
|
||||
|
||||
def create_cumsum_net(self, input_shape, axis, exclusive, reverse):
|
||||
import tensorflow as tf
|
||||
|
||||
tf.compat.v1.reset_default_graph()
|
||||
@@ -31,9 +23,9 @@ class TestCumsumOps(CommonTFLayerTest):
|
||||
# Create the graph and model
|
||||
with tf.compat.v1.Session() as sess:
|
||||
tf_input = tf.compat.v1.placeholder(tf.float32, input_shape, 'Input')
|
||||
tf_axis = tf.constant(axis)
|
||||
|
||||
tf.raw_ops.Cumsum(x = tf_input, axis = tf_axis, exclusive = exclusive, reverse = reverse)
|
||||
tf_axis = tf.constant(axis, dtype=tf.int32)
|
||||
tf.raw_ops.Cumsum(x=tf_input, axis=tf_axis, exclusive=exclusive, reverse=reverse)
|
||||
|
||||
tf.compat.v1.global_variables_initializer()
|
||||
tf_net = sess.graph_def
|
||||
@@ -43,20 +35,22 @@ class TestCumsumOps(CommonTFLayerTest):
|
||||
return tf_net, ref_net
|
||||
|
||||
test_data = [
|
||||
pytest.param(
|
||||
dict(input_shape=[2, 3], axis=1),
|
||||
marks=pytest.mark.precommit_tf_fe),
|
||||
dict(input_shape=[2], axis=-1),
|
||||
dict(input_shape=[2, 3], axis=0),
|
||||
dict(input_shape=[2, 3], axis=1),
|
||||
dict(input_shape=[2, 3], axis=-2),
|
||||
dict(input_shape=[2, 3, 3, 4], axis=2),
|
||||
dict(input_shape=[2, 3, 3, 4], axis=-3),
|
||||
]
|
||||
|
||||
@pytest.mark.parametrize("params", test_data)
|
||||
@pytest.mark.parametrize("exclusive", [False, True])
|
||||
@pytest.mark.parametrize("reverse", [False, True])
|
||||
@pytest.mark.parametrize("exclusive", [False, True, None])
|
||||
@pytest.mark.parametrize("reverse", [False, True, None])
|
||||
@pytest.mark.precommit
|
||||
@pytest.mark.precommit_tf_fe
|
||||
@pytest.mark.nightly
|
||||
def test_cumsum_ops_placeholder_const(self, params, exclusive, reverse, ie_device, precision, ir_version, temp_dir,
|
||||
use_new_frontend, use_old_api):
|
||||
self._test(*self.create_cumsum_ops_placeholder_const_net(**params, exclusive=exclusive, ir_version=ir_version,
|
||||
use_new_frontend=use_new_frontend, reverse=reverse),
|
||||
def test_cumsum_basic(self, params, exclusive, reverse, ie_device, precision, ir_version, temp_dir,
|
||||
use_new_frontend, use_old_api):
|
||||
self._test(*self.create_cumsum_net(**params, exclusive=exclusive, reverse=reverse),
|
||||
ie_device, precision, ir_version, temp_dir=temp_dir,
|
||||
use_new_frontend=use_new_frontend, use_old_api=use_old_api)
|
||||
|
||||
Reference in New Issue
Block a user