Handle seqence length == 0 for RNN (#749)

* Handle seqence length == 0 for RNN

* Zero out the output for seq_len=0

* add unit test for rnn seq len equal 0

* minor fix
This commit is contained in:
Yufeng Li 2019-04-02 20:33:45 -07:00 committed by GitHub
parent 2dbce4ebcf
commit 0cd4a9381c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 203 additions and 23 deletions

View file

@ -313,11 +313,21 @@ Status DeepCpuGruOp::ComputeImpl(OpKernelContext& context) const {
// GRU outputs are optional but must be in the same order
TensorShape Y_dims{seq_length, num_directions_, batch_size, hidden_size_};
Tensor* Y = context.Output(/*index*/ 0, Y_dims); // TODO: Adjust for however optional outputs gets implemented
Tensor* Y = context.Output(/*index*/ 0, Y_dims);
TensorShape Y_h_dims{num_directions_, batch_size, hidden_size_};
Tensor* Y_h = context.Output(/*index*/ 1, Y_h_dims);
// Reset output and return if max sequence length is 0
if (sequence_lens != nullptr) {
int32_t max_sequence_length = *std::max_element(sequence_lens->Data<int32_t>(), sequence_lens->Data<int32_t>() + sequence_lens->Shape().Size());
if (max_sequence_length == 0) {
if (Y != nullptr) std::fill_n(Y->MutableData<T>(), Y_dims.Size(), T{});
if (Y_h != nullptr) std::fill_n(Y_h->MutableData<T>(), Y_h_dims.Size(), T{});
return Status::OK();
}
}
AllocatorPtr alloc;
status = context.GetTempSpaceAllocator(&alloc);
ORT_RETURN_IF_ERROR(status);
@ -1024,20 +1034,24 @@ void UniDirectionalGru<T>::Compute(const gsl::span<const T>& inputs_arg,
}
}
if (output_sequence) {
// copy last output to final_hidden_state
for (int i = 0; i < batch_size_; i++) {
const int seq_len = sequence_lengths[i];
// copy last output to final_hidden_state
for (int i = 0; i < batch_size_; i++) {
const int seq_len = sequence_lengths[i];
if (seq_len == 0) {
auto final_hidden_state_dst = final_hidden_state.begin() + i * hidden_size_;
std::fill_n(final_hidden_state_dst, hidden_size_, T{});
continue;
} else if (output_sequence) {
auto src = outputs.subspan((seq_len - 1) * output_step_length + i * hidden_size_, hidden_size_);
auto dest = final_hidden_state.subspan(i * hidden_size_, hidden_size_);
gsl::copy(src, dest);
}
}
if (direction_ == kReverse) {
ReverseSequence<T>(outputs, original_outputs,
sequence_lengths, seq_length_,
batch_size_, hidden_size_, num_directions);
}
if (output_sequence && direction_ == kReverse) {
ReverseSequence<T>(outputs, original_outputs,
sequence_lengths, seq_length_,
batch_size_, hidden_size_, num_directions);
}
}

View file

@ -369,6 +369,17 @@ Status DeepCpuLstmOp::ComputeImpl(OpKernelContext& context) const {
TensorShape Y_c_dims{num_directions_, batch_size, hidden_size_};
Tensor* Y_c = context.Output(/*index*/ 2, Y_c_dims);
// Reset output and return if max sequence length is 0
if (sequence_lens != nullptr) {
int32_t max_sequence_length = *std::max_element(sequence_lens->Data<int32_t>(), sequence_lens->Data<int32_t>() + sequence_lens->Shape().Size());
if (max_sequence_length == 0) {
if (Y != nullptr) std::fill_n(Y->MutableData<T>(), Y_dims.Size(), T{});
if (Y_h != nullptr) std::fill_n(Y_h->MutableData<T>(), Y_h_dims.Size(), T{});
if (Y_c != nullptr) std::fill_n(Y_c->MutableData<T>(), Y_c_dims.Size(), T{});
return Status::OK();
}
}
AllocatorPtr alloc;
status = context.GetTempSpaceAllocator(&alloc);
ORT_RETURN_IF_ERROR(status);
@ -876,6 +887,10 @@ void UniDirectionalLstm<T>::Compute(const gsl::span<const T>& inputs_arg,
gsl::span<T> dst = final_cell_state.subspan(lrow * hidden_size_, hidden_size_);
gsl::copy(src, dst);
}
if (step == 0 && sequence_lengths[lrow] == 0) {
auto final_cell_state_dst = final_cell_state.begin() + lrow * hidden_size_;
std::fill_n(final_cell_state_dst, hidden_size_, T{});
}
}
if (output_sequence) {
@ -945,6 +960,10 @@ void UniDirectionalLstm<T>::Compute(const gsl::span<const T>& inputs_arg,
gsl::copy(batched_internal_memory_prev_.subspan(lrow * hidden_size_, hidden_size_),
final_cell_state.subspan(lrow * hidden_size_, hidden_size_));
}
if (step == 0 && sequence_lengths[lrow] == 0) {
auto final_cell_state_dst = final_cell_state.begin() + lrow * hidden_size_;
std::fill_n(final_cell_state_dst, hidden_size_, T{});
}
}
if (output_sequence) {
@ -962,19 +981,22 @@ void UniDirectionalLstm<T>::Compute(const gsl::span<const T>& inputs_arg,
}
}
if (output_sequence) {
// copy last output to final_hidden_state
for (int i = 0; i < batch_size_; i++) {
const int seq_len = sequence_lengths[i];
for (int i = 0; i < batch_size_; i++) {
const int seq_len = sequence_lengths[i];
if (seq_len == 0) { // zero out final_hidden_state if seq_len == 0
auto final_hidden_state_dst = final_hidden_state.begin() + i * hidden_size_;
std::fill_n(final_hidden_state_dst, hidden_size_, T{});
continue;
} else if (output_sequence) { // copy last output to final_hidden_state
auto src = outputs.subspan((seq_len - 1) * output_step_length + i * hidden_size_, hidden_size_);
auto dest = final_hidden_state.subspan(i * hidden_size_, hidden_size_);
gsl::copy(src, dest);
}
if (direction_ == Direction::kReverse)
ReverseSequence<T>(outputs, original_outputs, sequence_lengths, seq_length_,
batch_size_, hidden_size_, num_directions);
}
if (output_sequence && direction_ == Direction::kReverse)
ReverseSequence<T>(outputs, original_outputs, sequence_lengths, seq_length_,
batch_size_, hidden_size_, num_directions);
}
// #define PREVIOUS_BROKEN_VERSION

View file

@ -79,7 +79,7 @@ Status ValidateCommonRnnInputs(const Tensor& X,
auto sequence_len_entries = sequence_lens->DataAsSpan<int>();
if (std::any_of(sequence_len_entries.cbegin(),
sequence_len_entries.cend(),
[seq_length](int len) { return len <= 0 || len > seq_length; })) {
[seq_length](int len) { return len < 0 || len > seq_length; })) {
return ORT_MAKE_STATUS(
ONNXRUNTIME, INVALID_ARGUMENT,
"Invalid value/s in sequence_lens. All values must be > 0 and < seq_length. seq_length=", seq_length);

View file

@ -120,8 +120,6 @@ void ReverseSequence(gsl::span<const T> inputs,
for (int i = 0; i < batch_size; i++) {
int seq_len = sequence_lengths[i];
if (seq_len == 0)
continue;
#ifdef USE_OPENMP
// Parallel execute the loop.
#pragma omp parallel for

View file

@ -56,7 +56,7 @@ static void RunGruTest(const std::vector<float>& X_data,
test.AddAttribute<int64_t>("linear_before_reset", linear_before_reset);
// if clip is a very big number (usually it is default value), don't set the clip
if (clip < 999.f)
test.AddAttribute<float>("clip", clip);
test.AddAttribute<float>("clip", clip);
std::vector<int64_t> X_dims = {seq_length, batch_size, input_size};
std::vector<int64_t> W_dims = {num_directions, 3 * hidden_size, input_size};
@ -457,7 +457,7 @@ void DeepCpuGruOpTestContext::RunTest(const std::vector<float>& X,
const std::vector<float>* initial_h,
const std::vector<float>& expected_Y,
const std::vector<float>& expected_Y_h) {
// run with and without output_sequence
//run with and without output_sequence
::onnxruntime::test::RunGruTest(X, gru_input_weights_, gru_recurrent_weights_,
expected_Y, expected_Y_h,
input_size_, batch_size, hidden_dim_, seq_length,
@ -652,6 +652,62 @@ TEST(GRUTest, ONNXRuntime_TestGRUOpGrowBatchSequenceLength) {
ctx.RunTest(X2, batch2, seq_length2, sequence_length2, &initial_h2, expected_Y2, expected_Y_h2);
}
TEST(GRUTest, ONNXRuntime_TestGRUOpSequenceLengthWithPartialZero) {
const std::string direction = "bidirectional";
const std::vector<std::string> activations = {"sigmoid", "tanh", "sigmoid", "tanh"};
DeepCpuGruOpTestContext ctx(direction, activations);
const int batch_size = 2;
const int seq_length = 2;
std::vector<float> X = {-0.455351f, -0.276391f,
-0.455351f, -0.276391f,
-0.185934f, -0.269585f,
-0.185934f, -0.269585f};
std::vector<int> sequence_length = {2, 0};
std::vector<float> initial_h = {0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f};
std::vector<float> expected_Y = {-0.03255286f, 0.0774838f, 0.0f, 0.0f,
-0.05469977f, 0.1004222f, 0.0f, 0.0f,
-0.05556786f, 0.0785508f, 0.0f, 0.0f,
-0.04566499f, 0.04621252f, 0.0f, 0.0f};
std::vector<float> expected_Y_h = {-0.05556786f, 0.0785508f,
0.0f, 0.0f,
-0.05469977f, 0.1004222f,
0.0f, 0.0f};
ctx.RunTest(X, batch_size, seq_length, sequence_length, &initial_h, expected_Y, expected_Y_h);
}
TEST(GRUTest, ONNXRuntime_TestGRUOpSequenceLengthAllZeros) {
const std::string direction = "forward";
const std::vector<std::string> activations = {"sigmoid", "tanh"};
DeepCpuGruOpTestContext ctx(direction, activations);
const int batch = 2;
const int seq_length = 2;
std::vector<float> X = {-0.455351f, -0.276391f,
-0.455351f, -0.276391f,
-0.185934f, -0.269585f,
0.0f, 0.0f};
std::vector<int> sequence_length = {0, 0};
std::vector<float> initial_h = {0.0f, 0.0f,
0.0f, 0.0f};
std::vector<float> expected_Y = {0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f};
std::vector<float> expected_Y_h = {0.0f, 0.0f,
0.0f, 0.0f};
ctx.RunTest(X, batch, seq_length, sequence_length, &initial_h, expected_Y, expected_Y_h);
}
TEST(GRUTest, ONNXRuntime_TestGRUOpSingleBatchMultipleHiddenThreads) {
const std::string direction = "forward";
const std::vector<std::string> activations = {"sigmoid", "tanh"};

View file

@ -969,5 +969,95 @@ TEST(LSTMTest, ONNXRuntime_TestLSTMOutputWrite) {
nullptr, use_bias, use_peepholes);
}
TEST(LSTMTest, ONNXRuntime_TestLSTMSequenceLengthAllZeros) {
const int seq_len = 2;
int batch_size = 2;
std::vector<std::string> activations = {"tanh", "sigmoid", "tanh", "tanh", "sigmoid", "tanh"};
bool use_bias = true;
bool use_peepholes = false;
std::vector<float> X_data = {-0.455351f, -0.776391f,
-0.355351f, -0.576391f,
-0.185934f, -0.169585f,
-0.285934f, -0.469585f};
std::vector<int> sequence_length = {0, 0};
std::vector<float> Y_data = {0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f};
std::vector<float> Y_h_data = {0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f};
std::vector<float> Y_c_data = {0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f};
std::string direction = "bidirectional";
LstmOpContext2x1x2x2 context(direction, activations);
context.RunTest(X_data, batch_size, seq_len, nullptr, nullptr, Y_data, Y_h_data, Y_c_data,
&sequence_length, use_bias, use_peepholes);
}
TEST(LSTMTest, ONNXRuntime_TestLSTMSequenceLengthPartialZeros) {
const int seq_len = 2;
int batch_size = 2;
std::vector<std::string> activations = {"tanh", "sigmoid", "tanh", "tanh", "sigmoid", "tanh"};
bool use_bias = true;
bool use_peepholes = false;
std::vector<float> X_data = {-0.455351f, -0.776391f,
0.0f, 0.0f,
-0.185934f, -0.169585f,
0.0f, 0.0f};
std::vector<int> sequence_length = {2, 0};
std::vector<float> Y_data = {-0.1269719f, -0.01049645f,
0.0f, 0.0f,
-0.12206709f, -0.0051103f,
0.0f, 0.0f,
-0.02778835f, 0.00775075f,
0.0f, 0.0f,
-0.04350187f, 0.01127771f,
0.0f, 0.0f};
std::vector<float> Y_h_data = {-0.02778835f, 0.00775075f,
0.0f, 0.0f,
-0.12206709f, -0.0051103f,
0.0f, 0.0f};
std::vector<float> Y_c_data = {0.14675268f, 0.01759163f,
0.0f, 0.0f,
0.26577898f, -0.01694398f,
0.0f, 0.0f};
std::string direction = "bidirectional";
LstmOpContext2x1x2x2 context(direction, activations);
context.RunTest(X_data, batch_size, seq_len, nullptr, nullptr, Y_data, Y_h_data, Y_c_data,
&sequence_length, use_bias, use_peepholes);
}
} // namespace test
} // namespace onnxruntime