[TF FE] Support TF1 Control Flow: Switch, Merge (#18378)

* [TF FE] Support Switch and Merge to fuse into If operation

It introduces support of TF1 control flow with Switch and Merge nodes.

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Add script for test model generation

* Fix code-style

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Fix build issue

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Fix build issue with types

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Apply code-review feedback: optimizations in utils

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Fix build issue

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Apply code-review remarks and cover more cases

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Remove commented code

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Remove unused vars

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Update MO unit-tests wit Switch-Merge case

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Fix build issue: remove unused variable

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

---------

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>
This commit is contained in:
Roman Kazantsev authored and GitHub committed 2023-07-14 15:56:34 +02:00
1 parent 238c7fa47e
commit 826f345daf
22 files changed
+1236 -65

No files matched your search

@@ -27,6 +27,8 @@
#include "tf_framework_node.hpp"
#include "transformations/common_optimizations/remove_concat_zero_dim_input.hpp"
#include "transformations/common_optimizations/reverse_shape_and_type_infer.hpp"
#include "transformations/control_flow/unroll_if.hpp"
#include "transformations/switch_merge_resolve.hpp"
#include "transformations/transpose_sinking/ts_general.hpp"
#include "translate_session.hpp"
#include "utils.hpp"
@@ -469,6 +471,8 @@ void FrontEnd::normalize(const std::shared_ptr<ov::Model>& model) const {
manager.register_pass<pass::BlockLSTMReplacer>();
manager.register_pass<pass::GRUBlockCellReplacer>();
manager.register_pass<pass::ConstToResultRemover>();
manager.register_pass<pass::SwitchMergeResolver>();
manager.register_pass<ov::pass::UnrollIf>();
manager.register_pass<ov::pass::RemoveConcatZeroDimInput>();
manager.register_pass<ov::pass::TransposeSinkingGeneral>();
manager.register_pass<ov::pass::ReverseShapeAndTypeInfer>();
+5 -2
View File
@@ -338,9 +338,12 @@ std::vector<std::shared_ptr<OpPlace>> InputModel::InputModelTFImpl::topologicall
"', expected input port index: " + std::to_string(producer_output_port_idx) + '\n');
}
// skip conditional edges for all operators
if (is_conditional_edge(producer_name)) {
continue;
// 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_name = producer_name.substr(1);
}
// is_input is a flag to leave producer operation node or not.
+50
View File
@@ -0,0 +1,50 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "helper_ops/merge.hpp"
#include "common_op_table.hpp"
#include "openvino/frontend/tensorflow/node_context.hpp"
#include "openvino/op/constant.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_merge_op(const NodeContext& node) {
// Merge can have multiple inputs, one is minimum
auto node_name = node.get_name();
default_op_checks(node, 1, {"Merge"});
int input_size = static_cast<int>(node.get_input_size());
OutputVector inputs;
for (int input_ind = 0; input_ind < input_size; ++input_ind) {
inputs.push_back(node.get_input(input_ind));
}
// if Merge node has just one input, there is nothing to merge
// return the same input and value_index equal to 0
if (inputs.size() == 1) {
auto value_index = make_shared<v0::Constant>(element::i32, Shape{}, 0);
value_index->output(0).set_names({node_name + ":1"});
inputs[0].add_names({node_name + ":0"});
return OutputVector{inputs[0], value_index};
}
auto merge_node = make_shared<Merge>(inputs, node.get_decoder());
set_node_name(node.get_name(), merge_node);
return merge_node->outputs();
}
} // namespace op
} // namespace tensorflow
} // namespace frontend
} // namespace ov
@@ -0,0 +1,86 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "helper_ops/switch.hpp"
#include "common_op_table.hpp"
#include "openvino/frontend/node_context.hpp"
#include "openvino/frontend/tensorflow/node_context.hpp"
#include "tf_utils.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_switch_op(const NodeContext& node) {
default_op_checks(node, 2, {"Switch"});
auto switch_name = node.get_name();
auto data = node.get_input(0);
auto pred = node.get_input(1);
auto data_producer = data.get_node_shared_ptr();
// check if the condition goes to another Switch node
// and re-use its conditional flow marker for further grouping multiple Switch and Merge nodes
// walk through all consumers for pred and find Switch nodes
bool generate_new_marker = true;
int32_t switch_marker = 0;
auto pred_node = pred.get_node_shared_ptr();
auto pred_index = pred.get_index();
bool to_skip_switch = false;
for (const auto& target_input : pred_node->get_output_target_inputs(pred_index)) {
auto another_producer = target_input.get_node()->shared_from_this();
if (const auto& another_switch = as_type_ptr<Switch>(another_producer)) {
if (another_switch->is_cond_flow_marker_set() && cf_marker_exists(another_switch)) {
switch_marker = another_switch->get_cond_flow_marker();
generate_new_marker = false;
}
// in case two consecutive Switch nodes with the common predicate
// the successor can be skipped
if (another_switch == data_producer) {
to_skip_switch = true;
generate_new_marker = false;
break;
}
}
}
if (generate_new_marker) {
// generate marker for new conditioning flow
switch_marker = generate_cf_marker();
}
auto switch_node = make_shared<Switch>(data, pred, switch_marker, node.get_decoder());
// in case two consecutive Switch nodes with the common predicate
// it skips successive Switch node
if (to_skip_switch) {
auto data_output_index = data.get_index();
FRONT_END_GENERAL_CHECK(data_output_index == 0 || data_output_index == 1,
"[TensorFlow Frontend] internal error: Switch node must have two outputs");
size_t other_output_index = 1 - data_output_index;
data.add_names({switch_name + ":" + to_string(data_output_index)});
auto other_switch_output = switch_node->output(other_output_index);
other_switch_output.add_names({switch_name + ":" + to_string(other_output_index)});
if (data_output_index == 0) {
return OutputVector{data, other_switch_output};
} else {
return OutputVector{other_switch_output, data};
}
}
// set output tensor names and move the node name
set_node_name(switch_name, switch_node);
return switch_node->outputs();
}
} // namespace op
} // namespace tensorflow
} // namespace frontend
} // namespace ov
@@ -28,6 +28,7 @@ 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_merge_op);
TF_OP_CONVERTER(translate_mergev2checkpoint_op);
TF_OP_CONVERTER(translate_partitioned_call_op);
TF_OP_CONVERTER(translate_placeholder_linked_op);
@@ -40,6 +41,7 @@ TF_OP_CONVERTER(translate_sparse_reshape_op);
TF_OP_CONVERTER(translate_sparse_segment_sum_op);
TF_OP_CONVERTER(translate_staticregexfullmatch_op);
TF_OP_CONVERTER(translate_stringjoin_op);
TF_OP_CONVERTER(translate_switch_op);
TF_OP_CONVERTER(translate_varhandle_op);
TF_OP_CONVERTER(translate_variable_op);
TF_OP_CONVERTER(translate_varisinitialized_op);
@@ -182,6 +184,7 @@ const std::map<std::string, CreatorFunction> get_supported_ops() {
{"MaxPool", CreatorFunction(translate_max_pool_op)},
{"MaxPoolV2", CreatorFunction(translate_max_pool_op)},
{"MaxPool3D", CreatorFunction(translate_max_pool_op)},
{"Merge", CreatorFunction(translate_merge_op)},
{"MirrorPad", CreatorFunction(translate_mirror_pad_op)},
{"MutableHashTable", CreatorFunction(translate_hash_table_op)},
{"MutableHashTableV2", CreatorFunction(translate_hash_table_op)},
@@ -247,6 +250,7 @@ const std::map<std::string, CreatorFunction> get_supported_ops() {
{"StatelessIf", CreatorFunction(translate_if_op)},
{"StatelessWhile", CreatorFunction(translate_while_op)},
{"StridedSlice", CreatorFunction(translate_strided_slice_op)},
{"Switch", CreatorFunction(translate_switch_op)},
{"TensorListFromTensor", CreatorFunction(translate_tensor_list_from_tensor_op)},
{"TensorListGetItem", CreatorFunction(translate_tensor_list_get_item_op)},
{"TensorListPushBack", CreatorFunction(translate_tensor_list_push_back_op)},
+230 -45
View File
@@ -6,16 +6,61 @@
#include <stdint.h>
#include <vector>
#include "helper_ops/merge.hpp"
#include "helper_ops/switch.hpp"
#include "openvino/core/type/element_type.hpp"
#include "openvino/frontend/exception.hpp"
#include "openvino/runtime/tensor.hpp"
using namespace ov;
using namespace ov::element;
using namespace ov::frontend::tensorflow;
using namespace std;
namespace {
void copy_conditional_flow_marker_with_branches(const CfMarkerType& copy_from,
unordered_map<uint32_t, SetOfBranchIndices>& copy_to_braches) {
for (const auto& marker : copy_from.existing_markers_with_branches) {
const auto& switch_marker = marker.first;
const auto& branch_markers = marker.second;
copy_to_braches[switch_marker].insert(branch_markers.begin(), branch_markers.end());
}
}
void copy_conditional_flow_marker_with_switches(const CfMarkerType& copy_from,
unordered_map<uint32_t, SetOfSwitchNodes>& copy_to_switches) {
for (const auto& marker : copy_from.existing_markers_with_switches) {
const auto& switch_marker = marker.first;
const auto& branch_markers = marker.second;
copy_to_switches[switch_marker].insert(branch_markers.begin(), branch_markers.end());
}
}
void copy_conditional_flow_markers_for_producer(
unordered_map<uint32_t, SetOfBranchIndices>& combined_markers_with_braches,
unordered_map<uint32_t, SetOfSwitchNodes>& combined_markers_with_switches,
const Output<Node>& producer_output) {
// walk through all data producer and collect conditional flow markers
const shared_ptr<const Node>& producer_node = producer_output.get_node_shared_ptr();
uint32_t branch_index = static_cast<uint32_t>(producer_output.get_index());
if (!cf_marker_exists(producer_node)) {
return;
}
auto producer_markers = get_cf_marker(producer_node);
copy_conditional_flow_marker_with_branches(producer_markers, combined_markers_with_braches);
copy_conditional_flow_marker_with_switches(producer_markers, combined_markers_with_switches);
// if data goes from Switch node, it needs to create a new marker and branch marker
for (const auto& new_marker : producer_markers.new_markers) {
auto switch_nodes = new_marker.second;
combined_markers_with_braches[new_marker.first].insert(branch_index);
combined_markers_with_switches[new_marker.first].insert(switch_nodes.begin(), switch_nodes.end());
}
}
template <typename T>
void extract_tensor_content(const std::string& tensor_content, ov::Tensor* values) {
void extract_tensor_content(const string& tensor_content, Tensor* values) {
const auto tensor_content_size = tensor_content.size();
FRONT_END_GENERAL_CHECK(tensor_content_size % sizeof(T) == 0,
"Size of tensor_content (",
@@ -26,7 +71,7 @@ void extract_tensor_content(const std::string& tensor_content, ov::Tensor* value
const T* tensor_values = reinterpret_cast<const T*>(tensor_content.data());
FRONT_END_GENERAL_CHECK(values->get_size() == tensor_content_size / sizeof(T),
"Size of tensor is not equal to tensor_content size.");
std::copy(tensor_values, tensor_values + tensor_content_size / sizeof(T), values->data<T>());
copy(tensor_values, tensor_values + tensor_content_size / sizeof(T), values->data<T>());
}
#if defined(_MSC_VER)
@@ -37,7 +82,7 @@ void extract_tensor_content(const std::string& tensor_content, ov::Tensor* value
template <typename T>
void extract_compressed_tensor_content(const ::tensorflow::TensorProto& tensor_proto,
int64_t val_size,
ov::Tensor* values) {
Tensor* values) {
auto val_lastsaved = static_cast<T>(0);
auto values_data = values->data<T>();
for (size_t i = 0; i < values->get_size(); i++) {
@@ -47,22 +92,22 @@ void extract_compressed_tensor_content(const ::tensorflow::TensorProto& tensor_p
auto val_i = static_cast<T>(0);
switch (values->get_element_type()) {
// TODO: there are more element types to support here
case ov::element::boolean:
case boolean:
val_i = tensor_proto.bool_val()[i];
break;
case ov::element::i32:
case i32:
val_i = tensor_proto.int_val()[i];
break;
case ov::element::i64:
case i64:
val_i = tensor_proto.int64_val()[i];
break;
case ov::element::f16:
case f16:
val_i = float16::from_bits(tensor_proto.half_val()[i]);
break;
case ov::element::f32:
case f32:
val_i = tensor_proto.float_val()[i];
break;
case ov::element::f64:
case f64:
val_i = tensor_proto.double_val()[i];
break;
default:
@@ -80,78 +125,98 @@ void extract_compressed_tensor_content(const ::tensorflow::TensorProto& tensor_p
#endif
} // namespace
ov::element::Type ov::frontend::tensorflow::get_ov_type(const ::tensorflow::DataType& type) {
static const std::map<::tensorflow::DataType, ov::element::Type> type_map{
{::tensorflow::DataType::DT_BOOL, ov::element::boolean},
{::tensorflow::DataType::DT_INT16, ov::element::i16},
{::tensorflow::DataType::DT_INT32, ov::element::i32},
{::tensorflow::DataType::DT_INT64, ov::element::i64},
{::tensorflow::DataType::DT_HALF, ov::element::f16},
{::tensorflow::DataType::DT_FLOAT, ov::element::f32},
{::tensorflow::DataType::DT_DOUBLE, ov::element::f64},
{::tensorflow::DataType::DT_UINT8, ov::element::u8},
{::tensorflow::DataType::DT_INT8, ov::element::i8},
{::tensorflow::DataType::DT_BFLOAT16, ov::element::bf16}};
namespace ov {
namespace frontend {
namespace tensorflow {
void copy_conditional_flow_marker(const CfMarkerType& copy_from, CfMarkerType& copy_to) {
for (const auto& marker : copy_from.existing_markers_with_branches) {
const auto& switch_marker = marker.first;
const auto& branch_markers = marker.second;
copy_to.existing_markers_with_branches[switch_marker].insert(branch_markers.begin(), branch_markers.end());
}
for (const auto& marker : copy_from.existing_markers_with_switches) {
const auto& switch_marker = marker.first;
const auto& branch_markers = marker.second;
copy_to.existing_markers_with_switches[switch_marker].insert(branch_markers.begin(), branch_markers.end());
}
}
bool CfMarkerType::is_copyable() const {
return false;
}
Type get_ov_type(const ::tensorflow::DataType& type) {
static const map<::tensorflow::DataType, Type> type_map{{::tensorflow::DataType::DT_BOOL, boolean},
{::tensorflow::DataType::DT_INT16, i16},
{::tensorflow::DataType::DT_INT32, i32},
{::tensorflow::DataType::DT_INT64, i64},
{::tensorflow::DataType::DT_HALF, f16},
{::tensorflow::DataType::DT_FLOAT, f32},
{::tensorflow::DataType::DT_DOUBLE, f64},
{::tensorflow::DataType::DT_UINT8, u8},
{::tensorflow::DataType::DT_INT8, i8},
{::tensorflow::DataType::DT_BFLOAT16, bf16}};
auto it = type_map.find(type);
// for all unsupported types return dynamic type
return it == type_map.end() ? ov::element::dynamic : it->second;
return it == type_map.end() ? dynamic : it->second;
}
ov::Any ov::frontend::tensorflow::unpack_tensor_proto(const ::tensorflow::TensorProto& tensor_proto) {
Any unpack_tensor_proto(const ::tensorflow::TensorProto& tensor_proto) {
return unpack_tensor_proto(tensor_proto, tensor_proto.tensor_shape(), tensor_proto.dtype());
}
ov::Any ov::frontend::tensorflow::unpack_tensor_proto(const ::tensorflow::TensorProto& tensor_proto,
const ::tensorflow::TensorShapeProto& tensor_shape,
const ::tensorflow::DataType& tensor_type) {
ov::PartialShape pshape;
Any unpack_tensor_proto(const ::tensorflow::TensorProto& tensor_proto,
const ::tensorflow::TensorShapeProto& tensor_shape,
const ::tensorflow::DataType& tensor_type) {
PartialShape pshape;
for (int i = 0; i < tensor_shape.dim_size(); i++) {
pshape.push_back(tensor_shape.dim(i).size());
}
FRONT_END_GENERAL_CHECK(pshape.is_static(), "Dynamic shapes are not supported for Tensor attribute.");
ov::element::Type ov_type = get_ov_type(tensor_type);
Type ov_type = get_ov_type(tensor_type);
if (tensor_type != ::tensorflow::DataType::DT_STRING) {
FRONT_END_GENERAL_CHECK(
ov_type.is_static(),
"Encountered unknown element type " + DataType_Name(tensor_type) + " on an empty tensor_proto");
} else {
auto data = std::vector<std::string>();
auto data = vector<string>();
for (const auto& item : tensor_proto.string_val()) {
data.push_back(item);
}
return data;
}
ov::Tensor res(ov_type, pshape.get_shape());
Tensor res(ov_type, pshape.get_shape());
auto tensor_content = tensor_proto.tensor_content();
if (!tensor_content.empty() && tensor_proto.has_tensor_shape()) {
switch (ov_type) {
case ov::element::u8:
case u8:
extract_tensor_content<uint8_t>(tensor_content, &res);
break;
case ov::element::i8:
case i8:
extract_tensor_content<int8_t>(tensor_content, &res);
break;
case ov::element::i16:
case i16:
extract_tensor_content<int16_t>(tensor_content, &res);
break;
case ov::element::i32:
case i32:
extract_tensor_content<int32_t>(tensor_content, &res);
break;
case ov::element::i64:
case i64:
extract_tensor_content<int64_t>(tensor_content, &res);
break;
case ov::element::f16:
case f16:
extract_tensor_content<float16>(tensor_content, &res);
break;
case ov::element::f32:
case f32:
extract_tensor_content<float>(tensor_content, &res);
break;
case ov::element::f64:
case f64:
extract_tensor_content<double>(tensor_content, &res);
break;
case ov::element::bf16:
case bf16:
extract_tensor_content<bfloat16>(tensor_content, &res);
break;
default:
@@ -160,27 +225,27 @@ ov::Any ov::frontend::tensorflow::unpack_tensor_proto(const ::tensorflow::Tensor
} else {
int64_t val_size = 0;
switch (ov_type) {
case ov::element::boolean:
case boolean:
val_size = tensor_proto.bool_val_size();
extract_compressed_tensor_content<bool>(tensor_proto, val_size, &res);
break;
case ov::element::i32:
case i32:
val_size = tensor_proto.int_val_size();
extract_compressed_tensor_content<int32_t>(tensor_proto, val_size, &res);
break;
case ov::element::i64:
case i64:
val_size = tensor_proto.int64_val_size();
extract_compressed_tensor_content<int64_t>(tensor_proto, val_size, &res);
break;
case ov::element::f16:
case f16:
val_size = tensor_proto.half_val_size();
extract_compressed_tensor_content<float16>(tensor_proto, val_size, &res);
break;
case ov::element::f32:
case f32:
val_size = tensor_proto.float_val_size();
extract_compressed_tensor_content<float>(tensor_proto, val_size, &res);
break;
case ov::element::f64:
case f64:
val_size = tensor_proto.double_val_size();
extract_compressed_tensor_content<double>(tensor_proto, val_size, &res);
break;
@@ -190,3 +255,123 @@ ov::Any ov::frontend::tensorflow::unpack_tensor_proto(const ::tensorflow::Tensor
}
return res;
}
CfMarkerType get_cf_marker(const shared_ptr<const Node>& node) {
auto rt_info = node->get_rt_info();
FRONT_END_GENERAL_CHECK(rt_info.count(CF_MARKER_TAG) > 0,
"[TensorFlow Frontend] internal error: node does not contain conditional flow marker");
auto& ov_any_cf_marker = rt_info[CF_MARKER_TAG];
FRONT_END_GENERAL_CHECK(ov_any_cf_marker.is<CfMarkerType>(),
"[TensorFlow Frontend] internal error: incorrect type of conditional flow marker");
return ov_any_cf_marker.as<CfMarkerType>();
}
uint32_t generate_cf_marker() {
static uint32_t marker = 0;
return marker++;
}
bool propagate_conditional_flow(const OutputVector& ov_inputs,
const frontend::NamedOutputVector& ov_outputs,
const set<Output<Node>>& input_control_deps,
set<Output<Node>>& output_control_deps) {
// returns if there is conditional flow to propagate
// it checks all producer-nodes connected via data edges and control dependencies
// compute combined markers
// it is a map from conditional flow marker to a set of branch markers
unordered_map<uint32_t, SetOfBranchIndices> combined_markers_with_branches;
unordered_map<uint32_t, SetOfSwitchNodes> combined_markers_with_switches;
for (const auto& ov_input : ov_inputs) {
// walk through all input producers and propagate CF marker
copy_conditional_flow_markers_for_producer(combined_markers_with_branches,
combined_markers_with_switches,
ov_input);
}
// walk through all control dependencies and collect conditional flow markers
for (const auto& input_control_dep : input_control_deps) {
// walk through all control dependencies and collect conditional flow markers
copy_conditional_flow_markers_for_producer(combined_markers_with_branches,
combined_markers_with_switches,
input_control_dep);
}
// walk through all nodes and mark them if needed
bool to_propagate = false;
for (const auto& ov_output : ov_outputs) {
const auto& node = ov_output.port.get_node_shared_ptr();
// skip already marked node
// it can be a case of Identity node that the conversion rule skips
if (cf_marker_exists(node)) {
to_propagate = true;
continue;
}
// if this is Merge node, it needs to eliminate markers with multiple branch markers
// and put such markers into eliminated list
CfMarkerType resulted_cf_marker;
if (as_type_ptr<Merge>(node)) {
for (const auto& marker : combined_markers_with_branches) {
auto switch_marker = marker.first;
auto branch_markers = marker.second;
if (branch_markers.size() > 1) {
if (combined_markers_with_switches.count(switch_marker) > 0) {
resulted_cf_marker.merge_eliminated_markers[switch_marker] =
combined_markers_with_switches[switch_marker];
}
} else {
resulted_cf_marker.existing_markers_with_branches.insert(marker);
if (combined_markers_with_switches.count(switch_marker) > 0) {
resulted_cf_marker.existing_markers_with_switches[switch_marker] =
combined_markers_with_switches[switch_marker];
}
}
}
} else if (const auto& switch_node = as_type_ptr<Switch>(node)) {
// update conditional flow marker with new marker for the current Switch node
auto switch_marker = switch_node->get_switch_marker();
resulted_cf_marker.new_markers[switch_marker] = {switch_node};
resulted_cf_marker.existing_markers_with_branches = combined_markers_with_branches;
resulted_cf_marker.existing_markers_with_switches = combined_markers_with_switches;
} else {
// check that non-Merge node does not expect data/flow from different Switch branches
// it means inconsistent model
for (const auto& marker : combined_markers_with_branches) {
auto branch_markers = marker.second;
FRONT_END_GENERAL_CHECK(
branch_markers.size() < 2,
"[TensorFlow Frontend] inconsistent input model: non-Merge node expects data or "
"flow from different branches of one Switch node");
resulted_cf_marker.existing_markers_with_branches.insert(marker);
}
resulted_cf_marker.existing_markers_with_switches.insert(combined_markers_with_switches.begin(),
combined_markers_with_switches.end());
}
// set conditional flow marker only if one of the fields is not empty
// check if any input or input control dependency contain control flow
// if yes, it makes sense to continue
if (resulted_cf_marker.new_markers.size() > 0 || resulted_cf_marker.existing_markers_with_branches.size() > 0 ||
resulted_cf_marker.existing_markers_with_switches.size() > 0 ||
resulted_cf_marker.merge_eliminated_markers.size() > 0) {
set_cf_marker(resulted_cf_marker, node);
to_propagate = true;
}
}
// compute output control dependencies
// logically, the next nodes will dependend on outputs and input control dependencies
output_control_deps.clear();
if (to_propagate) {
output_control_deps = input_control_deps;
for (const auto& ov_output : ov_outputs) {
output_control_deps.insert(ov_output.port);
}
}
return to_propagate;
}
} // namespace tensorflow
} // namespace frontend
} // namespace ov
+76
View File
@@ -6,9 +6,12 @@
#include "attr_value.pb.h"
#include "node_def.pb.h"
#include "openvino/core/node.hpp"
#include "openvino/core/partial_shape.hpp"
#include "openvino/core/runtime_attribute.hpp"
#include "openvino/core/type.hpp"
#include "openvino/core/type/element_type.hpp"
#include "openvino/frontend/node_context.hpp"
#include "openvino/runtime/tensor.hpp"
#include "tensor.pb.h"
#include "tensor_shape.pb.h"
@@ -18,6 +21,8 @@ namespace ov {
namespace frontend {
namespace tensorflow {
#define CF_MARKER_TAG "tf_cf_marker_tag"
ov::element::Type get_ov_type(const ::tensorflow::DataType& type);
ov::Any unpack_tensor_proto(const ::tensorflow::TensorProto& tensor_proto);
@@ -26,6 +31,77 @@ ov::Any unpack_tensor_proto(const ::tensorflow::TensorProto& tensor_proto,
const ::tensorflow::TensorShapeProto& tensor_shape,
const ::tensorflow::DataType& tensor_type);
class Switch;
using SetOfSwitchNodes = std::unordered_set<std::shared_ptr<Switch>>;
using SetOfBranchIndices = std::unordered_set<uint32_t>;
// structure to save conditional flow marker
class CfMarkerType : public ov::RuntimeAttribute {
public:
OPENVINO_RTTI("CfMarkerType");
CfMarkerType() = default;
bool is_copyable() const override;
public:
// new_markers serves to mark Switch node and saves its own marker id
// Switch node contains only one marker
// for other type of nodes, new_markers vector is empty
// std::vector<std::pair<uint32_t, std::shared_ptr<ov::Node>>> new_markers;
std::unordered_map<uint32_t, SetOfSwitchNodes> new_markers;
// existing_markers contains Switch node markers collected so far
// and indices of conditional flow branches
// for example, If operation (represented with Switch-Merge node) has two branches with indices 0 and 1
std::unordered_map<uint32_t, SetOfBranchIndices> existing_markers_with_branches;
std::unordered_map<uint32_t, SetOfSwitchNodes> existing_markers_with_switches;
// merge_eliminated_markers is not empty only for Merge node when several branches of the same conditional flow
// are merging by this Merge node
// for example, if existing_markers contains element with a key = 4 (it means the fourth conditional flow)
// and value {0, 1}, it means Merge node eliminates this conditional flow with marker = 4
// std::vector<std::pair<uint32_t, SetOfSwitchNodes>> merge_eliminated_markers;
std::unordered_map<uint32_t, SetOfSwitchNodes> merge_eliminated_markers;
// a container with already eliminated markers for nodes going after Merge nodes
// that eliminated conditional flow with these markers
// this container is needed do not duplicate markers for If sub-graphs that goes after other If
// sub-graphs with common condition
// std::unordered_set<uint32_t> already_eliminated_markers;
};
// a map type to save data/control edges from which the node is dependent
using ControlDepsMap = std::unordered_map<std::string, std::set<ov::Output<ov::Node>>>;
// check if the given node contains conditional flow marker in run-time info
inline bool cf_marker_exists(const std::shared_ptr<const ov::Node>& node) {
const auto& rt_info = node->get_rt_info();
return rt_info.count(CF_MARKER_TAG) > 0;
}
// retrieves conditional flow marker in run-time info for the given node
CfMarkerType get_cf_marker(const std::shared_ptr<const ov::Node>& node);
// sets conditional flow marker for the given node in run-time info
// inline void set_cf_marker(const CfMarkerType& cf_marker, const std::shared_ptr<ov::Node>& node);
inline void set_cf_marker(const CfMarkerType& cf_marker, const std::shared_ptr<ov::Node>& node) {
node->get_rt_info()[CF_MARKER_TAG] = cf_marker;
}
// generates unique conditional flow marker index
// Note: the next TranslateSession (for conversion in parallel) does not reset initial marker
// and it must not affect conversion of models in different sessions
uint32_t generate_cf_marker();
// propagate conditional flow markers to nodes generating ov_outputs
// based on conditional flow presence in input nodes
// also, it creates a vector of tensor/edges output_control_deps from which the current node(s) is dependent
bool propagate_conditional_flow(const ov::OutputVector& ov_inputs,
const ov::frontend::NamedOutputVector& ov_outputs,
const std::set<ov::Output<ov::Node>>& input_control_deps,
std::set<ov::Output<ov::Node>>& output_control_deps);
// copy existing markers from copy_from to copy_to marker
void copy_conditional_flow_marker(const CfMarkerType& copy_from, CfMarkerType& copy_to);
} // namespace tensorflow
} // namespace frontend
} // namespace ov
@@ -0,0 +1,270 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "transformations/switch_merge_resolve.hpp"
#include <unordered_map>
#include <unordered_set>
#include "helper_ops/merge.hpp"
#include "helper_ops/switch.hpp"
#include "openvino/core/node.hpp"
#include "openvino/frontend/exception.hpp"
#include "openvino/op/constant.hpp"
#include "openvino/op/if.hpp"
#include "openvino/op/parameter.hpp"
#include "openvino/op/result.hpp"
#include "tf_utils.hpp"
using namespace ov;
using namespace ov::frontend::tensorflow;
using namespace ov::frontend;
using namespace ov::op;
using namespace std;
namespace ov {
namespace frontend {
namespace tensorflow {
namespace {
using ClusterType = pair<unordered_set<shared_ptr<Switch>>, unordered_set<shared_ptr<Merge>>>;
bool intersected(const unordered_set<shared_ptr<Switch>>& s1, const unordered_set<shared_ptr<Switch>>& s2) {
bool is_intersected = false;
for (const auto& node1 : s1) {
for (const auto& node2 : s2) {
if (node1 == node2) {
is_intersected = true;
break;
}
}
}
return is_intersected;
}
void generate_if_clusters(const shared_ptr<Model>& ov_model,
unordered_map<uint32_t, unordered_set<shared_ptr<Switch>>>& switch_clusters,
unordered_map<uint32_t, unordered_set<shared_ptr<Merge>>>& merge_clusters) {
// each pair represents a cluster circled with Switch and Merge nodes
vector<ClusterType> clusters;
for (const auto& node : ov_model->get_ordered_ops()) {
if (const auto& merge_node = as_type_ptr<Merge>(node)) {
if (!merge_node->is_cond_flow_eliminated()) {
continue;
}
auto eliminated_markers = merge_node->get_eliminated_cond_flow_marker();
if (eliminated_markers.size() != 1) {
continue;
}
auto eliminated_marker = eliminated_markers[0];
auto switch_nodes = merge_node->get_switch_nodes_set_by_cond_index(eliminated_marker);
// insert into clusters
ClusterType combined_cluster = {switch_nodes, {merge_node}};
vector<ClusterType> refined_clusters;
for (const auto& cluster : clusters) {
const auto& cluster_switches = cluster.first;
const auto& cluster_merges = cluster.second;
if (intersected(cluster_switches, combined_cluster.first)) {
combined_cluster.first.insert(cluster_switches.begin(), cluster_switches.end());
combined_cluster.second.insert(cluster_merges.begin(), cluster_merges.end());
} else {
refined_clusters.push_back(cluster);
}
}
refined_clusters.push_back(combined_cluster);
clusters = refined_clusters;
}
}
// repack clusters to two separate maps for Switch and Merge nodes
switch_clusters.clear();
merge_clusters.clear();
uint32_t clusters_size = static_cast<uint32_t>(clusters.size());
for (uint32_t cluster_id = 0; cluster_id < clusters_size; ++cluster_id) {
switch_clusters[cluster_id] = clusters[cluster_id].first;
merge_clusters[cluster_id] = clusters[cluster_id].second;
}
}
shared_ptr<v0::Parameter> replace_switch_output_with_parameter(const shared_ptr<Switch>& switch_node,
size_t output_ind) {
FRONT_END_GENERAL_CHECK(output_ind < 2,
"[TensorFlow Frontend] internal error: incorrect output index for Switch node");
auto switch_output = switch_node->output(output_ind);
auto parameter_node =
make_shared<v0::Parameter>(switch_output.get_element_type(), switch_output.get_partial_shape());
auto cf_marker = get_cf_marker(switch_node);
auto switch_marker = switch_node->get_cond_flow_marker();
cf_marker.existing_markers_with_branches[switch_marker].insert(static_cast<uint32_t>(output_ind));
cf_marker.existing_markers_with_switches[switch_marker].insert(switch_node);
cf_marker.new_markers.clear();
set_cf_marker(cf_marker, parameter_node);
switch_output.replace(parameter_node);
return parameter_node;
}
void insert_result_before_merge(const shared_ptr<Merge>& merge_node,
size_t input_ind,
uint32_t& branch_index,
shared_ptr<v0::Result>& result_output,
shared_ptr<v0::Result>& result_value_index) {
// check that handled Marge node contains conditional flow marker
auto merge_node_name = merge_node->get_friendly_name();
FRONT_END_GENERAL_CHECK(cf_marker_exists(merge_node),
"[TensorFlow Frontend] internal error: Merge node " + merge_node_name +
" does not have conditional flow marker");
// get eliminated marker and check that it is the single one
auto merge_cf_marker = get_cf_marker(merge_node);
FRONT_END_GENERAL_CHECK(merge_cf_marker.merge_eliminated_markers.size() == 1,
"[TensorFlow Frontend] internal error: Merge node " + merge_node_name +
" does not contain the single eliminated marker");
auto eliminated_marker = merge_cf_marker.merge_eliminated_markers.begin()->first;
// check that producer contains the same conditional flow marker
// and retrive branch index for it
const auto& merge_input = merge_node->input(input_ind);
const auto& input_value = merge_node->input_value(input_ind);
const shared_ptr<const Node>& merge_producer = merge_node->get_input_node_shared_ptr(input_ind);
auto producer_cf_marker = get_cf_marker(merge_producer);
FRONT_END_GENERAL_CHECK(
producer_cf_marker.existing_markers_with_branches.count(eliminated_marker) > 0,
"[TensorFlow Frontend] internal error: input producer for Merge node does not contain eliminated marker");
auto branch_index_set = producer_cf_marker.existing_markers_with_branches[eliminated_marker];
FRONT_END_GENERAL_CHECK(branch_index_set.size() == 1,
"[TensorFlow Frontend] internal error: it must contain the single branch index");
branch_index = *next(branch_index_set.begin(), 0);
auto value_index = make_shared<v0::Constant>(element::i32, Shape{}, input_ind);
result_output = make_shared<v0::Result>(input_value);
result_value_index = make_shared<v0::Result>(value_index);
input_value.remove_target_input(merge_input);
}
} // namespace
bool pass::SwitchMergeResolver::run_on_model(const shared_ptr<Model>& m) {
// split set of Switch and Merge nodes to clusters
// where each cluster of Switch and Merge nodes will represent
// the single If operation for fusing
unordered_map<uint32_t, unordered_set<shared_ptr<Switch>>> switch_clusters;
unordered_map<uint32_t, unordered_set<shared_ptr<Merge>>> merge_clusters;
generate_if_clusters(m, switch_clusters, merge_clusters);
// fuse Switch-Merge sub-graphs into If operation
for (const auto& marker_to_merge_nodes : merge_clusters) {
const auto& cluster_id = marker_to_merge_nodes.first;
const auto& merge_nodes = marker_to_merge_nodes.second;
const auto& switch_nodes = switch_clusters[cluster_id];
if (merge_nodes.size() == 0 || switch_nodes.size() == 0) {
continue;
}
auto cond = (*(switch_nodes.begin()))->input_value(1);
// collect Parameter nodes and Result nodes for then and else bodies
// set inputs and outputs for If node
// create then bodies for which condition is true
ParameterVector then_params, else_params;
ResultVector then_results, else_results;
OutputVector if_inputs, if_outputs;
vector<unordered_set<string>> if_outputs_names;
for (const auto& switch_node : switch_nodes) {
if_inputs.push_back(switch_node->input_value(0));
auto parameter_node_false = replace_switch_output_with_parameter(switch_node, 0);
auto parameter_node_true = replace_switch_output_with_parameter(switch_node, 1);
then_params.push_back(parameter_node_true);
else_params.push_back(parameter_node_false);
}
CfMarkerType if_cf_marker;
for (const auto& merge_node : merge_nodes) {
// combine conditional markers from all Merge nodes
// from which it results If node
const auto& merge_cf_marker = get_cf_marker(merge_node);
copy_conditional_flow_marker(merge_cf_marker, if_cf_marker);
for (size_t input_ind = 0; input_ind < merge_node->get_input_size(); ++input_ind) {
uint32_t branch_index = 0;
shared_ptr<v0::Result> result_output, result_value_index;
insert_result_before_merge(merge_node, input_ind, branch_index, result_output, result_value_index);
FRONT_END_GENERAL_CHECK(
branch_index == 0 || branch_index == 1,
"[TensorFlow Frontend] internal error: conditional branch with unexpected index");
if (branch_index == 0) {
// handle else-branch since the first output of Switch is output_false
else_results.push_back(result_output);
else_results.push_back(result_value_index);
} else if (branch_index == 1) {
// handle then-branch since the second output of Switch is output_true
then_results.push_back(result_output);
then_results.push_back(result_value_index);
}
}
for (const auto& merge_output : merge_node->outputs()) {
if_outputs.push_back(merge_output);
if_outputs_names.push_back(merge_output.get_names());
}
}
// create body graphs for then and else branches
auto then_body = make_shared<Model>(then_results, then_params);
auto else_body = make_shared<Model>(else_results, else_params);
auto if_op = make_shared<v8::If>(cond);
set_cf_marker(if_cf_marker, if_op);
if_op->set_then_body(then_body);
if_op->set_else_body(else_body);
// set inputs
size_t input_size = if_inputs.size();
FRONT_END_GENERAL_CHECK(
input_size == then_params.size(),
"[TensorFlow Frontend] internal error: number of Switch inputs does not match a number of outputs");
FRONT_END_GENERAL_CHECK(
input_size == else_params.size(),
"[TensorFlow Frontend] internal error: number of Switch inputs does not match a number of outputs");
for (size_t ind = 0; ind < input_size; ++ind) {
auto curr_input = if_inputs[ind];
auto then_param = then_params[ind];
auto else_param = else_params[ind];
if_op->set_input(curr_input, then_param, else_param);
}
// set outputs
FRONT_END_GENERAL_CHECK(then_results.size() == else_results.size(),
"[TensorFlow Frontend] internal error: number of result nodes in "
"then and else branches do not match.");
size_t output_size = then_results.size();
for (size_t ind = 0; ind < output_size; ++ind) {
if_op->set_output(then_results[ind], else_results[ind]);
}
auto ov_outputs = if_op->outputs();
FRONT_END_GENERAL_CHECK(if_op->outputs().size() == if_outputs.size(),
"[TensorFlow Frontend] internal error: number of actual If outputs does not match "
"expected number for Switch-Merge nodes fusing into If operation");
// replace source for all consumers of Merge nodes outputs
// and move their tensor names to new outputs
size_t output_ind = 0;
for (auto& if_output : if_op->outputs()) {
if_outputs[output_ind].replace(if_output);
if_output.set_names(if_outputs_names[output_ind]);
++output_ind;
}
}
return true;
}
} // namespace tensorflow
} // namespace frontend
} // namespace ov
@@ -0,0 +1,32 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/pass/pass.hpp"
namespace ov {
namespace frontend {
namespace tensorflow {
namespace pass {
// This transformation fuses Switch-Merge sub-graphs into If operation
// After control flow markers propagation, Switch and Merge nodes
// can contain CF markers so that Switch and Merge nodes with the same marker values
// in new_markers and eliminated markers will be fused into If operation where
// then_branch is represented by a graph below output_true branch of Switch and else_branch is by output_false.
// Moreover, several Switch nodes may have the same new_markers that means the resulted If will have several inputs.
// Merge nodes can have the same eliminated markers that means the fused If will have several outputs.
class SwitchMergeResolver : public ov::pass::ModelPass {
public:
OPENVINO_RTTI("ov::frontend::tensorflow::SwitchMergeResolver");
SwitchMergeResolver() = default;
bool run_on_model(const std::shared_ptr<ov::Model>& m) override;
};
} // namespace pass
} // namespace tensorflow
} // namespace frontend
} // namespace ov
@@ -9,6 +9,7 @@
#include "openvino/opsets/opset10.hpp"
#include "openvino/opsets/opset8.hpp"
#include "tf_framework_node.hpp"
#include "tf_utils.hpp"
#include "utils.hpp"
using namespace ov::frontend::tensorflow;
@@ -186,6 +187,8 @@ void TranslateSession::inject_body_model(std::shared_ptr<ov::Model> body_model,
void TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& input_model,
std::shared_ptr<ov::Model>& ov_model) {
OpMap ng_op_map;
ControlDepsMap control_deps_map;
ov::ParameterVector params;
ov::ResultVector results;
const auto& model_tf = std::dynamic_pointer_cast<InputModel>(input_model);
@@ -241,16 +244,13 @@ void TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& inpu
// prepare a list of OV node inputs for each node
ov::OutputVector ov_inputs;
size_t operation_input_size = operation_decoder->get_input_size();
std::vector<std::string> control_dependencies_names;
if (operation_decoder->get_op_type() == "NextIteration") {
// we expect no inputs for NextIteration because we break-up the cycle in InputModel
operation_input_size = 0;
}
for (size_t input_port_idx = 0; input_port_idx < operation_input_size; ++input_port_idx) {
// TODO: Implement more general approach. Skipping Constants that have input edges
if (operation_decoder->get_op_type() == "Const") {
break;
}
std::string producer_name;
size_t producer_port_idx;
try {
@@ -270,6 +270,8 @@ void TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& inpu
// skip conditional edges that must be resolved before operation translation
// now we can meet them because we still work with TensorFlow protobuf
if (is_conditional_edge(producer_name)) {
// control dependency contains "^" in the beginning
control_dependencies_names.push_back(producer_name.substr(1));
continue;
}
@@ -357,6 +359,20 @@ void TranslateSession::translate_graph(const ov::frontend::InputModel::Ptr& inpu
ov_outputs = named_from_indexed(fw_node->outputs());
}
// create input control dependencies set for the current operation node
std::set<ov::Output<ov::Node>> input_control_deps;
for (const auto& control_dep_name : control_dependencies_names) {
if (control_deps_map.count(control_dep_name) > 0) {
const auto& input_control_dep = control_deps_map[control_dep_name];
input_control_deps.insert(input_control_dep.cbegin(), input_control_dep.cend());
}
}
// register output control dependencies in control dependencies map
std::set<ov::Output<ov::Node>> output_control_deps;
if (propagate_conditional_flow(ov_inputs, ov_outputs, input_control_deps, output_control_deps)) {
control_deps_map[operation_name] = output_control_deps;
}
// register OV node outputs in the map for new operation node
for (const auto& output : ov_outputs) {
if (auto result = as_type_ptr<ov::opset10::Result>(output.port.get_node_shared_ptr())) {
@@ -716,3 +716,31 @@ TEST_F(FrontEndConversionWithReferenceTestsF, ControlDependencyNumberOutputs) {
model_ref = make_shared<Model>(OutputVector{sub}, ParameterVector{input1, input2});
}
}
TEST_F(FrontEndConversionWithReferenceTestsF, TF1IfWithNonExistentOpInBranch) {
// This test aims to check conversion of a model with TF1 If operation that
// contains unsupported operation in one branch
// the conversion must avoid such branch in case proper condition freezing
{
bool cond_value = false;
model = convert_model("tf1_if_with_nonexistent_op/tf1_if_with_nonexistent_op.pb",
nullptr,
{},
{},
{},
{"cond"},
{&cond_value});
}
{
auto y = make_shared<Parameter>(f32, Shape{2, 3});
auto ind = make_shared<Parameter>(i32, Shape{3});
auto const_two = make_shared<Constant>(i32, Shape{}, 2);
auto sub = make_shared<Subtract>(ind, const_two);
auto convert = make_shared<Convert>(sub, f32);
auto mul = make_shared<Multiply>(convert, y);
model_ref = make_shared<Model>(OutputVector{mul}, ParameterVector{y, ind});
}
}
@@ -0,0 +1,59 @@
# Copyright (C) 2018-2023 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import os
import sys
import numpy as np
import tensorflow as tf
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
# generate a model with TF1 If operation that contains unsupported operation
# in one condition branch
def main():
def simple_if_function(cond, ind, y):
def then_branch():
const_one = tf.constant(1, dtype=tf.int32)
add = tf.add(ind, const_one)
output = tf.multiply(tf.cast(add, tf.float32), y)
return output
def else_branch():
const_two = tf.constant(2, dtype=tf.int32)
sub = tf.subtract(ind, const_two)
output = tf.multiply(tf.cast(sub, tf.float32), y)
return output
if_output = tf.cond(cond, then_branch, else_branch)
return if_output
tf_if_graph = tf.function(simple_if_function)
cond = np.random.randint(0, 2, []).astype(bool)
ind = np.random.randint(1, 10, [3]).astype(np.int32)
y = np.random.randint(-50, 50, [2, 3]).astype(np.float32)
concrete_func = tf_if_graph.get_concrete_function(cond, ind, y)
# lower_control_flow defines representation of If operation
# in case of lower_control_flow=True it is decomposed into Switch and Merge nodes
frozen_func = convert_variables_to_constants_v2(concrete_func,
lower_control_flow=True)
graph_def = frozen_func.graph.as_graph_def(add_shapes=True)
tf.io.write_graph(graph_def, os.path.join(sys.argv[1], "tf1_if_with_nonexistent_op"),
"tf1_if_with_nonexistent_op.pb",
False)
with open(os.path.join(sys.argv[1], "tf1_if_with_nonexistent_op", "tf1_if_with_nonexistent_op.pb"),
mode='rb') as file:
modelContent = file.read()
modelContent = modelContent.replace(b"AddV2", b"Rrrrr")
with open(os.path.join(sys.argv[1], "tf1_if_with_nonexistent_op", "tf1_if_with_nonexistent_op.pb"),
mode='wb') as file:
file.write(modelContent)
if __name__ == "__main__":
main()
+12 -1
View File
@@ -20,7 +20,9 @@ shared_ptr<Model> convert_model(const string& model_path,
const ConversionExtension::Ptr& conv_ext,
const vector<string>& input_names,
const vector<element::Type>& input_types,
const vector<PartialShape>& input_shapes) {
const vector<PartialShape>& input_shapes,
const std::vector<std::string>& input_names_to_freeze,
const std::vector<void*>& freeze_values) {
FrontEndManager fem;
auto front_end = fem.load_by_framework(TF_FE);
if (!front_end) {
@@ -64,6 +66,15 @@ shared_ptr<Model> convert_model(const string& model_path,
input_model->override_all_inputs(input_places);
}
// freeze some places with concrete values
if (input_names_to_freeze.size() != freeze_values.size()) {
throw "The number of input places to freeze and their values do not match";
}
for (size_t ind = 0; ind < input_names_to_freeze.size(); ++ind) {
auto place_to_freeze = input_model->get_place_by_tensor_name(input_names_to_freeze[ind]);
input_model->set_tensor_value(place_to_freeze, freeze_values[ind]);
}
auto model = front_end->convert(input_model);
if (!model) {
throw "Model is not converted";
+3 -1
View File
@@ -22,7 +22,9 @@ std::shared_ptr<Model> convert_model(const std::string& model_path,
const ov::frontend::ConversionExtension::Ptr& conv_ext = nullptr,
const std::vector<std::string>& input_names = {},
const std::vector<ov::element::Type>& input_types = {},
const std::vector<ov::PartialShape>& input_shapes = {});
const std::vector<ov::PartialShape>& input_shapes = {},
const std::vector<std::string>& input_names_to_freeze = {},
const std::vector<void*>& freeze_values = {});
} // namespace tests
} // namespace tensorflow
@@ -0,0 +1,94 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <vector>
#include "helper_ops/switch.hpp"
#include "internal_operation.hpp"
#include "openvino/core/type/element_type.hpp"
#include "tf_utils.hpp"
namespace ov {
namespace frontend {
namespace tensorflow {
// Forwards the value of an available tensor from inputs to output
// It has two outputs:
// 1. output that is available by one of the inputs
// 2. value_index - index of available input
class Merge : public InternalOperation {
public:
OPENVINO_OP("Merge", "ov::frontend::tensorflow", InternalOperation);
Merge(OutputVector inputs, const std::shared_ptr<DecoderBase>& decoder = std::make_shared<DecoderFake>())
: InternalOperation(decoder, inputs, 2, "Merge") {
validate_and_infer_types();
}
void validate_and_infer_types() override {
ov::element::Type output_data_type = ov::element::dynamic;
ov::PartialShape output_data_shape = ov::PartialShape::dynamic();
auto input_size = get_input_size();
bool merge_output_shape = true;
for (size_t input_ind = 0; input_ind < input_size; ++input_ind) {
auto input_type = get_input_element_type(input_ind);
if (input_type.is_static()) {
output_data_type = input_type;
}
// check if it still needs to merge input shapes
// if yes, it tries to merge them
if (merge_output_shape &&
!PartialShape::merge_into(output_data_shape, get_input_partial_shape(input_ind))) {
merge_output_shape = false;
// reset output shape to dynamic rank
output_data_shape = ov::PartialShape::dynamic();
}
}
set_output_type(0, output_data_type, output_data_shape);
set_output_type(1, ov::element::i32, {});
}
bool is_cond_flow_eliminated() const {
const auto this_node = this->shared_from_this();
if (!cf_marker_exists(this_node)) {
return false;
}
auto cf_marker = get_cf_marker(this_node);
return cf_marker.merge_eliminated_markers.size() > 0;
}
std::vector<uint32_t> get_eliminated_cond_flow_marker() const {
std::vector<uint32_t> resulted_indices;
const auto this_node = this->shared_from_this();
FRONT_END_GENERAL_CHECK(
cf_marker_exists(this_node),
"[TensorFlow Frontend] internal error: Switch node has not set conditional flow marker");
auto cf_marker = get_cf_marker(this_node);
for (auto eliminated_marker : cf_marker.merge_eliminated_markers) {
resulted_indices.push_back(eliminated_marker.first);
}
return resulted_indices;
}
std::unordered_set<std::shared_ptr<Switch>> get_switch_nodes_set_by_cond_index(uint32_t switch_marker) {
const auto this_node = this->shared_from_this();
FRONT_END_GENERAL_CHECK(
cf_marker_exists(this_node),
"[TensorFlow Frontend] internal error: Switch node has not set conditional flow marker");
auto cf_marker = get_cf_marker(this_node);
FRONT_END_GENERAL_CHECK(
cf_marker.merge_eliminated_markers.count(switch_marker) > 0,
"[TensorFlow Frontend] internal error: no Switch node with requiested conditional flow marker");
return cf_marker.merge_eliminated_markers[switch_marker];
}
};
} // namespace tensorflow
} // namespace frontend
} // namespace ov
@@ -0,0 +1,76 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <string>
#include <vector>
#include "internal_operation.hpp"
#include "openvino/frontend/exception.hpp"
#include "tf_utils.hpp"
namespace ov {
namespace frontend {
namespace tensorflow {
// Internal operation for Switch
// Forwards data to the output port determined by predicate
// It has two outputs:
// 1. output_false - forward data to this output in case predicate to be false
// 2. output_true - forward data to this output in case predicate to be true
class Switch : public InternalOperation {
public:
OPENVINO_OP("Switch", "ov::frontend::tensorflow", InternalOperation);
Switch(const Output<Node>& data,
const Output<Node>& pred,
uint32_t switch_marker,
const std::shared_ptr<DecoderBase>& decoder = std::make_shared<DecoderFake>())
: InternalOperation(decoder, OutputVector{data, pred}, 2, "Switch"),
m_switch_marker(switch_marker) {
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);
set_output_type(1, data_type, data_shape);
}
bool is_cond_flow_marker_set() const {
const auto this_node = this->shared_from_this();
if (!cf_marker_exists(this_node)) {
return false;
}
auto cf_marker = get_cf_marker(this_node);
return cf_marker.new_markers.size() == 1;
}
uint32_t get_cond_flow_marker() const {
const auto this_node = this->shared_from_this();
FRONT_END_GENERAL_CHECK(
cf_marker_exists(this_node),
"[TensorFlow Frontend] internal error: Switch node has not set conditional flow marker");
auto cf_marker = get_cf_marker(this_node);
FRONT_END_GENERAL_CHECK(
cf_marker.new_markers.size() == 1,
"[TensorFlow Frontend] internal error: Switch node has incorrect value of conditional flow marker");
return cf_marker.new_markers.begin()->first;
}
uint32_t get_switch_marker() const {
return m_switch_marker;
}
private:
uint32_t m_switch_marker;
};
} // namespace tensorflow
} // namespace frontend
} // namespace ov
+13 -1
View File
@@ -82,12 +82,24 @@ def children(op, graph):
return set(op for out in op.outputs for op in out.consumers())
def collect_control_dependencies(graph):
control_dependents_map = {}
for op in graph.get_operations():
for control_input in op.control_inputs:
if control_input.name not in control_dependents_map:
control_dependents_map[control_input.name] = [op]
else:
control_dependents_map[control_input.name].append(op)
return control_dependents_map
def summarize_graph(model_path, output_nodes_for_freeze=None, reshape_net=None):
placeholders = dict()
variables = list()
outputs = list()
graph = load_graph(model_path, output_nodes_for_freeze)
unlikely_output_types = ['Const', 'Assign', 'NoOp', 'Placeholder', 'Assert', 'switch_t', 'switch_f']
control_dependents_map = collect_control_dependencies(graph)
for node in graph.as_graph_def().node:
if node.op == 'Placeholder':
node_dict = dict()
@@ -98,7 +110,7 @@ def summarize_graph(model_path, output_nodes_for_freeze=None, reshape_net=None):
placeholders[node.name] = node_dict
if node.op == "Variable" or node.op == "VariableV2":
variables.append(node.name)
if len(children(node.name, graph)) == 0:
if len(children(node.name, graph)) == 0 and node.name not in control_dependents_map:
if node.op not in unlikely_output_types and node.name.split('/')[-1] not in unlikely_output_types:
outputs.append(node.name)
result = dict()
@@ -59,9 +59,9 @@ class TestIfFloat(CommonTFLayerTest):
test_data_basic = [
dict(x_shape=[3], y_shape=[2, 3], lower_control_flow=False),
dict(x_shape=[3], y_shape=[2, 3], lower_control_flow=True),
dict(x_shape=[2, 1, 4], y_shape=[2, 1, 4], lower_control_flow=False),
pytest.param(dict(x_shape=[2, 1, 4], y_shape=[2, 1, 4], lower_control_flow=True),
marks=pytest.mark.xfail(reason="92632"))
dict(x_shape=[2, 1, 4], y_shape=[2, 1, 4], lower_control_flow=True)
]
@pytest.mark.parametrize("params", test_data_basic)
@@ -129,10 +129,9 @@ class TestIfInt(CommonTFLayerTest):
test_data_basic = [
dict(ind_shape=[3], y_shape=[2, 3], lower_control_flow=False),
dict(ind_shape=[3], y_shape=[2, 3], lower_control_flow=True),
dict(ind_shape=[2, 1, 4], y_shape=[2, 1, 4], lower_control_flow=False),
pytest.param(dict(ind_shape=[2, 1, 4], y_shape=[2, 1, 4], lower_control_flow=True),
marks=pytest.mark.xfail(reason="92632"))
dict(ind_shape=[2, 1, 4], y_shape=[2, 1, 4], lower_control_flow=True)
]
@pytest.mark.parametrize("params", test_data_basic)
@@ -145,3 +144,171 @@ class TestIfInt(CommonTFLayerTest):
self._test(*self.create_if_net(**params),
ie_device, precision, ir_version, temp_dir=temp_dir,
use_new_frontend=use_new_frontend, use_old_api=use_old_api)
class TestNestedIf(CommonTFLayerTest):
def _prepare_input(self, inputs_info):
assert 'x' in inputs_info, "Test error: inputs_info must contain `cond`"
assert 'y' in inputs_info, "Test error: inputs_info must contain `x`"
assert 'z' in inputs_info, "Test error: inputs_info must contain `y`"
x_shape = inputs_info['x']
y_shape = inputs_info['y']
z_shape = inputs_info['z']
inputs_data = {}
inputs_data['x'] = np.random.randint(0, 6, x_shape).astype(np.int32)
inputs_data['y'] = np.random.randint(1, 10, y_shape).astype(np.float32)
inputs_data['z'] = np.random.randint(-50, 50, z_shape).astype(np.float32)
return inputs_data
def create_if_net(self, y_shape, z_shape, lower_control_flow):
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
def if_function(x, y, z):
def nested_then_branch():
add = tf.add(y, z)
return add
def nested_else_branch():
mul = tf.multiply(y, z)
return mul
def then_branch():
output1 = tf.cond(x > 4, nested_then_branch, nested_else_branch)
output2 = tf.multiply(y, z)
output3 = tf.subtract(y, z)
return output1, output2, output3
def else_branch():
const_two = tf.constant(2.0, dtype=tf.float32)
output1 = tf.add(y, const_two)
output1 = tf.add(output1, z)
output2 = tf.multiply(z, y)
output3 = z - y + const_two
return output1, output2, output3
if_output = tf.cond(x < 2, then_branch, else_branch)
output1 = tf.identity(if_output[0], name='output1')
output2 = tf.identity(if_output[1], name='output2')
output3 = tf.identity(if_output[2], name='output3')
return output1, output2, output3
tf_if_graph = tf.function(if_function)
x = np.random.randint(0, 8, []).astype(np.int32)
y = np.random.randint(1, 10, y_shape).astype(np.float32)
z = np.random.randint(-50, 50, z_shape).astype(np.float32)
concrete_func = tf_if_graph.get_concrete_function(x, y, z)
# lower_control_flow defines representation of If operation
# in case of lower_control_flow=True it is decomposed into Switch and Merge nodes
frozen_func = convert_variables_to_constants_v2(concrete_func,
lower_control_flow=lower_control_flow)
tf_net = frozen_func.graph.as_graph_def(add_shapes=True)
return tf_net, None
test_data_basic = [
dict(y_shape=[3], z_shape=[2, 3], lower_control_flow=False),
dict(y_shape=[3], z_shape=[2, 3], lower_control_flow=True),
dict(y_shape=[2, 1, 4], z_shape=[2, 1, 4], lower_control_flow=False),
dict(y_shape=[2, 1, 4], z_shape=[2, 1, 4], lower_control_flow=True)
]
@pytest.mark.parametrize("params", test_data_basic)
@pytest.mark.precommit_tf_fe
@pytest.mark.nightly
def test_if_basic(self, params, ie_device, precision, ir_version, temp_dir,
use_new_frontend, use_old_api):
if ie_device == 'GPU':
pytest.xfail('104855')
self._test(*self.create_if_net(**params),
ie_device, precision, ir_version, temp_dir=temp_dir,
use_new_frontend=use_new_frontend, use_old_api=use_old_api)
class TestSequantialIfs(CommonTFLayerTest):
def _prepare_input(self, inputs_info):
assert 'cond' in inputs_info, "Test error: inputs_info must contain `cond`"
assert 'x' in inputs_info, "Test error: inputs_info must contain `x`"
assert 'y' in inputs_info, "Test error: inputs_info must contain `y`"
cond_shape = inputs_info['cond']
x_shape = inputs_info['x']
y_shape = inputs_info['y']
inputs_data = {}
inputs_data['cond'] = np.random.randint(0, 2, cond_shape).astype(bool)
inputs_data['x'] = np.random.randint(1, 10, x_shape).astype(np.float32)
inputs_data['y'] = np.random.randint(-50, 50, y_shape).astype(np.float32)
return inputs_data
def create_sequential_ifs_net(self, x_shape, y_shape, lower_control_flow):
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
def sequential_ifs(cond, x, y):
def if1(cond1, x1, y1):
def then_branch():
add = tf.add(x1, y1)
mul = tf.multiply(x1, y1)
return add, mul
def else_branch():
const_two = tf.constant(2.0, dtype=tf.float32)
add = tf.add(y1, const_two)
mul = tf.multiply(const_two, y1)
return add, mul
if1_op = tf.cond(cond1, then_branch, else_branch)
output1 = tf.identity(if1_op[0], name='output1')
output2 = tf.identity(if1_op[1], name='output2')
return output1, output2
def if2(cond1, x2, y2):
def then_branch():
const_two = tf.constant(2.0, dtype=tf.float32)
add = tf.add(y2, const_two)
mul = tf.multiply(const_two, y2)
return add, mul
def else_branch():
add = tf.add(x2, y2)
mul = tf.multiply(x2, y2)
return add, mul
if2_op = tf.cond(cond1, then_branch, else_branch)
output1 = tf.identity(if2_op[0], name='output1')
output2 = tf.identity(if2_op[1], name='output2')
return output1, output2
output1, output2 = if1(cond, x, y)
const_ten = tf.constant(10.0, dtype=tf.float32)
output1 = tf.add(output1, const_ten)
output1, output2 = if2(cond, output1, output2)
return output1, output2
tf_if_graph = tf.function(sequential_ifs)
cond = np.random.randint(0, 2, []).astype(bool)
x = np.random.randint(1, 10, x_shape).astype(np.float32)
y = np.random.randint(-50, 50, y_shape).astype(np.float32)
concrete_func = tf_if_graph.get_concrete_function(cond, x, y)
# lower_control_flow defines representation of If operation
# in case of lower_control_flow=True it is decomposed into Switch and Merge nodes
frozen_func = convert_variables_to_constants_v2(concrete_func,
lower_control_flow=lower_control_flow)
tf_net = frozen_func.graph.as_graph_def(add_shapes=True)
return tf_net, None
test_data_basic = [
dict(x_shape=[3], y_shape=[2, 3], lower_control_flow=False),
dict(x_shape=[3], y_shape=[2, 3], lower_control_flow=True),
dict(x_shape=[2, 1, 4], y_shape=[2, 1, 4], lower_control_flow=False),
dict(x_shape=[2, 1, 4], y_shape=[2, 1, 4], lower_control_flow=True)
]
@pytest.mark.parametrize("params", test_data_basic)
@pytest.mark.precommit_tf_fe
@pytest.mark.nightly
def test_if_basic(self, params, ie_device, precision, ir_version, temp_dir,
use_new_frontend, use_old_api):
if ie_device == 'GPU':
pytest.xfail('104855')
self._test(*self.create_sequential_ifs_net(**params),
ie_device, precision, ir_version, temp_dir=temp_dir,
use_new_frontend=use_new_frontend, use_old_api=use_old_api)
@@ -314,8 +314,6 @@ def update_fallback_with_conversion_error(use_new_frontend: bool, is_tf: bool, e
# corresponds to TF1 While operation
"TensorArrayScatterV3", "TensorArrayV3", "TensorArraySizeV3", "TensorArrayGatherV3",
"LoopCond", "Enter", "NextIteration", "Exit",
# corresponds to TF1 If and TF1 While operations
"Switch", "Merge",
# corresponds to operations with complex tensors
"FFT", "FFT2D", "FFT3D", "IFFT", "IFFT2D", "IFFT3D",
"RFFT", "RFFT2D", "RFFT3D", "IRFFT", "IRFFT2D", "IRFFT3D",
@@ -98,7 +98,7 @@ class TestInfoMessagesTFFEWithFallback(unittest.TestCase):
main(argparse.ArgumentParser())
std_out = f.getvalue()
tf_fe_message_found = get_tf_fe_message() in std_out
assert not tf_fe_message_found, 'TF FE Info message is found for the fallback case'
assert tf_fe_message_found, 'TF FE Info message is found for the fallback case'
class TestInfoMessagesCompressFP16(unittest.TestCase):
@@ -269,8 +269,6 @@ def update_fallback_with_conversion_error(use_new_frontend: bool, is_tf: bool, e
# corresponds to TF1 While operation
"TensorArrayScatterV3", "TensorArrayV3", "TensorArraySizeV3", "TensorArrayGatherV3",
"LoopCond", "Enter", "NextIteration", "Exit",
# corresponds to TF1 If and TF1 While operations
"Switch", "Merge",
# corresponds to operations with complex tensors
"FFT", "FFT2D", "FFT3D", "IFFT", "IFFT2D", "IFFT3D",
"RFFT", "RFFT2D", "RFFT3D", "IRFFT", "IRFFT2D", "IRFFT3D",
@@ -100,7 +100,7 @@ class TestInfoMessagesTFFEWithFallback(unittest.TestCase):
main()
std_out = f.getvalue()
tf_fe_message_found = get_try_legacy_fe_message() in std_out
assert not tf_fe_message_found, 'TF FE Info message is found for the fallback case'
assert tf_fe_message_found, 'TF FE Info message is found for the fallback case'
@patch('argparse.ArgumentParser.parse_args',
return_value=arg_parse_helper(input_model="model_int32.pbtxt",