[TF Hub][TF FE] Support TensorListLength and TensorListResize operations (#19390)
* [TF Hub][TF FE] Support TensorListLength and TensorListResize operations Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com> * Add test with empty tensor list * remove assert --------- Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>
This commit is contained in:
1 parent
ba6cca8740
commit
8df85badf8
5 files changed
+249
-46
No files matched your search
@@ -263,10 +263,12 @@ const std::map<std::string, CreatorFunction> get_supported_ops() {
|
||||
{"Switch", CreatorFunction(translate_switch_op)},
|
||||
{"TensorListFromTensor", CreatorFunction(translate_tensor_list_from_tensor_op)},
|
||||
{"TensorListGetItem", CreatorFunction(translate_tensor_list_get_item_op)},
|
||||
{"TensorListLength", CreatorFunction(translate_tensor_list_length_op)},
|
||||
{"TensorListPushBack", CreatorFunction(translate_tensor_list_push_back_op)},
|
||||
{"TensorListSetItem", CreatorFunction(translate_tensor_list_set_item_op)},
|
||||
{"TensorListStack", CreatorFunction(translate_tensor_list_stack_op)},
|
||||
{"TensorListReserve", CreatorFunction(translate_tensor_list_reserve_op)},
|
||||
{"TensorListResize", CreatorFunction(translate_tensor_list_resize_op)},
|
||||
{"Tile", CreatorFunction(translate_tile_op)},
|
||||
{"TopK", CreatorFunction(translate_top_k_op)},
|
||||
{"TopKV2", CreatorFunction(translate_top_k_v2_op)},
|
||||
|
||||
@@ -132,10 +132,12 @@ OP_CONVERTER(translate_strided_slice_op);
|
||||
OP_CONVERTER(translate_sqrt_op);
|
||||
OP_CONVERTER(translate_tensor_list_from_tensor_op);
|
||||
OP_CONVERTER(translate_tensor_list_get_item_op);
|
||||
OP_CONVERTER(translate_tensor_list_length_op);
|
||||
OP_CONVERTER(translate_tensor_list_push_back_op);
|
||||
OP_CONVERTER(translate_tensor_list_reserve_op);
|
||||
OP_CONVERTER(translate_tensor_list_set_item_op);
|
||||
OP_CONVERTER(translate_tensor_list_stack_op);
|
||||
OP_CONVERTER(translate_tensor_list_resize_op);
|
||||
OP_CONVERTER(translate_tile_op);
|
||||
OP_CONVERTER_NAMED(translate_top_k_op);
|
||||
OP_CONVERTER_NAMED(translate_top_k_v2_op);
|
||||
|
||||
@@ -3,12 +3,26 @@
|
||||
//
|
||||
|
||||
#include "common_op_table.hpp"
|
||||
#include "openvino/opsets/opset10.hpp"
|
||||
#include "openvino/op/add.hpp"
|
||||
#include "openvino/op/broadcast.hpp"
|
||||
#include "openvino/op/concat.hpp"
|
||||
#include "openvino/op/constant.hpp"
|
||||
#include "openvino/op/convert.hpp"
|
||||
#include "openvino/op/convert_like.hpp"
|
||||
#include "openvino/op/gather.hpp"
|
||||
#include "openvino/op/maximum.hpp"
|
||||
#include "openvino/op/reshape.hpp"
|
||||
#include "openvino/op/scatter_update.hpp"
|
||||
#include "openvino/op/shape_of.hpp"
|
||||
#include "openvino/op/slice.hpp"
|
||||
#include "openvino/op/squeeze.hpp"
|
||||
#include "openvino/op/subtract.hpp"
|
||||
#include "openvino/op/unsqueeze.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace ov;
|
||||
using namespace opset10;
|
||||
using namespace ov::op;
|
||||
|
||||
namespace ov {
|
||||
namespace frontend {
|
||||
@@ -22,7 +36,7 @@ OutputVector translate_tensor_list_reserve_op(const NodeContext& node) {
|
||||
// all tensor elements will be saved in the flatten form in the list
|
||||
// because we want to cover a case of dynamic rank tensor list
|
||||
// the real shape of the tensor elements will be restored by TensorListStack operations
|
||||
auto empty_constant = make_shared<Constant>(element_dtype, Shape{0, 0});
|
||||
auto empty_constant = make_shared<v0::Constant>(element_dtype, Shape{0, 0});
|
||||
set_node_name(node.get_name(), empty_constant);
|
||||
return {empty_constant};
|
||||
}
|
||||
@@ -41,14 +55,14 @@ OutputVector translate_tensor_list_stack_op(const NodeContext& node) {
|
||||
auto element_shape = node.get_input(1);
|
||||
|
||||
// compute number of tensor elements in the list
|
||||
Output<Node> num_elements = make_shared<ShapeOf>(input_handle, element::i32);
|
||||
auto zero_const = make_shared<Constant>(element::i32, Shape{1}, 0);
|
||||
auto one_const = make_shared<Constant>(element::i32, Shape{1}, 1);
|
||||
num_elements = make_shared<Slice>(num_elements, zero_const, one_const, one_const);
|
||||
Output<Node> num_elements = make_shared<v3::ShapeOf>(input_handle, element::i32);
|
||||
auto zero_const = make_shared<v0::Constant>(element::i32, Shape{1}, 0);
|
||||
auto one_const = make_shared<v0::Constant>(element::i32, Shape{1}, 1);
|
||||
num_elements = make_shared<v8::Slice>(num_elements, zero_const, one_const, one_const);
|
||||
|
||||
// restore the real shape of tensor elements
|
||||
auto new_shape = make_shared<Concat>(OutputVector{num_elements, element_shape}, 0);
|
||||
auto reshape = make_shared<Reshape>(input_handle, new_shape, false);
|
||||
auto new_shape = make_shared<v0::Concat>(OutputVector{num_elements, element_shape}, 0);
|
||||
auto reshape = make_shared<v1::Reshape>(input_handle, new_shape, false);
|
||||
|
||||
set_node_name(node.get_name(), reshape);
|
||||
return {reshape};
|
||||
@@ -62,12 +76,12 @@ OutputVector translate_tensor_list_get_item_op(const NodeContext& node) {
|
||||
auto element_dtype = node.get_attribute<element::Type>("element_dtype");
|
||||
|
||||
// squeeze index tensor to have a scalar
|
||||
index = make_shared<Squeeze>(index);
|
||||
index = make_shared<v0::Squeeze>(index);
|
||||
|
||||
// gather tensor element by the required position
|
||||
auto gather_axis = make_shared<Constant>(element::i32, Shape{1}, 0);
|
||||
Output<Node> tensor_element = make_shared<Gather>(input_handle, index, gather_axis);
|
||||
tensor_element = make_shared<Convert>(tensor_element, element_dtype);
|
||||
auto gather_axis = make_shared<v0::Constant>(element::i32, Shape{1}, 0);
|
||||
Output<Node> tensor_element = make_shared<v8::Gather>(input_handle, index, gather_axis);
|
||||
tensor_element = make_shared<v0::Convert>(tensor_element, element_dtype);
|
||||
|
||||
set_node_name(node.get_name(), tensor_element.get_node_shared_ptr());
|
||||
return {tensor_element};
|
||||
@@ -80,44 +94,44 @@ OutputVector translate_tensor_list_set_item_op(const NodeContext& node) {
|
||||
auto item = node.get_input(2);
|
||||
|
||||
// squeeze index tensor to have a scalar
|
||||
index = make_shared<Squeeze>(index);
|
||||
index = make_shared<v0::Squeeze>(index);
|
||||
|
||||
// flatten item to be inserted since
|
||||
// the tensor list saves elements in the flatten form
|
||||
auto new_item_shape = make_shared<Constant>(element::i32, Shape{1}, -1);
|
||||
item = make_shared<Reshape>(item, new_item_shape, false);
|
||||
auto item_shape = make_shared<ShapeOf>(item, element::i32);
|
||||
auto new_item_shape = make_shared<v0::Constant>(element::i32, Shape{1}, -1);
|
||||
item = make_shared<v1::Reshape>(item, new_item_shape, false);
|
||||
auto item_shape = make_shared<v3::ShapeOf>(item, element::i32);
|
||||
|
||||
// reshape the tensor list to the shape [num_elements, -1]
|
||||
// that is because in the first iteration we have empty constant of a shape [0,0]
|
||||
auto minus_one = make_shared<Constant>(element::i32, Shape{1}, -1);
|
||||
auto new_input_handle_shape = make_shared<Concat>(OutputVector{minus_one, item_shape}, 0);
|
||||
input_handle = make_shared<Reshape>(input_handle, new_input_handle_shape, false);
|
||||
input_handle = make_shared<ConvertLike>(input_handle, item);
|
||||
auto minus_one = make_shared<v0::Constant>(element::i32, Shape{1}, -1);
|
||||
auto new_input_handle_shape = make_shared<v0::Concat>(OutputVector{minus_one, item_shape}, 0);
|
||||
input_handle = make_shared<v1::Reshape>(input_handle, new_input_handle_shape, false);
|
||||
input_handle = make_shared<v1::ConvertLike>(input_handle, item);
|
||||
|
||||
// compute the current length of the list
|
||||
Output<Node> list_length = make_shared<ShapeOf>(input_handle, element::i32);
|
||||
auto zero_const = make_shared<Constant>(element::i32, Shape{1}, 0);
|
||||
auto one_const = make_shared<Constant>(element::i32, Shape{1}, 1);
|
||||
list_length = make_shared<Slice>(list_length, zero_const, one_const, one_const);
|
||||
Output<Node> list_length = make_shared<v3::ShapeOf>(input_handle, element::i32);
|
||||
auto zero_const = make_shared<v0::Constant>(element::i32, Shape{1}, 0);
|
||||
auto one_const = make_shared<v0::Constant>(element::i32, Shape{1}, 1);
|
||||
list_length = make_shared<v8::Slice>(list_length, zero_const, one_const, one_const);
|
||||
|
||||
// compute a size of the dummy tensor that serves to fill holes in the list
|
||||
// if no tensor is inserted at this position
|
||||
auto one_const_scalar = make_shared<Constant>(element::i32, Shape{1}, 1);
|
||||
auto index_plus_one = make_shared<Add>(index, one_const_scalar);
|
||||
Output<Node> max_length = make_shared<Maximum>(list_length, index_plus_one);
|
||||
Output<Node> dummy_tensor_size = make_shared<Subtract>(max_length, list_length);
|
||||
auto one_const_scalar = make_shared<v0::Constant>(element::i32, Shape{1}, 1);
|
||||
auto index_plus_one = make_shared<v1::Add>(index, one_const_scalar);
|
||||
Output<Node> max_length = make_shared<v1::Maximum>(list_length, index_plus_one);
|
||||
Output<Node> dummy_tensor_size = make_shared<v1::Subtract>(max_length, list_length);
|
||||
|
||||
// create dummy tensor and concatenate it
|
||||
auto zero_element = create_same_type_const_scalar<int32_t>(item, 0);
|
||||
auto dummy_tensor_shape = make_shared<Concat>(OutputVector{dummy_tensor_size, item_shape}, 0);
|
||||
auto dummy_tensor = make_shared<Broadcast>(zero_element, dummy_tensor_shape);
|
||||
input_handle = make_shared<Concat>(OutputVector{input_handle, dummy_tensor}, 0);
|
||||
auto dummy_tensor_shape = make_shared<v0::Concat>(OutputVector{dummy_tensor_size, item_shape}, 0);
|
||||
auto dummy_tensor = make_shared<v3::Broadcast>(zero_element, dummy_tensor_shape);
|
||||
input_handle = make_shared<v0::Concat>(OutputVector{input_handle, dummy_tensor}, 0);
|
||||
|
||||
// update the resulted tensor using ScatterUpdate
|
||||
index = make_shared<Unsqueeze>(index, zero_const);
|
||||
item = make_shared<Unsqueeze>(item, zero_const);
|
||||
auto scatter_update = make_shared<ScatterUpdate>(input_handle, index, item, zero_const);
|
||||
index = make_shared<v0::Unsqueeze>(index, zero_const);
|
||||
item = make_shared<v0::Unsqueeze>(item, zero_const);
|
||||
auto scatter_update = make_shared<v3::ScatterUpdate>(input_handle, index, item, zero_const);
|
||||
|
||||
set_node_name(node.get_name(), scatter_update);
|
||||
return {scatter_update};
|
||||
@@ -132,29 +146,82 @@ OutputVector translate_tensor_list_push_back_op(const NodeContext& node) {
|
||||
// the tensor list saves elements in the flatten form
|
||||
// because we want to cover a case of dynamic rank tensor list
|
||||
// the real shape of the tensor elements will be restored by TensorListStack operations
|
||||
auto new_tensor_shape = make_shared<Constant>(element::i32, Shape{1}, -1);
|
||||
tensor = make_shared<Reshape>(tensor, new_tensor_shape, false);
|
||||
auto tensor_shape = make_shared<ShapeOf>(tensor, element::i32);
|
||||
auto new_tensor_shape = make_shared<v0::Constant>(element::i32, Shape{1}, -1);
|
||||
tensor = make_shared<v1::Reshape>(tensor, new_tensor_shape, false);
|
||||
auto tensor_shape = make_shared<v3::ShapeOf>(tensor, element::i32);
|
||||
|
||||
// reshape the tensor list to the shape [num_elements, -1]
|
||||
// that is because in the first iteration we have empty constant of a shape [0,0]
|
||||
Output<Node> num_elements = make_shared<ShapeOf>(input_handle, element::i32);
|
||||
auto zero_const = make_shared<Constant>(element::i32, Shape{1}, 0);
|
||||
auto one_const = make_shared<Constant>(element::i32, Shape{1}, 1);
|
||||
num_elements = make_shared<Slice>(num_elements, zero_const, one_const, one_const);
|
||||
auto new_input_handle_shape = make_shared<Concat>(OutputVector{num_elements, tensor_shape}, 0);
|
||||
input_handle = make_shared<Reshape>(input_handle, new_input_handle_shape, false);
|
||||
Output<Node> num_elements = make_shared<v3::ShapeOf>(input_handle, element::i32);
|
||||
auto zero_const = make_shared<v0::Constant>(element::i32, Shape{1}, 0);
|
||||
auto one_const = make_shared<v0::Constant>(element::i32, Shape{1}, 1);
|
||||
num_elements = make_shared<v8::Slice>(num_elements, zero_const, one_const, one_const);
|
||||
auto new_input_handle_shape = make_shared<v0::Concat>(OutputVector{num_elements, tensor_shape}, 0);
|
||||
input_handle = make_shared<v1::Reshape>(input_handle, new_input_handle_shape, false);
|
||||
|
||||
// unsqueeze tensor to be inserted into the list
|
||||
tensor = make_shared<Unsqueeze>(tensor, zero_const);
|
||||
tensor = make_shared<v0::Unsqueeze>(tensor, zero_const);
|
||||
|
||||
// insert the tensor into the end
|
||||
auto updated_list = make_shared<Concat>(OutputVector{input_handle, tensor}, 0);
|
||||
auto updated_list = make_shared<v0::Concat>(OutputVector{input_handle, tensor}, 0);
|
||||
|
||||
set_node_name(node.get_name(), updated_list);
|
||||
return {updated_list};
|
||||
}
|
||||
|
||||
OutputVector translate_tensor_list_resize_op(const NodeContext& node) {
|
||||
default_op_checks(node, 2, {"TensorListResize"});
|
||||
auto input_handle = node.get_input(0);
|
||||
auto size = node.get_input(1);
|
||||
|
||||
// create auxiliary constants
|
||||
auto zero_const = make_shared<v0::Constant>(element::i32, Shape{1}, 0);
|
||||
auto one_const = make_shared<v0::Constant>(element::i32, Shape{1}, 1);
|
||||
auto max_const = make_shared<v0::Constant>(element::i32, Shape{1}, numeric_limits<int32_t>::max());
|
||||
|
||||
// compute the current length of the list and item shape
|
||||
auto tensor_list_shape = make_shared<v3::ShapeOf>(input_handle, element::i32);
|
||||
auto list_length = make_shared<v8::Slice>(tensor_list_shape, zero_const, one_const, one_const);
|
||||
auto item_shape = make_shared<v8::Slice>(tensor_list_shape, one_const, max_const, one_const);
|
||||
|
||||
// compute a size of the dummy tensor to resize
|
||||
// and clip it by zero if it is negative
|
||||
Output<Node> dummy_tensor_size = make_shared<v1::Subtract>(size, list_length);
|
||||
dummy_tensor_size = make_shared<v1::Maximum>(dummy_tensor_size, zero_const);
|
||||
|
||||
// create dummy tensor and concatenate it
|
||||
auto zero_const_same_type = create_same_type_const<float>(input_handle, vector<float>{0.0f}, Shape{});
|
||||
auto dummy_tensor_shape = make_shared<v0::Concat>(OutputVector{dummy_tensor_size, item_shape}, 0);
|
||||
auto dummy_tensor = make_shared<v3::Broadcast>(zero_const_same_type, dummy_tensor_shape);
|
||||
input_handle = make_shared<v0::Concat>(OutputVector{input_handle, dummy_tensor}, 0);
|
||||
|
||||
// reshape size to have 1D tensor with one element
|
||||
auto new_size_shape = make_shared<v0::Constant>(element::i32, Shape{1}, 1);
|
||||
size = make_shared<v1::Reshape>(size, new_size_shape, false);
|
||||
|
||||
// resize can also shrink the input tensor list
|
||||
input_handle = make_shared<v8::Slice>(input_handle, zero_const, size, one_const);
|
||||
|
||||
set_node_name(node.get_name(), input_handle.get_node_shared_ptr());
|
||||
return {input_handle};
|
||||
}
|
||||
|
||||
OutputVector translate_tensor_list_length_op(const NodeContext& node) {
|
||||
default_op_checks(node, 1, {"TensorListLength"});
|
||||
auto input_handle = node.get_input(0);
|
||||
|
||||
// create auxiliary constants
|
||||
auto zero_const = make_shared<v0::Constant>(element::i32, Shape{1}, 0);
|
||||
auto one_const = make_shared<v0::Constant>(element::i32, Shape{1}, 1);
|
||||
|
||||
// compute the current length of the list
|
||||
auto tensor_list_shape = make_shared<v3::ShapeOf>(input_handle, element::i32);
|
||||
auto list_length = make_shared<v8::Slice>(tensor_list_shape, zero_const, one_const, one_const);
|
||||
|
||||
set_node_name(node.get_name(), list_length);
|
||||
return {list_length};
|
||||
}
|
||||
|
||||
} // namespace op
|
||||
} // namespace tensorflow
|
||||
} // namespace frontend
|
||||
|
||||
Reference in new issue
Block a user