Fix Concat behavior in Inception V3 quant

PiperOrigin-RevId: 297220987
Change-Id: Icd045f9dac3a7cd6aa42bf5070c206bc7b375046
This commit is contained in:
Sachin Joglekar 2020-02-25 15:38:55 -08:00 committed by TensorFlower Gardener
parent 8df05fc187
commit 78b9c0aa02
2 changed files with 98 additions and 14 deletions

View File

@ -41,6 +41,10 @@ TfLiteStatus ConcatOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
int tensor_id;
// Input data tensors.
// input_bound_minimum & input_bound_maximum track the minimum & maximum
// min/max bounds across all inputs.
float input_bound_minimum = std::numeric_limits<float>::max();
float input_bound_maximum = std::numeric_limits<float>::min();
input_minima_.reserve(inputs->size);
input_maxima_.reserve(inputs->size);
for (int i = 0; i < inputs->size; ++i) {
@ -53,6 +57,8 @@ TfLiteStatus ConcatOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
std::numeric_limits<uint8_t>::max()));
input_minima_.push_back(data_min);
input_maxima_.push_back(data_max);
if (data_min < input_bound_minimum) input_bound_minimum = data_min;
if (data_max > input_bound_maximum) input_bound_maximum = data_max;
}
// Minima tensors.
@ -96,19 +102,27 @@ TfLiteStatus ConcatOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
auto* output_max_const = graph_builder_->AddConstNodeWithData(
quant_bound_shape, (char*)&output_max_, sizeof(output_max_));
auto* requantize_op = graph_builder_->AddNode(GetTFLiteNodeID());
requantize_op->SetOpType(OP_Requantize_8to8);
requantize_op->AddInput(concat_out);
requantize_op->AddInput(concat_out_min);
requantize_op->AddInput(concat_out_max);
requantize_op->AddInput(TensorID(output_min_const->GetID(), 0));
requantize_op->AddInput(TensorID(output_max_const->GetID(), 0));
node_output_ =
requantize_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
requantize_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1});
requantize_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1});
if (output_min_ == input_bound_minimum &&
output_max_ == input_bound_maximum) {
// If the input min/max (across all tensors) is same as the output min/max,
// Hexagon's Requantize causes errors in InceptionV3.
// TODO(b/150137234): Figure out why this is.
node_output_ = concat_out;
} else {
auto* requantize_op = graph_builder_->AddNode(GetTFLiteNodeID());
requantize_op->SetOpType(OP_Requantize_8to8);
requantize_op->AddInput(concat_out);
requantize_op->AddInput(concat_out_min);
requantize_op->AddInput(concat_out_max);
requantize_op->AddInput(TensorID(output_min_const->GetID(), 0));
requantize_op->AddInput(TensorID(output_max_const->GetID(), 0));
node_output_ =
requantize_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
requantize_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1});
requantize_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1});
}
return kTfLiteOk;
}

View File

@ -12,12 +12,36 @@ 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 <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/experimental/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
namespace tflite {
using testing::ElementsAreArray;
void GenerateUniformRandomVector(int size, float min, float max,
std::minstd_rand* random_engine,
std::vector<float>* result) {
// Never use std::uniform_*_distribution in tests, it's
// implementation-defined. Likewise, don't use std::default_random_engine,
// implementation-defined. Implementation-defined is bad because it means that
// any toolchain update or new platform may run into test failures.
// std::minstd_rand is a standard instantiation of
// std::linear_congruential_engine, the cheapest generator in c++11 stdlib,
// it's good enough here.
result->resize(size);
for (int i = 0; i < size; i++) {
// We don't care whether the `max` value may ever be produced exactly.
// It may actually be thanks to rounding, as std::minstd_rand::modulus
// is 2^31 - 1 is greater than the inverse float epsilon.
float random_value_scaled_0_1 =
(*random_engine)() *
(1.0f / static_cast<float>(std::minstd_rand::modulus));
(*result)[i] = min + (max - min) * random_value_scaled_0_1;
}
}
class QuantizedConcatenationOpModel : public SingleOpModelWithHexagon {
public:
QuantizedConcatenationOpModel(const std::vector<TensorData>& input_template,
@ -37,7 +61,7 @@ class QuantizedConcatenationOpModel : public SingleOpModelWithHexagon {
}
template <typename T>
void SetInput(int index, std::initializer_list<float> data) {
void SetInput(int index, std::vector<float> data) {
QuantizeAndPopulate<T>(index, data);
}
@ -95,4 +119,50 @@ TEST(QuantizedConcatenationOpModel, FourInputsQuantizedMixedRange) {
/*max_abs_error=*/0.2)));
}
// If the input min/max (across all tensors) is same as the output min/max,
// Hexagon's Requantize causes errors in InceptionV3.
// So, we diable it for that case in the builder.
// This unit test ensures that the math still works.
TEST(QuantizedConcatenationOpModel, FourInputsQuantizedMixedRange_LargeData) {
// Problem specification.
// Adapted from CONCAT node at #15 in Inceptionv3 quantized.
std::vector<float> params1 = {0, 11.30514f};
std::vector<float> params2 = {0, 10.38416f};
std::vector<float> params3 = {0, 13.52495f};
std::vector<float> params4 = {0, 5.883808f};
std::vector<float> params_output = {0, 13.52495f};
QuantizedConcatenationOpModel m0(
{{TensorType_UINT8, {1, 35, 35, 64}, params1[0], params1[1]},
{TensorType_UINT8, {1, 35, 35, 64}, params2[0], params2[1]},
{TensorType_UINT8, {1, 35, 35, 96}, params3[0], params3[1]},
{TensorType_UINT8, {1, 35, 35, 32}, params4[0], params4[1]}},
/*axis=*/3, {TensorType_UINT8, {}, params_output[0], params_output[1]});
// Generate random data.
std::minstd_rand random_engine;
std::vector<float> data1, data2, data3, data4;
int num_elements_multiplier = 1 * 35 * 35;
GenerateUniformRandomVector(num_elements_multiplier * 64, params1[0],
params1[1], &random_engine, &data1);
GenerateUniformRandomVector(num_elements_multiplier * 64, params2[0],
params2[1], &random_engine, &data2);
GenerateUniformRandomVector(num_elements_multiplier * 96, params3[0],
params3[1], &random_engine, &data3);
GenerateUniformRandomVector(num_elements_multiplier * 32, params4[0],
params4[1], &random_engine, &data4);
m0.SetInput<uint8_t>(0, data1);
m0.SetInput<uint8_t>(1, data2);
m0.SetInput<uint8_t>(2, data3);
m0.SetInput<uint8_t>(3, data4);
// Reference output.
m0.Invoke();
std::vector<float> reference_output = m0.GetDequantizedOutput<uint8_t>();
m0.ApplyDelegateAndInvoke();
EXPECT_THAT(m0.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output,
/*max_abs_error=*/0.1)));
}
} // namespace tflite