STT-tensorflow/tensorflow/lite/kernels/quant_basic_lstm_test.cc
Robert David bec38f2829 Separate AddVariableInput and AddInput functions in test_util.h/.cc
Some of the usages were wrong before; the bool input was invoked as {}.

PiperOrigin-RevId: 330623020
Change-Id: I9505c199297d6a1e7465c0610184652168f61430
2020-09-08 17:45:35 -07:00

285 lines
10 KiB
C++

/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <initializer_list>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "flatbuffers/flatbuffers.h" // from @flatbuffers
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace {
using ::testing::ElementsAreArray;
class QuantizedLSTMOpModel : public MultiOpModel {
public:
QuantizedLSTMOpModel(int numBatches, int inputSize, float weightsScale,
int32_t weightsZeroPoint, int outputSize,
std::initializer_list<uint8_t> weights,
std::initializer_list<int32_t> biases,
// If true the LTSM node will be preceded by a noop
// one (add to 0)
bool prepend_noop) {
std::vector<uint32_t> inputs;
input_size_ = inputSize;
output_size_ = outputSize;
prepend_noop_ = prepend_noop;
std::vector<int> input_shape{numBatches, inputSize};
std::vector<int> output_shape{numBatches, outputSize};
std::vector<int> weight_shape{4 * outputSize, outputSize + inputSize};
std::vector<int> state_shape{numBatches, outputSize};
std::vector<int> bias_shape{4 * outputSize};
std::vector<int> lstm_inputs;
const TensorData input_tensor_data{
TensorType_UINT8, input_shape, 0.0f, 0.0f, 1. / 128., 128};
if (prepend_noop) {
zero_input_ = AddInput(input_tensor_data);
} else {
zero_input_ = 0;
}
input_ = AddInput(input_tensor_data);
prev_output_ = AddVariableInput(
{TensorType_UINT8, output_shape, 0.0f, 0.0f, 1. / 128., 128});
// Biases and Weights have to be constant in order to allow NNAPI
// delegation
weights_ = AddConstInput<uint8_t>({TensorType_UINT8, weight_shape, 0.0f,
0.0f, weightsScale, weightsZeroPoint},
weights);
biases_ = AddConstInput<int32_t>(
{TensorType_INT32, bias_shape, 0.0f, 0.0f, weightsScale / 128, 0},
biases);
prev_cell_state_ = AddVariableInput(
{TensorType_INT16, state_shape, 0.0f, 0.0f, 1. / 2048., 0});
sum_out_ = AddOutput(input_tensor_data);
output_ =
AddOutput({TensorType_UINT8, output_shape, 0.0f, 0.0f, 1. / 128., 128});
cell_state_out_ =
AddOutput({TensorType_INT16, state_shape, 0.0f, 0.0f, 1. / 2048., 0});
output_concat_temp_ =
AddOutput({TensorType_UINT8, output_shape, 0.0f, 0.0f, 1. / 128., 128});
output_activation_temp_ =
AddOutput({TensorType_INT16, output_shape, 0.0f, 0.0f, 1. / 128., 128});
if (prepend_noop) {
AddBuiltinOp(
BuiltinOperator_ADD, BuiltinOptions_AddOptions,
CreateAddOptions(builder_, ActivationFunctionType_NONE).Union(),
{zero_input_, input_}, {sum_out_});
lstm_inputs.push_back(sum_out_);
} else {
lstm_inputs.push_back(input_);
}
lstm_inputs.push_back(prev_output_);
lstm_inputs.push_back(weights_);
lstm_inputs.push_back(biases_);
lstm_inputs.push_back(prev_cell_state_);
std::vector<int> lstm_outputs{output_, cell_state_out_, output_concat_temp_,
output_activation_temp_};
AddBuiltinOp(BuiltinOperator_LSTM, BuiltinOptions_LSTMOptions,
CreateLSTMOptions(builder_, ActivationFunctionType_TANH, 0.0,
0.0, LSTMKernelType_BASIC)
.Union(),
lstm_inputs, lstm_outputs);
if (prepend_noop) {
BuildInterpreter({GetShape(input_), GetShape(zero_input_),
GetShape(prev_output_), GetShape(weights_),
GetShape(biases_), GetShape(prev_cell_state_)});
} else {
BuildInterpreter({GetShape(input_), GetShape(prev_output_),
GetShape(weights_), GetShape(biases_),
GetShape(prev_cell_state_)});
}
// init feedback inputs to zero
std::vector<int16_t> initial_state(GetTensorSize(cell_state_out_), 0);
PopulateTensor(prev_cell_state_, initial_state);
std::vector<uint8_t> initial_prev_output(GetTensorSize(output_), 0);
PopulateTensor(prev_output_, initial_prev_output);
}
int inputSize() { return input_size_; }
int outputSize() { return output_size_; }
void setInput(const std::vector<uint8_t>& input) {
PopulateTensor(input_, input);
if (prepend_noop_) {
std::vector<uint8_t> zero(GetTensorSize(zero_input_), 128);
PopulateTensor(zero_input_, zero);
}
}
std::vector<uint8_t> getOutput() { return ExtractVector<uint8_t>(output_); }
private:
// Inputs
int input_;
int weights_;
int biases_;
int prev_cell_state_;
int prev_output_;
// Outputs
int cell_state_out_;
int output_;
int output_concat_temp_;
int output_activation_temp_;
int input_size_;
int output_size_;
bool prepend_noop_;
int zero_input_;
int sum_out_;
};
class QuantizedLstmTest : public ::testing::Test,
public testing::WithParamInterface<bool> {
protected:
void VerifyGoldens(const std::vector<std::vector<uint8_t>>& input,
const std::vector<std::vector<uint8_t>>& output,
QuantizedLSTMOpModel* lstm) {
const int numBatches = input.size();
ASSERT_GT(numBatches, 0);
const int inputSize = lstm->inputSize();
ASSERT_GT(inputSize, 0);
const int inputSequenceSize = input[0].size() / inputSize;
ASSERT_GT(inputSequenceSize, 0);
for (int i = 0; i < inputSequenceSize; ++i) {
std::vector<uint8_t> inputStep;
for (int b = 0; b < numBatches; ++b) {
const uint8_t* batchStart = input[b].data() + i * inputSize;
const uint8_t* batchEnd = batchStart + inputSize;
inputStep.insert(inputStep.end(), batchStart, batchEnd);
}
lstm->setInput(inputStep);
lstm->Invoke();
const int outputSize = lstm->outputSize();
std::vector<float> expected;
for (int b = 0; b < numBatches; ++b) {
const uint8_t* goldenBatchStart = output[b].data() + i * outputSize;
const uint8_t* goldenBatchEnd = goldenBatchStart + outputSize;
expected.insert(expected.end(), goldenBatchStart, goldenBatchEnd);
}
EXPECT_THAT(lstm->getOutput(), ElementsAreArray(expected));
}
}
};
// Inputs and weights in this test are random and the test only checks that the
// outputs are equal to outputs obtained from running TF Lite version of
// quantized LSTM on the same inputs.
TEST_P(QuantizedLstmTest, BasicQuantizedLstmTest) {
const int numBatches = 2;
const int inputSize = 2;
const int outputSize = 4;
float weightsScale = 0.00408021;
int weightsZeroPoint = 100;
bool prepend_dummy_node = GetParam();
QuantizedLSTMOpModel lstm(
numBatches, inputSize, weightsScale, weightsZeroPoint, outputSize,
// This data are copied from QuantizedLSTMTest.cpp in NNAPI source code
// I have to recompose the weight matrix before passing it to the model
// recurrentToInputWeights inputToInputWeights
{254, 206, 77, 168, 146, 250, 71, 20, 215, 6, 235, 171, 223, 7, 118, 225,
10, 218, 59, 130, 174, 26, 171, 108,
// recurrentToCellWeights inputToCellWeights
172, 60, 205, 65, 133, 34, 14, 0, 140, 168, 29, 49, 240, 223, 133, 56,
206, 109, 142, 64, 246, 216, 54, 183,
// recurrentToForgetWeights inputToForgetWeights
137, 240, 103, 52, 24, 50, 68, 51, 237, 112, 132, 179, 0, 220, 89, 23,
158, 110, 69, 4, 207, 253, 3, 169,
// recurrentToOutputWeights inputToOutputWeights
106, 214, 67, 23, 195, 187, 59, 158, 45, 3, 11, 99, 119, 132, 49, 205,
109, 10, 129, 218, 11, 98, 218, 48},
// inputGateBias
{-7876, 13488, -726, 32839,
// cellGateBias
39481, 48624, 48976, -21419,
// forgetGateBias
9206, -46884, -11693, -38724,
// outputGateBias
-58999, -17050, -41852, -40538},
prepend_dummy_node);
// clang-format on
// LSTM input is stored as numBatches x (sequenceLength x inputSize) vector.
std::vector<std::vector<uint8_t>> lstmInput;
// clang-format off
lstmInput = {{154, 166,
166, 179,
141, 141},
{100, 200,
50, 150,
111, 222}};
// clang-format on
// LSTM output is stored as numBatches x (sequenceLength x outputSize) vector.
std::vector<std::vector<uint8_t>> lstmGoldenOutput;
/*
This is the output used in NNAPI's QuantizedLSTMTest.cpp
I get slightly different values that are consistent running with or
without acceleration
lstmGoldenOutput = {{136, 150, 140, 115,
140, 151, 146, 112,
139, 153, 146, 114},
{135, 152, 138, 112,
136, 156, 142, 112,
141, 154, 146, 108}};
*/
// clang-format off
lstmGoldenOutput = {{131, 152, 136, 109,
138, 150, 145, 111,
139, 152, 146, 113},
{131, 153, 135, 107,
134, 154, 140, 111,
140, 154, 145, 108}};
// clang-format on
VerifyGoldens(lstmInput, lstmGoldenOutput, &lstm);
}
INSTANTIATE_TEST_SUITE_P(QuantizedLstmTest, QuantizedLstmTest,
testing::Values(false, true));
} // namespace
} // namespace tflite