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:
co-authored by
Andrew Park
parent
c08e01d6d7
commit
51da30b48d
@@ -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();
|
||||
|
||||
@@ -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 ¶m : 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
|
||||
Reference in New Issue
Block a user