From b45e1a25a5d000fea514176677f421a23a055727 Mon Sep 17 00:00:00 2001 From: Ivan Tikhonov Date: Tue, 17 Nov 2020 07:04:20 +0300 Subject: [PATCH] LSTM/GRU/RNN Sequences : support for seq_lengths input (#2788) * sequences to ti transformations, support for seq_lengths input, update reference implemetations, add new tests * fix python api, update sequences to ti transformation * fix sequences to ti transformation * Update sequences to TI transformation: fix reverse sequence support * update single layer tests, fix TI reference impl, fix Sequences to TI transformations * ngraph code style * fix build * fix ngraph python api * resolver review comments, refactoring * Resolve review remarks * delete xfail --- inference-engine/src/cldnn_engine/cldnn_engine.cpp | 4 + .../src/mkldnn_plugin/mkldnn_plugin.cpp | 6 +- .../convert_sequences_to_tensor_iterator.hpp | 58 ++ .../common_optimizations/common_optimizations.cpp | 6 +- .../convert_sequences_to_tensor_iterator.cpp | 586 +++++++++++++++++++++ .../inference_engine/ngraph_reader/sequences.cpp | 470 +++++++++++++++++ .../single_layer_tests/gru_sequence.cpp | 14 +- .../single_layer_tests/lstm_sequence.cpp | 8 +- .../single_layer_tests/rnn_sequence.cpp | 11 +- .../include/single_layer_tests/gru_sequence.hpp | 10 +- .../include/single_layer_tests/lstm_sequence.hpp | 8 +- .../include/single_layer_tests/rnn_sequence.hpp | 7 +- .../shared/src/single_layer_tests/gru_sequence.cpp | 56 +- .../src/single_layer_tests/lstm_sequence.cpp | 54 +- .../shared/src/single_layer_tests/rnn_sequence.cpp | 52 +- .../src/single_layer_tests/tensor_iterator.cpp | 6 +- .../include/ngraph_functions/builders.hpp | 9 +- .../ngraph_functions/utils/ngraph_helpers.hpp | 12 + .../tests/ngraph_functions/src/gru_cell.cpp | 32 +- .../tests/ngraph_functions/src/lstm_cell.cpp | 32 +- .../tests/ngraph_functions/src/rnn_cell.cpp | 32 +- .../ngraph_functions/src/utils/ngraph_helpers.cpp | 35 ++ .../include/ngraph/runtime/reference/sequences.hpp | 266 +++++++--- .../src/runtime/reference/tensor_iterator.cpp | 13 +- ngraph/python/src/ngraph/opset1/ops.py | 20 +- ngraph/python/src/ngraph/opset3/ops.py | 8 +- ngraph/python/src/ngraph/opset5/ops.py | 285 ++++++---- ngraph/python/tests/__init__.py | 1 - ngraph/python/tests/test_ngraph/test_create_op.py | 585 +++++++++++++++++++- ngraph/python/tests/test_onnx/test_zoo_models.py | 4 +- ngraph/test/onnx/onnx_import_rnn.in.cpp | 2 +- ngraph/test/runtime/ie/unit_test.manifest | 12 +- ngraph/test/runtime/interpreter/int_executable.hpp | 222 ++++++-- ngraph/test/runtime/interpreter/unit_test.manifest | 11 - 34 files changed, 2594 insertions(+), 343 deletions(-) create mode 100644 inference-engine/src/transformations/include/transformations/op_conversions/convert_sequences_to_tensor_iterator.hpp create mode 100644 inference-engine/src/transformations/src/transformations/op_conversions/convert_sequences_to_tensor_iterator.cpp create mode 100644 inference-engine/tests/functional/inference_engine/ngraph_reader/sequences.cpp diff --git a/inference-engine/src/cldnn_engine/cldnn_engine.cpp b/inference-engine/src/cldnn_engine/cldnn_engine.cpp index b53c39d..0c9d657 100644 --- a/inference-engine/src/cldnn_engine/cldnn_engine.cpp +++ b/inference-engine/src/cldnn_engine/cldnn_engine.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -149,6 +150,9 @@ InferenceEngine::ICNNNetwork::Ptr clDNNEngine::CloneAndTransformNetwork(const In // WA: ConvertPriorBox must be executed before the 1st ConstantFolding pass manager.register_pass(); manager.register_pass(); + manager.register_pass(); + manager.register_pass(); + manager.register_pass(); manager.register_pass(); manager.register_pass(); diff --git a/inference-engine/src/mkldnn_plugin/mkldnn_plugin.cpp b/inference-engine/src/mkldnn_plugin/mkldnn_plugin.cpp index 220d306..21fc23c 100644 --- a/inference-engine/src/mkldnn_plugin/mkldnn_plugin.cpp +++ b/inference-engine/src/mkldnn_plugin/mkldnn_plugin.cpp @@ -32,7 +32,6 @@ #include #include -#include #include #include #include @@ -44,6 +43,8 @@ #include #include #include +#include +#include #include #include #include @@ -96,6 +97,9 @@ static void Transformation(ICNNNetwork::Ptr& clonedNetwork, const Config& conf) // WA: ConvertPriorBox must be executed before the 1st ConstantFolding pass manager.register_pass(); manager.register_pass(); + manager.register_pass(); + manager.register_pass(); + manager.register_pass(); manager.register_pass(); manager.register_pass(); diff --git a/inference-engine/src/transformations/include/transformations/op_conversions/convert_sequences_to_tensor_iterator.hpp b/inference-engine/src/transformations/include/transformations/op_conversions/convert_sequences_to_tensor_iterator.hpp new file mode 100644 index 0000000..550ee25 --- /dev/null +++ b/inference-engine/src/transformations/include/transformations/op_conversions/convert_sequences_to_tensor_iterator.hpp @@ -0,0 +1,58 @@ +// Copyright (C) 2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include + +#include + +#include + +namespace ngraph { +namespace pass { + +class TRANSFORMATIONS_API ConvertRNNSequenceToTensorIterator; +class TRANSFORMATIONS_API ConvertGRUSequenceToTensorIterator; +class TRANSFORMATIONS_API ConvertLSTMSequenceToTensorIterator; + +} // namespace pass +} // namespace ngraph + +/** + * @ingroup ie_transformation_common_api + * @brief ConvertRNNSequenceToTensorIterator transformation converts RNNSequence layer to TensorIterator + * * + */ + +class ngraph::pass::ConvertRNNSequenceToTensorIterator: public ngraph::pass::MatcherPass { +public: + NGRAPH_RTTI_DECLARATION; + ConvertRNNSequenceToTensorIterator(); +}; + +/** + * @ingroup ie_transformation_common_api + * @brief ConvertGRUSequenceToTensorIterator transformation converts GRUSequence layer to TensorIterator + * * + */ + +class ngraph::pass::ConvertGRUSequenceToTensorIterator: public ngraph::pass::MatcherPass { +public: + NGRAPH_RTTI_DECLARATION; + ConvertGRUSequenceToTensorIterator(); +}; + +/** + * @ingroup ie_transformation_common_api + * @brief ConvertLSTMSequenceToTensorIterator transformation converts LSTMSequence layer to TensorIterator + * * + */ + +class ngraph::pass::ConvertLSTMSequenceToTensorIterator: public ngraph::pass::MatcherPass { +public: + NGRAPH_RTTI_DECLARATION; + ConvertLSTMSequenceToTensorIterator(); +}; \ No newline at end of file diff --git a/inference-engine/src/transformations/src/transformations/common_optimizations/common_optimizations.cpp b/inference-engine/src/transformations/src/transformations/common_optimizations/common_optimizations.cpp index a0fc084..b3b5801 100644 --- a/inference-engine/src/transformations/src/transformations/common_optimizations/common_optimizations.cpp +++ b/inference-engine/src/transformations/src/transformations/common_optimizations/common_optimizations.cpp @@ -73,11 +73,11 @@ bool ngraph::pass::CommonOptimizations::run_on_function(std::shared_ptr(); manager.register_pass(); manager.register_pass(); - manager.register_pass(); - manager.register_pass(); - manager.register_pass(); auto decomp = manager.register_pass(); + decomp->add_matcher(); + decomp->add_matcher(); + decomp->add_matcher(); decomp->add_matcher(); decomp->add_matcher(); decomp->add_matcher(); diff --git a/inference-engine/src/transformations/src/transformations/op_conversions/convert_sequences_to_tensor_iterator.cpp b/inference-engine/src/transformations/src/transformations/op_conversions/convert_sequences_to_tensor_iterator.cpp new file mode 100644 index 0000000..6eabe50 --- /dev/null +++ b/inference-engine/src/transformations/src/transformations/op_conversions/convert_sequences_to_tensor_iterator.cpp @@ -0,0 +1,586 @@ +// Copyright (C) 2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// +#include "ngraph/builder/autobroadcast.hpp" +#include "transformations/op_conversions/convert_sequences_to_tensor_iterator.hpp" + +#include +#include + +#include +#include +#include +#include + +NGRAPH_RTTI_DEFINITION(ngraph::pass::ConvertRNNSequenceToTensorIterator, "ConvertRNNSequenceToTensorIterator", 0); +NGRAPH_RTTI_DEFINITION(ngraph::pass::ConvertGRUSequenceToTensorIterator, "ConvertGRUSequenceToTensorIterator", 0); +NGRAPH_RTTI_DEFINITION(ngraph::pass::ConvertLSTMSequenceToTensorIterator, "ConvertLSTMSequenceToTensorIterator", 0); + +namespace { + ngraph::Output get_current_iter(ngraph::ParameterVector &body_params, + ngraph::ResultVector &body_results, + const ngraph::Output &seq_lengths) { + auto curr_iter_body_param = std::make_shared(seq_lengths.get_element_type(), + ngraph::Shape{1}); + // increment current iteration + auto one = std::make_shared(seq_lengths.get_element_type(), ngraph::Shape{1}, + std::vector{1}); + auto add = std::make_shared(curr_iter_body_param, one); + auto curr_iter_result = std::make_shared(add); + body_params.push_back(curr_iter_body_param); + body_results.push_back(curr_iter_result); + return curr_iter_body_param; + } + + ngraph::Output get_masked_value(const std::shared_ptr &ti, + ngraph::ParameterVector &body_params, + ngraph::ResultVector &body_results, + const ngraph::Output ¤t_iter, + const ngraph::Output &data, + const ngraph::Output &seq_lengths) { + const auto &data_type = data.get_element_type(); + const auto &data_shape = data.get_shape(); + const auto &data_shape_size = ngraph::shape_size(data_shape); + + // body parameters + auto aggregated_Y_h_body_param = std::make_shared(data_type, data_shape); + + body_params.push_back(aggregated_Y_h_body_param); + + // Create mask node deciding whether or not to mask batch data. + ngraph::Output batch_seq_length = ngraph::builder::opset1::legacy_broadcast_for_binary_operation( + data, seq_lengths, 0); + auto mask_value = std::make_shared(data_type, data_shape, std::vector(data_shape_size, 0.f)); + auto mask_condition = std::make_shared(current_iter, batch_seq_length); + auto mask_Y_h = std::make_shared(current_iter, batch_seq_length); + + // Select values depending on mask. + // Select(, , ) + auto select_aggregated_H = std::make_shared(mask_Y_h, data, aggregated_Y_h_body_param); + auto aggregated_result = std::make_shared(select_aggregated_H); + body_results.push_back(aggregated_result); + return std::make_shared(mask_condition, mask_value, data); + } + + ngraph::NodeVector squeeze_nodes(const ngraph::OutputVector &nodes_to_squeeze, const ngraph::OutputVector &axes) { + ngraph::NodeVector squeezed_nodes(nodes_to_squeeze.size()); + for (size_t i = 0; i < nodes_to_squeeze.size(); ++i) { + squeezed_nodes[i] = std::make_shared(nodes_to_squeeze[i], axes[i]); + } + return squeezed_nodes; + } + + bool should_enable_mask(const ngraph::Output &seq_lengths, size_t max_seq_len) { + // disable the mask if all values of seq_lengths input are equal to max_seq_len (X_shape[1]) + if (const auto &seq_len_const = std::dynamic_pointer_cast( + seq_lengths.get_node_shared_ptr())) { + const auto &seq_len_values = seq_len_const->cast_vector(); + return std::any_of(seq_len_values.begin(), seq_len_values.end(), [max_seq_len](const int64_t val) { + return val != max_seq_len; + }); + } + return true; + } +} // namespace + +ngraph::pass::ConvertRNNSequenceToTensorIterator::ConvertRNNSequenceToTensorIterator() { + // X, H, seq_lengths - static, W,R,B - any + auto rnn_seq = ngraph::pattern::wrap_type({pattern::any_input(pattern::has_static_shape()), + pattern::any_input(pattern::has_static_shape()), + pattern::any_input(pattern::has_static_shape()), + pattern::any_input(), + pattern::any_input(), + pattern::any_input()}); + ngraph::matcher_pass_callback callback = [](ngraph::pattern::Matcher &m) { + auto sequence = std::dynamic_pointer_cast(m.get_match_root()); + + // Bidirectional Sequence op should be decomposed to Reverse + Forward + // (e.g. apply BidirectionalRNNSequenceDecomposition transformation before this one) + if (!sequence || sequence->get_direction() == ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL) { + return false; + } + + NodeVector new_nodes; + const auto &X = sequence->input_value(0); // split + const auto &H_t = sequence->input_value(1); // merged (init value + back edge) + const auto &seq_lengths = sequence->input_value(2); // invariant + const auto &W = sequence->input_value(3); // const in the body + const auto &R = sequence->input_value(4); // const in the body + const auto &B = sequence->input_value(5); // const in the body + bool is_reverse = sequence->get_direction() == ngraph::op::RecurrentSequenceDirection::REVERSE; + + auto tensor_iterator = std::make_shared(); + auto max_seq_len = X.get_shape().at(1); + bool enable_mask = should_enable_mask(seq_lengths, max_seq_len); + + std::shared_ptr reverse_seq_before; + if (is_reverse && enable_mask) { + reverse_seq_before = std::make_shared(X, seq_lengths, 0, 1); + } + + // TensorIterator Body: begin + Shape X_param_shape = X.get_shape(); + X_param_shape.at(1) = 1; // split by seq_lengths dimension + auto X_body_param = std::make_shared(X.get_element_type(), X_param_shape); + auto H_body_param = std::make_shared(H_t.get_element_type(), + H_t.get_shape()); + auto seq_body_param = std::make_shared(seq_lengths.get_element_type(), + seq_lengths.get_partial_shape()); + + auto axis_0 = std::make_shared(element::i64, Shape{1}, std::vector{0}); + auto axis_1 = std::make_shared(element::i64, Shape{1}, std::vector{1}); + + const auto& ins = squeeze_nodes({X_body_param, H_body_param, W, R, B}, {axis_1, axis_1, axis_0, axis_0, axis_0}); + auto cell = std::make_shared(ins[0], + ins[1], + ins[2], + ins[3], + ins[4], + sequence->get_hidden_size(), + sequence->get_activations(), + sequence->get_activations_alpha(), + sequence->get_activations_beta(), + sequence->get_clip()); + + ParameterVector body_params; + ResultVector body_results; + auto unsqueeze_dum_dir = std::make_shared(cell->output(0), axis_1); + Output h_node_to_result = unsqueeze_dum_dir; + if (enable_mask) { + auto current_iter = get_current_iter(body_params, body_results, seq_body_param); + h_node_to_result = get_masked_value(tensor_iterator, body_params, body_results, current_iter, + unsqueeze_dum_dir, seq_body_param); + } + + auto H_res = std::make_shared(h_node_to_result); + auto unsqueeze_seq_len = std::make_shared(h_node_to_result, axis_1); + auto concat_res = std::make_shared(unsqueeze_seq_len); + body_params.push_back(X_body_param); + body_params.push_back(H_body_param); + body_params.push_back(seq_body_param); + body_results.push_back(concat_res); + body_results.push_back(H_res); + + auto body = std::make_shared(body_results, body_params); + tensor_iterator->set_function(body); + // TensorIterator Body: end + + if (is_reverse) { + if (!enable_mask) { + // Reversed order, stride -1 + tensor_iterator->set_sliced_input(X_body_param, X, -1, -1, 1, 0, 1); + tensor_iterator->get_concatenated_slices(concat_res, -1, -1, 1, 0, 2); + } else { + // use ReverseSequence as initializer + tensor_iterator->set_sliced_input(X_body_param, reverse_seq_before, 0, 1, 1, -1, 1); + tensor_iterator->get_concatenated_slices(concat_res, 0, 1, 1, -1, 2); + } + } else { + // forward order + tensor_iterator->set_sliced_input(X_body_param, X, 0, 1, 1, -1, 1); + tensor_iterator->get_concatenated_slices(concat_res, 0, 1, 1, -1, 2); + } + tensor_iterator->set_merged_input(H_body_param, H_t, H_res); + tensor_iterator->set_invariant_input(seq_body_param, seq_lengths); + Output H_out = H_res; + if (enable_mask) { + // create initial values for body_parameters in outer graph + // aggregated Y_h - concatenation of the last non-zero values for each batch + auto aggregated_Y_h = std::make_shared(H_body_param->get_element_type(), + H_body_param->get_shape(), + std::vector(shape_size(H_body_param->get_shape()), + 0.f)); + auto init_val_curr_iter = std::make_shared(seq_lengths.get_element_type(), + ngraph::Shape{1}, + std::vector{1}); + ngraph::copy_runtime_info(sequence, {aggregated_Y_h, init_val_curr_iter}); + + // set initial value and back edge for current iteration + tensor_iterator->set_merged_input(body_params.at(0), init_val_curr_iter, body_results.at(0)); + // set initial value and back edge for aggregated H + tensor_iterator->set_merged_input(body_params.at(1), aggregated_Y_h, body_results.at(1)); + H_out = tensor_iterator->get_function()->get_results()[1]; + } + tensor_iterator->get_iter_value(H_out); + tensor_iterator->set_friendly_name(sequence->get_friendly_name()); + if (enable_mask && is_reverse) { + auto reverse_seq_after = std::make_shared(tensor_iterator->output(0), seq_lengths, 0, 2); + // Resolve a collision of names data nodes in CNN Network in Reverse case with mask. + /* + * Before transformation (no collisions) + * RNN/LSTM/GRU Sequence [rnn_name] -- (data_node: rnn_name.0) - > Result1 + * -- (data_node: rnn_name.1) - > Result2 + * + * + * After transformation (without identity, there are collisions): + * We need to set rnn_name.0 to RevSequence to store result name. + * TI [rnn_name] -- (DATA_NODE: rnn_name.0) --> RevSequence [rnn_name.0] -- (DATA_NODE: rnn_name.0) -> Result1 + * -- (data_node: rnn_name.1) --> Result2 + * + * + * After transformation (with identity, no collisions): + * TI has other_name, but it doesn't affect result names due TI is not connected to Results directly. + * TI [other_name] -- (data_node: other_name.0) --> RevSequence [rnn_name.0] -- (data_node: rnn_name.0) -> Result1 + * -- (data_node: other_name.1) --> Identity(rnn_name.1) -- (data_node: rnn_name.1) -> Result2 + */ + auto identity_1 = std::make_shared(tensor_iterator->output(1), axis_1); + auto identity_2 = std::make_shared(identity_1, axis_1); + ngraph::copy_runtime_info(sequence, {reverse_seq_after, tensor_iterator, identity_1, identity_2, reverse_seq_before}); + ngraph::replace_node(sequence, {reverse_seq_after, identity_2}); + tensor_iterator->set_friendly_name(sequence->get_friendly_name() + "/tensor_iterator"); + reverse_seq_after->set_friendly_name(sequence->get_friendly_name() + ".0"); + identity_2->set_friendly_name(sequence->get_friendly_name() + ".1"); + } else { + ngraph::copy_runtime_info(sequence, tensor_iterator); + ngraph::replace_node(sequence, tensor_iterator); + } + return true; + }; + + auto m = std::make_shared(rnn_seq, "ConvertRNNSequenceToTensorIterator"); + register_matcher(m, callback); +} + +ngraph::pass::ConvertGRUSequenceToTensorIterator::ConvertGRUSequenceToTensorIterator() { + // X, H, seq_lengths - static, W,R,B - any + auto rnn_seq = ngraph::pattern::wrap_type({pattern::any_input(pattern::has_static_shape()), + pattern::any_input(pattern::has_static_shape()), + pattern::any_input(pattern::has_static_shape()), + pattern::any_input(), + pattern::any_input(), + pattern::any_input()}); + ngraph::matcher_pass_callback callback = [](ngraph::pattern::Matcher &m) { + auto sequence = std::dynamic_pointer_cast(m.get_match_root()); + + // Bidirectional Sequence op should be decomposed to Reverse + Forward + // (e.g. apply BidirectionalRNNSequenceDecomposition transformation before this one) + if (!sequence || sequence->get_direction() == ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL) { + return false; + } + + NodeVector new_nodes; + const auto &X = sequence->input_value(0); // split + const auto &H_t = sequence->input_value(1); // merged (init value + back edge) + const auto &seq_lengths = sequence->input_value(2); // invariant + const auto &W = sequence->input_value(3); // const in the body + const auto &R = sequence->input_value(4); // const in the body + const auto &B = sequence->input_value(5); // const in the body + bool is_reverse = sequence->get_direction() == ngraph::op::RecurrentSequenceDirection::REVERSE; + + auto tensor_iterator = std::make_shared(); + auto max_seq_len = X.get_shape().at(1); + bool enable_mask = should_enable_mask(seq_lengths, max_seq_len); + + std::shared_ptr reverse_seq_before; + if (is_reverse && enable_mask) { + reverse_seq_before = std::make_shared(X, seq_lengths, 0, 1); + } + // TensorIterator Body: begin + Shape X_param_shape = X.get_shape(); + X_param_shape.at(1) = 1; // split by seq_lengths dimension + auto X_body_param = std::make_shared(X.get_element_type(), X_param_shape); + auto H_body_param = std::make_shared(H_t.get_element_type(), + H_t.get_shape()); + auto seq_body_param = std::make_shared(seq_lengths.get_element_type(), + seq_lengths.get_partial_shape()); + + auto axis_0 = std::make_shared(element::i64, Shape{1}, std::vector{0}); + auto axis_1 = std::make_shared(element::i64, Shape{1}, std::vector{1}); + + const auto& ins = squeeze_nodes({X_body_param, H_body_param, W, R, B}, {axis_1, axis_1, axis_0, axis_0, axis_0}); + auto cell = std::make_shared(ins[0], + ins[1], + ins[2], + ins[3], + ins[4], + sequence->get_hidden_size(), + sequence->get_activations(), + sequence->get_activations_alpha(), + sequence->get_activations_beta(), + sequence->get_clip(), + sequence->get_linear_before_reset()); + + ParameterVector body_params; + ResultVector body_results; + auto unsqueeze_dum_dir = std::make_shared(cell->output(0), axis_1); + Output h_node_to_result = unsqueeze_dum_dir; + if (enable_mask) { + auto current_iter = get_current_iter(body_params, body_results, seq_body_param); + h_node_to_result = get_masked_value(tensor_iterator, body_params, body_results, current_iter, + unsqueeze_dum_dir, seq_body_param); + } + + auto H_res = std::make_shared(h_node_to_result); + auto unsqueeze_seq_len = std::make_shared(h_node_to_result, axis_1); + auto concat_res = std::make_shared(unsqueeze_seq_len); + body_params.push_back(X_body_param); + body_params.push_back(H_body_param); + body_params.push_back(seq_body_param); + body_results.push_back(concat_res); + body_results.push_back(H_res); + + auto body = std::make_shared(body_results, body_params); + tensor_iterator->set_function(body); + // TensorIterator Body: end + + if (is_reverse) { + if (!enable_mask) { + // Reversed order, stride -1 + tensor_iterator->set_sliced_input(X_body_param, X, -1, -1, 1, 0, 1); + tensor_iterator->get_concatenated_slices(concat_res, -1, -1, 1, 0, 2); + } else { + // use ReverseSequence as initializer + tensor_iterator->set_sliced_input(X_body_param, reverse_seq_before, 0, 1, 1, -1, 1); + tensor_iterator->get_concatenated_slices(concat_res, 0, 1, 1, -1, 2); + } + } else { + // forward order + tensor_iterator->set_sliced_input(X_body_param, X, 0, 1, 1, -1, 1); + tensor_iterator->get_concatenated_slices(concat_res, 0, 1, 1, -1, 2); + } + tensor_iterator->set_merged_input(H_body_param, H_t, H_res); + tensor_iterator->set_invariant_input(seq_body_param, seq_lengths); + Output H_out = H_res; + if (enable_mask) { + // create initial values for body_parameters in outer graph + // aggregated Y_h - concatenation of the last non-zero values for each batch + auto aggregated_Y_h = std::make_shared(H_body_param->get_element_type(), + H_body_param->get_shape(), + std::vector(shape_size(H_body_param->get_shape()), + 0.f)); + auto init_val_curr_iter = std::make_shared(seq_lengths.get_element_type(), + ngraph::Shape{1}, + std::vector{1}); + ngraph::copy_runtime_info(sequence, {aggregated_Y_h, init_val_curr_iter}); + + // set initial value and back edge for current iteration + tensor_iterator->set_merged_input(body_params.at(0), init_val_curr_iter, body_results.at(0)); + // set initial value and back edge for aggregated H + tensor_iterator->set_merged_input(body_params.at(1), aggregated_Y_h, body_results.at(1)); + H_out = tensor_iterator->get_function()->get_results()[1]; + } + tensor_iterator->get_iter_value(H_out); + tensor_iterator->set_friendly_name(sequence->get_friendly_name()); + if (enable_mask && is_reverse) { + auto reverse_seq_after = std::make_shared(tensor_iterator->output(0), seq_lengths, 0, 2); + // Resolve a collision of names data nodes in CNN Network in Reverse case with mask. + /* + * Before transformation (no collisions) + * RNN/LSTM/GRU Sequence [rnn_name] -- (data_node: rnn_name.0) - > Result1 + * -- (data_node: rnn_name.1) - > Result2 + * + * + * After transformation (without identity, there are collisions): + * We need to set rnn_name.0 to RevSequence to store result name. + * TI [rnn_name] -- (DATA_NODE: rnn_name.0) --> RevSequence [rnn_name.0] -- (DATA_NODE: rnn_name.0) -> Result1 + * -- (data_node: rnn_name.1) --> Result2 + * + * + * After transformation (with identity, no collisions): + * TI has other_name, but it doesn't affect result names due TI is not connected to Results directly. + * TI [other_name] -- (data_node: other_name.0) --> RevSequence [rnn_name.0] -- (data_node: rnn_name.0) -> Result1 + * -- (data_node: other_name.1) --> Identity(rnn_name.1) -- (data_node: rnn_name.1) -> Result2 + */ + auto identity_1 = std::make_shared(tensor_iterator->output(1), axis_1); + auto identity_2 = std::make_shared(identity_1, axis_1); + ngraph::copy_runtime_info(sequence, {reverse_seq_after, tensor_iterator, reverse_seq_before, identity_2, identity_1}); + ngraph::replace_node(sequence, {reverse_seq_after, identity_2}); + tensor_iterator->set_friendly_name(sequence->get_friendly_name() + "/tensor_iterator"); + reverse_seq_after->set_friendly_name(sequence->get_friendly_name() + ".0"); + identity_2->set_friendly_name(sequence->get_friendly_name() + ".1"); + } else { + ngraph::copy_runtime_info(sequence, tensor_iterator); + ngraph::replace_node(sequence, tensor_iterator); + } + return true; + }; + + auto m = std::make_shared(rnn_seq, "ConvertGRUSequenceToTensorIterator"); + register_matcher(m, callback); +} + +ngraph::pass::ConvertLSTMSequenceToTensorIterator::ConvertLSTMSequenceToTensorIterator() { + // X, H, C, seq_lengths - static, W,R,B - any + auto rnn_seq = ngraph::pattern::wrap_type({pattern::any_input(pattern::has_static_shape()), + pattern::any_input(pattern::has_static_shape()), + pattern::any_input(pattern::has_static_shape()), + pattern::any_input(pattern::has_static_shape()), + pattern::any_input(), + pattern::any_input(), + pattern::any_input()}); + ngraph::matcher_pass_callback callback = [](ngraph::pattern::Matcher &m) { + auto sequence = std::dynamic_pointer_cast(m.get_match_root()); + + // Bidirectional Sequence op should be decomposed to Reverse + Forward + // (e.g. apply BidirectionalRNNSequenceDecomposition transformation before this one) + if (!sequence || sequence->get_direction() == ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL) { + return false; + } + + NodeVector new_nodes; + const auto &X = sequence->input_value(0); // split + const auto &H_t = sequence->input_value(1); // merged (init value + back edge) + const auto &C_t = sequence->input_value(2); // merged (init value + back edge) + const auto &seq_lengths = sequence->input_value(3); // invariant + const auto &W = sequence->input_value(4); // const in the body + const auto &R = sequence->input_value(5); // const in the body + const auto &B = sequence->input_value(6); // const in the body + bool is_reverse = sequence->get_direction() == ngraph::op::RecurrentSequenceDirection::REVERSE; + + auto tensor_iterator = std::make_shared(); + auto max_seq_len = X.get_shape().at(1); + bool enable_mask = should_enable_mask(seq_lengths, max_seq_len); + + std::shared_ptr reverse_seq_before; + if (is_reverse && enable_mask) { + reverse_seq_before = std::make_shared(X, seq_lengths, 0, 1); + } + + // TensorIterator Body: begin + Shape X_param_shape = X.get_shape(); + X_param_shape.at(1) = 1; // split by seq_lengths dimension + auto X_body_param = std::make_shared(X.get_element_type(), X_param_shape); + auto H_body_param = std::make_shared(H_t.get_element_type(), + H_t.get_shape()); + auto C_body_param = std::make_shared(C_t.get_element_type(), + C_t.get_partial_shape()); + auto seq_body_param = std::make_shared(seq_lengths.get_element_type(), + seq_lengths.get_partial_shape()); + + auto axis_0 = std::make_shared(element::i64, Shape{1}, std::vector{0}); + auto axis_1 = std::make_shared(element::i64, Shape{1}, std::vector{1}); + + const auto& ins = squeeze_nodes({X_body_param, H_body_param, C_body_param, W, R, B}, + {axis_1, axis_1, axis_1, axis_0, axis_0, axis_0}); + auto cell = std::make_shared(ins[0], + ins[1], + ins[2], + ins[3], + ins[4], + ins[5], + sequence->get_hidden_size(), + sequence->get_activations(), + sequence->get_activations_alpha(), + sequence->get_activations_beta(), + sequence->get_clip()); + + ParameterVector body_params; + ResultVector body_results; + auto unsqueeze_dum_dir_h = std::make_shared(cell->output(0), axis_1); + auto unsqueeze_dum_dir_c = std::make_shared(cell->output(1), axis_1); + Output h_node_to_result = unsqueeze_dum_dir_h; + Output c_node_to_result = unsqueeze_dum_dir_c; + if (enable_mask) { + auto current_iter = get_current_iter(body_params, body_results, seq_body_param); + h_node_to_result = get_masked_value(tensor_iterator, body_params, body_results, current_iter, + unsqueeze_dum_dir_h, seq_body_param); + c_node_to_result = get_masked_value(tensor_iterator, body_params, body_results, current_iter, + unsqueeze_dum_dir_c, seq_body_param); + } + + auto H_res = std::make_shared(h_node_to_result); + auto C_res = std::make_shared(c_node_to_result); + auto unsqueeze_seq_len = std::make_shared(h_node_to_result, axis_1); + auto concat_res = std::make_shared(unsqueeze_seq_len); + body_params.push_back(X_body_param); + body_params.push_back(H_body_param); + body_params.push_back(C_body_param); + body_params.push_back(seq_body_param); + body_results.push_back(concat_res); + body_results.push_back(H_res); + body_results.push_back(C_res); + + auto body = std::make_shared(body_results, body_params); + tensor_iterator->set_function(body); + // TensorIterator Body: end + if (is_reverse) { + if (!enable_mask) { + // Reversed order, stride -1 + tensor_iterator->set_sliced_input(X_body_param, X, -1, -1, 1, 0, 1); + tensor_iterator->get_concatenated_slices(concat_res, -1, -1, 1, 0, 2); + } else { + // use ReverseSequence as initializer + tensor_iterator->set_sliced_input(X_body_param, reverse_seq_before, 0, 1, 1, -1, 1); + tensor_iterator->get_concatenated_slices(concat_res, 0, 1, 1, -1, 2); + } + } else { + // forward order + tensor_iterator->set_sliced_input(X_body_param, X, 0, 1, 1, -1, 1); + tensor_iterator->get_concatenated_slices(concat_res, 0, 1, 1, -1, 2); + } + tensor_iterator->set_merged_input(H_body_param, H_t, H_res); + tensor_iterator->set_merged_input(C_body_param, C_t, C_res); + tensor_iterator->set_invariant_input(seq_body_param, seq_lengths); + Output H_out = H_res; + Output C_out = C_res; + if (enable_mask) { + // create initial values for body_parameters in outer graph + // aggregated Y_h - concatenation of the last non-zero values for each batch + auto aggregated_Y_h = std::make_shared(H_body_param->get_element_type(), + H_body_param->get_shape(), + std::vector(shape_size(H_body_param->get_shape()), + 0.f)); + auto aggregated_Y_c = std::make_shared(C_body_param->get_element_type(), + C_body_param->get_shape(), + std::vector(shape_size(C_body_param->get_shape()), + 0.f)); + auto init_val_curr_iter = std::make_shared(seq_lengths.get_element_type(), + ngraph::Shape{1}, + std::vector{1}); + ngraph::copy_runtime_info(sequence, {aggregated_Y_h, init_val_curr_iter, aggregated_Y_c}); + + // set initial value and back edge for current iteration + tensor_iterator->set_merged_input(body_params.at(0), init_val_curr_iter, body_results.at(0)); + // set initial value and back edge for aggregated H + tensor_iterator->set_merged_input(body_params.at(1), aggregated_Y_h, body_results.at(1)); + // set initial value and back edge for aggregated H + tensor_iterator->set_merged_input(body_params.at(2), aggregated_Y_c, body_results.at(2)); + H_out = tensor_iterator->get_function()->get_results()[1]; + C_out = tensor_iterator->get_function()->get_results()[2]; + } + tensor_iterator->get_iter_value(H_out); + tensor_iterator->get_iter_value(C_out); + tensor_iterator->set_friendly_name(sequence->get_friendly_name()); + if (enable_mask && is_reverse) { + auto reverse_seq_after = std::make_shared(tensor_iterator->output(0), seq_lengths, 0, 2); + // Resolve a collision of names data nodes in CNN Network in Reverse case with mask. + /* + * Before transformation (no collisions) + * RNN/LSTM/GRU Sequence [rnn_name] -- (data_node: rnn_name.0) - > Result1 + * -- (data_node: rnn_name.1) - > Result2 + * + * + * After transformation (without identity, there are collisions): + * We need to set rnn_name.0 to RevSequence to store result name. + * TI [rnn_name] -- (DATA_NODE: rnn_name.0) --> RevSequence [rnn_name.0] -- (DATA_NODE: rnn_name.0) -> Result1 + * -- (data_node: rnn_name.1) --> Result2 + * + * + * After transformation (with identity, no collisions): + * TI has other_name, but it doesn't affect result names due TI is not connected to Results directly. + * TI [other_name] -- (data_node: other_name.0) --> RevSequence [rnn_name.0] -- (data_node: rnn_name.0) -> Result1 + * -- (data_node: other_name.1) --> Identity(rnn_name.1) -- (data_node: rnn_name.1) -> Result2 + */ + auto identity_1_h = std::make_shared(tensor_iterator->output(1), axis_1); + auto identity_2_h = std::make_shared(identity_1_h, axis_1); + + auto identity_1_c = std::make_shared(tensor_iterator->output(2), axis_1); + auto identity_2_c = std::make_shared(identity_1_c, axis_1); + + ngraph::copy_runtime_info(sequence, {reverse_seq_after, tensor_iterator, reverse_seq_before, identity_2_c, identity_1_c, + identity_1_h, identity_2_h}); + ngraph::replace_node(sequence, {reverse_seq_after, identity_2_h, identity_2_c}); + tensor_iterator->set_friendly_name(sequence->get_friendly_name() + "/tensor_iterator"); + reverse_seq_after->set_friendly_name(sequence->get_friendly_name() + ".0"); + identity_2_h->set_friendly_name(sequence->get_friendly_name() + ".1"); + identity_2_c->set_friendly_name(sequence->get_friendly_name() + ".2"); + } else { + ngraph::copy_runtime_info(sequence, tensor_iterator); + ngraph::replace_node(sequence, tensor_iterator); + } + return true; + }; + + auto m = std::make_shared(rnn_seq, "ConvertLSTMSequenceToTensorIterator"); + register_matcher(m, callback); +} \ No newline at end of file diff --git a/inference-engine/tests/functional/inference_engine/ngraph_reader/sequences.cpp b/inference-engine/tests/functional/inference_engine/ngraph_reader/sequences.cpp new file mode 100644 index 0000000..a724d34 --- /dev/null +++ b/inference-engine/tests/functional/inference_engine/ngraph_reader/sequences.cpp @@ -0,0 +1,470 @@ +// Copyright (C) 2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include "ngraph_reader_tests.hpp" +#include "common_test_utils/data_utils.hpp" + +TEST_F(NGraphReaderTests, LSTMSeqNetwork) { + std::string model = R"V0G0N( + + + + + + + 10 + 3 + 512 + + + + + + + + 10 + 1 + 256 + + + + + + + + 10 + 1 + 256 + + + + + + + + 10 + + + + + + + + 1 + 1024 + 512 + + + + + + + + 1 + 1024 + 256 + + + + + + + + 1 + 1024 + + + + + + + + 10 + 3 + 512 + + + 10 + 1 + 256 + + + 10 + 1 + 256 + + + 10 + + + 1 + 1024 + 512 + + + 1 + 1024 + 256 + + + 1 + 1024 + + + + + 10 + 1 + 3 + 256 + + + 10 + 1 + 256 + + + 10 + 1 + 256 + + + + + + + 10 + 1 + 3 + 256 + + + + + + + 10 + 1 + 256 + + + + + + + 10 + 1 + 256 + + + + + + + + + + + + + + + + + +)V0G0N"; + + Blob::CPtr blob; + Core reader; + reader.ReadNetwork(model, blob); +} + +TEST_F(NGraphReaderTests, GRUSeqNetwork) { + std::string model = R"V0G0N( + + + + + + + 10 + 3 + 512 + + + + + + + + 10 + 1 + 256 + + + + + + + + 10 + + + + + + + + 1 + 768 + 512 + + + + + + + + 1 + 768 + 256 + + + + + + + + 1 + 768 + + + + + + + + 10 + 3 + 512 + + + 10 + 1 + 256 + + + 10 + + + 1 + 768 + 512 + + + 1 + 768 + 256 + + + 1 + 768 + + + + + 10 + 1 + 3 + 256 + + + 10 + 1 + 256 + + + + + + + 10 + 1 + 3 + 256 + + + + + + + 10 + 1 + 256 + + + + + + + + + + + + + + + +)V0G0N"; + + Blob::CPtr blob; + Core reader; + reader.ReadNetwork(model, blob); +} + +TEST_F(NGraphReaderTests, RNNSeqNetwork) { + std::string model = R"V0G0N( + + + + + + + 10 + 3 + 512 + + + + + + + + 10 + 1 + 256 + + + + + + + + 10 + + + + + + + + 1 + 256 + 512 + + + + + + + + 1 + 256 + 256 + + + + + + + + 1 + 256 + + + + + + + + 10 + 3 + 512 + + + 10 + 1 + 256 + + + 10 + + + 1 + 256 + 512 + + + 1 + 256 + 256 + + + 1 + 256 + + + + + 10 + 1 + 3 + 256 + + + 10 + 1 + 256 + + + + + + + 10 + 1 + 3 + 256 + + + + + + + 10 + 1 + 256 + + + + + + + + + + + + + + + +)V0G0N"; + + Blob::CPtr blob; + Core reader; + reader.ReadNetwork(model, blob); +} \ No newline at end of file diff --git a/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/gru_sequence.cpp b/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/gru_sequence.cpp index b5333e7..01b4133 100644 --- a/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/gru_sequence.cpp +++ b/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/gru_sequence.cpp @@ -10,12 +10,16 @@ using namespace LayerTestsDefinitions; namespace { + std::vector mode{ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_CONST, + ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_CONST, + ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM, + ngraph::helpers::SequenceTestsMode::PURE_SEQ}; // output values increase rapidly without clip, so use only seq_lenghts = 2 std::vector seq_lengths_zero_clip{2}; std::vector seq_lengths_clip_non_zero{20}; - std::vector batch{1, 10}; + std::vector batch{10}; std::vector hidden_size{1, 10}; - std::vector input_size{10}; + // std::vector input_size{10}; std::vector> activations = {{"relu", "tanh"}, {"tanh", "sigmoid"}, {"sigmoid", "tanh"}, {"tanh", "relu"}}; std::vector linear_before_reset = {true, false}; @@ -30,10 +34,11 @@ namespace { INSTANTIATE_TEST_CASE_P(smoke_GRUSequenceCommonZeroClip, GRUSequenceTest, ::testing::Combine( + ::testing::ValuesIn(mode), ::testing::ValuesIn(seq_lengths_zero_clip), ::testing::ValuesIn(batch), ::testing::ValuesIn(hidden_size), - ::testing::ValuesIn(input_size), + // ::testing::ValuesIn(input_size), // hardcoded to 10 due to Combine supports up to 10 args ::testing::ValuesIn(activations), ::testing::ValuesIn(clip), ::testing::ValuesIn(linear_before_reset), @@ -44,10 +49,11 @@ namespace { INSTANTIATE_TEST_CASE_P(smoke_GRUSequenceCommonClip, GRUSequenceTest, ::testing::Combine( + ::testing::ValuesIn(mode), ::testing::ValuesIn(seq_lengths_clip_non_zero), ::testing::ValuesIn(batch), ::testing::ValuesIn(hidden_size), - ::testing::ValuesIn(input_size), + // ::testing::ValuesIn(input_size), // hardcoded to 10 due to Combine supports up to 10 args ::testing::ValuesIn(activations), ::testing::ValuesIn(clip_non_zeros), ::testing::ValuesIn(linear_before_reset), diff --git a/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/lstm_sequence.cpp b/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/lstm_sequence.cpp index d61acb3..c698862 100644 --- a/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/lstm_sequence.cpp +++ b/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/lstm_sequence.cpp @@ -10,10 +10,14 @@ using namespace LayerTestsDefinitions; namespace { + std::vector mode{ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_CONST, + ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_CONST, + ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM, + ngraph::helpers::SequenceTestsMode::PURE_SEQ}; // output values increase rapidly without clip, so use only seq_lenghts = 2 std::vector seq_lengths_zero_clip{2}; std::vector seq_lengths_clip_non_zero{20}; - std::vector batch{1, 10}; + std::vector batch{10}; std::vector hidden_size{1, 10}; std::vector input_size{10}; std::vector> activations = {{"relu", "sigmoid", "tanh"}, {"sigmoid", "tanh", "tanh"}, @@ -30,6 +34,7 @@ namespace { INSTANTIATE_TEST_CASE_P(smoke_LSTMSequenceCommonZeroClip, LSTMSequenceTest, ::testing::Combine( + ::testing::ValuesIn(mode), ::testing::ValuesIn(seq_lengths_zero_clip), ::testing::ValuesIn(batch), ::testing::ValuesIn(hidden_size), @@ -43,6 +48,7 @@ namespace { INSTANTIATE_TEST_CASE_P(smoke_LSTMSequenceCommonClip, LSTMSequenceTest, ::testing::Combine( + ::testing::ValuesIn(mode), ::testing::ValuesIn(seq_lengths_clip_non_zero), ::testing::ValuesIn(batch), ::testing::ValuesIn(hidden_size), diff --git a/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/rnn_sequence.cpp b/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/rnn_sequence.cpp index c48ade7..94be745 100644 --- a/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/rnn_sequence.cpp +++ b/inference-engine/tests/functional/plugin/cpu/shared_tests_instances/single_layer_tests/rnn_sequence.cpp @@ -10,6 +10,10 @@ using namespace LayerTestsDefinitions; namespace { + std::vector mode{ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_CONST, + ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_CONST, + ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM, + ngraph::helpers::SequenceTestsMode::PURE_SEQ}; // output values increase rapidly without clip, so use only seq_lenghts = 2 std::vector seq_lengths_zero_clip{2}; std::vector seq_lengths_clip_non_zero{20}; @@ -21,13 +25,13 @@ namespace { std::vector clip_non_zeros{0.7f}; std::vector direction = {ngraph::op::RecurrentSequenceDirection::FORWARD, ngraph::op::RecurrentSequenceDirection::REVERSE, - ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL + ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL, }; - std::vector netPrecisions = {InferenceEngine::Precision::FP32, - InferenceEngine::Precision::FP16}; + std::vector netPrecisions = {InferenceEngine::Precision::FP32}; INSTANTIATE_TEST_CASE_P(smoke_RNNSequenceCommonZeroClip, RNNSequenceTest, ::testing::Combine( + ::testing::ValuesIn(mode), ::testing::ValuesIn(seq_lengths_zero_clip), ::testing::ValuesIn(batch), ::testing::ValuesIn(hidden_size), @@ -41,6 +45,7 @@ namespace { INSTANTIATE_TEST_CASE_P(smoke_RNNSequenceCommonClip, RNNSequenceTest, ::testing::Combine( + ::testing::ValuesIn(mode), ::testing::ValuesIn(seq_lengths_clip_non_zero), ::testing::ValuesIn(batch), ::testing::ValuesIn(hidden_size), diff --git a/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/gru_sequence.hpp b/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/gru_sequence.hpp index b48f8db..9953b88 100644 --- a/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/gru_sequence.hpp +++ b/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/gru_sequence.hpp @@ -16,11 +16,12 @@ namespace LayerTestsDefinitions { using GRUSequenceParams = typename std::tuple< - // bool, // using decompose to sub-ops transformation + ngraph::helpers::SequenceTestsMode, // pure Sequence or TensorIterator size_t, // seq_lengths size_t, // batch size_t, // hidden size - size_t, // input size + // todo: fix. input size hardcoded to 10 due to limitation (10 args) of gtests Combine() func. + //size_t, // input size std::vector, // activations float, // clip bool, // linear_before_reset @@ -35,6 +36,11 @@ public: protected: void SetUp() override; + void Infer() override; + +private: + ngraph::helpers::SequenceTestsMode m_mode; + int64_t m_max_seq_len = 0; }; } // namespace LayerTestsDefinitions diff --git a/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/lstm_sequence.hpp b/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/lstm_sequence.hpp index 8dd2351..6bd1067 100644 --- a/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/lstm_sequence.hpp +++ b/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/lstm_sequence.hpp @@ -16,7 +16,7 @@ namespace LayerTestsDefinitions { using LSTMSequenceParams = typename std::tuple< - // bool, // using decompose to sub-ops transformation + ngraph::helpers::SequenceTestsMode, // pure Sequence or TensorIterator size_t, // seq_lengths size_t, // batch size_t, // hidden size @@ -31,9 +31,13 @@ class LSTMSequenceTest : public testing::WithParamInterface, virtual public LayerTestsUtils::LayerTestsCommon { public: static std::string getTestCaseName(const testing::TestParamInfo &obj); - protected: + void Infer() override; void SetUp() override; + +private: + ngraph::helpers::SequenceTestsMode m_mode; + int64_t m_max_seq_len = 0; }; } // namespace LayerTestsDefinitions diff --git a/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/rnn_sequence.hpp b/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/rnn_sequence.hpp index b6f3949..bcfdade 100644 --- a/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/rnn_sequence.hpp +++ b/inference-engine/tests/functional/plugin/shared/include/single_layer_tests/rnn_sequence.hpp @@ -16,7 +16,7 @@ namespace LayerTestsDefinitions { using RNNSequenceParams = typename std::tuple< - // bool, // using decompose to sub-ops transformation + ngraph::helpers::SequenceTestsMode, // pure Sequence or TensorIterator size_t, // seq_lengths size_t, // batch size_t, // hidden size @@ -34,6 +34,11 @@ public: protected: void SetUp() override; + void Infer() override; + +private: + ngraph::helpers::SequenceTestsMode m_mode; + int64_t m_max_seq_len = 0; }; } // namespace LayerTestsDefinitions diff --git a/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/gru_sequence.cpp b/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/gru_sequence.cpp index c4654f2..875f1bf 100644 --- a/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/gru_sequence.cpp +++ b/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/gru_sequence.cpp @@ -18,15 +18,16 @@ #include "single_layer_tests/gru_sequence.hpp" #include +#include namespace LayerTestsDefinitions { std::string GRUSequenceTest::getTestCaseName(const testing::TestParamInfo &obj) { - //bool should_decompose; + ngraph::helpers::SequenceTestsMode mode; size_t seq_lenghts; size_t batch; size_t hidden_size; - size_t input_size; + size_t input_size = 10; std::vector activations; std::vector activations_alpha; std::vector activations_beta; @@ -35,13 +36,14 @@ namespace LayerTestsDefinitions { ngraph::op::RecurrentSequenceDirection direction; InferenceEngine::Precision netPrecision; std::string targetDevice; - std::tie(seq_lenghts, batch, hidden_size, input_size, activations, clip, linear_before_reset, direction, netPrecision, + std::tie(mode, seq_lenghts, batch, hidden_size, activations, clip, linear_before_reset, direction, netPrecision, targetDevice) = obj.param; std::vector> inputShapes = { {{batch, input_size}, {batch, hidden_size}, {batch, hidden_size}, {3 * hidden_size, input_size}, {3 * hidden_size, hidden_size}, {(linear_before_reset ? 4 : 3) * hidden_size}}, }; std::ostringstream result; + result << "mode=" << mode << "_"; result << "seq_lenghts=" << seq_lenghts << "_"; result << "batch=" << batch << "_"; result << "hidden_size=" << hidden_size << "_"; @@ -57,10 +59,9 @@ namespace LayerTestsDefinitions { void GRUSequenceTest::SetUp() { size_t seq_lenghts; - // bool should_decompose; size_t batch; size_t hidden_size; - size_t input_size; + size_t input_size = 10; std::vector activations; std::vector activations_alpha; std::vector activations_beta; @@ -68,7 +69,7 @@ namespace LayerTestsDefinitions { bool linear_before_reset; ngraph::op::RecurrentSequenceDirection direction; InferenceEngine::Precision netPrecision; - std::tie(seq_lenghts, batch, hidden_size, input_size, activations, clip, linear_before_reset, direction, netPrecision, + std::tie(m_mode, seq_lenghts, batch, hidden_size, activations, clip, linear_before_reset, direction, netPrecision, targetDevice) = this->GetParam(); size_t num_directions = direction == ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL ? 2 : 1; std::vector> inputShapes = { @@ -76,16 +77,57 @@ namespace LayerTestsDefinitions { {num_directions, 3 * hidden_size, input_size}, {num_directions, 3 * hidden_size, hidden_size}, {num_directions, (linear_before_reset ? 4 : 3) * hidden_size}}, }; + m_max_seq_len = seq_lenghts; auto ngPrc = FuncTestUtils::PrecisionUtils::convertIE2nGraphPrc(netPrecision); auto params = ngraph::builder::makeParams(ngPrc, {inputShapes[0], inputShapes[1]}); + if (m_mode == ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_PARAM || + m_mode == ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM) { + auto seq_lengths = ngraph::builder::makeParams(ngraph::element::i64, {inputShapes[2]}).at(0); + seq_lengths->set_friendly_name("seq_lengths"); + params.push_back(seq_lengths); + } std::vector WRB = {inputShapes[3], inputShapes[4], inputShapes[5], inputShapes[2]}; auto gru_sequence = ngraph::builder::makeGRU(ngraph::helpers::convert2OutputVector(ngraph::helpers::castOps2Nodes(params)), - WRB, hidden_size, activations, {}, {}, clip, linear_before_reset, true, direction); + WRB, hidden_size, activations, {}, {}, clip, linear_before_reset, true, direction, + m_mode); ngraph::ResultVector results{std::make_shared(gru_sequence->output(0)), std::make_shared(gru_sequence->output(1))}; function = std::make_shared(results, params, "gru_sequence"); + if (m_mode != ngraph::helpers::SequenceTestsMode::PURE_SEQ) { + ngraph::pass::Manager manager; + if (direction == ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL) + manager.register_pass(); + manager.register_pass(); + manager.run_passes(function); + bool ti_found = ngraph::helpers::is_tensor_iterator_exist(function); + EXPECT_EQ(ti_found, true); + } else { + bool ti_found = ngraph::helpers::is_tensor_iterator_exist(function); + EXPECT_EQ(ti_found, false); + } } + void GRUSequenceTest::Infer() { + inferRequest = executableNetwork.CreateInferRequest(); + inputs.clear(); + + for (const auto &input : executableNetwork.GetInputsInfo()) { + const auto &info = input.second; + auto blob = GenerateInput(*info); + if (input.first == "seq_lengths") { + blob = FuncTestUtils::createAndFillBlob(info->getTensorDesc(), m_max_seq_len, 0); + } + + inferRequest.SetBlob(info->name(), blob); + inputs.push_back(blob); + } + if (configuration.count(InferenceEngine::PluginConfigParams::KEY_DYN_BATCH_ENABLED) && + configuration.count(InferenceEngine::PluginConfigParams::YES)) { + auto batchSize = executableNetwork.GetInputsInfo().begin()->second->getTensorDesc().getDims()[0] / 2; + inferRequest.SetBatch(batchSize); + } + inferRequest.Infer(); + } TEST_P(GRUSequenceTest, CompareWithRefs) { Run(); diff --git a/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/lstm_sequence.cpp b/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/lstm_sequence.cpp index a929c79..88c1139 100644 --- a/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/lstm_sequence.cpp +++ b/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/lstm_sequence.cpp @@ -18,11 +18,13 @@ #include "single_layer_tests/lstm_sequence.hpp" #include +#include +#include namespace LayerTestsDefinitions { std::string LSTMSequenceTest::getTestCaseName(const testing::TestParamInfo &obj) { - //bool should_decompose; + ngraph::helpers::SequenceTestsMode mode; size_t seq_lenghts; size_t batch; size_t hidden_size; @@ -34,13 +36,14 @@ namespace LayerTestsDefinitions { ngraph::op::RecurrentSequenceDirection direction; InferenceEngine::Precision netPrecision; std::string targetDevice; - std::tie(seq_lenghts, batch, hidden_size, input_size, activations, clip, direction, netPrecision, + std::tie(mode, seq_lenghts, batch, hidden_size, input_size, activations, clip, direction, netPrecision, targetDevice) = obj.param; std::vector> inputShapes = { {{batch, input_size}, {batch, hidden_size}, {batch, hidden_size}, {4 * hidden_size, input_size}, {4 * hidden_size, hidden_size}, {4 * hidden_size}}, }; std::ostringstream result; + result << "mode=" << mode << "_"; result << "seq_lenghts=" << seq_lenghts << "_"; result << "batch=" << batch << "_"; result << "hidden_size=" << hidden_size << "_"; @@ -56,7 +59,7 @@ namespace LayerTestsDefinitions { void LSTMSequenceTest::SetUp() { size_t seq_lenghts; - // bool should_decompose; + size_t batch; size_t hidden_size; size_t input_size; @@ -66,24 +69,65 @@ namespace LayerTestsDefinitions { float clip; ngraph::op::RecurrentSequenceDirection direction; InferenceEngine::Precision netPrecision; - std::tie(seq_lenghts, batch, hidden_size, input_size, activations, clip, direction, netPrecision, + std::tie(m_mode, seq_lenghts, batch, hidden_size, input_size, activations, clip, direction, netPrecision, targetDevice) = this->GetParam(); size_t num_directions = direction == ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL ? 2 : 1; + m_max_seq_len = seq_lenghts; std::vector> inputShapes = { {{batch, seq_lenghts, input_size}, {batch, num_directions, hidden_size}, {batch, num_directions, hidden_size}, {batch}, {num_directions, 4 * hidden_size, input_size}, {num_directions, 4 * hidden_size, hidden_size}, {num_directions, 4 * hidden_size}}, }; auto ngPrc = FuncTestUtils::PrecisionUtils::convertIE2nGraphPrc(netPrecision); auto params = ngraph::builder::makeParams(ngPrc, {inputShapes[0], inputShapes[1], inputShapes[2]}); + if (m_mode == ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_PARAM || + m_mode == ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM) { + auto seq_lengths = ngraph::builder::makeParams(ngraph::element::i64, {inputShapes[3]}).at(0); + seq_lengths->set_friendly_name("seq_lengths"); + params.push_back(seq_lengths); + } std::vector WRB = {inputShapes[4], inputShapes[5], inputShapes[6], inputShapes[3]}; auto lstm_sequence = ngraph::builder::makeLSTM(ngraph::helpers::convert2OutputVector(ngraph::helpers::castOps2Nodes(params)), - WRB, hidden_size, activations, {}, {}, clip, true, direction); + WRB, hidden_size, activations, {}, {}, clip, true, direction, + m_mode); ngraph::ResultVector results{std::make_shared(lstm_sequence->output(0)), std::make_shared(lstm_sequence->output(1)), std::make_shared(lstm_sequence->output(2))}; function = std::make_shared(results, params, "lstm_sequence"); + if (m_mode != ngraph::helpers::SequenceTestsMode::PURE_SEQ) { + ngraph::pass::Manager manager; + if (direction == ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL) + manager.register_pass(); + manager.register_pass(); + manager.run_passes(function); + bool ti_found = ngraph::helpers::is_tensor_iterator_exist(function); + EXPECT_EQ(ti_found, true); + } else { + bool ti_found = ngraph::helpers::is_tensor_iterator_exist(function); + EXPECT_EQ(ti_found, false); + } } + void LSTMSequenceTest::Infer() { + inferRequest = executableNetwork.CreateInferRequest(); + inputs.clear(); + + for (const auto &input : executableNetwork.GetInputsInfo()) { + const auto &info = input.second; + auto blob = GenerateInput(*info); + if (input.first == "seq_lengths") { + blob = FuncTestUtils::createAndFillBlob(info->getTensorDesc(), m_max_seq_len, 0); + } + + inferRequest.SetBlob(info->name(), blob); + inputs.push_back(blob); + } + if (configuration.count(InferenceEngine::PluginConfigParams::KEY_DYN_BATCH_ENABLED) && + configuration.count(InferenceEngine::PluginConfigParams::YES)) { + auto batchSize = executableNetwork.GetInputsInfo().begin()->second->getTensorDesc().getDims()[0] / 2; + inferRequest.SetBatch(batchSize); + } + inferRequest.Infer(); + } TEST_P(LSTMSequenceTest, CompareWithRefs) { Run(); diff --git a/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/rnn_sequence.cpp b/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/rnn_sequence.cpp index 20a4fb3..42aab9d 100644 --- a/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/rnn_sequence.cpp +++ b/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/rnn_sequence.cpp @@ -18,11 +18,12 @@ #include "single_layer_tests/rnn_sequence.hpp" #include +#include namespace LayerTestsDefinitions { std::string RNNSequenceTest::getTestCaseName(const testing::TestParamInfo &obj) { - //bool should_decompose; + ngraph::helpers::SequenceTestsMode mode; size_t seq_lenghts; size_t batch; size_t hidden_size; @@ -34,13 +35,14 @@ namespace LayerTestsDefinitions { ngraph::op::RecurrentSequenceDirection direction; InferenceEngine::Precision netPrecision; std::string targetDevice; - std::tie(seq_lenghts, batch, hidden_size, input_size, activations, clip, direction, netPrecision, + std::tie(mode, seq_lenghts, batch, hidden_size, input_size, activations, clip, direction, netPrecision, targetDevice) = obj.param; std::vector> inputShapes = { {{batch, input_size}, {batch, hidden_size}, {batch, hidden_size}, {hidden_size, input_size}, {hidden_size, hidden_size}, {hidden_size}}, }; std::ostringstream result; + result << "mode=" << mode << "_"; result << "seq_lenghts=" << seq_lenghts << "_"; result << "batch=" << batch << "_"; result << "hidden_size=" << hidden_size << "_"; @@ -56,7 +58,6 @@ namespace LayerTestsDefinitions { void RNNSequenceTest::SetUp() { size_t seq_lenghts; - // bool should_decompose; size_t batch; size_t hidden_size; size_t input_size; @@ -66,7 +67,7 @@ namespace LayerTestsDefinitions { float clip; ngraph::op::RecurrentSequenceDirection direction; InferenceEngine::Precision netPrecision; - std::tie(seq_lenghts, batch, hidden_size, input_size, activations, clip, direction, netPrecision, + std::tie(m_mode, seq_lenghts, batch, hidden_size, input_size, activations, clip, direction, netPrecision, targetDevice) = this->GetParam(); size_t num_directions = direction == ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL ? 2 : 1; std::vector> inputShapes = { @@ -74,16 +75,57 @@ namespace LayerTestsDefinitions { {num_directions, hidden_size, input_size}, {num_directions, hidden_size, hidden_size}, {num_directions, hidden_size}}, }; + m_max_seq_len = seq_lenghts; auto ngPrc = FuncTestUtils::PrecisionUtils::convertIE2nGraphPrc(netPrecision); auto params = ngraph::builder::makeParams(ngPrc, {inputShapes[0], inputShapes[1]}); + if (m_mode == ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_PARAM || + m_mode == ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM) { + auto seq_lengths = ngraph::builder::makeParams(ngraph::element::i64, {inputShapes[2]}).at(0); + seq_lengths->set_friendly_name("seq_lengths"); + params.push_back(seq_lengths); + } std::vector WRB = {inputShapes[3], inputShapes[4], inputShapes[5], inputShapes[2]}; auto rnn_sequence = ngraph::builder::makeRNN(ngraph::helpers::convert2OutputVector(ngraph::helpers::castOps2Nodes(params)), - WRB, hidden_size, activations, {}, {}, clip, true, direction); + WRB, hidden_size, activations, {}, {}, clip, true, direction, + m_mode); ngraph::ResultVector results{std::make_shared(rnn_sequence->output(0)), std::make_shared(rnn_sequence->output(1))}; function = std::make_shared(results, params, "rnn_sequence"); + if (m_mode != ngraph::helpers::SequenceTestsMode::PURE_SEQ) { + ngraph::pass::Manager manager; + if (direction == ngraph::op::RecurrentSequenceDirection::BIDIRECTIONAL) + manager.register_pass(); + manager.register_pass(); + manager.run_passes(function); + bool ti_found = ngraph::helpers::is_tensor_iterator_exist(function); + EXPECT_EQ(ti_found, true); + } else { + bool ti_found = ngraph::helpers::is_tensor_iterator_exist(function); + EXPECT_EQ(ti_found, false); + } } + void RNNSequenceTest::Infer() { + inferRequest = executableNetwork.CreateInferRequest(); + inputs.clear(); + + for (const auto &input : executableNetwork.GetInputsInfo()) { + const auto &info = input.second; + auto blob = GenerateInput(*info); + if (input.first == "seq_lengths") { + blob = FuncTestUtils::createAndFillBlob(info->getTensorDesc(), m_max_seq_len, 0); + } + + inferRequest.SetBlob(info->name(), blob); + inputs.push_back(blob); + } + if (configuration.count(InferenceEngine::PluginConfigParams::KEY_DYN_BATCH_ENABLED) && + configuration.count(InferenceEngine::PluginConfigParams::YES)) { + auto batchSize = executableNetwork.GetInputsInfo().begin()->second->getTensorDesc().getDims()[0] / 2; + inferRequest.SetBatch(batchSize); + } + inferRequest.Infer(); + } TEST_P(RNNSequenceTest, CompareWithRefs) { Run(); diff --git a/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/tensor_iterator.cpp b/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/tensor_iterator.cpp index 8cc226b..e084443 100644 --- a/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/tensor_iterator.cpp +++ b/inference-engine/tests/functional/plugin/shared/src/single_layer_tests/tensor_iterator.cpp @@ -124,7 +124,7 @@ namespace LayerTestsDefinitions { NGRAPH_CHECK(false, "Bidirectional case is not supported."); } - tensor_iterator->set_invariant_input(body_params[1], outer_params[1]); + tensor_iterator->set_merged_input(body_params[1], outer_params[1], results[1]); tensor_iterator->set_invariant_input(body_params[2], outer_params[2]); tensor_iterator->get_iter_value(results[1]); tensor_iterator->get_iter_value(results[2]); @@ -166,7 +166,7 @@ namespace LayerTestsDefinitions { NGRAPH_CHECK(false, "Bidirectional case is not supported."); } - tensor_iterator->set_invariant_input(body_params[1], outer_params[1]); + tensor_iterator->set_merged_input(body_params[1], outer_params[1], results[0]); tensor_iterator->get_iter_value(results[0]); // 3. Outer function @@ -205,7 +205,7 @@ namespace LayerTestsDefinitions { NGRAPH_CHECK(false, "Bidirectional case is not supported."); } - tensor_iterator->set_invariant_input(body_params[1], outer_params[1]); + tensor_iterator->set_merged_input(body_params[1], outer_params[1], results[0]); tensor_iterator->get_iter_value(results[0]); // 3. Outer function diff --git a/inference-engine/tests/ngraph_functions/include/ngraph_functions/builders.hpp b/inference-engine/tests/ngraph_functions/include/ngraph_functions/builders.hpp index 35bbb63..adb1ad2 100644 --- a/inference-engine/tests/ngraph_functions/include/ngraph_functions/builders.hpp +++ b/inference-engine/tests/ngraph_functions/include/ngraph_functions/builders.hpp @@ -407,7 +407,8 @@ std::shared_ptr makeLSTM(const OutputVector& in, const std::vector& activations_beta = {}, float clip = 0.f, bool make_sequence = false, - ngraph::op::RecurrentSequenceDirection direction = ngraph::op::RecurrentSequenceDirection::FORWARD); + ngraph::op::RecurrentSequenceDirection direction = ngraph::op::RecurrentSequenceDirection::FORWARD, + ngraph::helpers::SequenceTestsMode mode = ngraph::helpers::SequenceTestsMode::PURE_SEQ); std::shared_ptr makeGRU(const OutputVector& in, const std::vector& constants, @@ -419,7 +420,8 @@ std::shared_ptr makeGRU(const OutputVector& in, float clip = 0.f, bool linear_before_reset = false, bool make_sequence = false, - ngraph::op::RecurrentSequenceDirection direction = ngraph::op::RecurrentSequenceDirection::FORWARD); + ngraph::op::RecurrentSequenceDirection direction = ngraph::op::RecurrentSequenceDirection::FORWARD, + ngraph::helpers::SequenceTestsMode mode = ngraph::helpers::SequenceTestsMode::PURE_SEQ); std::shared_ptr makeRNN(const OutputVector& in, const std::vector& constants, @@ -429,7 +431,8 @@ std::shared_ptr makeRNN(const OutputVector& in, const std::vector& activations_beta = {}, float clip = 0.f, bool make_sequence = false, - ngraph::op::RecurrentSequenceDirection direction = ngraph::op::RecurrentSequenceDirection::FORWARD); + ngraph::op::RecurrentSequenceDirection direction = ngraph::op::RecurrentSequenceDirection::FORWARD, + ngraph::helpers::SequenceTestsMode mode = ngraph::helpers::SequenceTestsMode::PURE_SEQ); std::shared_ptr makeGatherND( const ngraph::Output& dataNode, diff --git a/inference-engine/tests/ngraph_functions/include/ngraph_functions/utils/ngraph_helpers.hpp b/inference-engine/tests/ngraph_functions/include/ngraph_functions/utils/ngraph_helpers.hpp index 9daf286..3e578e9 100644 --- a/inference-engine/tests/ngraph_functions/include/ngraph_functions/utils/ngraph_helpers.hpp +++ b/inference-engine/tests/ngraph_functions/include/ngraph_functions/utils/ngraph_helpers.hpp @@ -189,9 +189,19 @@ enum class TensorIteratorBody { // CNN todo: implement }; +enum class SequenceTestsMode { + PURE_SEQ, + CONVERT_TO_TI_MAX_SEQ_LEN_CONST, + CONVERT_TO_TI_MAX_SEQ_LEN_PARAM, + CONVERT_TO_TI_RAND_SEQ_LEN_CONST, + CONVERT_TO_TI_RAND_SEQ_LEN_PARAM, +}; + std::ostream &operator<<(std::ostream &os, const ReductionType &m); std::ostream &operator<<(std::ostream &os, const PadMode &m); +bool is_tensor_iterator_exist(const std::shared_ptr & func); + inline std::string quantizationGranularityToString(const QuantizationGranularity &granularity) { static std::map names = { {Pertensor, "Pertensor"}, @@ -267,5 +277,7 @@ std::ostream& operator<<(std::ostream & os, ngraph::op::v4::Interpolate::ShapeCa std::ostream& operator<<(std::ostream & os, TensorIteratorBody type); +std::ostream& operator<<(std::ostream & os, SequenceTestsMode type); + } // namespace helpers } // namespace ngraph diff --git a/inference-engine/tests/ngraph_functions/src/gru_cell.cpp b/inference-engine/tests/ngraph_functions/src/gru_cell.cpp index 364f0cd..1b69aaa 100644 --- a/inference-engine/tests/ngraph_functions/src/gru_cell.cpp +++ b/inference-engine/tests/ngraph_functions/src/gru_cell.cpp @@ -19,7 +19,8 @@ std::shared_ptr makeGRU(const OutputVector& in, float clip, bool linear_before_reset, bool make_sequence, - ngraph::op::RecurrentSequenceDirection direction) { + ngraph::op::RecurrentSequenceDirection direction, + ngraph::helpers::SequenceTestsMode mode) { std::vector empty; auto W = ngraph::builder::makeConstant(in[0].get_element_type(), constants[0], empty, true); auto R = ngraph::builder::makeConstant(in[0].get_element_type(), constants[1], empty, true); @@ -29,9 +30,32 @@ std::shared_ptr makeGRU(const OutputVector& in, activations_alpha, activations_beta, clip, linear_before_reset); } else { - std::vector lenghts(in[0].get_shape()[0], in[0].get_shape()[1]); - auto seq_lenghts = ngraph::builder::makeConstant(in[0].get_element_type(), constants[3], lenghts, false); - return std::make_shared(in[0], in[1], seq_lenghts, W, R, B, hidden_size, direction, + std::shared_ptr seq_lengths; + switch (mode) { + case ngraph::helpers::SequenceTestsMode::PURE_SEQ: + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_CONST: { + std::vector lengths(in[0].get_shape()[0], in[0].get_shape()[1]); + seq_lengths = ngraph::builder::makeConstant(element::i64, constants[3], lengths, false); + break; + } + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_CONST: { + for (size_t i = 0; i <= in[0].get_shape().at(0); ++i) { + std::vector lengths; + seq_lengths = ngraph::builder::makeConstant(element::i64, constants[3], lengths, true, + in[0].get_shape()[1], 0); + } + break; + } + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM: + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_PARAM: { + // Seq_lengths should be as a Parameter node for these two modes + seq_lengths = in.at(2).get_node_shared_ptr(); + break; + } + default: + throw std::runtime_error("Incorrect mode for creation of Sequence operation"); + } + return std::make_shared(in[0], in[1], seq_lengths, W, R, B, hidden_size, direction, activations, activations_alpha, activations_beta, clip, linear_before_reset); } } diff --git a/inference-engine/tests/ngraph_functions/src/lstm_cell.cpp b/inference-engine/tests/ngraph_functions/src/lstm_cell.cpp index 8ff4c5a..4053563 100644 --- a/inference-engine/tests/ngraph_functions/src/lstm_cell.cpp +++ b/inference-engine/tests/ngraph_functions/src/lstm_cell.cpp @@ -18,7 +18,8 @@ std::shared_ptr makeLSTM(const std::vector>& const std::vector& activations_beta, float clip, bool make_sequence, - ngraph::op::RecurrentSequenceDirection direction) { + ngraph::op::RecurrentSequenceDirection direction, + ngraph::helpers::SequenceTestsMode mode) { std::vector empty; auto W = ngraph::builder::makeConstant(in[0].get_element_type(), constants[0], empty, true); auto R = ngraph::builder::makeConstant(in[0].get_element_type(), constants[1], empty, true); @@ -27,9 +28,32 @@ std::shared_ptr makeLSTM(const std::vector>& return std::make_shared(in[0], in[1], in[2], W, R, B, hidden_size, activations, activations_alpha, activations_beta, clip); } else { - std::vector lenghts(in[0].get_shape()[0], in[0].get_shape()[1]); - auto seq_lenghts = ngraph::builder::makeConstant(in[0].get_element_type(), constants[3], lenghts, false); - return std::make_shared(in[0], in[1], in[2], seq_lenghts, W, R, B, hidden_size, direction, + std::shared_ptr seq_lengths; + switch (mode) { + case ngraph::helpers::SequenceTestsMode::PURE_SEQ: + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_CONST: { + std::vector lengths(in[0].get_shape()[0], in[0].get_shape()[1]); + seq_lengths = ngraph::builder::makeConstant(element::i64, constants[3], lengths, false); + break; + } + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_CONST: { + for (size_t i = 0; i <= in[0].get_shape().at(0); ++i) { + std::vector lengths; + seq_lengths = ngraph::builder::makeConstant(element::i64, constants[3], lengths, true, + in[0].get_shape()[1], 0); + } + break; + } + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM: + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_PARAM: { + // Seq_lengths should be as a Parameter node for these two modes + seq_lengths = in.at(3).get_node_shared_ptr(); + break; + } + default: + throw std::runtime_error("Incorrect mode for creation of Sequence operation"); + } + return std::make_shared(in[0], in[1], in[2], seq_lengths, W, R, B, hidden_size, direction, activations_alpha, activations_beta, activations, clip); } } diff --git a/inference-engine/tests/ngraph_functions/src/rnn_cell.cpp b/inference-engine/tests/ngraph_functions/src/rnn_cell.cpp index 5234ef4..480a0ae 100644 --- a/inference-engine/tests/ngraph_functions/src/rnn_cell.cpp +++ b/inference-engine/tests/ngraph_functions/src/rnn_cell.cpp @@ -18,7 +18,8 @@ std::shared_ptr makeRNN(const OutputVector& in, const std::vector& activations_beta, float clip, bool make_sequence, - ngraph::op::RecurrentSequenceDirection direction) { + ngraph::op::RecurrentSequenceDirection direction, + ngraph::helpers::SequenceTestsMode mode) { std::vector empty; auto W = ngraph::builder::makeConstant(in[0].get_element_type(), constants[0], empty, true); auto R = ngraph::builder::makeConstant(in[0].get_element_type(), constants[1], empty, true); @@ -27,9 +28,32 @@ std::shared_ptr makeRNN(const OutputVector& in, return std::make_shared(in[0], in[1], W, R, B, hidden_size, activations, activations_alpha, activations_beta, clip); } else { - std::vector lenghts(in[0].get_shape()[0], in[0].get_shape()[1]); - auto seq_lenghts = ngraph::builder::makeConstant(in[0].get_element_type(), constants[3], lenghts, false); - return std::make_shared(in[0], in[1], seq_lenghts, W, R, B, hidden_size, direction, + std::shared_ptr seq_lengths; + switch (mode) { + case ngraph::helpers::SequenceTestsMode::PURE_SEQ: + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_CONST: { + std::vector lengths(in[0].get_shape()[0], in[0].get_shape()[1]); + seq_lengths = ngraph::builder::makeConstant(element::i64, constants[3], lengths, false); + break; + } + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_CONST: { + for (size_t i = 0; i <= in[0].get_shape().at(0); ++i) { + std::vector lengths; + seq_lengths = ngraph::builder::makeConstant(element::i64, constants[3], lengths, true, + in[0].get_shape()[1], 0); + } + break; + } + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM: + case ngraph::helpers::SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_PARAM: { + // Seq_lengths should be as a Parameter node for these two modes + seq_lengths = in.at(2).get_node_shared_ptr(); + break; + } + default: + throw std::runtime_error("Incorrect mode for creation of Sequence operation"); + } + return std::make_shared(in[0], in[1], seq_lengths, W, R, B, hidden_size, direction, activations, activations_alpha, activations_beta, clip); } } diff --git a/inference-engine/tests/ngraph_functions/src/utils/ngraph_helpers.cpp b/inference-engine/tests/ngraph_functions/src/utils/ngraph_helpers.cpp index 89ae8e0..5fdb43c 100644 --- a/inference-engine/tests/ngraph_functions/src/utils/ngraph_helpers.cpp +++ b/inference-engine/tests/ngraph_functions/src/utils/ngraph_helpers.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -255,6 +256,17 @@ std::vector convertPrecision(std::vector &buffer, co return convertedData; } +bool is_tensor_iterator_exist(const std::shared_ptr & func) { + const auto& ops = func->get_ops(); + for (const auto& node : ops) { + const auto& ti = std::dynamic_pointer_cast(node); + if (ti) { + return true; + } + } + return false; +} + std::vector convertOutputPrecision(std::vector &output, const element::Type_t &fromPrecision, const element::Type_t &toPrecision, const size_t elementsCount) { switch (fromPrecision) { @@ -748,5 +760,28 @@ std::ostream& operator<<(std::ostream & os, TensorIteratorBody type) { } return os; } + +std::ostream& operator<<(std::ostream & os, SequenceTestsMode type) { + switch (type) { + case SequenceTestsMode::PURE_SEQ: + os << "PURE_SEQ"; + break; + case SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_PARAM: + os << "CONVERT_TO_TI_RAND_SEQ_LEN_PARAM"; + break; + case SequenceTestsMode::CONVERT_TO_TI_RAND_SEQ_LEN_CONST: + os << "CONVERT_TO_TI_RAND_SEQ_LEN_CONST"; + break; + case SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_PARAM: + os << "CONVERT_TO_TI_MAX_SEQ_LEN_PARAM"; + break; + case SequenceTestsMode::CONVERT_TO_TI_MAX_SEQ_LEN_CONST: + os << "CONVERT_TO_TI_MAX_SEQ_LEN_CONST"; + break; + default: + throw std::runtime_error("NOT_SUPPORTED_OP_TYPE"); + } + return os; +} } // namespace helpers } // namespace ngraph diff --git a/ngraph/core/reference/include/ngraph/runtime/reference/sequences.hpp b/ngraph/core/reference/include/ngraph/runtime/reference/sequences.hpp index 894f1c3..3bdf1bc 100644 --- a/ngraph/core/reference/include/ngraph/runtime/reference/sequences.hpp +++ b/ngraph/core/reference/include/ngraph/runtime/reference/sequences.hpp @@ -45,7 +45,7 @@ namespace ngraph bool linear_before_reset = false; // GRU }; - template + template void cell_pass(CellType type, const std::vector& inputs, const std::vector& shapes, @@ -70,17 +70,49 @@ namespace ngraph // split X size_t num_splits = shapes[0].at(1); - std::vector> in_seqs( - num_splits, std::vector(x_shape_size / num_splits * sizeof(T))); + size_t part_size = x_shape_size / num_splits * sizeof(T); + std::vector in_seqs(x_shape_size * sizeof(T)); std::vector pointers(num_splits); + + // in case of seq_lengths input was provided and filled with values != + // max_seq_lengths + // we have to fill some of the outputs with zeros (apply mask) + size_t batch = shapes[0][0]; + size_t hidden_size = shapes[2][2]; + int64_t max_seq_lengths = num_splits; + const auto* seq_len_values = reinterpret_cast(inputs[1]); + bool enable_mask = false; + for (size_t i = 0; i < batch; ++i) + { + enable_mask |= (max_seq_lengths != seq_len_values[i]); + } + + std::vector temp_buffer(x_shape_size * sizeof(T)); + if (is_reverse) + { + reference::reverse_sequence(reinterpret_cast(inputs[0]), + reinterpret_cast(temp_buffer.data()), + shapes[0], + 0, + 1, + seq_len_values); + } + else + { + memcpy(temp_buffer.data(), inputs[0], x_shape_size * sizeof(T)); + } for (size_t i = 0; i < num_splits; ++i) - pointers[is_reverse ? num_splits - i - 1 : i] = in_seqs[i].data(); - reference::split(inputs[0], shapes[0], sizeof(T), 1, num_splits, pointers.data()); + pointers[i] = in_seqs.data() + i * part_size; + + reference::split( + temp_buffer.data(), shapes[0], sizeof(T), 1, num_splits, pointers.data()); - Shape part_shape{shapes[0][0], 1, shapes[2][2]}; + Shape part_shape{batch, 1, hidden_size}; size_t part_shape_size = ngraph::shape_size(part_shape); std::vector> h_list( - num_splits, std::vector(ngraph::shape_size(part_shape) * sizeof(T))); + num_splits, std::vector(part_shape_size * sizeof(T), 0)); + std::vector> c_list( + num_splits, std::vector(part_shape_size * sizeof(T), 0)); // use outputs as a buffer for temporarily values char* H_i = outputs[1]; @@ -98,7 +130,7 @@ namespace ngraph if (type == CellType::LSTM) { runtime::reference::lstm_cell( - reinterpret_cast(in_seqs[time_step].data()), + reinterpret_cast(in_seqs.data() + time_step * part_size), squeeze_axis(shapes[0], 1), reinterpret_cast(H_i), squeeze_axis(shapes[2], 1), @@ -120,7 +152,7 @@ namespace ngraph else if (type == CellType::RNN) { runtime::reference::rnn_cell( - reinterpret_cast(in_seqs[time_step].data()), + reinterpret_cast(in_seqs.data() + time_step * part_size), squeeze_axis(shapes[0], 1), reinterpret_cast(H_i), squeeze_axis(shapes[2], 1), @@ -137,7 +169,7 @@ namespace ngraph else if (type == CellType::GRU) { runtime::reference::gru_cell( - reinterpret_cast(in_seqs[time_step].data()), + reinterpret_cast(in_seqs.data() + time_step * part_size), squeeze_axis(shapes[0], 1), reinterpret_cast(H_i), squeeze_axis(shapes[2], 1), @@ -153,23 +185,87 @@ namespace ngraph args.clip, args.linear_before_reset); } - std::memcpy(h_list[time_step].data(), outputs[1], part_shape_size * sizeof(T)); + + if (enable_mask) + { + size_t part_size_single_batch = part_shape_size / batch * sizeof(T); + for (int i = 0; i < batch; ++i) + { + if ((time_step + 1) > seq_len_values[i]) + { + continue; + } + std::memcpy(h_list[time_step].data() + i * part_size_single_batch, + outputs[1] + i * part_size_single_batch, + part_size_single_batch); + if (type == CellType::LSTM) + { + std::memcpy(c_list[time_step].data() + i * part_size_single_batch, + outputs[2] + i * part_size_single_batch, + part_size_single_batch); + } + } + if ((num_splits - time_step) > 1) + { + std::memcpy( + outputs[1], h_list[time_step].data(), part_shape_size * sizeof(T)); + if (type == CellType::LSTM) + { + std::memcpy(outputs[2], + c_list[time_step].data(), + part_shape_size * sizeof(T)); + } + } + else + { + for (int i = 0; i < batch; ++i) + { + std::memcpy(outputs[1] + i * part_size_single_batch, + h_list[seq_len_values[i] - 1].data() + + i * part_size_single_batch, + part_size_single_batch); + if (type == CellType::LSTM) + { + std::memcpy(outputs[2] + i * part_size_single_batch, + c_list[seq_len_values[i] - 1].data() + + i * part_size_single_batch, + part_size_single_batch); + } + } + } + } + else + { + std::memcpy( + h_list[time_step].data(), outputs[1], part_shape_size * sizeof(T)); + } } // The tensor that concats all the intermediate output values of the hidden. // It has shape [batch_size, seq_length, hidden_size] std::vector in_shapes(num_splits, part_shape); std::vector to_concat_pointers(num_splits); + Shape out_shape{batch, num_splits, hidden_size}; + for (size_t i = 0; i < num_splits; ++i) - to_concat_pointers[is_reverse ? num_splits - i - 1 : i] = h_list[i].data(); - runtime::reference::concat(to_concat_pointers, - outputs[0], - in_shapes, - {shapes[0][0], shapes[0][1], shapes[2][2]}, - 1, - sizeof(T)); + to_concat_pointers[i] = h_list[i].data(); + + runtime::reference::concat( + to_concat_pointers, outputs[0], in_shapes, out_shape, 1, sizeof(T)); + + if (is_reverse) // enable_mask + { + temp_buffer.resize(shape_size(out_shape) * sizeof(T)); + reference::reverse_sequence(reinterpret_cast(outputs[0]), + reinterpret_cast(temp_buffer.data()), + out_shape, + 0, + 1, + seq_len_values); + std::memcpy(outputs[0], temp_buffer.data(), shape_size(out_shape) * sizeof(T)); + } } - template + template void lstm_sequence(const char* X, const Shape& X_shape, const char* H, @@ -206,12 +302,12 @@ namespace ngraph std::vector outputs = {Y, Ho, Co}; std::vector shapes = { X_shape, seq_lengths_shape, H_shape, C_shape, W_shape, R_shape, B_shape}; - cell_pass(CellType::LSTM, - inputs, - shapes, - outputs, - args, - direction == op::RecurrentSequenceDirection::REVERSE); + cell_pass(CellType::LSTM, + inputs, + shapes, + outputs, + args, + direction == op::RecurrentSequenceDirection::REVERSE); } else if (direction == op::RecurrentSequenceDirection::BIDIRECTIONAL) { @@ -261,7 +357,7 @@ namespace ngraph shapes[i][0] = 1; } // forward pass - cell_pass( + cell_pass( CellType::LSTM, {X, seq_lengths, @@ -275,7 +371,7 @@ namespace ngraph args, false); // reverse pass - cell_pass( + cell_pass( CellType::LSTM, {X, seq_lengths, @@ -318,7 +414,7 @@ namespace ngraph } } - template + template void gru_sequence(const char* X, const Shape& X_shape, const char* H, @@ -352,12 +448,12 @@ namespace ngraph std::vector outputs = {Y, Ho}; std::vector shapes = { X_shape, seq_lengths_shape, H_shape, W_shape, R_shape, B_shape}; - cell_pass(CellType::GRU, - inputs, - shapes, - outputs, - args, - direction == op::RecurrentSequenceDirection::REVERSE); + cell_pass(CellType::GRU, + inputs, + shapes, + outputs, + args, + direction == op::RecurrentSequenceDirection::REVERSE); } else if (direction == op::RecurrentSequenceDirection::BIDIRECTIONAL) { @@ -400,29 +496,29 @@ namespace ngraph shapes[i][0] = 1; } // forward pass - cell_pass(CellType::GRU, - {X, - seq_lengths, - h_pointers[0], - w_pointers[0], - r_pointers[0], - b_pointers[0]}, - shapes, - {forward_res_y.data(), forward_res_h.data()}, - args, - false); + cell_pass(CellType::GRU, + {X, + seq_lengths, + h_pointers[0], + w_pointers[0], + r_pointers[0], + b_pointers[0]}, + shapes, + {forward_res_y.data(), forward_res_h.data()}, + args, + false); // reverse pass - cell_pass(CellType::GRU, - {X, - seq_lengths, - h_pointers[1], - w_pointers[1], - r_pointers[1], - b_pointers[1]}, - shapes, - {reverse_res_y.data(), reverse_res_h.data()}, - args, - true); + cell_pass(CellType::GRU, + {X, + seq_lengths, + h_pointers[1], + w_pointers[1], + r_pointers[1], + b_pointers[1]}, + shapes, + {reverse_res_y.data(), reverse_res_h.data()}, + args, + true); // Stack together respective outputs from both forward and reverse passes. std::vector in_shapes_y = {{H_shape[0], 1, X_shape[1], H_shape[2]}, @@ -447,7 +543,7 @@ namespace ngraph } } - template + template void rnn_sequence(const char* X, const Shape& X_shape, const char* H, @@ -477,12 +573,12 @@ namespace ngraph std::vector outputs = {Y, Ho}; std::vector shapes = { X_shape, seq_lengths_shape, H_shape, W_shape, R_shape, B_shape}; - cell_pass(CellType::RNN, - inputs, - shapes, - outputs, - args, - direction == op::RecurrentSequenceDirection::REVERSE); + cell_pass(CellType::RNN, + inputs, + shapes, + outputs, + args, + direction == op::RecurrentSequenceDirection::REVERSE); } else if (direction == op::RecurrentSequenceDirection::BIDIRECTIONAL) { @@ -523,29 +619,29 @@ namespace ngraph shapes[i][0] = 1; } // forward pass - cell_pass(CellType::RNN, - {X, - seq_lengths, - h_pointers[0], - w_pointers[0], - r_pointers[0], - b_pointers[0]}, - shapes, - {forward_res_y.data(), forward_res_h.data()}, - args, - false); + cell_pass(CellType::RNN, + {X, + seq_lengths, + h_pointers[0], + w_pointers[0], + r_pointers[0], + b_pointers[0]}, + shapes, + {forward_res_y.data(), forward_res_h.data()}, + args, + false); // reverse pass - cell_pass(CellType::RNN, - {X, - seq_lengths, - h_pointers[1], - w_pointers[1], - r_pointers[1], - b_pointers[1]}, - shapes, - {reverse_res_y.data(), reverse_res_h.data()}, - args, - true); + cell_pass(CellType::RNN, + {X, + seq_lengths, + h_pointers[1], + w_pointers[1], + r_pointers[1], + b_pointers[1]}, + shapes, + {reverse_res_y.data(), reverse_res_h.data()}, + args, + true); // Stack together respective outputs from both forward and reverse passes. std::vector in_shapes_y = {{H_shape[0], 1, X_shape[1], H_shape[2]}, diff --git a/ngraph/core/reference/src/runtime/reference/tensor_iterator.cpp b/ngraph/core/reference/src/runtime/reference/tensor_iterator.cpp index 80b8784..08f80cd 100644 --- a/ngraph/core/reference/src/runtime/reference/tensor_iterator.cpp +++ b/ngraph/core/reference/src/runtime/reference/tensor_iterator.cpp @@ -91,7 +91,9 @@ namespace ngraph std::vector pointers_to_data(num_iterations); for (size_t j = 0; j < pointers_to_data.size(); ++j) { - pointers_to_data[j] = + pointers_to_data[slice_desc->m_stride > 0 + ? j + : (pointers_to_data.size() - j - 1)] = sliced_values[slice_in_idx][j]->get_data_ptr(); } reference::split(args[slice_desc->m_input_index]->get_data_ptr(), @@ -118,6 +120,7 @@ namespace ngraph } // Evaluate body + body_outputs.clear(); if (!evaluate) { reference::function(func, inputs_to_body, body_outputs); @@ -164,9 +167,13 @@ namespace ngraph out[concat_desc->m_output_index]->set_shape(shape); std::vector pointers_on_values; pointers_on_values.reserve(values_to_concat[i].size()); - for (const auto& vec : values_to_concat[i]) + for (size_t j = 0; j < values_to_concat[i].size(); ++j) { - pointers_on_values.push_back(vec->get_data_ptr()); + pointers_on_values.push_back( + values_to_concat[i][concat_desc->m_stride > 0 + ? j + : (values_to_concat[i].size() - j - 1)] + ->get_data_ptr()); } reference::concat(pointers_on_values, out[concat_desc->m_output_index]->get_data_ptr(), diff --git a/ngraph/python/src/ngraph/opset1/ops.py b/ngraph/python/src/ngraph/opset1/ops.py index 00c1d5c..5af81cf 100644 --- a/ngraph/python/src/ngraph/opset1/ops.py +++ b/ngraph/python/src/ngraph/opset1/ops.py @@ -1500,17 +1500,17 @@ def lstm_sequence( Shape: [batch_size]. Integer type. @param W: Tensor with weights for matrix multiplication operation with input portion of data. Shape: [num_directions, 4*hidden_size, input_size]. - @param R: The tensor with weights for matrix multiplication operation with hidden state. - Shape: [num_directions, 4*hidden_size, input_size]. - @param B: The tensor with biases. + :param R: The tensor with weights for matrix multiplication operation with hidden state. Shape: [num_directions, 4*hidden_size, hidden_size]. - @param hidden_size: Specifies hidden state size. - @param direction: Specifies if the RNN is forward, reverse, or bidirectional. - @param activations: The list of three activation functions for gates. - @param activations_alpha: The list of alpha parameters for activation functions. - @param activations_beta: The list of beta parameters for activation functions. - @param clip: Specifies bound values [-C, C] for tensor clipping performed before activations. - @param name: An optional name of the output node. + :param B: The tensor with biases. + Shape: [num_directions, 4*hidden_size]. + :param hidden_size: Specifies hidden state size. + :param direction: Specifies if the RNN is forward, reverse, or bidirectional. + :param activations: The list of three activation functions for gates. + :param activations_alpha: The list of alpha parameters for activation functions. + :param activations_beta: The list of beta parameters for activation functions. + :param clip: Specifies bound values [-C, C] for tensor clipping performed before activations. + :param name: An optional name of the output node. @return The new node represents LSTMSequence. Node outputs count: 3. """ diff --git a/ngraph/python/src/ngraph/opset3/ops.py b/ngraph/python/src/ngraph/opset3/ops.py index a170974..119bd66 100644 --- a/ngraph/python/src/ngraph/opset3/ops.py +++ b/ngraph/python/src/ngraph/opset3/ops.py @@ -312,7 +312,7 @@ def gru_cell( @return The new node performing a GRUCell operation on tensor from input node. """ if activations is None: - activations = ["relu", "sigmoid", "tanh"] + activations = ["sigmoid", "tanh"] if activations_alpha is None: activations_alpha = [] if activations_beta is None: @@ -432,7 +432,7 @@ def rnn_cell( @param W: The weight tensor with shape: [hidden_size, input_size]. @param R: The recurrence weight tensor with shape: [hidden_size, hidden_size]. - @param B: The bias tensor for input gate with shape: [2*hidden_size]. + @param B: The sum of biases (weight and recurrence) with shape: [hidden_size]. @param hidden_size: The number of hidden units for recurrent cell. Specifies hidden state size. @param activations: The vector of activation functions used inside recurrent cell. @@ -446,7 +446,7 @@ def rnn_cell( @return The new node performing a RNNCell operation on tensor from input node. """ if activations is None: - activations = ["sigmoid", "tanh"] + activations = ["tanh"] if activations_alpha is None: activations_alpha = [] if activations_beta is None: @@ -557,7 +557,7 @@ def shape_of(data: NodeInput, output_type: str = "i64", name: Optional[str] = No """! Return a node which produces a tensor containing the shape of its input data. @param data: The tensor containing the input data. - :para output_type: Output element type. + @param output_type: Output element type. @return ShapeOf node """ return _get_node_factory_opset3().create( diff --git a/ngraph/python/src/ngraph/opset5/ops.py b/ngraph/python/src/ngraph/opset5/ops.py index 56fee6b..468f9ef 100644 --- a/ngraph/python/src/ngraph/opset5/ops.py +++ b/ngraph/python/src/ngraph/opset5/ops.py @@ -70,15 +70,15 @@ def batch_norm_inference( ) -> Node: """Perform layer normalizes a input tensor by mean and variance with appling scale and offset. - :param data: The input tensor with data for normalization. - :param gamma: The scalar scaling for normalized value. - :param beta: The bias added to the scaled normalized value. - :param mean: The value for mean normalization. - :param variance: The value for variance normalization. - :param epsilon: The number to be added to the variance to avoid division + @param data: The input tensor with data for normalization. + @param gamma: The scalar scaling for normalized value. + @param beta: The bias added to the scaled normalized value. + @param mean: The value for mean normalization. + @param variance: The value for variance normalization. + @param epsilon: The number to be added to the variance to avoid division by zero when normalizing a value. - :param name: The optional name of the output node. - :return: The new node which performs BatchNormInference. + @param name: The optional name of the output node. + @return: The new node which performs BatchNormInference. """ inputs = as_nodes(data, gamma, beta, mean, variance) return _get_node_factory_opset5().create("BatchNormInference", inputs, {"epsilon": epsilon}) @@ -93,10 +93,10 @@ def gather_nd( ) -> Node: """Return a node which performs GatherND. - :param data: N-D tensor with data for gathering - :param indices: K-D tensor of tuples with indices by which data is gathered - :param batch_dims: Scalar value of batch dimensions - :return: The new node which performs GatherND + @param data: N-D tensor with data for gathering + @param indices: K-D tensor of tuples with indices by which data is gathered + @param batch_dims: Scalar value of batch dimensions + @return: The new node which performs GatherND """ inputs = as_nodes(data, indices) @@ -111,9 +111,9 @@ def gather_nd( def log_softmax(data: NodeInput, axis: int, name: Optional[str] = None) -> Node: """Apply LogSoftmax operation on each element of input tensor. - :param data: The tensor providing input data. - :param axis: An axis along which LogSoftmax should be calculated - :return: The new node with LogSoftmax operation applied on each element. + @param data: The tensor providing input data. + @param axis: An axis along which LogSoftmax should be calculated + @return: The new node with LogSoftmax operation applied on each element. """ return _get_node_factory_opset5().create("LogSoftmax", [as_node(data)], {"axis": axis}) @@ -133,18 +133,18 @@ def non_max_suppression( ) -> Node: """Return a node which performs NonMaxSuppression. - :param boxes: Tensor with box coordinates. - :param scores: Tensor with box scores. - :param max_output_boxes_per_class: Tensor Specifying maximum number of boxes + @param boxes: Tensor with box coordinates. + @param scores: Tensor with box scores. + @param max_output_boxes_per_class: Tensor Specifying maximum number of boxes to be selected per class. - :param iou_threshold: Tensor specifying intersection over union threshold - :param score_threshold: Tensor specifying minimum score to consider box for the processing. - :param soft_nms_sigma: Tensor specifying the sigma parameter for Soft-NMS. - :param box_encoding: Format of boxes data encoding. - :param sort_result_descending: Flag that specifies whenever it is necessary to sort selected + @param iou_threshold: Tensor specifying intersection over union threshold + @param score_threshold: Tensor specifying minimum score to consider box for the processing. + @param soft_nms_sigma: Tensor specifying the sigma parameter for Soft-NMS. + @param box_encoding: Format of boxes data encoding. + @param sort_result_descending: Flag that specifies whenever it is necessary to sort selected boxes across batches or not. - :param output_type: Output element type. - :return: The new node which performs NonMaxSuppression + @param output_type: Output element type. + @return: The new node which performs NonMaxSuppression """ if max_output_boxes_per_class is None: max_output_boxes_per_class = make_constant_node(0, np.int64) @@ -174,63 +174,131 @@ def non_max_suppression( def round(data: NodeInput, mode: str = "half_to_even", name: Optional[str] = None) -> Node: """Apply Round operation on each element of input tensor. - :param data: The tensor providing input data. - :param mode: Rule to round halfway cases. If set to 'half_to_even' then halfs round to the nearest even + @param data: The tensor providing input data. + @param mode: Rule to round halfway cases. If set to 'half_to_even' then halfs round to the nearest even integer or rounding in such a way that the result heads away from zero if `mode` attribute is 'half_away_from_zero`. - :param name: An optional name of the output node. - :return: The new node with Round operation applied on each element. + @param name: An optional name of the output node. + @return: The new node with Round operation applied on each element. """ return _get_node_factory_opset5().create("Round", as_nodes(data), {"mode": mode.upper()}) @nameable_op +def lstm_sequence( + X: NodeInput, + initial_hidden_state: NodeInput, + initial_cell_state: NodeInput, + sequence_lengths: NodeInput, + W: NodeInput, + R: NodeInput, + B: NodeInput, + hidden_size: int, + direction: str, + activations: List[str] = None, + activations_alpha: List[float] = None, + activations_beta: List[float] = None, + clip: float = 0.0, + name: Optional[str] = None, +) -> Node: + """Return a node which performs LSTMSequence operation. + + @param X: The input tensor. Shape: [batch_size, seq_length, input_size]. + @param initial_hidden_state: The hidden state tensor. + Shape: [batch_size, num_directions, hidden_size]. + @param initial_cell_state: The cell state tensor. + Shape: [batch_size, num_directions, hidden_size]. + @param sequence_lengths: Specifies real sequence lengths for each batch element. + Shape: [batch_size]. Integer type. + @param W: Tensor with weights for matrix multiplication operation with input portion of data. + Expected format: fico + Shape: [num_directions, 4*hidden_size, input_size]. + @param R: The tensor with weights for matrix multiplication operation with hidden state. + Expected format: fico + Shape: [num_directions, 4*hidden_size, hidden_size]. + @param B: The sum of biases (weight and recurrence). Expected format: fico + Shape: [num_directions, 4*hidden_size]. + @param hidden_size: Specifies hidden state size. + @param direction: Specifies if the RNN is forward, reverse, or bidirectional. + @param activations: The list of three activation functions for gates. + @param activations_alpha: The list of alpha parameters for activation functions. + @param activations_beta: The list of beta parameters for activation functions. + @param clip: Specifies bound values [-C, C] for tensor clipping performed before activations. + @param name: An optional name of the output node. + + @return: The new node represents LSTMSequence. Node outputs count: 3. + """ + if activations is None: + activations = ["sigmoid", "tanh", "tanh"] + if activations_alpha is None: + activations_alpha = [] + if activations_beta is None: + activations_beta = [] + + node_inputs = as_nodes(X, initial_hidden_state, initial_cell_state, sequence_lengths, W, R, B) + + attributes = { + "hidden_size": hidden_size, + "direction": direction.lower(), + "activations": activations, + "activations_alpha": activations_alpha, + "activations_beta": activations_beta, + "clip": clip, + } + return _get_node_factory_opset5().create("LSTMSequence", node_inputs, attributes) + + def hsigmoid(data: NodeInput, name: Optional[str] = None,) -> Node: """Return a node which performs HSigmoid. - :param data: Tensor with input data floating point type. - :return: The new node which performs HSigmoid + @param data: Tensor with input data floating point type. + @return: The new node which performs HSigmoid """ return _get_node_factory_opset5().create("HSigmoid", as_nodes(data), {}) @nameable_op def gru_sequence( - X: NodeInput, - H_t: NodeInput, - sequence_lengths: NodeInput, - W: NodeInput, - R: NodeInput, - B: NodeInput, - hidden_size: int, - direction: str, - activations: List[str] = None, - activations_alpha: List[float] = None, - activations_beta: List[float] = None, - clip: float = 0.0, - linear_before_reset: bool = False, - name: Optional[str] = None, + X: NodeInput, + initial_hidden_state: NodeInput, + sequence_lengths: NodeInput, + W: NodeInput, + R: NodeInput, + B: NodeInput, + hidden_size: int, + direction: str, + activations: List[str] = None, + activations_alpha: List[float] = None, + activations_beta: List[float] = None, + clip: float = 0.0, + linear_before_reset: bool = False, + name: Optional[str] = None, ) -> Node: - """Return a node which performs GRUSequence. - - :param X: 3D tensor, input data. - :param H_t: 3D tensor, input hidden state data. - :param sequence_lengths: 1D tensor, specifies sequence lenghts - for each batch element. - :param W: 3D tensor, weights matrix. - :param R: 3D tensor, recurrence weights matrix. - :param B: 2D tensor, sum of biases. - :param hidden_size: Size of the hidden state. - :param direction: Specify if the RNN is forward, reverse, or bidirectional. - :param activations: Activation functions for gates. - :param activations_alpha: Attributes of function; applicability and meaning - of these attributes depends on choosen activation function. - :param activations_beta: Attributes of function; applicability and meaning - of these attributes depends on choosen activation function. - :param clip: Specifies bound values *[-clip, clip]* for tensor clipping. - :param linear_before_reset: During the computation of the output of - the hidden gate, apply the linear transformation. - :return: The new node which performs GRUSequence + """Return a node which performs GRUSequence operation. + + @param X: The input tensor. Shape: [batch_size, seq_length, input_size]. + @param initial_hidden_state: The hidden state tensor. + Shape: [batch_size, num_directions, hidden_size]. + @param sequence_lengths: Specifies real sequence lengths for each batch element. + Shape: [batch_size]. Integer type. + @param W: Tensor with weights for matrix multiplication operation with input portion of data. + Shape: [num_directions, 3*hidden_size, input_size]. + @param R: The tensor with weights for matrix multiplication operation with hidden state. + Shape: [num_directions, 3*hidden_size, hidden_size]. + @param B: The sum of biases (weight and recurrence). + For linear_before_reset set True the shape is [num_directions, 4*hidden_size]. + Otherwise the shape is [num_directions, 3*hidden_size]. + @param hidden_size: Specifies hidden state size. + @param direction: Specifies if the RNN is forward, reverse, or bidirectional. + @param activations: The list of three activation functions for gates. + @param activations_alpha: The list of alpha parameters for activation functions. + @param activations_beta: The list of beta parameters for activation functions. + @param clip: Specifies bound values [-C, C] for tensor clipping performed before activations. + @param linear_before_reset: Flag denotes if the layer behaves according to the modification + of GRU described in the formula in the ONNX documentation. + @param name: An optional name of the output node. + + @return: The new node represents GRUSequence. Node outputs count: 2. """ if activations is None: activations = ["sigmoid", "tanh"] @@ -239,54 +307,58 @@ def gru_sequence( if activations_beta is None: activations_beta = [] - inputs = as_nodes(X, H_t, sequence_lengths, W, R, B) + node_inputs = as_nodes(X, initial_hidden_state, sequence_lengths, W, R, B) attributes = { "hidden_size": hidden_size, + "direction": direction.lower(), "activations": activations, "activations_alpha": activations_alpha, - "activations_beta": activations_alpha, - "clip": clip, + "activations_beta": activations_beta, "linear_before_reset": linear_before_reset, + "clip": clip, } - - return _get_node_factory_opset5().create("GRUSequence", inputs, attributes) + return _get_node_factory_opset5().create("GRUSequence", node_inputs, attributes) @nameable_op def rnn_sequence( - X: NodeInput, - H_t: NodeInput, - sequence_lengths: NodeInput, - W: NodeInput, - R: NodeInput, - B: NodeInput, - hidden_size: int, - direction: str, - activations: List[str] = None, - activations_alpha: List[float] = None, - activations_beta: List[float] = None, - clip: float = 0.0, - name: Optional[str] = None, + X: NodeInput, + initial_hidden_state: NodeInput, + sequence_lengths: NodeInput, + W: NodeInput, + R: NodeInput, + B: NodeInput, + hidden_size: int, + direction: str, + activations: List[str] = None, + activations_alpha: List[float] = None, + activations_beta: List[float] = None, + clip: float = 0.0, + name: Optional[str] = None, ) -> Node: - """Return a node which performs RNNSequence. - - :param X: 3D tensor, input data. - :param H_t: 3D tensor, input hidden state data. - :param sequence_lengths: 1D tensor, specifies sequence lenghts - for each batch element. - :param W: 3D tensor, weights matrix. - :param R: 3D tensor, recurrence weights matrix. - :param B: 2D tensor, sum of biases. - :param hidden_size: Size of the hidden state. - :param direction: Specify if the RNN is forward, reverse, or bidirectional. - :param activations: Activation functions for gates. - :param activations_alpha: Attributes of function; applicability and meaning - of these attributes depends on choosen activation function. - :param activations_beta: Attributes of function; applicability and meaning - of these attributes depends on choosen activation function. - :param clip: Specifies bound values *[-clip, clip]* for tensor clipping. - :return: The new node which performs RNNSequence + """Return a node which performs RNNSequence operation. + + @param X: The input tensor. Shape: [batch_size, seq_length, input_size]. + @param initial_hidden_state: The hidden state tensor. + Shape: [batch_size, num_directions, hidden_size]. + @param sequence_lengths: Specifies real sequence lengths for each batch element. + Shape: [batch_size]. Integer type. + @param W: Tensor with weights for matrix multiplication operation with input portion of data. + Shape: [num_directions, hidden_size, input_size]. + @param R: The tensor with weights for matrix multiplication operation with hidden state. + Shape: [num_directions, hidden_size, hidden_size]. + @param B: The sum of biases (weight and recurrence). + Shape: [num_directions, hidden_size]. + @param hidden_size: Specifies hidden state size. + @param direction: Specifies if the RNN is forward, reverse, or bidirectional. + @param activations: The list of three activation functions for gates. + @param activations_alpha: The list of alpha parameters for activation functions. + @param activations_beta: The list of beta parameters for activation functions. + @param clip: Specifies bound values [-C, C] for tensor clipping performed before activations. + @param name: An optional name of the output node. + + @return: The new node represents RNNSequence. Node outputs count: 2. """ if activations is None: activations = ["tanh"] @@ -295,13 +367,14 @@ def rnn_sequence( if activations_beta is None: activations_beta = [] - inputs = as_nodes(X, H_t, sequence_lengths, W, R, B) + inputs = as_nodes(X, initial_hidden_state, sequence_lengths, W, R, B) attributes = { "hidden_size": hidden_size, + "direction": direction.lower(), "activations": activations, "activations_alpha": activations_alpha, - "activations_beta": activations_alpha, + "activations_beta": activations_beta, "clip": clip, } @@ -316,11 +389,11 @@ def loop( ) -> Node: """Return a node which performs Loop. - :param trip_count: A scalar or 1D tensor with 1 element specifying + @param trip_count: A scalar or 1D tensor with 1 element specifying maximum number of iterations. - :param execution_condition: A scalar or 1D tensor with 1 element + @param execution_condition: A scalar or 1D tensor with 1 element specifying whether to execute the first iteration or not. - :return: The new node which performs Loop. + @return: The new node which performs Loop. """ inputs = as_nodes(trip_count, execution_condition) diff --git a/ngraph/python/tests/__init__.py b/ngraph/python/tests/__init__.py index e2f7830..a39054a 100644 --- a/ngraph/python/tests/__init__.py +++ b/ngraph/python/tests/__init__.py @@ -235,4 +235,3 @@ xfail_issue_39663 = xfail_test(reason="RuntimeError: Unsupported primitive of ty xfail_issue_41815 = xfail_test(reason="RuntimeError: Unsupported dynamic ops: v5::NonMaxSuppression casted " "(yolo_evaluation_layer_1/concat_6:0_btc[0]:f32{1,2535,4},") xfail_issue_41894 = xfail_test(reason="CPU plugin elementwise computation missmatch") -xfail_issue_42818 = xfail_test(reason="AssertionError: This model has no test data") diff --git a/ngraph/python/tests/test_ngraph/test_create_op.py b/ngraph/python/tests/test_ngraph/test_create_op.py index 094a6a6..996cad6 100644 --- a/ngraph/python/tests/test_ngraph/test_create_op.py +++ b/ngraph/python/tests/test_ngraph/test_create_op.py @@ -290,7 +290,7 @@ def test_lstm_cell_operator_opset1(dtype): @pytest.mark.parametrize("dtype", [np.float32, np.float64]) -def test_lstm_sequence_operator_bidirectional(dtype): +def test_lstm_sequence_operator_bidirectional_opset1(dtype): batch_size = 1 input_size = 16 hidden_size = 128 @@ -355,7 +355,7 @@ def test_lstm_sequence_operator_bidirectional(dtype): @pytest.mark.parametrize("dtype", [np.float32, np.float64]) -def test_lstm_sequence_operator_reverse(dtype): +def test_lstm_sequence_operator_reverse_opset1(dtype): batch_size = 2 input_size = 4 hidden_size = 3 @@ -421,7 +421,7 @@ def test_lstm_sequence_operator_reverse(dtype): @pytest.mark.parametrize("dtype", [np.float32, np.float64]) -def test_lstm_sequence_operator_forward(dtype): +def test_lstm_sequence_operator_forward_opset1(dtype): batch_size = 2 input_size = 4 hidden_size = 3 @@ -1089,3 +1089,582 @@ def test_extract_image_patches(): assert node.get_output_size() == 1 assert list(node.get_output_shape(0)) == [64, 27, 2, 2] assert node.get_output_element_type(0) == Type.i32 + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_lstm_sequence_operator_bidirectional(dtype): + batch_size = 1 + input_size = 16 + hidden_size = 128 + num_directions = 2 + seq_length = 2 + + X_shape = [batch_size, seq_length, input_size] + H_t_shape = [batch_size, num_directions, hidden_size] + C_t_shape = [batch_size, num_directions, hidden_size] + seq_len_shape = [batch_size] + W_shape = [num_directions, 4 * hidden_size, input_size] + R_shape = [num_directions, 4 * hidden_size, hidden_size] + B_shape = [num_directions, 4 * hidden_size] + + parameter_X = ng.parameter(X_shape, name="X", dtype=dtype) + parameter_H_t = ng.parameter(H_t_shape, name="H_t", dtype=dtype) + parameter_C_t = ng.parameter(C_t_shape, name="C_t", dtype=dtype) + parameter_seq_len = ng.parameter(seq_len_shape, name="seq_len", dtype=np.int32) + parameter_W = ng.parameter(W_shape, name="W", dtype=dtype) + parameter_R = ng.parameter(R_shape, name="R", dtype=dtype) + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + direction = "BIDIRECTIONAL" + node = ng.lstm_sequence( + parameter_X, + parameter_H_t, + parameter_C_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + ) + + assert node.get_type_name() == "LSTMSequence" + assert node.get_output_size() == 3 + + activations = ["RELU", "tanh", "Sigmoid"] + activation_alpha = [1.0, 2.0, 3.0] + activation_beta = [3.0, 2.0, 1.0] + clip = 1.22 + + node_param = ng.lstm_sequence( + parameter_X, + parameter_H_t, + parameter_C_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + activations, + activation_alpha, + activation_beta, + clip, + ) + + assert node_param.get_type_name() == "LSTMSequence" + assert node_param.get_output_size() == 3 + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_lstm_sequence_operator_reverse(dtype): + batch_size = 2 + input_size = 4 + hidden_size = 3 + num_directions = 1 + seq_length = 2 + + X_shape = [batch_size, seq_length, input_size] + H_t_shape = [batch_size, num_directions, hidden_size] + C_t_shape = [batch_size, num_directions, hidden_size] + seq_len_shape = [batch_size] + W_shape = [num_directions, 4 * hidden_size, input_size] + R_shape = [num_directions, 4 * hidden_size, hidden_size] + B_shape = [num_directions, 4 * hidden_size] + + parameter_X = ng.parameter(X_shape, name="X", dtype=dtype) + parameter_H_t = ng.parameter(H_t_shape, name="H_t", dtype=dtype) + parameter_C_t = ng.parameter(C_t_shape, name="C_t", dtype=dtype) + parameter_seq_len = ng.parameter(seq_len_shape, name="seq_len", dtype=np.int32) + parameter_W = ng.parameter(W_shape, name="W", dtype=dtype) + parameter_R = ng.parameter(R_shape, name="R", dtype=dtype) + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + direction = "REVERSE" + + node_default = ng.lstm_sequence( + parameter_X, + parameter_H_t, + parameter_C_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + ) + + assert node_default.get_type_name() == "LSTMSequence" + assert node_default.get_output_size() == 3 + + activations = ["RELU", "tanh", "Sigmoid"] + activation_alpha = [1.0, 2.0, 3.0] + activation_beta = [3.0, 2.0, 1.0] + clip = 1.22 + + node_param = ng.lstm_sequence( + parameter_X, + parameter_H_t, + parameter_C_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + activations, + activation_alpha, + activation_beta, + clip, + ) + + assert node_param.get_type_name() == "LSTMSequence" + assert node_param.get_output_size() == 3 + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_lstm_sequence_operator_forward(dtype): + batch_size = 2 + input_size = 4 + hidden_size = 3 + num_directions = 1 + seq_length = 2 + + X_shape = [batch_size, seq_length, input_size] + H_t_shape = [batch_size, num_directions, hidden_size] + C_t_shape = [batch_size, num_directions, hidden_size] + seq_len_shape = [batch_size] + W_shape = [num_directions, 4 * hidden_size, input_size] + R_shape = [num_directions, 4 * hidden_size, hidden_size] + B_shape = [num_directions, 4 * hidden_size] + + parameter_X = ng.parameter(X_shape, name="X", dtype=dtype) + parameter_H_t = ng.parameter(H_t_shape, name="H_t", dtype=dtype) + parameter_C_t = ng.parameter(C_t_shape, name="C_t", dtype=dtype) + parameter_seq_len = ng.parameter(seq_len_shape, name="seq_len", dtype=np.int32) + parameter_W = ng.parameter(W_shape, name="W", dtype=dtype) + parameter_R = ng.parameter(R_shape, name="R", dtype=dtype) + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + direction = "forward" + + node_default = ng.lstm_sequence( + parameter_X, + parameter_H_t, + parameter_C_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + ) + + assert node_default.get_type_name() == "LSTMSequence" + assert node_default.get_output_size() == 3 + + activations = ["RELU", "tanh", "Sigmoid"] + activation_alpha = [2.0] + activation_beta = [1.0] + clip = 0.5 + + node = ng.lstm_sequence( + parameter_X, + parameter_H_t, + parameter_C_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + activations, + activation_alpha, + activation_beta, + clip, + ) + + assert node.get_type_name() == "LSTMSequence" + assert node.get_output_size() == 3 + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_gru_sequence_operator_bidirectional(dtype): + batch_size = 1 + input_size = 16 + hidden_size = 128 + num_directions = 2 + seq_length = 2 + + X_shape = [batch_size, seq_length, input_size] + H_t_shape = [batch_size, num_directions, hidden_size] + seq_len_shape = [batch_size] + W_shape = [num_directions, 3 * hidden_size, input_size] + R_shape = [num_directions, 3 * hidden_size, hidden_size] + B_shape = [num_directions, 3 * hidden_size] + + parameter_X = ng.parameter(X_shape, name="X", dtype=dtype) + parameter_H_t = ng.parameter(H_t_shape, name="H_t", dtype=dtype) + parameter_seq_len = ng.parameter(seq_len_shape, name="seq_len", dtype=np.int32) + parameter_W = ng.parameter(W_shape, name="W", dtype=dtype) + parameter_R = ng.parameter(R_shape, name="R", dtype=dtype) + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + direction = "BIDIRECTIONAL" + node = ng.gru_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + ) + + assert node.get_type_name() == "GRUSequence" + assert node.get_output_size() == 2 + + activations = ["RELU", "tanh"] + activation_alpha = [1.0, 2.0, 3.0] + activation_beta = [3.0, 2.0, 1.0] + clip = 1.22 + linear_before_reset = True + B_shape = [num_directions, 4 * hidden_size] + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + node_param = ng.gru_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + activations, + activation_alpha, + activation_beta, + clip, + linear_before_reset + ) + + assert node_param.get_type_name() == "GRUSequence" + assert node_param.get_output_size() == 2 + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_gru_sequence_operator_reverse(dtype): + batch_size = 2 + input_size = 4 + hidden_size = 3 + num_directions = 1 + seq_length = 2 + + X_shape = [batch_size, seq_length, input_size] + H_t_shape = [batch_size, num_directions, hidden_size] + seq_len_shape = [batch_size] + W_shape = [num_directions, 3 * hidden_size, input_size] + R_shape = [num_directions, 3 * hidden_size, hidden_size] + B_shape = [num_directions, 3 * hidden_size] + + parameter_X = ng.parameter(X_shape, name="X", dtype=dtype) + parameter_H_t = ng.parameter(H_t_shape, name="H_t", dtype=dtype) + parameter_seq_len = ng.parameter(seq_len_shape, name="seq_len", dtype=np.int32) + parameter_W = ng.parameter(W_shape, name="W", dtype=dtype) + parameter_R = ng.parameter(R_shape, name="R", dtype=dtype) + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + direction = "REVERSE" + + node_default = ng.gru_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + ) + + assert node_default.get_type_name() == "GRUSequence" + assert node_default.get_output_size() == 2 + + activations = ["RELU", "tanh"] + activation_alpha = [1.0, 2.0, 3.0] + activation_beta = [3.0, 2.0, 1.0] + clip = 1.22 + linear_before_reset = True + B_shape = [num_directions, 4 * hidden_size] + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + node_param = ng.gru_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + activations, + activation_alpha, + activation_beta, + clip, + linear_before_reset + ) + + assert node_param.get_type_name() == "GRUSequence" + assert node_param.get_output_size() == 2 + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_gru_sequence_operator_forward(dtype): + batch_size = 2 + input_size = 4 + hidden_size = 3 + num_directions = 1 + seq_length = 2 + + X_shape = [batch_size, seq_length, input_size] + H_t_shape = [batch_size, num_directions, hidden_size] + seq_len_shape = [batch_size] + W_shape = [num_directions, 3 * hidden_size, input_size] + R_shape = [num_directions, 3 * hidden_size, hidden_size] + B_shape = [num_directions, 3 * hidden_size] + + parameter_X = ng.parameter(X_shape, name="X", dtype=dtype) + parameter_H_t = ng.parameter(H_t_shape, name="H_t", dtype=dtype) + parameter_seq_len = ng.parameter(seq_len_shape, name="seq_len", dtype=np.int32) + parameter_W = ng.parameter(W_shape, name="W", dtype=dtype) + parameter_R = ng.parameter(R_shape, name="R", dtype=dtype) + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + direction = "forward" + + node_default = ng.gru_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + ) + + assert node_default.get_type_name() == "GRUSequence" + assert node_default.get_output_size() == 2 + + activations = ["RELU", "tanh"] + activation_alpha = [2.0] + activation_beta = [1.0] + clip = 0.5 + linear_before_reset = True + B_shape = [num_directions, 4 * hidden_size] + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + node = ng.gru_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + activations, + activation_alpha, + activation_beta, + clip, + linear_before_reset + ) + + assert node.get_type_name() == "GRUSequence" + assert node.get_output_size() == 2 + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_rnn_sequence_operator_bidirectional(dtype): + batch_size = 1 + input_size = 16 + hidden_size = 128 + num_directions = 2 + seq_length = 2 + + X_shape = [batch_size, seq_length, input_size] + H_t_shape = [batch_size, num_directions, hidden_size] + seq_len_shape = [batch_size] + W_shape = [num_directions, hidden_size, input_size] + R_shape = [num_directions, hidden_size, hidden_size] + B_shape = [num_directions, hidden_size] + + parameter_X = ng.parameter(X_shape, name="X", dtype=dtype) + parameter_H_t = ng.parameter(H_t_shape, name="H_t", dtype=dtype) + parameter_seq_len = ng.parameter(seq_len_shape, name="seq_len", dtype=np.int32) + parameter_W = ng.parameter(W_shape, name="W", dtype=dtype) + parameter_R = ng.parameter(R_shape, name="R", dtype=dtype) + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + direction = "BIDIRECTIONAL" + node = ng.rnn_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + ) + + assert node.get_type_name() == "RNNSequence" + assert node.get_output_size() == 2 + + activations = ["RELU", "tanh"] + activation_alpha = [1.0, 2.0, 3.0] + activation_beta = [3.0, 2.0, 1.0] + clip = 1.22 + + node_param = ng.rnn_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + activations, + activation_alpha, + activation_beta, + clip, + ) + + assert node_param.get_type_name() == "RNNSequence" + assert node_param.get_output_size() == 2 + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_rnn_sequence_operator_reverse(dtype): + batch_size = 2 + input_size = 4 + hidden_size = 3 + num_directions = 1 + seq_length = 2 + + X_shape = [batch_size, seq_length, input_size] + H_t_shape = [batch_size, num_directions, hidden_size] + seq_len_shape = [batch_size] + W_shape = [num_directions, hidden_size, input_size] + R_shape = [num_directions, hidden_size, hidden_size] + B_shape = [num_directions, hidden_size] + + parameter_X = ng.parameter(X_shape, name="X", dtype=dtype) + parameter_H_t = ng.parameter(H_t_shape, name="H_t", dtype=dtype) + parameter_seq_len = ng.parameter(seq_len_shape, name="seq_len", dtype=np.int32) + parameter_W = ng.parameter(W_shape, name="W", dtype=dtype) + parameter_R = ng.parameter(R_shape, name="R", dtype=dtype) + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + direction = "REVERSE" + + node_default = ng.rnn_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + ) + + assert node_default.get_type_name() == "RNNSequence" + assert node_default.get_output_size() == 2 + + activations = ["RELU", "tanh"] + activation_alpha = [1.0, 2.0, 3.0] + activation_beta = [3.0, 2.0, 1.0] + clip = 1.22 + + node_param = ng.rnn_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + activations, + activation_alpha, + activation_beta, + clip, + ) + + assert node_param.get_type_name() == "RNNSequence" + assert node_param.get_output_size() == 2 + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_rnn_sequence_operator_forward(dtype): + batch_size = 2 + input_size = 4 + hidden_size = 3 + num_directions = 1 + seq_length = 2 + + X_shape = [batch_size, seq_length, input_size] + H_t_shape = [batch_size, num_directions, hidden_size] + seq_len_shape = [batch_size] + W_shape = [num_directions, hidden_size, input_size] + R_shape = [num_directions, hidden_size, hidden_size] + B_shape = [num_directions, hidden_size] + + parameter_X = ng.parameter(X_shape, name="X", dtype=dtype) + parameter_H_t = ng.parameter(H_t_shape, name="H_t", dtype=dtype) + parameter_seq_len = ng.parameter(seq_len_shape, name="seq_len", dtype=np.int32) + parameter_W = ng.parameter(W_shape, name="W", dtype=dtype) + parameter_R = ng.parameter(R_shape, name="R", dtype=dtype) + parameter_B = ng.parameter(B_shape, name="B", dtype=dtype) + + direction = "forward" + + node_default = ng.rnn_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + ) + + assert node_default.get_type_name() == "RNNSequence" + assert node_default.get_output_size() == 2 + + activations = ["RELU", "tanh"] + activation_alpha = [2.0] + activation_beta = [1.0] + clip = 0.5 + + node = ng.rnn_sequence( + parameter_X, + parameter_H_t, + parameter_seq_len, + parameter_W, + parameter_R, + parameter_B, + hidden_size, + direction, + activations, + activation_alpha, + activation_beta, + clip, + ) + + assert node.get_type_name() == "RNNSequence" + assert node.get_output_size() == 2 diff --git a/ngraph/python/tests/test_onnx/test_zoo_models.py b/ngraph/python/tests/test_onnx/test_zoo_models.py index d587b91..29773c2 100644 --- a/ngraph/python/tests/test_onnx/test_zoo_models.py +++ b/ngraph/python/tests/test_onnx/test_zoo_models.py @@ -38,8 +38,7 @@ from tests import ( xfail_issue_39669, xfail_issue_38726, xfail_issue_40686, - xfail_issue_42779, - xfail_issue_42818) + xfail_issue_42779) MODELS_ROOT_DIR = tests.MODEL_ZOO_DIR @@ -182,7 +181,6 @@ if len(zoo_models) > 0: (xfail_issue_41815, "test_MSFT_opset11_tinyyolov3_yolov3_tiny_cpu"), (xfail_issue_41815, "test_MSFT_opset10_yolov3_yolov3_cpu"), - (xfail_issue_42818, "test_MSFT_opset9_LSTM_Seq_lens_unpacked_model_cpu"), ] for test_case in import_xfail_list + execution_xfail_list: xfail, test_name = test_case diff --git a/ngraph/test/onnx/onnx_import_rnn.in.cpp b/ngraph/test/onnx/onnx_import_rnn.in.cpp index aeb4829..b147e14 100644 --- a/ngraph/test/onnx/onnx_import_rnn.in.cpp +++ b/ngraph/test/onnx/onnx_import_rnn.in.cpp @@ -1689,7 +1689,7 @@ NGRAPH_TEST_F(${BACKEND_NAME}, RNNSequenceOp, onnx_model_rnn_fwd_mixed_seq_len_c -0.18203181f, 0.9996245f, }); - test_case.run(DEFAULT_FLOAT_TOLERANCE_BITS + 3); + test_case.run(DEFAULT_FLOAT_TOLERANCE_BITS + 4); } NGRAPH_TEST_F(${BACKEND_NAME}, RNNSequenceOp, onnx_model_rnn_fwd_mixed_seq_len) diff --git a/ngraph/test/runtime/ie/unit_test.manifest b/ngraph/test/runtime/ie/unit_test.manifest index 4529064..a63bade 100644 --- a/ngraph/test/runtime/ie/unit_test.manifest +++ b/ngraph/test/runtime/ie/unit_test.manifest @@ -237,12 +237,12 @@ IE_CPU.onnx_model_rnn_fwd_bias_initial_h IE_CPU.onnx_model_rnn_bidirectional ## RNN/GRU/LSTM Sequence: Output values mismatch - seq_lengths not supported -IE_CPU.onnx_model_lstm_fwd_mixed_seq_const -IE_CPU.onnx_model_lstm_reverse_mixed_seq_const -IE_CPU.onnx_model_rnn_fwd_mixed_seq_len -IE_CPU.onnx_model_rnn_fwd_mixed_seq_len_const -IE_CPU.onnx_model_gru_fwd_mixed_seq_len -IE_CPU.onnx_model_gru_fwd_mixed_seq_len_const +IE_GPU.onnx_model_lstm_fwd_mixed_seq_const +IE_GPU.onnx_model_lstm_reverse_mixed_seq_const +IE_GPU.onnx_model_rnn_fwd_mixed_seq_len +IE_GPU.onnx_model_rnn_fwd_mixed_seq_len_const +IE_GPU.onnx_model_gru_fwd_mixed_seq_len +IE_GPU.onnx_model_gru_fwd_mixed_seq_len_const #------------------------------------------------------------------------------- diff --git a/ngraph/test/runtime/interpreter/int_executable.hpp b/ngraph/test/runtime/interpreter/int_executable.hpp index b85e07b..cd5f849 100644 --- a/ngraph/test/runtime/interpreter/int_executable.hpp +++ b/ngraph/test/runtime/interpreter/int_executable.hpp @@ -770,74 +770,171 @@ protected: case OP_TYPEID::LSTMSequence_v5: { auto lstm_seq = static_cast(&node); - runtime::reference::lstm_sequence(args[0]->get_data_ptr(), - args[0]->get_shape(), - args[1]->get_data_ptr(), - args[1]->get_shape(), - args[2]->get_data_ptr(), - args[2]->get_shape(), - args[3]->get_data_ptr(), - args[3]->get_shape(), - args[4]->get_data_ptr(), - args[4]->get_shape(), - args[5]->get_data_ptr(), - args[5]->get_shape(), - args[6]->get_data_ptr(), - args[6]->get_shape(), - out[0]->get_data_ptr(), - out[1]->get_data_ptr(), - out[2]->get_data_ptr(), - lstm_seq->get_activations()[0], - lstm_seq->get_activations()[1], - lstm_seq->get_activations()[2], - lstm_seq->get_clip(), - lstm_seq->get_direction()); + auto type = args[3]->get_element_type(); + if (type == element::i64 || type == element::u64) + { + runtime::reference::lstm_sequence(args[0]->get_data_ptr(), + args[0]->get_shape(), + args[1]->get_data_ptr(), + args[1]->get_shape(), + args[2]->get_data_ptr(), + args[2]->get_shape(), + args[3]->get_data_ptr(), + args[3]->get_shape(), + args[4]->get_data_ptr(), + args[4]->get_shape(), + args[5]->get_data_ptr(), + args[5]->get_shape(), + args[6]->get_data_ptr(), + args[6]->get_shape(), + out[0]->get_data_ptr(), + out[1]->get_data_ptr(), + out[2]->get_data_ptr(), + lstm_seq->get_activations()[0], + lstm_seq->get_activations()[1], + lstm_seq->get_activations()[2], + lstm_seq->get_clip(), + lstm_seq->get_direction()); + } + else if (type == element::i32 || type == element::u32) + { + runtime::reference::lstm_sequence(args[0]->get_data_ptr(), + args[0]->get_shape(), + args[1]->get_data_ptr(), + args[1]->get_shape(), + args[2]->get_data_ptr(), + args[2]->get_shape(), + args[3]->get_data_ptr(), + args[3]->get_shape(), + args[4]->get_data_ptr(), + args[4]->get_shape(), + args[5]->get_data_ptr(), + args[5]->get_shape(), + args[6]->get_data_ptr(), + args[6]->get_shape(), + out[0]->get_data_ptr(), + out[1]->get_data_ptr(), + out[2]->get_data_ptr(), + lstm_seq->get_activations()[0], + lstm_seq->get_activations()[1], + lstm_seq->get_activations()[2], + lstm_seq->get_clip(), + lstm_seq->get_direction()); + } + else + { + std::stringstream ss; + ss << "unsupported element type " << type << " op LSTMSequence"; + throw std::runtime_error(ss.str()); + } break; } case OP_TYPEID::GRUSequence_v5: { auto gru_seq = static_cast(&node); - runtime::reference::gru_sequence(args[0]->get_data_ptr(), - args[0]->get_shape(), - args[1]->get_data_ptr(), - args[1]->get_shape(), - args[2]->get_data_ptr(), - args[2]->get_shape(), - args[3]->get_data_ptr(), - args[3]->get_shape(), - args[4]->get_data_ptr(), - args[4]->get_shape(), - args[5]->get_data_ptr(), - args[5]->get_shape(), - out[0]->get_data_ptr(), - out[1]->get_data_ptr(), - gru_seq->get_activations()[0], - gru_seq->get_activations()[1], - gru_seq->get_clip(), - gru_seq->get_direction(), - gru_seq->get_linear_before_reset()); + auto type = args[2]->get_element_type(); + if (type == element::i64 || type == element::u64) + { + runtime::reference::gru_sequence(args[0]->get_data_ptr(), + args[0]->get_shape(), + args[1]->get_data_ptr(), + args[1]->get_shape(), + args[2]->get_data_ptr(), + args[2]->get_shape(), + args[3]->get_data_ptr(), + args[3]->get_shape(), + args[4]->get_data_ptr(), + args[4]->get_shape(), + args[5]->get_data_ptr(), + args[5]->get_shape(), + out[0]->get_data_ptr(), + out[1]->get_data_ptr(), + gru_seq->get_activations()[0], + gru_seq->get_activations()[1], + gru_seq->get_clip(), + gru_seq->get_direction(), + gru_seq->get_linear_before_reset()); + } + else if (type == element::i32 || type == element::u32) + { + runtime::reference::gru_sequence(args[0]->get_data_ptr(), + args[0]->get_shape(), + args[1]->get_data_ptr(), + args[1]->get_shape(), + args[2]->get_data_ptr(), + args[2]->get_shape(), + args[3]->get_data_ptr(), + args[3]->get_shape(), + args[4]->get_data_ptr(), + args[4]->get_shape(), + args[5]->get_data_ptr(), + args[5]->get_shape(), + out[0]->get_data_ptr(), + out[1]->get_data_ptr(), + gru_seq->get_activations()[0], + gru_seq->get_activations()[1], + gru_seq->get_clip(), + gru_seq->get_direction(), + gru_seq->get_linear_before_reset()); + } + else + { + std::stringstream ss; + ss << "unsupported element type " << type << " op GRUSequence"; + throw std::runtime_error(ss.str()); + } break; } case OP_TYPEID::RNNSequence_v5: { auto rnn_seq = static_cast(&node); - runtime::reference::rnn_sequence(args[0]->get_data_ptr(), - args[0]->get_shape(), - args[1]->get_data_ptr(), - args[1]->get_shape(), - args[2]->get_data_ptr(), - args[2]->get_shape(), - args[3]->get_data_ptr(), - args[3]->get_shape(), - args[4]->get_data_ptr(), - args[4]->get_shape(), - args[5]->get_data_ptr(), - args[5]->get_shape(), - out[0]->get_data_ptr(), - out[1]->get_data_ptr(), - rnn_seq->get_activations()[0], - rnn_seq->get_clip(), - rnn_seq->get_direction()); + auto type = args[2]->get_element_type(); + if (type == element::i64 || type == element::u64) + { + runtime::reference::rnn_sequence(args[0]->get_data_ptr(), + args[0]->get_shape(), + args[1]->get_data_ptr(), + args[1]->get_shape(), + args[2]->get_data_ptr(), + args[2]->get_shape(), + args[3]->get_data_ptr(), + args[3]->get_shape(), + args[4]->get_data_ptr(), + args[4]->get_shape(), + args[5]->get_data_ptr(), + args[5]->get_shape(), + out[0]->get_data_ptr(), + out[1]->get_data_ptr(), + rnn_seq->get_activations()[0], + rnn_seq->get_clip(), + rnn_seq->get_direction()); + } + else if (type == element::i32 || type == element::u32) + { + runtime::reference::rnn_sequence(args[0]->get_data_ptr(), + args[0]->get_shape(), + args[1]->get_data_ptr(), + args[1]->get_shape(), + args[2]->get_data_ptr(), + args[2]->get_shape(), + args[3]->get_data_ptr(), + args[3]->get_shape(), + args[4]->get_data_ptr(), + args[4]->get_shape(), + args[5]->get_data_ptr(), + args[5]->get_shape(), + out[0]->get_data_ptr(), + out[1]->get_data_ptr(), + rnn_seq->get_activations()[0], + rnn_seq->get_clip(), + rnn_seq->get_direction()); + } + else + { + std::stringstream ss; + ss << "unsupported element type " << type << " op RNNSequence"; + throw std::runtime_error(ss.str()); + } break; } case OP_TYPEID::Log: @@ -1171,6 +1268,15 @@ protected: reverse->get_sequence_axis(), args[1]->get_data_ptr()); } + else if (node.get_input_element_type(1) == element::i64) + { + reference::reverse_sequence(args[0]->get_data_ptr(), + out[0]->get_data_ptr(), + node.get_input_shape(0), + reverse->get_batch_axis(), + reverse->get_sequence_axis(), + args[1]->get_data_ptr()); + } else { throw ngraph_error("only int32 indices are supported"); diff --git a/ngraph/test/runtime/interpreter/unit_test.manifest b/ngraph/test/runtime/interpreter/unit_test.manifest index 9ba1066..c7b1cfd 100644 --- a/ngraph/test/runtime/interpreter/unit_test.manifest +++ b/ngraph/test/runtime/interpreter/unit_test.manifest @@ -96,17 +96,6 @@ INTERPRETER.onnx_model_conv_integer_zero_point_zero INTERPRETER.onnx_model_conv_integer_no_zero_point INTERPRETER.onnx_model_conv_integer_pads -# GRU/RNN/LSTM Sequence: Output values mismatch - seq_lengths not supported -onnx_model_lstm_fwd_mixed_seq_const -onnx_model_lstm_reverse_mixed_seq_const -onnx_model_lstm_fwd_mixed_seq -onnx_model_lstm_mixed_seq_reverse -onnx_model_gru_fwd_mixed_seq_len -onnx_model_gru_fwd_mixed_seq_len_const -onnx_model_rnn_fwd_mixed_seq_len -onnx_model_rnn_fwd_mixed_seq_len_const - - # Activation function hardsigmoid is not supported. gru_cell_activation_function lstm_cell_activaction_functions -- 2.7.4