Support LSTMSequence with -1 sequence length (#20935)

* [GPU] Support LSTMSequence w/ -1 seq_length

Co-authored-by:Taylor Yeonbok Lee <taylor.lee@intel.com>
Co-authored-by:Andrew Park <andrew.park@intel.com>

* Fix GetInputInfo to retrieve input pid from LSTMCell

* LSTMCell use ov::PartialShape instead of cldnn::tensor
* implement lstm_elt_inst::calc_output_layouts
* implement lstm_elt_impl::static_canonicalize_shapes

* Add functional tests

* Fix unit test failure

---------

Co-authored-by: Andrew Park <andrew.park@intel.com>
This commit is contained in:
Paul Youngsoo Ahn
2023-11-11 00:06:08 -08:00
committed by GitHub
co-authored by Andrew Park
parent c08e01d6d7
commit 51da30b48d
6 changed files with 486 additions and 61 deletions
@@ -71,6 +71,52 @@ public:
return {params, optional_params};
}
static kernel_impl_params static_canonicalize_shapes(const kernel_impl_params& impl_params) {
if (impl_params.get_input_layout().get_partial_shape().size() != 2) {
return primitive_impl::static_canonicalize_shapes(impl_params);
}
auto updated_impl_params = canonicalize_fused_shapes(impl_params);
auto& input_layout = updated_impl_params.input_layouts[0];
auto& weights_layout = updated_impl_params.input_layouts[1];
auto& output_layout = updated_impl_params.output_layouts[0];
auto input_pshape = input_layout.get_partial_shape();
auto weights_pshape = weights_layout.get_partial_shape();
auto output_pshape = output_layout.get_partial_shape();
auto lstm_input_size = static_cast<cldnn::tensor::value_type>(input_pshape[1].get_length());
auto lstm_batch_size = static_cast<cldnn::tensor::value_type>(input_pshape[0].get_length());
auto lstm_hidden_size = static_cast<cldnn::tensor::value_type>(lstm_input_size / 4);
GPU_DEBUG_LOG << "lstm_input_size : " << lstm_input_size << std::endl;
GPU_DEBUG_LOG << "lstm_batch_size : " << lstm_batch_size << std::endl;
GPU_DEBUG_LOG << "lstm_hidden_size : " << lstm_hidden_size << std::endl;
GPU_DEBUG_LOG << "origin input_pshape : " << input_layout.to_short_string() << std::endl;
GPU_DEBUG_LOG << "origin weights_layout : " << weights_layout.to_short_string() << std::endl;
input_pshape = {lstm_batch_size, 1, 1, lstm_input_size};
input_layout.set_partial_shape(input_pshape);
weights_pshape = {lstm_batch_size, 1, 1, lstm_hidden_size}; // {batch, direction, 1, hidden_size}
weights_layout.format = format::adjust_to_rank(weights_layout.format, weights_pshape.size());
weights_layout.set_partial_shape(weights_pshape);
updated_impl_params.weights_layout = weights_layout;
GPU_DEBUG_LOG << "input_layout : " << input_layout.to_short_string() << std::endl;
GPU_DEBUG_LOG << "weights_layout : " << weights_layout.to_short_string() << std::endl;
GPU_DEBUG_LOG << "output_layout : " << output_layout.to_short_string() << std::endl;
OPENVINO_ASSERT(input_pshape.size() == 4 && weights_pshape.size() == 4, "input and weights shape should be rank 4");
return updated_impl_params;
}
kernel_impl_params canonicalize_shapes(const kernel_impl_params& impl_params) const override {
return static_canonicalize_shapes(impl_params);
}
};
namespace detail {
@@ -38,6 +38,8 @@ class typed_primitive_inst<lstm_elt> : public typed_primitive_inst_base<lstm_elt
using parent::parent;
public:
template<typename ShapeType>
static std::vector<layout> calc_output_layouts(lstm_elt_node const& node, kernel_impl_params const& impl_param);
static layout calc_output_layout(lstm_elt_node const& node, kernel_impl_params const& impl_param);
static std::string to_string(lstm_elt_node const& node);
@@ -27,6 +27,25 @@ layout lstm_elt_inst::calc_output_layout(lstm_elt_node const& node, kernel_impl_
return result;
}
template<typename ShapeType>
std::vector<layout> lstm_elt_inst::calc_output_layouts(lstm_elt_node const& node, kernel_impl_params const& impl_param) {
std::vector<layout> output_layouts;
// input partial shape [batch, input_size (= hidden_size * 4)]
auto input_layout = impl_param.get_input_layout();
auto input_pshape = input_layout.get_partial_shape();
OPENVINO_ASSERT(static_cast<bool>(impl_param.desc->output_data_types[0]) == false, "Output data type forcing is not supported for lstm_elt_node!");
OPENVINO_ASSERT(input_pshape.rank().get_length() == 2, "input_layout rank should be 2 on dynamic shape.");
auto lstm_input_size = static_cast<cldnn::tensor::value_type>(input_pshape[1].get_length());
auto lstm_batch_size = static_cast<cldnn::tensor::value_type>(input_pshape[0].get_length());
auto lstm_hidden_size = static_cast<cldnn::tensor::value_type>(lstm_input_size / 4);
return {cldnn::layout{ov::PartialShape{lstm_batch_size, 2, 1, lstm_hidden_size}, input_layout.data_type, input_layout.format}};
}
template std::vector<layout> lstm_elt_inst::calc_output_layouts<ov::PartialShape>(lstm_elt_node const& node, const kernel_impl_params& impl_param);
std::string lstm_elt_inst::to_string(lstm_elt_node const& node) {
auto desc = node.get_primitive();
auto node_info = node.desc_to_json();
+91 -59
View File
@@ -76,10 +76,9 @@ static void CreateLSTMCellOp(ProgramBuilder& p, const std::shared_ptr<ov::op::v4
const auto in_dims0 = op->get_input_shape(0);
const auto out_dims0 = op->get_output_shape(0);
if (in_dims0.size() != 2 ||
op->get_input_shape(1).size() != 2 ||
op->get_input_shape(2).size() != 2)
OPENVINO_THROW("Wrong input shapes for LSTMCell op ", op->get_friendly_name());
OPENVINO_ASSERT((op->get_input_shape(0).size() == 2 &&
op->get_input_shape(1).size() == 2 &&
op->get_input_shape(2).size() == 2), "Wrong input shapes for LSTMCell op ", op->get_friendly_name());
lstm_input_size = static_cast<int>(in_dims0.back());
lstm_batch_size = static_cast<int>(in_dims0.at(in_dims0.size()-2));
@@ -91,69 +90,102 @@ static void CreateLSTMCellOp(ProgramBuilder& p, const std::shared_ptr<ov::op::v4
GetLSTMActivationParams(op, activations, activation_params);
float clip = op->get_clip();
// LSTM primitive works with single precision for all in/out/weights tensors
auto lstm_dtype = cldnn::element_type_to_data_type(op->get_output_element_type(0));
if (p.use_new_shape_infer()) {
cldnn::primitive_id input_concatID = layerName + "_inputConcat";
p.add_primitive(*op, cldnn::concatenation(input_concatID, { inputs[0], inputs[1] }, 1));
cldnn::primitive_id inReshapeID = layerName + "_inReshape";
cldnn::primitive_id permuteID = layerName + "_inputReorder";
cldnn::primitive_id inHiddenReshapeID = layerName + "_inHiddenReshape";
cldnn::primitive_id inHiddenReorderID = layerName + "_inHiddenReorder";
cldnn::primitive_id gemmReshapeID = layerName + "_gemmReshape";
cldnn::primitive_id gemmReorderID = layerName + "_gemmReorder";
cldnn::primitive_id input_concatID = layerName + "_inputConcat";
cldnn::primitive_id lstm_fc_id = layerName + "_fully_connected";
cldnn::primitive_id lstm_elt_id = layerName + "_lstm_elt";
cldnn::primitive_id wr_concat_id = layerName + "_WRconcat";
p.add_primitive(*op, cldnn::concatenation(wr_concat_id, { inputs[3], inputs[4] }, 1));
p.add_primitive(*op, cldnn::fully_connected(lstm_fc_id, cldnn::input_info(input_concatID), wr_concat_id, bias.pid));
p.add_primitive(*op, cldnn::lstm_elt(lstm_elt_id, cldnn::input_info(lstm_fc_id), inputs[2].pid, clip, 0, activations,
activation_params, cldnn::lstm_weights_order::fizo, 0));
cldnn::tensor inputShape = { lstm_batch_size, 1, lstm_input_size, 1 };
cldnn::tensor inStateShape = { lstm_batch_size, 1, lstm_hidden_size, 1 };
cldnn::layout inputLayout = cldnn::layout(lstm_dtype, cldnn::format::bfyx, inputShape);
cldnn::layout hiddenLayout = cldnn::layout(lstm_dtype, cldnn::format::bfyx, inStateShape);
p.add_primitive(*op, cldnn::reshape(inReshapeID, inputs[0], inputShape));
p.add_primitive(*op, cldnn::reorder(permuteID, inReshapeID, inputLayout));
auto outSz = op->get_output_partial_shape(0).to_shape();
std::vector<int64_t> outSzPt;
for (auto i : outSz) {
outSzPt.push_back(i);
}
cldnn::tensor hiddenSz = cldnn::tensor{ lstm_batch_size, 1, lstm_hidden_size, 1 };
cldnn::primitive_id outputHiddenCropID = layerName + "_hc";
cldnn::primitive_id outputHiddenID = layerName + ".out0";
p.add_primitive(*op, cldnn::crop(outputHiddenCropID, cldnn::input_info(lstm_elt_id), hiddenSz, cldnn::tensor{0, 0, 0, 0}));
p.add_primitive(*op, cldnn::reshape(outputHiddenID, cldnn::input_info(outputHiddenCropID),
false, outSzPt, op->get_output_partial_shape(0)), {layerName});
cldnn::primitive_id outputCellCropID = layerName + "_cc";
cldnn::primitive_id outputCellID = layerName + ".out1";
p.add_primitive(*op, cldnn::crop(outputCellCropID, cldnn::input_info(lstm_elt_id), hiddenSz, cldnn::tensor{0, 1, 0, 0}));
p.add_primitive(*op, cldnn::reshape(outputCellID, cldnn::input_info(outputCellCropID),
false, outSzPt, op->get_output_partial_shape(1)));
} else {
// LSTM primitive works with single precision for all in/out/weights tensors
auto lstm_dtype = cldnn::element_type_to_data_type(op->get_output_element_type(0));
cldnn::primitive_id inReshapeID = layerName + "_inReshape";
cldnn::primitive_id permuteID = layerName + "_inputReorder";
cldnn::primitive_id inHiddenReshapeID = layerName + "_inHiddenReshape";
cldnn::primitive_id inHiddenReorderID = layerName + "_inHiddenReorder";
cldnn::primitive_id gemmReshapeID = layerName + "_gemmReshape";
cldnn::primitive_id gemmReorderID = layerName + "_gemmReorder";
cldnn::primitive_id input_concatID = layerName + "_inputConcat";
cldnn::tensor inputShape = { lstm_batch_size, 1, lstm_input_size, 1 };
cldnn::tensor inStateShape = { lstm_batch_size, 1, lstm_hidden_size, 1 };
cldnn::layout inputLayout = cldnn::layout(lstm_dtype, cldnn::format::bfyx, inputShape);
cldnn::layout hiddenLayout = cldnn::layout(lstm_dtype, cldnn::format::bfyx, inStateShape);
p.add_primitive(*op, cldnn::reshape(inReshapeID, inputs[0], inputShape));
p.add_primitive(*op, cldnn::reorder(permuteID, inReshapeID, inputLayout));
std::string hiddenInResh = inHiddenReshapeID + "_1";
std::string hiddenInStr = inHiddenReorderID + "_1";
std::string cellInResh = inHiddenReshapeID + "_2";
std::string cellInStr = inHiddenReorderID + "_2";
p.add_primitive(*op, cldnn::reshape(hiddenInResh, inputs[1], inStateShape));
p.add_primitive(*op, cldnn::reorder(hiddenInStr, cldnn::input_info(hiddenInResh), hiddenLayout));
p.add_primitive(*op, cldnn::reshape(cellInResh, inputs[2], inStateShape));
p.add_primitive(*op, cldnn::reorder(cellInStr, cldnn::input_info(cellInResh), hiddenLayout));
p.add_primitive(*op, cldnn::concatenation(input_concatID,
{ permuteID, hiddenInStr },
3));
std::string hiddenInResh = inHiddenReshapeID + "_1";
std::string hiddenInStr = inHiddenReorderID + "_1";
std::string cellInResh = inHiddenReshapeID + "_2";
std::string cellInStr = inHiddenReorderID + "_2";
p.add_primitive(*op, cldnn::reshape(hiddenInResh, inputs[1], inStateShape));
p.add_primitive(*op, cldnn::reorder(hiddenInStr, cldnn::input_info(hiddenInResh), hiddenLayout));
p.add_primitive(*op, cldnn::reshape(cellInResh, inputs[2], inStateShape));
p.add_primitive(*op, cldnn::reorder(cellInStr, cldnn::input_info(cellInResh), hiddenLayout));
p.add_primitive(*op, cldnn::concatenation(input_concatID,
{ permuteID, hiddenInStr },
3));
cldnn::tensor gemmSz = cldnn::tensor{ lstm_batch_size, 1, 4 * lstm_hidden_size, 1 };
cldnn::layout gemmLayout = cldnn::layout(lstm_dtype, cldnn::format::bfyx, gemmSz);
cldnn::tensor hiddenSz = cldnn::tensor{ lstm_batch_size, 1, lstm_hidden_size, 1 };
cldnn::tensor cellCropSz = cldnn::tensor{0, 1, 0, 0};
cldnn::tensor gemmSz = cldnn::tensor{ lstm_batch_size, 1, 4 * lstm_hidden_size, 1 };
cldnn::layout gemmLayout = cldnn::layout(lstm_dtype, cldnn::format::bfyx, gemmSz);
cldnn::tensor hiddenSz = cldnn::tensor{ lstm_batch_size, 1, lstm_hidden_size, 1 };
cldnn::tensor cellCropSz = cldnn::tensor{0, 1, 0, 0};
std::string lstm_fc_id = layerName + "_fully_connected";
std::string lstm_elt_id = layerName + "_lstm_elt";
std::string lstm_fc_id = layerName + "_fully_connected";
std::string lstm_elt_id = layerName + "_lstm_elt";
cldnn::primitive_id WRconcatID = layerName + "_WRconcat";
p.add_primitive(*op, cldnn::concatenation(WRconcatID, { weight, recurrent }, 1));
cldnn::primitive_id WRconcatID = layerName + "_WRconcat";
p.add_primitive(*op, cldnn::concatenation(WRconcatID, { weight, recurrent }, 1));
cldnn::primitive_id FCInputReshapeID = "Reshape_bf_" + lstm_fc_id + "_for_input";
cldnn::tensor FCInputReshapeSz = { lstm_batch_size, inputShape.spatial[0] + inStateShape.spatial[0], 1, 1 };
p.add_primitive(*op, cldnn::reshape(FCInputReshapeID, cldnn::input_info(input_concatID), FCInputReshapeSz));
cldnn::primitive_id FCInputReshapeID = "Reshape_bf_" + lstm_fc_id + "_for_input";
cldnn::tensor FCInputReshapeSz = { lstm_batch_size, inputShape.spatial[0] + inStateShape.spatial[0], 1, 1 };
p.add_primitive(*op, cldnn::reshape(FCInputReshapeID, cldnn::input_info(input_concatID), FCInputReshapeSz));
p.add_primitive(*op, cldnn::fully_connected(lstm_fc_id, cldnn::input_info(FCInputReshapeID), WRconcatID, bias.pid));
p.add_primitive(*op, cldnn::reshape(gemmReshapeID, cldnn::input_info(lstm_fc_id), gemmSz));
p.add_primitive(*op, cldnn::reorder(gemmReorderID, cldnn::input_info(gemmReshapeID), gemmLayout));
p.add_primitive(*op, cldnn::lstm_elt(lstm_elt_id, cldnn::input_info(gemmReorderID), cellInStr, clip, 0, activations,
activation_params, cldnn::lstm_weights_order::fizo, 0));
p.add_primitive(*op, cldnn::fully_connected(lstm_fc_id, cldnn::input_info(FCInputReshapeID), WRconcatID, bias.pid));
p.add_primitive(*op, cldnn::reshape(gemmReshapeID, cldnn::input_info(lstm_fc_id), gemmSz));
p.add_primitive(*op, cldnn::reorder(gemmReorderID, cldnn::input_info(gemmReshapeID), gemmLayout));
p.add_primitive(*op, cldnn::lstm_elt(lstm_elt_id, cldnn::input_info(gemmReorderID), cellInStr, clip, 0, activations,
activation_params, cldnn::lstm_weights_order::fizo, 0));
cldnn::tensor outSz = cldnn::tensor{ lstm_batch_size, lstm_hidden_size, 1, 1 };
cldnn::primitive_id outputHiddenCropID = layerName + "_hc";
cldnn::primitive_id outputHiddenID = layerName + ".out0";
p.add_primitive(*op, cldnn::crop(outputHiddenCropID, cldnn::input_info(lstm_elt_id), hiddenSz, cldnn::tensor{0, 0, 0, 0}));
p.add_primitive(*op, cldnn::reshape(outputHiddenID, cldnn::input_info(outputHiddenCropID), outSz), {layerName});
cldnn::tensor outSz = cldnn::tensor{ lstm_batch_size, lstm_hidden_size, 1, 1 };
cldnn::primitive_id outputHiddenCropID = layerName + "_hc";
cldnn::primitive_id outputHiddenID = layerName + ".out0";
p.add_primitive(*op, cldnn::crop(outputHiddenCropID, cldnn::input_info(lstm_elt_id), hiddenSz, cldnn::tensor{0, 0, 0, 0}));
p.add_primitive(*op, cldnn::reshape(outputHiddenID, cldnn::input_info(outputHiddenCropID), outSz), {layerName});
cldnn::primitive_id outputCellCropID = layerName + "_cc";
cldnn::primitive_id outputCellID = layerName + ".out1";
p.add_primitive(*op, cldnn::crop(outputCellCropID, cldnn::input_info(lstm_elt_id), hiddenSz, cellCropSz));
p.add_primitive(*op, cldnn::reshape(outputCellID, cldnn::input_info(outputCellCropID), outSz));
cldnn::primitive_id outputCellCropID = layerName + "_cc";
cldnn::primitive_id outputCellID = layerName + ".out1";
p.add_primitive(*op, cldnn::crop(outputCellCropID, cldnn::input_info(lstm_elt_id), hiddenSz, cellCropSz));
p.add_primitive(*op, cldnn::reshape(outputCellID, cldnn::input_info(outputCellCropID), outSz));
}
}
static void CreateLSTMSequenceOp(ProgramBuilder& p, const std::shared_ptr<ov::op::v5::LSTMSequence>& op) {
@@ -217,12 +249,12 @@ static void CreateLSTMSequenceOp(ProgramBuilder& p, const std::shared_ptr<ov::op
cldnn::primitive_id cellStr = inHiddenReshapeID + "_2";
cldnn::primitive_id inputCropID = layerName + "_inputCrop";
cldnn::primitive_id WRconcatID = layerName + "_WRconcat";
p.add_primitive(*op, cldnn::concatenation(WRconcatID, { weight, recurrent }, 2));
cldnn::primitive_id wr_concat_id = layerName + "_WRconcat";
p.add_primitive(*op, cldnn::concatenation(wr_concat_id, { weight, recurrent }, 2));
std::vector<size_t> WRreshapeSize = { 4 * size_t(lstm_hidden_size), size_t(lstm_input_size + lstm_hidden_size) };
cldnn::primitive_id WRreshapeID = WRconcatID + "_reshape";
auto reshapeInPrim = cldnn::reshape(WRreshapeID, cldnn::input_info(WRconcatID), tensor_from_dims(WRreshapeSize));
cldnn::primitive_id WRreshapeID = wr_concat_id + "_reshape";
auto reshapeInPrim = cldnn::reshape(WRreshapeID, cldnn::input_info(wr_concat_id), tensor_from_dims(WRreshapeSize));
p.add_primitive(*op, reshapeInPrim);
for (int i = 0; i < lstm_sequence_len; ++i) {
@@ -7,6 +7,7 @@
#include "openvino/op/constant.hpp"
#include "openvino/op/split.hpp"
#include "openvino/op/variadic_split.hpp"
#include "openvino/op/lstm_cell.hpp"
#include "intel_gpu/plugin/program_builder.hpp"
#include "intel_gpu/plugin/transformations_pipeline.hpp"
@@ -250,10 +251,13 @@ std::vector<cldnn::input_info> ProgramBuilder::GetInputInfo(const std::shared_pt
for (size_t i = 0; i < op->get_input_size(); i++) {
auto prevOp = op->get_input_node_ptr(i);
std::string prevName = layer_type_name_ID(prevOp);
// Note: Currently Split/Variadic Split are divided to multiple crops
// LSTMCell contains its own body network, and each output has a unique pid
// But there is no need to maintain output port index for the next node e.g. Result
bool is_legacy_multiple_outputs = !allow_new_shape_infer
// Note:: Currently Split/Variadic Split are divided to multiple crops
|| ov::is_type<ov::op::v1::Split>(prevOp)
|| ov::is_type<ov::op::v1::VariadicSplit>(prevOp);
|| ov::is_type<ov::op::v1::VariadicSplit>(prevOp)
|| ov::is_type<ov::op::v4::LSTMCell>(prevOp);
if (prevOp->get_output_size() > 1 && is_legacy_multiple_outputs) {
prevName += ".out" + std::to_string(op->get_input_source_output(i).get_index());
}
@@ -0,0 +1,322 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <tuple>
#include <string>
#include <vector>
#include <memory>
#include "ov_models/utils/ov_helpers.hpp"
#include "shared_test_classes/base/layer_test_utils.hpp"
#include "ov_models/builders.hpp"
#include "shared_test_classes/base/ov_subgraph.hpp"
#include "common_test_utils/test_constants.hpp"
#include "shared_test_classes/base/utils/ranges.hpp"
#include <common_test_utils/ov_tensor_utils.hpp>
#include "shared_test_classes/base/utils/compare_results.hpp"
#include "openvino/pass/constant_folding.hpp"
#include <transformations/control_flow/unroll_tensor_iterator.hpp>
#include "shared_test_classes/base/utils/generate_inputs.hpp"
using namespace InferenceEngine;
using namespace ov::test;
namespace GPULayerTestsDefinitions {
/*
* Generate TensorIterator with LSTMCell
* @param ngPrc precision of model
* @param initShape initial shape {N, L(sequence length), I}
* @param N batch size
* @param I input size
* @param H hidden layer
*/
static std::shared_ptr<ov::Model> makeTIwithLSTMcell(ov::element::Type_t ngPRC, ov::PartialShape initShape,
size_t N, size_t I, size_t H, size_t sequence_axis,
ngraph::op::RecurrentSequenceDirection seq_direction) {
auto SENT = std::make_shared<ov::op::v0::Parameter>(ngPRC, initShape);
SENT->set_friendly_name("SENT");
// initial_hidden_state
auto H_init = std::make_shared<ov::op::v0::Parameter>(ngPRC, ov::Shape{N, 1, H});
H_init->set_friendly_name("H_init");
// initial_cell_state
auto C_init = std::make_shared<ov::op::v0::Parameter>(ngPRC, ov::Shape{N, 1, H});
C_init->set_friendly_name("C_init");
auto H_t = std::make_shared<ov::op::v0::Parameter>(ngPRC, ov::Shape{N, 1, H});
H_t->set_friendly_name("H_t");
auto C_t = std::make_shared<ov::op::v0::Parameter>(ngPRC, ov::Shape{N, 1, H});
C_t->set_friendly_name("C_t");
// Body
// input data
auto X = std::make_shared<ov::op::v0::Parameter>(ngPRC, ov::Shape{N, 1, I});
X->set_friendly_name("X");
// the weights for matrix multiplication, gate order: fico
std::vector<uint64_t> dataW(4 * H * I, 0);
auto W_body = std::make_shared<ov::op::v0::Constant>(ngPRC, ov::Shape{4 * H, I}, dataW);
W_body->set_friendly_name("W_body");
// the recurrence weights for matrix multiplication, gate order: fico
std::vector<uint64_t> dataR(4 * H * H, 0);
auto R_body = std::make_shared<ov::op::v0::Constant>(ngPRC, ov::Shape{4 * H, H}, dataR);
R_body->set_friendly_name("R_body");
std::vector<uint64_t> inShape = {N, H};
auto constantH = std::make_shared<ov::op::v0::Constant>(ov::element::i64, ov::Shape{2}, inShape);
constantH->set_friendly_name("constantH");
inShape = {N, I};
auto constantX = std::make_shared<ov::op::v0::Constant>(ov::element::i64, ov::Shape{2}, inShape);
constantX->set_friendly_name("constantX");
auto LSTM_cell =
std::make_shared<ov::op::v4::LSTMCell>(std::make_shared<ov::op::v1::Reshape>(X, constantX, false),
std::make_shared<ov::op::v1::Reshape>(H_t, constantH, false),
std::make_shared<ov::op::v1::Reshape>(C_t, constantH, false),
W_body,
R_body,
H);
LSTM_cell->set_friendly_name("LSTM_cell");
inShape = {N, 1, H};
auto constantHo = std::make_shared<ov::op::v0::Constant>(ov::element::i64, ov::Shape{3}, inShape);
constantHo->set_friendly_name("constantHo");
auto H_o = std::make_shared<ov::op::v1::Reshape>(LSTM_cell->output(0), constantHo, false);
H_o->set_friendly_name("H_o_reshape");
auto C_o = std::make_shared<ov::op::v1::Reshape>(LSTM_cell->output(1), constantHo, false);
C_o->set_friendly_name("C_o_reshape");
auto body = std::make_shared<ov::Model>(ov::OutputVector{H_o, C_o}, ov::ParameterVector{X, H_t, C_t});
body->set_friendly_name("body");
auto tensor_iterator = std::make_shared<ov::op::v0::TensorIterator>();
tensor_iterator->set_friendly_name("tensor_iterator");
tensor_iterator->set_body(body);
// H_t is Hinit on the first iteration, Ho after that
tensor_iterator->set_merged_input(H_t, H_init, H_o);
tensor_iterator->set_merged_input(C_t, C_init, C_o);
// Set PortMap
if (seq_direction == ngraph::op::RecurrentSequenceDirection::FORWARD) {
tensor_iterator->set_sliced_input(X, SENT, 0, 1, 1, -1, sequence_axis);
} else if (seq_direction == ngraph::op::RecurrentSequenceDirection::REVERSE) {
tensor_iterator->set_sliced_input(X, SENT, -1, -1, 1, 0, sequence_axis);
} else {
OPENVINO_THROW("Bidirectional case is not supported.");
}
// Output 0 is last Ho, result 0 of body
auto out0 = tensor_iterator->get_iter_value(H_o, -1);
// Output 1 is last Co, result 1 of body
auto out1 = tensor_iterator->get_iter_value(C_o, -1);
auto results =
ov::ResultVector{std::make_shared<ov::op::v0::Result>(out0), std::make_shared<ov::op::v0::Result>(out1)};
auto fn_ptr = std::make_shared<ov::Model>(results, ov::ParameterVector{SENT, H_init, C_init});
fn_ptr->set_friendly_name("TIwithLSTMcell");
return fn_ptr;
}
/*
* Generate LSTMSequence
* @param ngPrc precision of model
* @param initShape initial shape {N, L(sequence length), I}
* @param N batch size
* @param I input size
* @param H hidden layer
*/
static std::shared_ptr<ov::Model> makeLSTMSequence(ov::element::Type_t ngPRC, ov::PartialShape initShape,
size_t N, size_t I, size_t H, size_t sequence_axis,
ngraph::op::RecurrentSequenceDirection seq_direction) {
auto X = std::make_shared<ov::op::v0::Parameter>(ngPRC, initShape);
auto Y = std::make_shared<ov::op::v0::Parameter>(ngPRC, ov::Shape{N, 1, H});
auto Z = std::make_shared<ov::op::v0::Parameter>(ngPRC, ov::Shape{N, 1, H});
auto shape_of = std::make_shared<ov::op::v3::ShapeOf>(X);
auto indices = ov::op::v0::Constant::create(ov::element::i32, {1}, {1});
auto axis = ov::op::v0::Constant::create(ov::element::i32, {}, {0});
auto seq_lengths = std::make_shared<ov::op::v1::Gather>(shape_of, indices, axis);
auto w_val = std::vector<float>(4 * H * I, 0);
auto r_val = std::vector<float>(4 * H * H, 0);
auto b_val = std::vector<float>(4 * H, 0);
auto W = ov::op::v0::Constant::create(ngPRC, ov::Shape{N, 4 * H, I}, w_val);
auto R = ov::op::v0::Constant::create(ngPRC, ov::Shape{N, 4 * H, H}, r_val);
auto B = ov::op::v0::Constant::create(ngPRC, ov::Shape{N, 4 * H}, b_val);
auto rnn_sequence = std::make_shared<ov::op::v5::LSTMSequence>(X,
Y,
Z,
seq_lengths,
W,
R,
B,
128,
seq_direction);
auto Y_out = std::make_shared<ov::op::v0::Result>(rnn_sequence->output(0));
auto Ho = std::make_shared<ov::op::v0::Result>(rnn_sequence->output(1));
auto Co = std::make_shared<ov::op::v0::Result>(rnn_sequence->output(2));
Y_out->set_friendly_name("Y_out");
Ho->set_friendly_name("Ho");
Co->set_friendly_name("Co");
auto fn_ptr = std::make_shared<ov::Model>(ov::NodeVector{Y_out, Ho, Co}, ov::ParameterVector{X, Y, Z});
fn_ptr->set_friendly_name("LSTMSequence");
return fn_ptr;
}
enum class LSTMType {
LSTMCell = 0,
LSTMSequence = 1 // will be updated at next step.
};
using DynamicTensorIteratorParams = typename std::tuple<
LSTMType, // LSTM type (LSTMCell, LSTMSequence)
InputShape, // input shapes (N[batch], L[seq_length], I[input_size])
int32_t, // hidden size
ngraph::op::RecurrentSequenceDirection, // sequence direction
std::string, // device name
InferenceEngine::Precision, // precision
ov::AnyMap // configuration
>;
/**
* Test case with Dynamic SHAPE version of loop operation.
* Total iteration count is dynamic.
*/
class DynamicTensorIteratorTest : public testing::WithParamInterface<DynamicTensorIteratorParams>,
virtual public SubgraphBaseTest {
public:
static std::string getTestCaseName(const testing::TestParamInfo<DynamicTensorIteratorParams> &obj) {
LSTMType type;
InputShape data_shapes;
int32_t hidden_size;
ngraph::op::RecurrentSequenceDirection seq_direction;
std::string target_device;
InferenceEngine::Precision data_precision;
ov::Any configuration;
std::tie(type, data_shapes,
hidden_size,
seq_direction,
target_device,
data_precision,
configuration) = obj.param;
std::ostringstream result;
result << "TestType=" << (type == LSTMType::LSTMCell? "LSTMCell" : "LSTMSequence") << "_";
result << "IS=(";
result << ov::test::utils::partialShape2str({data_shapes.first}) << "_";
result << ov::test::utils::vec2str(data_shapes.second) << "_";
result << ")_";
result << "hidden_size=" << hidden_size << "_";
result << "direction=" << seq_direction << "_";
result << "netPRC=" << data_precision << "_";
result << "targetDevice=" << target_device << "_";
return result.str();
}
private:
InputShape data_shapes;
ngraph::op::RecurrentSequenceDirection seq_direction;
InferenceEngine::Precision data_prc;
size_t hidden_size;
size_t batch_size;
size_t input_size;
LSTMType type;
protected:
void SetUp() override {
SKIP_IF_CURRENT_TEST_IS_DISABLED()
ov::AnyMap configuration_new;
std::tie(type, data_shapes,
hidden_size,
seq_direction,
targetDevice,
data_prc,
configuration_new) = GetParam();
auto ngPrc = FuncTestUtils::PrecisionUtils::convertIE2nGraphPrc(data_prc);
if (targetDevice == ov::test::utils::DEVICE_GPU) {
configuration = configuration_new;
}
size_t sequence_axis = 1;
auto init_shape = data_shapes.first;
init_input_shapes({data_shapes});
batch_size = static_cast<size_t>(init_shape[0].get_length());
input_size = static_cast<size_t>(init_shape[init_shape.size()-1].get_length());
if (type == LSTMType::LSTMCell)
function = makeTIwithLSTMcell(ngPrc, init_shape, batch_size, input_size, hidden_size, sequence_axis, seq_direction);
else
function = makeLSTMSequence(ngPrc, init_shape, batch_size, input_size, hidden_size, sequence_axis, seq_direction);
}
void generate_inputs(const std::vector<ngraph::Shape>& targetInputStaticShapes) override {
inputs.clear();
ov::Shape default_shape{batch_size, 1, hidden_size};
auto inputMap = ov::test::utils::getInputMap();
auto itTargetShape = targetInputStaticShapes.begin();
for (const auto &param : function->get_parameters()) {
std::shared_ptr<ov::Node> inputNode = param;
for (size_t i = 0; i < param->get_output_size(); i++) {
for (const auto &node : param->get_output_target_inputs(i)) {
std::shared_ptr<ov::Node> nodePtr = node.get_node()->shared_from_this();
auto it = inputMap.find(nodePtr->get_type_info());
ASSERT_NE(it, inputMap.end());
for (size_t port = 0; port < nodePtr->get_input_size(); ++port) {
if (itTargetShape != targetInputStaticShapes.end()) {
if (nodePtr->get_input_node_ptr(port)->shared_from_this() == inputNode->shared_from_this()) {
inputs.insert({param, it->second(nodePtr, port, param->get_element_type(), *itTargetShape)});
break;
}
} else {
inputs.insert({param, it->second(nodePtr, port, param->get_element_type(), default_shape)});
}
}
}
}
if (itTargetShape != targetInputStaticShapes.end())
itTargetShape++;
}
}
};
TEST_P(DynamicTensorIteratorTest, CompareWithRefs) {
SKIP_IF_CURRENT_TEST_IS_DISABLED()
run();
}
std::vector<InputShape> input_shapes = {
InputShape(ov::PartialShape({1, -1, 512}), {{1, 30, 512}, {1, 10, 512}, {1, 5, 512}})
};
std::vector<int32_t> hidden_sizes = {
128
};
ov::AnyMap net_configuration = {
{GPUConfigParams::KEY_GPU_ENABLE_LOOP_UNROLLING, PluginConfigParams::NO}
};
std::vector<InferenceEngine::Precision> net_precision = {
InferenceEngine::Precision::FP32,
};
std::vector<ngraph::op::RecurrentSequenceDirection> reccurent_sequence_direction = {
ngraph::op::RecurrentSequenceDirection::FORWARD,
ngraph::op::RecurrentSequenceDirection::REVERSE,
};
INSTANTIATE_TEST_SUITE_P(smoke_DynamicTensorIterator_LSTMCell, DynamicTensorIteratorTest,
testing::Combine(
/* lstm_type */ testing::ValuesIn({LSTMType::LSTMCell}),
/* data_shape */ testing::ValuesIn(input_shapes),
/* hidden_size */ testing::ValuesIn(hidden_sizes),
/* direction */ testing::ValuesIn(reccurent_sequence_direction),
/* device */ testing::Values<std::string>(ov::test::utils::DEVICE_GPU),
/* data_prc */ testing::ValuesIn(net_precision),
/* configuration */ testing::Values<ov::AnyMap>(net_configuration)),
DynamicTensorIteratorTest::getTestCaseName);
} // namespace GPULayerTestsDefinitions