diff --git a/src/frontends/tensorflow/src/input_model.cpp b/src/frontends/tensorflow/src/input_model.cpp index 6e2103ca463..65672ae13a6 100644 --- a/src/frontends/tensorflow/src/input_model.cpp +++ b/src/frontends/tensorflow/src/input_model.cpp @@ -137,6 +137,10 @@ void InputModel::InputModelTFImpl::load_places() { auto op_name = node_decoder->get_op_name(); auto op_type = node_decoder->get_op_type(); + if (op_type == "Placeholder" && op_name.rfind("unused_control_flow_input", 0) != std::string::npos) { + continue; + } + if (m_telemetry) { op_statistics[op_type]++; } @@ -320,9 +324,6 @@ std::vector> InputModel::InputModelTFImpl::topologicall std::stack> ops_to_do; std::unordered_set> ops_done; - // TODO: implement logic to check direct cycles in the graph - // and break them - // probably not only NextIteration can generate cycles for (const auto& output_place : m_outputs) { FRONT_END_GENERAL_CHECK(output_place->get_names().size() > 0, "TensorPlace must have at least one name."); auto output_place_name = output_place->get_names()[0]; @@ -336,6 +337,23 @@ std::vector> InputModel::InputModelTFImpl::topologicall ops_to_do.push(output_operation_place); } + // walk through all NextIteration nodes and put their producers into ops_to_do + // this is needed to avoid missed nodes in the body graph of TF1 While operation + for (const auto& op_place : m_op_places) { + auto op_decoder = op_place->get_decoder(); + if (op_decoder->get_op_type() == "NextIteration") { + std::string producer_name; + std::string producer_output_port_name; + size_t producer_output_port_idx; + op_decoder->get_input_node(0, producer_name, producer_output_port_name, producer_output_port_idx); + FRONT_END_GENERAL_CHECK(m_op_places_map.count(producer_name), + "[TensorFlow Frontend] internal error or inconsistent model: producer of " + "NextIteration is not found among operation places " + + producer_name); + ops_to_do.push(m_op_places_map.at(producer_name)); + } + } + // the traversing algorithm to compute topologically sorted nodes is taken from topological_sort in // core/graph_util.hpp while (ops_to_do.size() > 0) { @@ -350,6 +368,14 @@ std::vector> InputModel::InputModelTFImpl::topologicall if (current_operation_type == "NextIteration") { // break the cycle created by NextIteration input_count = 0; + std::string producer_name; + std::string producer_output_port_name; + size_t producer_output_port_idx; + current_operation_decoder->get_input_node(0, + producer_name, + producer_output_port_name, + producer_output_port_idx); + current_operation_place->set_next_iteration_back_edge(producer_name, producer_output_port_idx); } for (size_t input_port_idx = 0; input_port_idx < input_count; ++input_port_idx) { diff --git a/src/frontends/tensorflow/src/op/enter.cpp b/src/frontends/tensorflow/src/op/enter.cpp new file mode 100644 index 00000000000..c0719f83e36 --- /dev/null +++ b/src/frontends/tensorflow/src/op/enter.cpp @@ -0,0 +1,34 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "helper_ops/enter.hpp" + +#include "common_op_table.hpp" +#include "openvino/frontend/tensorflow/node_context.hpp" +#include "utils.hpp" + +using namespace std; +using namespace ov; +using namespace ov::frontend::tensorflow; + +namespace ov { +namespace frontend { +namespace tensorflow { +namespace op { + +OutputVector translate_enter_op(const NodeContext& node) { + default_op_checks(node, 1, {"Enter"}); + auto data = node.get_input(0); + auto frame_name = node.get_attribute("frame_name"); + + auto enter_node = make_shared(data, frame_name, node.get_decoder()); + set_node_name(node.get_name(), enter_node); + + return enter_node->outputs(); +} + +} // namespace op +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/src/frontends/tensorflow/src/op/exit.cpp b/src/frontends/tensorflow/src/op/exit.cpp new file mode 100644 index 00000000000..e85bddf21c6 --- /dev/null +++ b/src/frontends/tensorflow/src/op/exit.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "helper_ops/exit.hpp" + +#include "common_op_table.hpp" +#include "openvino/frontend/tensorflow/node_context.hpp" +#include "utils.hpp" + +using namespace std; +using namespace ov; +using namespace ov::frontend::tensorflow; + +namespace ov { +namespace frontend { +namespace tensorflow { +namespace op { + +OutputVector translate_exit_op(const NodeContext& node) { + default_op_checks(node, 1, {"Exit"}); + auto data = node.get_input(0); + + auto exit_node = make_shared(data, node.get_decoder()); + set_node_name(node.get_name(), exit_node); + + return exit_node->outputs(); +} + +} // namespace op +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/src/frontends/tensorflow/src/op/loop_cond.cpp b/src/frontends/tensorflow/src/op/loop_cond.cpp new file mode 100644 index 00000000000..286192a017f --- /dev/null +++ b/src/frontends/tensorflow/src/op/loop_cond.cpp @@ -0,0 +1,34 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "helper_ops/loop_cond.hpp" + +#include "common_op_table.hpp" +#include "openvino/frontend/tensorflow/node_context.hpp" +#include "utils.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_loop_cond_op(const NodeContext& node) { + default_op_checks(node, 1, {"LoopCond"}); + auto input = node.get_input(0); + + auto loop_cond_node = make_shared(input, node.get_decoder()); + set_node_name(node.get_name(), loop_cond_node); + + return loop_cond_node->outputs(); +} + +} // namespace op +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/src/frontends/tensorflow/src/op/next_iteration.cpp b/src/frontends/tensorflow/src/op/next_iteration.cpp new file mode 100644 index 00000000000..f977f277ad9 --- /dev/null +++ b/src/frontends/tensorflow/src/op/next_iteration.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "helper_ops/next_iteration.hpp" + +#include "common_op_table.hpp" +#include "helper_ops/merge.hpp" +#include "openvino/frontend/tensorflow/node_context.hpp" +#include "utils.hpp" + +using namespace std; +using namespace ov; +using namespace ov::frontend::tensorflow; + +namespace ov { +namespace frontend { +namespace tensorflow { +namespace op { + +OutputVector translate_next_iteration_op(const NodeContext& node) { + default_op_checks(node, 0, {"NextIteration"}); + + auto next_iteration_node = make_shared(node.get_decoder()); + set_node_name(node.get_name(), next_iteration_node); + + return next_iteration_node->outputs(); +} + +} // namespace op +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/src/frontends/tensorflow/src/op/partitioned_call.cpp b/src/frontends/tensorflow/src/op/partitioned_call.cpp index 6228d80e86a..635ea1a802d 100644 --- a/src/frontends/tensorflow/src/op/partitioned_call.cpp +++ b/src/frontends/tensorflow/src/op/partitioned_call.cpp @@ -4,7 +4,7 @@ #include "common_op_table.hpp" #include "input_model.hpp" -#include "openvino/opsets/opset10.hpp" +#include "tf_utils.hpp" using namespace std; using namespace ov; @@ -18,7 +18,7 @@ OutputVector translate_partitioned_call_op(const NodeContext& node) { auto node_name = node.get_name(); auto translate_session = node.get_translate_session(); FRONT_END_GENERAL_CHECK(translate_session, "[TensorFlow Frontend] Internal error: Translate session is nullptr."); - auto operation_type = node.get_attribute("f"); + auto operation_type = node.get_attribute("f"); // prepare a vector of inputs OutputVector ov_inputs; @@ -33,9 +33,8 @@ OutputVector translate_partitioned_call_op(const NodeContext& node) { // of StatefulPartitionedCall. And because otherwise they will cause a duplicates. But we need to keep them // for "internal functions of Saved Model", which are named "__inference_signature_wrapper" or // "__inference_wrapped_model". - auto body_model = translate_session->get_body_ov_model(operation_type, - ov_inputs, - operation_type.find("wrappe") == std::string::npos); + auto body_model = + translate_session->get_body_ov_model(operation_type, ov_inputs, operation_type.find("wrappe") == string::npos); FRONT_END_OP_CONVERSION_CHECK( body_model, "[TensorFlow Frontend] Internal error or incorrect input model: body graph is not found for " + operation_type + @@ -43,11 +42,11 @@ OutputVector translate_partitioned_call_op(const NodeContext& node) { // inject the body graph into the parent graph OutputVector ov_outputs; - translate_session->inject_body_model(body_model, operation_type, ov_inputs, ov_outputs); + inject_body_model(body_model, operation_type, ov_inputs, ov_outputs); // set output tensor names for (size_t idx = 0; idx < ov_outputs.size(); ++idx) { - set_out_name({node_name + ":" + std::to_string(idx)}, ov_outputs[idx]); + set_out_name({node_name + ":" + to_string(idx)}, ov_outputs[idx]); } return ov_outputs; diff --git a/src/frontends/tensorflow/src/op/while.cpp b/src/frontends/tensorflow/src/op/while.cpp index 7bffea10a78..fa414b44550 100644 --- a/src/frontends/tensorflow/src/op/while.cpp +++ b/src/frontends/tensorflow/src/op/while.cpp @@ -4,11 +4,10 @@ #include "common_op_table.hpp" #include "input_model.hpp" -#include "openvino/opsets/opset10.hpp" +#include "tf_utils.hpp" using namespace std; using namespace ov; -using namespace ov::opset10; namespace ov { namespace frontend { @@ -21,7 +20,7 @@ OutputVector translate_while_op(const NodeContext& node) { auto input_size_t = node.get_input_size(); auto input_size = static_cast(input_size_t); - ov::OutputVector ov_inputs; + OutputVector ov_inputs; for (int input_ind = 0; input_ind < input_size; ++input_ind) { ov_inputs.push_back(node.get_input(input_ind)); } @@ -30,8 +29,8 @@ OutputVector translate_while_op(const NodeContext& node) { translate_session, "[TensorFlow Frontend] Internal error: Translate session is nullptr."); // retrieve condition and body graphs - auto cond_type = node.get_attribute("cond"); - auto body_type = node.get_attribute("body"); + auto cond_type = node.get_attribute("cond"); + auto body_type = node.get_attribute("body"); auto cond_model = translate_session->get_body_ov_model(cond_type, ov_inputs); TENSORFLOW_OP_VALIDATION( node, @@ -43,82 +42,7 @@ OutputVector translate_while_op(const NodeContext& node) { body_model, "[TensorFlow Frontend] Internal error or incorrect input model. Cannot find body graph with name " + body_type); - // inject condition body graph prior to Loop node - // to check condition before to start iterations - auto cond_params = cond_model->get_parameters(); - // type setting for body graph parameters is needed for TensorList support since DT_VARIANT type is present - // also for more accurate execution_condition variable shape deducing we need shape inference for condition graph - for (int input_ind = 0; input_ind < input_size; ++input_ind) { - cond_params[input_ind]->set_element_type(node.get_input(input_ind).get_element_type()); - cond_params[input_ind]->set_partial_shape(node.get_input(input_ind).get_partial_shape()); - } - cond_model->validate_nodes_and_infer_types(); - - auto cond_prior = cond_model->clone(); - ov::OutputVector ov_outputs; - translate_session->inject_body_model(cond_prior, node.get_name() + "/cond", ov_inputs, ov_outputs); - TENSORFLOW_OP_VALIDATION( - node, - ov_outputs.size() == 1, - "[TensorFlow Frontend] Internal error or inconsistent model: condition body must contain one Result node."); - auto exec_cond = ov_outputs[0]; - auto trip_count = make_shared(element::i32, Shape{}, -1); - auto loop = make_shared(trip_count, exec_cond); - - // prepare body model to be set for the Loop node - // note that condition should be computed on the updated input - // because this is while(cond) {} construction, - // that is why condition graph is stitched to the body results - auto body_params = body_model->get_parameters(); - auto body_results = body_model->get_results(); - auto cond_results = cond_model->get_results(); - auto cond_params_size = cond_params.size(); - TENSORFLOW_OP_VALIDATION(node, - body_params.size() == input_size_t, - "[TensorFlow Frontend] Internal error or inconsistent model: body graph " - " must have the same number of Parameter nodes as a number of inputs to While."); - TENSORFLOW_OP_VALIDATION(node, - body_results.size() == input_size_t, - "[TensorFlow Frontend] Internal error or inconsistent model: body graphs " - " must have the same number of Result nodes as a number of inputs to While."); - TENSORFLOW_OP_VALIDATION(node, - cond_params.size() == input_size_t, - "[TensorFlow Frontend] Internal error or inconsistent model: condition graph " - " must have the same number of Parameter nodes as a number of inputs to While."); - for (size_t param_ind = 0; param_ind < cond_params_size; ++param_ind) { - cond_params[param_ind]->output(0).replace(body_results[param_ind]->input_value(0)); - } - - // update body model with the new result that corresponds to execution condition - TENSORFLOW_OP_VALIDATION( - node, - cond_results.size() == 1 && cond_results[0], - "[TensorFlow Frontend] Internal error or inconsistent model: condition body must contain one Result node."); - auto body_condition_output_idx = static_cast(body_results.size()); - body_model->add_results(cond_results); - - // type setting for body graph parameters is needed for TensorList support since DT_VARIANT type is present - for (int input_ind = 0; input_ind < input_size; ++input_ind) { - body_params[input_ind]->set_element_type(node.get_input(input_ind).get_element_type()); - } - - // set data for the Loop node - loop->set_function(body_model); - - for (int input_ind = 0; input_ind < input_size; ++input_ind) { - loop->set_merged_input(body_params[input_ind], - node.get_input(input_ind), - body_results[input_ind]->input_value(0)); - } - loop->set_special_body_ports({-1, body_condition_output_idx}); - - // set external outputs for Loop node - // do not get execution condition outside of the Loop node - for (size_t output_ind = 0; output_ind < input_size_t; ++output_ind) { - loop->get_iter_value(body_results[output_ind]); - } - loop->validate_and_infer_types(); - + auto loop = create_loop_for_tf_while(node.get_name(), body_model, cond_model, ov_inputs); set_node_name(node.get_name(), loop); return loop->outputs(); } diff --git a/src/frontends/tensorflow/src/op_table.cpp b/src/frontends/tensorflow/src/op_table.cpp index db2456ec50f..b1313ec07ba 100644 --- a/src/frontends/tensorflow/src/op_table.cpp +++ b/src/frontends/tensorflow/src/op_table.cpp @@ -22,14 +22,18 @@ namespace op { TF_OP_CONVERTER(translate_assignvariable_op); TF_OP_CONVERTER(translate_block_lstm_op); +TF_OP_CONVERTER(translate_enter_op); +TF_OP_CONVERTER(translate_exit_op); TF_OP_CONVERTER(translate_fifo_queue_op); TF_OP_CONVERTER(translate_gru_block_cell_op); TF_OP_CONVERTER(translate_hash_table_op); TF_OP_CONVERTER(translate_if_op); TF_OP_CONVERTER(translate_iterator_get_next_op); TF_OP_CONVERTER(translate_iterator_op); +TF_OP_CONVERTER(translate_loop_cond_op); TF_OP_CONVERTER(translate_merge_op); TF_OP_CONVERTER(translate_mergev2checkpoint_op); +TF_OP_CONVERTER(translate_next_iteration_op); TF_OP_CONVERTER(translate_partitioned_call_op); TF_OP_CONVERTER(translate_placeholder_linked_op); TF_OP_CONVERTER(translate_queue_dequeue_op); @@ -310,6 +314,12 @@ const std::map get_supported_ops() { // XLA operations {"XlaConvV2", CreatorFunction(translate_xla_conv_v2_op)}, {"XlaDotV2", CreatorFunction(translate_xla_dot_op)}, + + // TF1 Control Flow operations + {"Enter", CreatorFunction(translate_enter_op)}, + {"Exit", CreatorFunction(translate_exit_op)}, + {"LoopCond", CreatorFunction(translate_loop_cond_op)}, + {"NextIteration", CreatorFunction(translate_next_iteration_op)}, }; }; } // namespace op diff --git a/src/frontends/tensorflow/src/tf_utils.cpp b/src/frontends/tensorflow/src/tf_utils.cpp index ba4bce049a0..c72e8e7bb90 100644 --- a/src/frontends/tensorflow/src/tf_utils.cpp +++ b/src/frontends/tensorflow/src/tf_utils.cpp @@ -12,9 +12,12 @@ #include "helper_ops/switch.hpp" #include "openvino/core/type/element_type.hpp" #include "openvino/frontend/exception.hpp" +#include "openvino/op/constant.hpp" +#include "openvino/op/parameter.hpp" #include "openvino/runtime/tensor.hpp" using namespace ov; +using namespace ov::op; using namespace ov::element; using namespace ov::frontend::tensorflow; using namespace std; @@ -369,6 +372,112 @@ bool propagate_conditional_flow(const OutputVector& ov_inputs, return to_propagate; } +// create Loop operation corresponding to TensorFlow While operation +shared_ptr create_loop_for_tf_while(const std::string& while_node_name, + const shared_ptr& body_model, + const shared_ptr& cond_model, + const OutputVector& ov_inputs) { + size_t input_size = ov_inputs.size(); + // inject condition body graph prior to Loop node + // to check condition before to start iterations + auto cond_params = cond_model->get_parameters(); + FRONT_END_GENERAL_CHECK(input_size == cond_params.size(), + "[TensorFlow Frontend] internal error: mismatch number of inputs to While and a number of " + "inputs in a conditional graph"); + // type setting for body graph parameters is needed for TensorList support since DT_VARIANT type is present + // also for more accurate execution_condition variable shape deducing we need shape inference for condition graph + for (size_t input_ind = 0; input_ind < input_size; ++input_ind) { + cond_params[input_ind]->set_element_type(ov_inputs[input_ind].get_element_type()); + cond_params[input_ind]->set_partial_shape(ov_inputs[input_ind].get_partial_shape()); + } + cond_model->validate_nodes_and_infer_types(); + + auto cond_prior = cond_model->clone(); + ov::OutputVector ov_outputs; + inject_body_model(cond_prior, while_node_name + "/cond", ov_inputs, ov_outputs); + FRONT_END_GENERAL_CHECK( + ov_outputs.size() == 1, + "[TensorFlow Frontend] Internal error or inconsistent model: condition body must contain one Result node."); + auto exec_cond = ov_outputs[0]; + auto trip_count = make_shared(element::i32, Shape{}, -1); + auto loop = make_shared(trip_count, exec_cond); + + // prepare body model to be set for the Loop node + // note that condition should be computed on the updated input + // because this is while(cond) {} construction, + // that is why condition graph is stitched to the body results + auto body_params = body_model->get_parameters(); + auto body_results = body_model->get_results(); + auto cond_results = cond_model->get_results(); + FRONT_END_GENERAL_CHECK(body_params.size() == input_size, + "[TensorFlow Frontend] Internal error or inconsistent model: body graph " + " must have the same number of Parameter nodes as a number of inputs to While."); + FRONT_END_GENERAL_CHECK(cond_params.size() == input_size, + "[TensorFlow Frontend] Internal error or inconsistent model: condition graph " + " must have the same number of Parameter nodes as a number of inputs to While."); + for (size_t param_ind = 0; param_ind < body_results.size(); ++param_ind) { + cond_params[param_ind]->output(0).replace(body_results[param_ind]->input_value(0)); + } + + // update body model with the new result that corresponds to execution condition + FRONT_END_GENERAL_CHECK( + cond_results.size() == 1 && cond_results[0], + "[TensorFlow Frontend] Internal error or inconsistent model: condition body must contain one Result node."); + auto body_condition_output_idx = static_cast(body_results.size()); + body_model->add_results(cond_results); + + // type setting for body graph parameters is needed for TensorList support since DT_VARIANT type is present + for (size_t input_ind = 0; input_ind < input_size; ++input_ind) { + body_params[input_ind]->set_element_type(ov_inputs[input_ind].get_element_type()); + } + + // set data for the Loop node + loop->set_function(body_model); + + // body_results may contain less nodes than body_params that means back edge exists not for all body_params + for (size_t input_ind = 0; input_ind < static_cast(body_condition_output_idx); ++input_ind) { + loop->set_merged_input(body_params[input_ind], ov_inputs[input_ind], body_results[input_ind]->input_value(0)); + } + loop->set_special_body_ports({-1, body_condition_output_idx}); + + // set external outputs for Loop node + // do not get execution condition outside of the Loop node + for (size_t output_ind = 0; output_ind < static_cast(body_condition_output_idx); ++output_ind) { + loop->get_iter_value(body_results[output_ind]); + } + loop->validate_and_infer_types(); + return loop; +} + +void inject_body_model(std::shared_ptr ov_model_to_inject, + const std::string& operation_type, + const ov::OutputVector& ov_inputs, + ov::OutputVector& ov_outputs) { + ov_outputs.clear(); + auto body_parameters = ov_model_to_inject->get_parameters(); + FRONT_END_GENERAL_CHECK(body_parameters.size() == ov_inputs.size(), + "[TensorFlow Error] Internal error or incorrect input models: number of " + "inputs and arguments to the function " + + operation_type + " do not match."); + for (size_t param_ind = 0; param_ind < body_parameters.size(); ++param_ind) { + auto orig_type = body_parameters[param_ind]->get_element_type(); + // avoid not needed tensor names from body graph Parameter node after replacing + body_parameters[param_ind]->output(0).set_names({}); + body_parameters[param_ind]->output(0).replace(ov_inputs[param_ind]); + if (auto ext_parameter = as_type_ptr(ov_inputs[param_ind].get_node_shared_ptr())) { + // save type of a Parameter as converted in the body + // this is important if the external conversion extension is applied to body graph node + // with setting its own type + if (orig_type != element::dynamic) { + ext_parameter->set_element_type(orig_type); + } + } + } + for (const auto& result_node : ov_model_to_inject->get_results()) { + ov_outputs.push_back(result_node->input_value(0)); + } +} + } // namespace tensorflow } // namespace frontend } // namespace ov diff --git a/src/frontends/tensorflow/src/tf_utils.hpp b/src/frontends/tensorflow/src/tf_utils.hpp index a7a80a522b7..5de9029a816 100644 --- a/src/frontends/tensorflow/src/tf_utils.hpp +++ b/src/frontends/tensorflow/src/tf_utils.hpp @@ -12,6 +12,7 @@ #include "openvino/core/type.hpp" #include "openvino/core/type/element_type.hpp" #include "openvino/frontend/node_context.hpp" +#include "openvino/op/loop.hpp" #include "openvino/runtime/tensor.hpp" #include "tensor.pb.h" #include "tensor_shape.pb.h" @@ -102,6 +103,18 @@ bool propagate_conditional_flow(const ov::OutputVector& ov_inputs, // copy existing markers from copy_from to copy_to marker void copy_conditional_flow_marker(const CfMarkerType& copy_from, CfMarkerType& copy_to); + +// create Loop operation corresponding to TensorFlow While operation +std::shared_ptr create_loop_for_tf_while(const std::string& while_node_name, + const std::shared_ptr& body_model, + const std::shared_ptr& cond_model, + const ov::OutputVector& ov_inputs); + +// inject a graph by given inputs and return outputs of the injected graph +void inject_body_model(std::shared_ptr ov_model_to_inject, + const std::string& operation_type, + const ov::OutputVector& ov_inputs, + ov::OutputVector& ov_outputs); } // namespace tensorflow } // namespace frontend } // namespace ov diff --git a/src/frontends/tensorflow/src/translate_session.cpp b/src/frontends/tensorflow/src/translate_session.cpp index 933bc0d900e..51f0addb7c3 100644 --- a/src/frontends/tensorflow/src/translate_session.cpp +++ b/src/frontends/tensorflow/src/translate_session.cpp @@ -4,6 +4,11 @@ #include "translate_session.hpp" +#include "helper_ops/enter.hpp" +#include "helper_ops/loop_cond.hpp" +#include "helper_ops/merge.hpp" +#include "helper_ops/next_iteration.hpp" +#include "helper_ops/switch.hpp" #include "input_model.hpp" #include "openvino/op/util/framework_node.hpp" #include "openvino/opsets/opset10.hpp" @@ -154,6 +159,148 @@ size_t get_flat_index_by_name_and_id(const ov::frontend::NamedOutputVector& outp return idx; } } + +// create Parameter node that will produce given tensor +std::shared_ptr create_parameter_node_for_tensor(ov::Output output_tensor) { + auto param = + std::make_shared(output_tensor.get_element_type(), output_tensor.get_partial_shape()); + param->output(0).set_names(output_tensor.get_names()); + output_tensor.replace(param->output(0)); + return param; +} + +void fuse_loop_cond(std::shared_ptr& loop_cond, + OpMap& ov_tensors_map, + const std::vector>& enter_ops) { + // ov_tensors_map maps a operation name to a vector of its output tensors + auto node_name = loop_cond->get_friendly_name(); + // find key points for condition and body graphs + FRONT_END_GENERAL_CHECK(loop_cond, "[TensorFlow Frontend] internal error: pointer to LoopCond node is nullptr"); + + // extract condition and body graphs + // scan LoopCond node vicinity + // 1. LoopCond has just one output + // walk through all consuming inputs that are expected to be only for Switch nodes + std::vector> switch_nodes; + for (const auto& consuming_input : loop_cond->get_output_target_inputs(0)) { + auto switch_node = ov::as_type_ptr(consuming_input.get_node()->shared_from_this()); + FRONT_END_GENERAL_CHECK(switch_node, + "[TensorFlow Frontend] internal error or inconsistent model: consumer of LoopCond " + "output is not Switch operation"); + switch_nodes.push_back(switch_node); + } + + // collect all output tensors for Loop + // the created Loop node outputs will be connected with ov_outputs + size_t num_inputs = switch_nodes.size(); + FRONT_END_GENERAL_CHECK(num_inputs > 0, + "[TensorFlow Frontend] internal error: LoopCond node has no output Switch nodes"); + ov::OutputVector ov_outputs(num_inputs); + // collect ov_inputs (a list of Tensors) that will provide input data for the created Loop node + ov::OutputVector ov_inputs(num_inputs); + ov::ParameterVector cond_params(num_inputs); + ov::ParameterVector body_params(num_inputs); + ov::OutputVector ov_body_outputs(num_inputs); + std::vector output_tensor_names(num_inputs); + std::set> met_enter_ops; + std::string frame_name; + for (size_t ind = 0; ind < num_inputs; ++ind) { + // Switch node has two outputs: + // 0 (output_false) - interrupt the loop, 1 (output_true) - continue the loop + // check if Exit node exists + auto switch_node = switch_nodes[ind]; + FRONT_END_GENERAL_CHECK( + switch_node->get_output_target_inputs(0).size() < 2, + "[TensorFlow Frontend] internal error or inconsistent model: Switch node has more than one Exit nodes"); + if (switch_node->get_output_target_inputs(0).size() == 1) { + auto exit_node = (*switch_node->get_output_target_inputs(0).begin()).get_node(); + ov_outputs[ind] = exit_node->output(0); + output_tensor_names[ind] = exit_node->get_friendly_name() + ":0"; + } + + auto merge_node = ov::as_type_ptr(switch_node->input_value(0).get_node_shared_ptr()); + FRONT_END_GENERAL_CHECK(merge_node, + "[TensorFlow Frontend] internal error or inconsistent model: Data for Switch node is " + "not produced by Merge node for While operation"); + + // create Parameter node for condition graph + cond_params[ind] = create_parameter_node_for_tensor(merge_node->output(0)); + body_params[ind] = create_parameter_node_for_tensor(switch_node->output(1)); + + // check that Merge node has Enter and NextIteration producers + auto enter = ov::as_type_ptr(merge_node->input_value(0).get_node_shared_ptr()); + auto next_iteration = ov::as_type_ptr(merge_node->input_value(0).get_node_shared_ptr()); + if (!enter) { + enter = ov::as_type_ptr(merge_node->input_value(1).get_node_shared_ptr()); + } + if (!next_iteration) { + next_iteration = ov::as_type_ptr(merge_node->input_value(1).get_node_shared_ptr()); + } + FRONT_END_GENERAL_CHECK(enter && next_iteration, + "[TensorFlow Frontend] internal error or inconsistent model: inputs of Merge node in " + "While sub-graph are not Enter and NextIteration"); + ov_inputs[ind] = enter->input_value(0); + met_enter_ops.insert(enter); + frame_name = enter->get_frame_name(); + + // retrieve output tensor for body graph that is an input to NextIteration node + std::string producer_name; + size_t producer_output_port_idx; + next_iteration->get_producer(producer_name, producer_output_port_idx); + FRONT_END_GENERAL_CHECK( + ov_tensors_map.count(producer_name) > 0, + "[TensorFlow Frontend] internal error: NextIteration producer is not found in the tensor map"); + auto producer_outputs = ov_tensors_map.at(producer_name); + FRONT_END_GENERAL_CHECK( + producer_output_port_idx < producer_outputs.size(), + "[TensorFlow Frontend] internal error: NextIteration producer has insufficient number of outputs"); + auto ov_body_output = producer_outputs[producer_output_port_idx].port; + if (ov_body_output.get_node_shared_ptr() == switch_node) { + // this is case when NextIteration node is connected with Switch node + ov_body_outputs[ind] = body_params[ind]->output(0); + } else { + ov_body_outputs[ind] = ov_body_output; + } + } + auto ov_cond_output = loop_cond->input_values(); + + // insert additional inputs for future Loop node + for (auto& enter : enter_ops) { + if (met_enter_ops.find(enter) == met_enter_ops.end() && enter->get_frame_name() == frame_name) { + ov_inputs.push_back(enter->input_value(0)); + auto additional_param = create_parameter_node_for_tensor(enter->output(0)); + cond_params.push_back(additional_param); + body_params.push_back(additional_param); + } + } + + // create a copy of conditional graph + auto cond_model = std::make_shared(ov_cond_output, cond_params); + auto body_model = std::make_shared(ov_body_outputs, body_params); + + auto loop_node = create_loop_for_tf_while(node_name, body_model, cond_model, ov_inputs); + + auto loop_model = std::make_shared(loop_node->outputs()); + + size_t loop_node_output_size = loop_node->get_output_size(); + FRONT_END_GENERAL_CHECK(loop_node_output_size == num_inputs, + "[TensorFlow Frontend] internal error: the created Loop node to replace TF1 While has " + "unexpected number of outputs"); + for (size_t output_ind = 0; output_ind < loop_node_output_size; ++output_ind) { + auto producer_node = ov_outputs[output_ind].get_node_shared_ptr(); + if (producer_node) { + std::string producer_name = producer_node->get_friendly_name(); + size_t producer_output_port_idx = ov_outputs[output_ind].get_index(); + // work only for non-empty ov::Output + ov_outputs[output_ind].replace(loop_node->output(output_ind)); + ov_outputs[output_ind].set_names({output_tensor_names[output_ind]}); + if (ov_tensors_map.count(producer_name) && + producer_output_port_idx < ov_tensors_map.at(producer_name).size()) { + ov_tensors_map.at(producer_name)[producer_output_port_idx] = ov_outputs[output_ind]; + } + } + } +} } // namespace TranslateSession::TranslateSession(const ov::frontend::InputModel::Ptr& input_model, @@ -173,37 +320,12 @@ std::shared_ptr TranslateSession::get_converted_model() { return m_ov_model; } -void TranslateSession::inject_body_model(std::shared_ptr body_model, - const std::string& operation_type, - const ov::OutputVector& ov_inputs, - ov::OutputVector& ov_outputs) { - ov_outputs.clear(); - auto body_parameters = body_model->get_parameters(); - FRONT_END_GENERAL_CHECK(body_parameters.size() == ov_inputs.size(), - "[TensorFlow Error] Internal error or incorrect input models: number of " - "inputs and arguments to the function " + - operation_type + " do not match."); - for (size_t param_ind = 0; param_ind < body_parameters.size(); ++param_ind) { - auto orig_type = body_parameters[param_ind]->get_element_type(); - body_parameters[param_ind]->output(0).replace(ov_inputs[param_ind]); - if (auto ext_parameter = as_type_ptr(ov_inputs[param_ind].get_node_shared_ptr())) { - // save type of a Parameter as converted in the body - // this is important if the external conversion extension is applied to body graph node - // with setting its own type - if (orig_type != element::dynamic) { - ext_parameter->set_element_type(orig_type); - } - } - } - for (const auto& result_node : body_model->get_results()) { - ov_outputs.push_back(result_node->input_value(0)); - } -} - void TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& input_model, std::shared_ptr& ov_model) { OpMap ng_op_map; ControlDepsMap control_deps_map; + std::vector> loop_cond_ops; + std::vector> enter_ops; ov::ParameterVector params; ov::ResultVector results; @@ -375,6 +497,19 @@ void TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& inpu ov_outputs = named_from_indexed(fw_node->outputs()); } + // save LoopCond operations in topological order for further fusing + if (ov_outputs.size() == 1 && as_type_ptr(ov_outputs[0].port.get_node_shared_ptr())) { + loop_cond_ops.push_back(as_type_ptr(ov_outputs[0].port.get_node_shared_ptr())); + } else if (ov_outputs.size() == 1 && as_type_ptr(ov_outputs[0].port.get_node_shared_ptr())) { + enter_ops.push_back(as_type_ptr(ov_outputs[0].port.get_node_shared_ptr())); + } else if (ov_outputs.size() == 1 && as_type_ptr(ov_outputs[0].port.get_node_shared_ptr())) { + std::string producer_name; + size_t producer_output_port_idx; + operation_place->get_next_iteration_back_edge(producer_name, producer_output_port_idx); + auto next_iteration = as_type_ptr(ov_outputs[0].port.get_node_shared_ptr()); + next_iteration->set_producer(producer_name, producer_output_port_idx); + } + // create input control dependencies set for the current operation node std::set> input_control_deps; for (const auto& control_dep_name : control_dependencies_names) { @@ -526,6 +661,14 @@ void TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& inpu ov::ParameterVector ordered_params = reorder_ops_by_names(input_names, params); ov::ResultVector ordered_results = reorder_ops_by_names(output_names, results); + // before adding Result nodes to terminal nodes + // it fuses TF1 Control flow based While operation to Loop operation + // it needs to perform this in the reverse order + std::reverse(loop_cond_ops.begin(), loop_cond_ops.end()); + for (auto& loop_cond_op : loop_cond_ops) { + fuse_loop_cond(loop_cond_op, ng_op_map, enter_ops); + } + ov_model = std::make_shared(ordered_results, ordered_params, m_model_name); } diff --git a/src/frontends/tensorflow/src/translate_session.hpp b/src/frontends/tensorflow/src/translate_session.hpp index 1bf7627a9ee..9415b19a6e0 100644 --- a/src/frontends/tensorflow/src/translate_session.hpp +++ b/src/frontends/tensorflow/src/translate_session.hpp @@ -42,11 +42,6 @@ public: void translate_graph(const ov::frontend::InputModel::Ptr& input_model, std::shared_ptr& ov_model); - void inject_body_model(std::shared_ptr body_model, - const std::string& operation_type, - const ov::OutputVector& ov_inputs, - ov::OutputVector& ov_outputs); - std::shared_ptr get_body_ov_model(const std::string& body_graph_name, const ov::OutputVector& ov_inputs, bool clear_names = true); diff --git a/src/frontends/tensorflow/tests/convert_unsupported.cpp b/src/frontends/tensorflow/tests/convert_unsupported.cpp index 21d350cc2da..7d9a83045b5 100644 --- a/src/frontends/tensorflow/tests/convert_unsupported.cpp +++ b/src/frontends/tensorflow/tests/convert_unsupported.cpp @@ -115,33 +115,6 @@ TEST(FrontEndConvertModelTest, test_unsupported_op) { ASSERT_NO_THROW(frontEnd->convert(model)); } -TEST(FrontEndConvertModelTest, test_unsupported_tf1_while) { - FrontEndManager fem; - FrontEnd::Ptr frontEnd; - InputModel::Ptr inputModel; - ASSERT_NO_THROW(frontEnd = fem.load_by_framework(TF_FE)); - ASSERT_NE(frontEnd, nullptr); - auto model_filename = FrontEndTestUtils::make_model_path(string(TEST_TENSORFLOW_MODELS_DIRNAME) + - string("model_tf1_while/model_tf1_while.pbtxt")); - ASSERT_NO_THROW(inputModel = frontEnd->load(model_filename)); - ASSERT_NE(inputModel, nullptr); - shared_ptr model; - - try { - model = frontEnd->convert(inputModel); - FAIL() << "TensorFlow 1 While is not supported in TF FE but conversion passed without errors. " - "OpConversionFailure is expected."; - } catch (const OpConversionFailure& error) { - string error_message = error.what(); - string ref_message = "[TensorFlow Frontend] Internal error, no translator found for operation(s): Enter, Exit, " - "LoopCond, Merge, NextIteration, Switch"; - ASSERT_TRUE(error_message.find(ref_message) != string::npos); - ASSERT_EQ(model, nullptr); - } catch (...) { - FAIL() << "Conversion of TensorFlow 1 While failed by wrong reason."; - } -} - TEST_F(FrontEndConversionWithReferenceTestsF, ModelWithDynamicType) { { model = convert_model_partially("dynamic_type_model/dynamic_type_model.pb"); } { @@ -169,11 +142,12 @@ TEST(FrontEndConvertModelTest, test_unsupported_tf1_while_and_incorrect_less_tra "OpConversionFailure is expected."; } catch (const OpConversionFailure& error) { string error_message = error.what(); - string ref_message = "Less expects ten inputs.\n" - "\n" - "[TensorFlow Frontend] Internal error, no translator found for operation(s): Enter, Exit, " - "LoopCond, Merge, NextIteration, Switch"; + string ref_message = "Less expects ten inputs.\n"; + string not_found_message = + "[TensorFlow Frontend] Internal error, no translator found for operation(s): Enter, Exit, " + "LoopCond, Merge, NextIteration, Switch"; ASSERT_TRUE(error_message.find(ref_message) != string::npos); + ASSERT_TRUE(error_message.find(not_found_message) == string::npos); ASSERT_EQ(model, nullptr); } catch (...) { FAIL() << "Conversion of TensorFlow 1 While failed by wrong reason."; @@ -191,14 +165,12 @@ TEST(FrontEndConvertModelTest, conversion_with_unknown_exception) { "OpConversionFailure is expected."; } catch (const OpConversionFailure& error) { string error_message = error.what(); - string ref_message = "Unknown exception type\n" - "[TensorFlow Frontend] Internal error, no translator found for operation(s): Enter, Exit, " - "LoopCond, Merge, NextIteration, Switch"; + string ref_message = "Unknown exception type\n"; string doc_message = "To facilitate the conversion of unsupported operations, refer to Frontend Extension documentation: " "https://docs.openvino.ai/latest/openvino_docs_Extensibility_UG_Frontend_Extensions.html"; ASSERT_TRUE(error_message.find(ref_message) != string::npos); - ASSERT_TRUE(error_message.find(doc_message) != string::npos); + ASSERT_TRUE(error_message.find(doc_message) == string::npos); ASSERT_EQ(model, nullptr); } catch (...) { FAIL() << "Conversion of TensorFlow 1 While failed by wrong reason."; diff --git a/src/frontends/tensorflow_common/include/helper_ops/enter.hpp b/src/frontends/tensorflow_common/include/helper_ops/enter.hpp new file mode 100644 index 00000000000..12beaaff8e6 --- /dev/null +++ b/src/frontends/tensorflow_common/include/helper_ops/enter.hpp @@ -0,0 +1,47 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include + +#include "internal_operation.hpp" +#include "tf_utils.hpp" + +namespace ov { +namespace frontend { +namespace tensorflow { + +// Internal operation for Enter that marks entry point for data going to Loop in the graph +// It is used along with Exit operation +class Enter : public InternalOperation { +public: + OPENVINO_OP("Enter", "ov::frontend::tensorflow", InternalOperation); + + Enter(const Output& data, + const std::string frame_name, + const std::shared_ptr& decoder = std::make_shared()) + : InternalOperation(decoder, OutputVector{data}, 1, "Enter"), + m_frame_name(frame_name) { + validate_and_infer_types(); + } + + void validate_and_infer_types() override { + auto data_type = get_input_element_type(0); + auto data_shape = get_input_partial_shape(0); + + set_output_type(0, data_type, data_shape); + } + + std::string get_frame_name() const { + return m_frame_name; + } + +private: + std::string m_frame_name; +}; + +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/src/frontends/tensorflow_common/include/helper_ops/exit.hpp b/src/frontends/tensorflow_common/include/helper_ops/exit.hpp new file mode 100644 index 00000000000..879a780d030 --- /dev/null +++ b/src/frontends/tensorflow_common/include/helper_ops/exit.hpp @@ -0,0 +1,37 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include + +#include "internal_operation.hpp" +#include "tf_utils.hpp" + +namespace ov { +namespace frontend { +namespace tensorflow { + +// Internal operation for Exit that marks exit point for data going from Loop in the graph +// It is used along with Enter operation +class Exit : public InternalOperation { +public: + OPENVINO_OP("Exit", "ov::frontend::tensorflow", InternalOperation); + + Exit(const Output& data, const std::shared_ptr& decoder = std::make_shared()) + : InternalOperation(decoder, OutputVector{data}, 1, "Exit") { + validate_and_infer_types(); + } + + void validate_and_infer_types() override { + auto data_type = get_input_element_type(0); + auto data_shape = get_input_partial_shape(0); + + set_output_type(0, data_type, data_shape); + } +}; + +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/src/frontends/tensorflow_common/include/helper_ops/loop_cond.hpp b/src/frontends/tensorflow_common/include/helper_ops/loop_cond.hpp new file mode 100644 index 00000000000..c889fbd74b1 --- /dev/null +++ b/src/frontends/tensorflow_common/include/helper_ops/loop_cond.hpp @@ -0,0 +1,37 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include + +#include "internal_operation.hpp" +#include "tf_utils.hpp" + +namespace ov { +namespace frontend { +namespace tensorflow { + +// Internal operation for Loop that represents the loop termination condition +// by the pivot switches of a loop +class LoopCond : public InternalOperation { +public: + OPENVINO_OP("LoopCond", "ov::frontend::tensorflow", InternalOperation); + + LoopCond(const Output& input, const std::shared_ptr& decoder = std::make_shared()) + : InternalOperation(decoder, OutputVector{input}, 1, "LoopCond") { + validate_and_infer_types(); + } + + void validate_and_infer_types() override { + auto data_type = get_input_element_type(0); + auto data_shape = get_input_partial_shape(0); + + set_output_type(0, data_type, data_shape); + } +}; + +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/src/frontends/tensorflow_common/include/helper_ops/next_iteration.hpp b/src/frontends/tensorflow_common/include/helper_ops/next_iteration.hpp new file mode 100644 index 00000000000..eb262b4307a --- /dev/null +++ b/src/frontends/tensorflow_common/include/helper_ops/next_iteration.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include + +#include "internal_operation.hpp" +#include "merge.hpp" +#include "tf_utils.hpp" + +namespace ov { +namespace frontend { +namespace tensorflow { + +// Internal operation for NextIteration that makes its input available to the next iteration +// the output is going to Merge node. +class NextIteration : public InternalOperation { +public: + OPENVINO_OP("NextIteration", "ov::frontend::tensorflow", InternalOperation); + + NextIteration(const std::shared_ptr& decoder = std::make_shared()) + : InternalOperation(decoder, OutputVector{}, 1, "NextIteration"), + m_back_edge_set(false) { + validate_and_infer_types(); + } + + void validate_and_infer_types() override { + set_output_type(0, ov::element::dynamic, ov::PartialShape::dynamic()); + } + + void set_producer(const std::string& producer_name, size_t producer_output_port_idx) { + m_producer_name = producer_name; + m_producer_output_port_idx = producer_output_port_idx; + m_back_edge_set = true; + } + + void get_producer(std::string& producer_name, size_t& producer_output_port_idx) const { + FRONT_END_GENERAL_CHECK(m_back_edge_set, + "[TensorFlow Frontend] internal error: back edge for NextIteration is not set"); + producer_name = m_producer_name; + producer_output_port_idx = m_producer_output_port_idx; + } + +private: + bool m_back_edge_set; + std::string m_producer_name; + size_t m_producer_output_port_idx; +}; + +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/src/frontends/tensorflow_common/include/place.hpp b/src/frontends/tensorflow_common/include/place.hpp index ba946e59466..0559f53c6c1 100644 --- a/src/frontends/tensorflow_common/include/place.hpp +++ b/src/frontends/tensorflow_common/include/place.hpp @@ -131,10 +131,22 @@ public: Ptr get_target_tensor() const override; Ptr get_target_tensor(int outputPortIndex) const override; + // set back edge for OpPlace of NextIteration operation + // this is needed since we break a cycle in a graph + void set_next_iteration_back_edge(const std::string& next_iteration_producer_name, + size_t next_iteration_producer_output_port_idx); + void get_next_iteration_back_edge(std::string& next_iteration_producer_name, + size_t& next_iteration_producer_output_port_idx) const; + private: std::shared_ptr m_op_decoder; std::map>> m_input_ports; std::vector> m_output_ports; + + // flag if back edge is set + bool m_back_edge_set; + std::string m_next_iteration_producer_name; + size_t m_next_iteration_producer_output_port_idx; }; class TensorPlace : public Place { diff --git a/src/frontends/tensorflow_common/include/tf_framework_node.hpp b/src/frontends/tensorflow_common/include/tf_framework_node.hpp index cffba769751..fa2e949706d 100644 --- a/src/frontends/tensorflow_common/include/tf_framework_node.hpp +++ b/src/frontends/tensorflow_common/include/tf_framework_node.hpp @@ -35,7 +35,9 @@ public: } std::shared_ptr clone_with_new_inputs(const OutputVector& inputs) const override { - return std::make_shared(m_decoder, inputs, get_output_size()); + auto fw_node = std::make_shared(m_decoder, inputs, get_output_size()); + fw_node->set_attrs(get_attrs()); + return fw_node; } std::string get_op_type() const { diff --git a/src/frontends/tensorflow_common/src/place.cpp b/src/frontends/tensorflow_common/src/place.cpp index c1fddd0fc4c..91dfb2a2cc1 100644 --- a/src/frontends/tensorflow_common/src/place.cpp +++ b/src/frontends/tensorflow_common/src/place.cpp @@ -29,7 +29,23 @@ bool Place::is_output() const { OpPlace::OpPlace(const ov::frontend::InputModel& input_model, std::shared_ptr op_decoder) : Place(input_model, {op_decoder->get_op_name()}), - m_op_decoder(op_decoder) {} + m_op_decoder(op_decoder), + m_back_edge_set(false) {} + +void OpPlace::set_next_iteration_back_edge(const std::string& next_iteration_producer_name, + size_t next_iteration_producer_output_port_idx) { + m_next_iteration_producer_name = next_iteration_producer_name; + m_next_iteration_producer_output_port_idx = next_iteration_producer_output_port_idx; + m_back_edge_set = true; +} + +void OpPlace::get_next_iteration_back_edge(std::string& next_iteration_producer_name, + size_t& next_iteration_producer_output_port_idx) const { + FRONT_END_GENERAL_CHECK(m_back_edge_set, + "[TensorFlow Frontend] internal error: back edge for NextIteration is not set"); + next_iteration_producer_name = m_next_iteration_producer_name; + next_iteration_producer_output_port_idx = m_next_iteration_producer_output_port_idx; +} const std::vector>& OpPlace::get_output_ports() const { return m_output_ports; diff --git a/tests/layer_tests/tensorflow_tests/test_tf_While.py b/tests/layer_tests/tensorflow_tests/test_tf_While.py index 6ca7fc5c195..3a6f9b7f291 100644 --- a/tests/layer_tests/tensorflow_tests/test_tf_While.py +++ b/tests/layer_tests/tensorflow_tests/test_tf_While.py @@ -49,8 +49,7 @@ class TestWhile(CommonTFLayerTest): test_data_basic = [ dict(y_shape=[2, 3], data_type=np.int32, lower_control_flow=False), dict(y_shape=[2, 1, 4], data_type=np.int32, lower_control_flow=False), - pytest.param(dict(y_shape=[2, 1, 4], data_type=np.int32, lower_control_flow=True), - marks=pytest.mark.xfail(reason="105670")) + dict(y_shape=[2, 1, 4], data_type=np.int32, lower_control_flow=True) ] @pytest.mark.parametrize("params", test_data_basic) @@ -108,8 +107,7 @@ class TestWhileShapeVariant(CommonTFLayerTest): test_data_basic = [ dict(y_shape=[2, 3], lower_control_flow=False), dict(y_shape=[2, 1, 4], lower_control_flow=False), - pytest.param(dict(y_shape=[2, 1, 4], lower_control_flow=True), - marks=pytest.mark.xfail(reason="105670")) + dict(y_shape=[2, 1, 4], lower_control_flow=True) ] @pytest.mark.parametrize("params", test_data_basic) diff --git a/tools/mo/openvino/tools/mo/convert_impl.py b/tools/mo/openvino/tools/mo/convert_impl.py index d0d2cd10f77..ae6c39a144b 100644 --- a/tools/mo/openvino/tools/mo/convert_impl.py +++ b/tools/mo/openvino/tools/mo/convert_impl.py @@ -312,9 +312,8 @@ def update_fallback_with_conversion_error(use_new_frontend: bool, is_tf: bool, e conversion_error_re = r"^(\[TensorFlow\ Frontend\]\ Internal\ error\,\ no\ translator\ found\ for\ operation\(s\)\:\ )((\w+)(\,\ \w+)*)$" conversion_error_match = re.findall(conversion_error_re, ex_msg, re.MULTILINE) all_fallback_operations = [ - # corresponds to TF1 While operation + # corresponds to TF1 TensorList operation "TensorArrayScatterV3", "TensorArrayV3", "TensorArraySizeV3", "TensorArrayGatherV3", - "LoopCond", "Enter", "NextIteration", "Exit", # corresponds to operations with complex tensors "FFT", "FFT2D", "FFT3D", "IFFT", "IFFT2D", "IFFT3D", "RFFT", "RFFT2D", "RFFT3D", "IRFFT", "IRFFT2D", "IRFFT3D", diff --git a/tools/mo/unit_tests/moc_tf_fe/conversion_basic_models_test.py b/tools/mo/unit_tests/moc_tf_fe/conversion_basic_models_test.py index 4ceb2fc3c15..3e5c46ecb3a 100644 --- a/tools/mo/unit_tests/moc_tf_fe/conversion_basic_models_test.py +++ b/tools/mo/unit_tests/moc_tf_fe/conversion_basic_models_test.py @@ -243,8 +243,7 @@ class TestMoFreezePlaceholderTFFE(unittest.TestCase): def test_conversion_failure_fallback_use_new_frontend(self): with self.assertRaisesRegex(Exception, "\[TensorFlow Frontend\] Internal error, no translator found for operation\(s\)\: " - "Enter\, Exit\, LoopCond\, Merge\, NextIteration\, Switch\, TensorArrayGatherV3\, " - "TensorArraySizeV3\, TensorArrayV3"): + "TensorArrayGatherV3\, TensorArrayReadV3\, TensorArraySizeV3\, TensorArrayV3\, TensorArrayWriteV3"): self.basic("ctc_model_based.pbtxt", None, None, None, None, None, None, True, True, True, False)