[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:
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>();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)},
|
||||
|
||||
@@ -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
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
+59
@@ -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()
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user