chore: import upstream snapshot with attribution
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
@@ -0,0 +1,197 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//tensorflow:tensorflow.bzl", "tf_cc_test")
load("//tensorflow/lite:build_def.bzl", "tflite_copts")
load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = [
"//visibility:public",
],
licenses = ["notice"],
)
cc_library(
name = "builtin_logging_op",
srcs = ["builtin_logging_ops/lstm.cc"],
hdrs = ["builtin_logging_ops/lstm.h"],
copts = tflite_copts(),
deps = [
":calibration_logger",
"//tensorflow/lite:framework",
"//tensorflow/lite/core:framework_stable",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/kernels:kernel_util",
"//tensorflow/lite/kernels:lstm_shared",
"//tensorflow/lite/kernels:op_macros",
"//tensorflow/lite/kernels/internal:reference",
"//tensorflow/lite/kernels/internal:tensor_utils",
"@ruy//ruy/profiler:instrumentation",
],
)
cc_library(
name = "custom_logging_op",
srcs = ["custom_logging_ops/lstm.cc"],
hdrs = ["custom_logging_ops/lstm.h"],
copts = tflite_copts(),
deps = [
":calibration_logger",
"//tensorflow/lite:framework",
"//tensorflow/lite/core:framework_stable",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/kernels:kernel_util",
"//tensorflow/lite/kernels:lstm_shared",
"//tensorflow/lite/kernels:op_macros",
"//tensorflow/lite/kernels/internal:reference",
"//tensorflow/lite/kernels/internal:tensor_utils",
],
)
cc_library(
name = "calibrator_lib",
srcs = ["calibrator.cc"],
hdrs = ["calibrator.h"],
copts = tflite_copts(),
deps = [
":builtin_logging_op",
":calibration_common",
":calibration_logger",
":calibration_reader",
":custom_logging_op",
":logging_op",
":logging_op_resolver",
"//tensorflow/compiler/mlir/lite:allocation",
"//tensorflow/compiler/mlir/lite/schema:schema_utils",
"//tensorflow/lite:framework",
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite:string",
"//tensorflow/lite/core:framework",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_absl//absl/container:flat_hash_map",
"@flatbuffers",
],
)
tf_cc_test(
name = "calibrator_test",
srcs = ["calibrator_test.cc"],
args = [
"--test_model_file=$(location //tensorflow/lite:testdata/multi_add.bin)",
],
data = [
"//tensorflow/lite:testdata/call_once_mul.bin",
"//tensorflow/lite:testdata/custom_lstm.bin",
"//tensorflow/lite:testdata/lstm.bin",
"//tensorflow/lite:testdata/multi_add.bin",
"//tensorflow/lite:testdata/multi_subgraphs_while.bin",
"//tensorflow/lite:testdata/unidirectional_sequence_lstm.bin",
],
tags = [
"tflite_not_portable_android",
"tflite_not_portable_ios",
],
deps = [
":calibration_reader",
":calibrator_lib",
"//tensorflow/core:framework_internal",
"//tensorflow/core:lib",
"//tensorflow/lite:framework",
"//tensorflow/lite:string",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core:framework",
"//tensorflow/lite/core/kernels:builtin_ops",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_googletest//:gtest",
],
)
cc_library(
name = "logging_op_resolver",
srcs = ["logging_op_resolver.cc"],
hdrs = ["logging_op_resolver.h"],
copts = tflite_copts(),
deps = [
":calibration_common",
"//tensorflow/lite:framework",
"//tensorflow/lite:util",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core/api",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "logging_op_resolver_test",
srcs = ["logging_op_resolver_test.cc"],
deps = [
":calibration_common",
":logging_op_resolver",
"//tensorflow/lite:framework",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/kernels:builtin_ops",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "calibration_reader",
srcs = ["calibration_reader.cc"],
hdrs = ["calibration_reader.h"],
copts = tflite_copts(),
deps = [
":calibration_logger",
"//tensorflow/lite:framework",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core:framework",
"//tensorflow/lite/core/c:c_api_types",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_absl//absl/container:flat_hash_map",
],
)
cc_library(
name = "calibration_logger",
srcs = ["calibration_logger.cc"],
hdrs = ["calibration_logger.h"],
copts = tflite_copts(),
deps = [
"//tensorflow/lite:framework",
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:common",
"@com_google_absl//absl/container:flat_hash_map",
],
)
cc_library(
name = "calibration_common",
hdrs = ["calibration_common.h"],
copts = tflite_copts(),
deps = [
"//tensorflow/lite:framework",
],
)
cc_library(
name = "logging_op",
hdrs = ["logging_op.h"],
copts = tflite_copts(),
deps = [
":calibration_logger",
"//tensorflow/lite/core/c:common",
],
)
tflite_portable_test_suite()
@@ -0,0 +1,657 @@
/* Copyright 2018 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 "tensorflow/lite/tools/optimize/calibration/builtin_logging_ops/lstm.h"
#include <algorithm>
#include <cstdio>
#include <vector>
#include "ruy/profiler/instrumentation.h" // from @ruy
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/kernels/internal/portable_tensor_utils.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/internal/tensor_utils.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/kernels/lstm_shared.h"
#include "tensorflow/lite/kernels/op_macros.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_logger.h"
namespace tflite {
namespace optimize {
namespace calibration {
namespace builtin {
namespace {
inline void CalculateLstmGateFloat(
const float* input, const float* input_to_gate_weights,
const float* aux_input, const float* aux_input_to_gate_weights,
const float* output_state, const float* recurrent_to_gate_weights,
const float* cell_state, const float* cell_to_gate_weights,
const float* layer_norm_coefficients, const float* gate_bias,
const int n_batch, const int n_input, const int n_aux_input,
const int n_output, const int n_cell,
const TfLiteFusedActivation activation, float* gate,
const bool is_input_all_zeros, const bool is_aux_input_all_zeros,
Logger* logger, int intermediate_tensor_index, const int subgraph_index,
ErrorReporter* error_reporter) {
const bool use_peephole = (cell_to_gate_weights != nullptr);
const bool use_layer_norm = (layer_norm_coefficients != nullptr);
// Initialize scratch buffers with bias for regular lstm or initialize with
// zero for layer norm lstm.
if (use_layer_norm) {
std::fill_n(gate, n_cell * n_batch, 0.0f);
} else {
tensor_utils::VectorBatchVectorAssign(gate_bias, n_cell, n_batch, gate);
}
// For each batch and cell: compute input_weight * input.
// Skip if input is all zeros.
if (!is_input_all_zeros) {
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
input_to_gate_weights, n_cell, n_input, input, n_batch, gate);
}
// For each batch and cell: compute aux_input_weight * aux_input.
// Skip if auxiliary input is not available or all zeros.
if (!is_aux_input_all_zeros) {
tensor_utils::MatrixBatchVectorMultiplyAccumulate(aux_input_to_gate_weights,
n_cell, n_aux_input,
aux_input, n_batch, gate);
}
// For each batch and cell: compute recurrent_weight * output_state.
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
recurrent_to_gate_weights, n_cell, n_output, output_state, n_batch, gate);
// For each batch and cell: compute cell_weight .* cell_state (peephole LSTM)
if (use_peephole) {
tensor_utils::VectorBatchVectorCwiseProductAccumulate(
cell_to_gate_weights, n_cell, cell_state, n_batch, gate);
}
// Do layer normalization (if layer norm LSTM)
if (use_layer_norm) {
logger->LogTensorValue(subgraph_index, intermediate_tensor_index, gate,
n_cell * n_batch, error_reporter);
tensor_utils::MeanStddevNormalization(gate, gate, n_cell, n_batch);
tensor_utils::VectorBatchVectorCwiseProduct(layer_norm_coefficients, n_cell,
gate, n_batch, gate);
tensor_utils::VectorBatchVectorAdd(gate_bias, n_cell, n_batch, gate);
}
// Apply activation
tensor_utils::ApplyActivationToVector(gate, n_batch * n_cell, activation,
gate);
}
// TODO(b/159066113): This is the exact same function as UpdateLstmCellFloat in
// kernels/lstm_eval.cc, make that public and remove this.
void UpdateLstmCellFloat(int n_batch, int n_cell, float* cell_state,
const float* input_gate, float* forget_gate,
const float* cell_gate, bool use_cifg, float clip) {
tensor_utils::VectorVectorCwiseProduct(forget_gate, cell_state,
n_batch * n_cell, cell_state);
if (use_cifg) {
// With CIFG, input_gate = 1-forget_gate. Use the forget_gate array as
// scratch, as input_gate array is not allocated in this case. (Be careful
// not to write to the scratch before reading the forget gate data.)
float* scratch = forget_gate;
tensor_utils::Sub1Vector(forget_gate, n_batch * n_cell, scratch);
tensor_utils::VectorVectorCwiseProductAccumulate(
cell_gate, scratch, n_batch * n_cell, cell_state);
} else {
tensor_utils::VectorVectorCwiseProductAccumulate(
cell_gate, input_gate, n_batch * n_cell, cell_state);
}
if (clip > 0.0f) {
tensor_utils::CwiseClipping(cell_state, n_batch * n_cell, clip);
}
}
void CalculateLstmOutputCalibration(
int n_batch, int n_cell, int n_output, const float* cell_state,
const float* output_gate, TfLiteFusedActivation activation,
const float* projection_weights, const float* projection_bias,
const float proj_clip, float* output_state, float* scratch, Logger* logger,
int intermediate_tensor_index, const int subgraph_index,
ErrorReporter* error_reporter) {
tensor_utils::ApplyActivationToVector(cell_state, n_batch * n_cell,
activation, scratch);
tensor_utils::VectorVectorCwiseProduct(output_gate, scratch, n_batch * n_cell,
scratch);
logger->LogTensorValue(subgraph_index, intermediate_tensor_index, scratch,
n_cell * n_batch, error_reporter);
const bool use_projection = (projection_weights != nullptr);
const bool use_projection_bias = (projection_bias != nullptr);
if (use_projection) {
if (use_projection_bias) {
tensor_utils::VectorBatchVectorAssign(projection_bias, n_output, n_batch,
output_state);
} else {
std::fill_n(output_state, n_batch * n_output, 0.0f);
}
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
projection_weights, n_output, n_cell, scratch, n_batch, output_state);
if (proj_clip > 0.0f) {
tensor_utils::CwiseClipping(output_state, n_batch * n_output, proj_clip);
}
} else {
std::copy_n(scratch, n_batch * n_output, output_state);
}
}
inline void LstmStepCalibration(
const float* input_ptr, const float* input_to_input_weights_ptr,
const float* input_to_forget_weights_ptr,
const float* input_to_cell_weights_ptr,
const float* input_to_output_weights_ptr, const float* aux_input_ptr,
const float* aux_input_to_input_weights_ptr,
const float* aux_input_to_forget_weights_ptr,
const float* aux_input_to_cell_weights_ptr,
const float* aux_input_to_output_weights_ptr,
const float* recurrent_to_input_weights_ptr,
const float* recurrent_to_forget_weights_ptr,
const float* recurrent_to_cell_weights_ptr,
const float* recurrent_to_output_weights_ptr,
const float* cell_to_input_weights_ptr,
const float* cell_to_forget_weights_ptr,
const float* cell_to_output_weights_ptr,
const float* input_layer_norm_coefficients_ptr,
const float* forget_layer_norm_coefficients_ptr,
const float* cell_layer_norm_coefficients_ptr,
const float* output_layer_norm_coefficients_ptr,
const float* input_gate_bias_ptr, const float* forget_gate_bias_ptr,
const float* cell_gate_bias_ptr, const float* output_gate_bias_ptr,
const float* projection_weights_ptr, const float* projection_bias_ptr,
const TfLiteLSTMParams* params, int n_batch, int n_cell, int n_input,
int n_aux_input, int n_output, int output_batch_leading_dim,
float* output_state_ptr, float* cell_state_ptr, float* scratch0,
float* scratch1, float* scratch2, float* scratch3, float* output_ptr,
Logger* logger, const std::vector<int>& intermediate_tensor_indexes,
const int subgraph_index, ErrorReporter* error_reporter) {
ruy::profiler::ScopeLabel label("LstmStepCalibration");
// Since we have already checked that weights are all there or none, we can
// check the existence of only one to the get the condition.
const bool use_cifg = (input_to_input_weights_ptr == nullptr);
// Make named scratch buffers.
float* input_gate_scratch = scratch0;
float* forget_gate_scratch = scratch1;
float* cell_gate_scratch = scratch2;
float* output_gate_scratch = scratch3;
// Check if inputs are all zeros so we can skip some computations.
const bool is_input_all_zeros =
tensor_utils::IsZeroVector(input_ptr, n_batch * n_input);
const bool is_aux_input_all_zeros =
(aux_input_ptr == nullptr ||
tensor_utils::IsZeroVector(aux_input_ptr, n_batch * n_aux_input));
if (!use_cifg) {
// Calculate the input gate. (If not CIFG.)
CalculateLstmGateFloat(
input_ptr, input_to_input_weights_ptr, aux_input_ptr,
aux_input_to_input_weights_ptr, output_state_ptr,
recurrent_to_input_weights_ptr, cell_state_ptr,
cell_to_input_weights_ptr, input_layer_norm_coefficients_ptr,
input_gate_bias_ptr, n_batch, n_input, n_aux_input, n_output, n_cell,
/*activation=*/kTfLiteActSigmoid, input_gate_scratch,
is_input_all_zeros, is_aux_input_all_zeros, logger,
intermediate_tensor_indexes[0], subgraph_index, error_reporter);
}
// Calculate the forget gate.
CalculateLstmGateFloat(
input_ptr, input_to_forget_weights_ptr, aux_input_ptr,
aux_input_to_forget_weights_ptr, output_state_ptr,
recurrent_to_forget_weights_ptr, cell_state_ptr,
cell_to_forget_weights_ptr, forget_layer_norm_coefficients_ptr,
forget_gate_bias_ptr, n_batch, n_input, n_aux_input, n_output, n_cell,
/*activation=*/kTfLiteActSigmoid, forget_gate_scratch, is_input_all_zeros,
is_aux_input_all_zeros, logger, intermediate_tensor_indexes[1],
subgraph_index, error_reporter);
// Calculate the cell update gate.
CalculateLstmGateFloat(
input_ptr, input_to_cell_weights_ptr, aux_input_ptr,
aux_input_to_cell_weights_ptr, output_state_ptr,
recurrent_to_cell_weights_ptr, /*cell_state=*/nullptr,
/*cell_to_gate_weights=*/nullptr, cell_layer_norm_coefficients_ptr,
cell_gate_bias_ptr, n_batch, n_input, n_aux_input, n_output, n_cell,
params->activation, cell_gate_scratch, is_input_all_zeros,
is_aux_input_all_zeros, logger, intermediate_tensor_indexes[2],
subgraph_index, error_reporter);
// Update the cell state.
UpdateLstmCellFloat(n_batch, n_cell, cell_state_ptr, input_gate_scratch,
forget_gate_scratch, cell_gate_scratch, use_cifg,
params->cell_clip);
// Calculate output gate.
CalculateLstmGateFloat(
input_ptr, input_to_output_weights_ptr, aux_input_ptr,
aux_input_to_output_weights_ptr, output_state_ptr,
recurrent_to_output_weights_ptr, cell_state_ptr,
cell_to_output_weights_ptr, output_layer_norm_coefficients_ptr,
output_gate_bias_ptr, n_batch, n_input, n_aux_input, n_output, n_cell,
/*activation=*/kTfLiteActSigmoid, output_gate_scratch, is_input_all_zeros,
is_aux_input_all_zeros, logger, intermediate_tensor_indexes[3],
subgraph_index, error_reporter);
// Update the output state.
CalculateLstmOutputCalibration(
n_batch, n_cell, n_output, cell_state_ptr, output_gate_scratch,
params->activation, projection_weights_ptr, projection_bias_ptr,
params->proj_clip, output_state_ptr, scratch2, logger,
intermediate_tensor_indexes[4], subgraph_index, error_reporter);
// Copy output state to the output. Note that the output's rows may not be
// contiguous (output_batch_leading_dim != n_output).
for (int b = 0; b < n_batch; b++) {
std::copy_n(output_state_ptr + b * n_output, n_output,
output_ptr + b * output_batch_leading_dim);
}
}
TfLiteStatus EvalCalibration(
const TfLiteTensor* input, const TfLiteTensor* input_to_input_weights,
const TfLiteTensor* input_to_forget_weights,
const TfLiteTensor* input_to_cell_weights,
const TfLiteTensor* input_to_output_weights,
const TfLiteTensor* recurrent_to_input_weights,
const TfLiteTensor* recurrent_to_forget_weights,
const TfLiteTensor* recurrent_to_cell_weights,
const TfLiteTensor* recurrent_to_output_weights,
const TfLiteTensor* cell_to_input_weights,
const TfLiteTensor* cell_to_forget_weights,
const TfLiteTensor* cell_to_output_weights,
const TfLiteTensor* input_layer_norm_coefficients,
const TfLiteTensor* forget_layer_norm_coefficients,
const TfLiteTensor* cell_layer_norm_coefficients,
const TfLiteTensor* output_layer_norm_coefficients,
const TfLiteTensor* aux_input,
const TfLiteTensor* aux_input_to_input_weights,
const TfLiteTensor* aux_input_to_forget_weights,
const TfLiteTensor* aux_input_to_cell_weights,
const TfLiteTensor* aux_input_to_output_weights,
const TfLiteTensor* input_gate_bias, const TfLiteTensor* forget_gate_bias,
const TfLiteTensor* cell_gate_bias, const TfLiteTensor* output_gate_bias,
const TfLiteTensor* projection_weights, const TfLiteTensor* projection_bias,
const TfLiteLSTMParams* params, bool forward_sequence, bool time_major,
int output_offset, TfLiteTensor* scratch_buffer, TfLiteTensor* output_state,
TfLiteTensor* cell_state, TfLiteTensor* output, Logger* logger,
const std::vector<int>& intermediate_tensor_indexes,
const int subgraph_index, ErrorReporter* error_reporter) {
TF_LITE_ASSERT(input->dims->size >= 2 && input->dims->size <= 3);
int max_time, n_batch;
if (input->dims->size == 3) {
max_time = (time_major) ? input->dims->data[0] : input->dims->data[1];
n_batch = (time_major) ? input->dims->data[1] : input->dims->data[0];
} else {
max_time = 1;
n_batch = input->dims->data[0];
}
const int n_input = input->dims->data[input->dims->size - 1];
const int aux_input_size =
(aux_input) ? aux_input->dims->data[aux_input->dims->size - 1] : 0;
// n_cell and n_output will be the same size when there is no projection.
const int n_cell = input_to_output_weights->dims->data[0];
const int n_output = recurrent_to_output_weights->dims->data[1];
// Since we have already checked that weights are all there or none, we can
// check the existence of only one to the get the condition.
const bool use_cifg = (input_to_input_weights == nullptr);
// Index the scratch buffers pointers to the global scratch buffer.
float* scratch_buffer_ptr = GetTensorData<float>(scratch_buffer);
float* input_gate_scratch = nullptr;
float* cell_gate_scratch = nullptr;
float* forget_gate_scratch = nullptr;
float* output_gate_scratch = nullptr;
if (use_cifg) {
cell_gate_scratch = scratch_buffer_ptr;
forget_gate_scratch = scratch_buffer_ptr + n_cell * n_batch;
output_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch;
} else {
input_gate_scratch = scratch_buffer_ptr;
cell_gate_scratch = scratch_buffer_ptr + n_cell * n_batch;
forget_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch;
output_gate_scratch = scratch_buffer_ptr + 3 * n_cell * n_batch;
}
const int output_batch_leading_dim =
output->dims->data[output->dims->size - 1];
if (time_major) {
// Loop through the sequence.
const int input_step = n_batch * n_input;
const int output_step = n_batch * output_batch_leading_dim;
for (int t = 0; t < max_time; t++) {
// If this is the forward_sequence, step forward, otherwise step
// backwards.
const int t_rel = forward_sequence ? t : max_time - t - 1;
const float* input_ptr = GetTensorData<float>(input) + t_rel * input_step;
const float* aux_input_ptr = nullptr;
if (aux_input) {
aux_input_ptr = GetTensorData<float>(aux_input) + t_rel * input_step;
}
float* output_ptr_time =
GetTensorData<float>(output) + t_rel * output_step + output_offset;
LstmStepCalibration(
input_ptr, GetTensorData<float>(input_to_input_weights),
GetTensorData<float>(input_to_forget_weights),
GetTensorData<float>(input_to_cell_weights),
GetTensorData<float>(input_to_output_weights), aux_input_ptr,
GetTensorData<float>(aux_input_to_input_weights),
GetTensorData<float>(aux_input_to_forget_weights),
GetTensorData<float>(aux_input_to_cell_weights),
GetTensorData<float>(aux_input_to_output_weights),
GetTensorData<float>(recurrent_to_input_weights),
GetTensorData<float>(recurrent_to_forget_weights),
GetTensorData<float>(recurrent_to_cell_weights),
GetTensorData<float>(recurrent_to_output_weights),
GetTensorData<float>(cell_to_input_weights),
GetTensorData<float>(cell_to_forget_weights),
GetTensorData<float>(cell_to_output_weights),
GetTensorData<float>(input_layer_norm_coefficients),
GetTensorData<float>(forget_layer_norm_coefficients),
GetTensorData<float>(cell_layer_norm_coefficients),
GetTensorData<float>(output_layer_norm_coefficients),
GetTensorData<float>(input_gate_bias),
GetTensorData<float>(forget_gate_bias),
GetTensorData<float>(cell_gate_bias),
GetTensorData<float>(output_gate_bias),
GetTensorData<float>(projection_weights),
GetTensorData<float>(projection_bias), params, n_batch, n_cell,
n_input, aux_input_size, n_output, output_batch_leading_dim,
GetTensorData<float>(output_state), GetTensorData<float>(cell_state),
input_gate_scratch, forget_gate_scratch, cell_gate_scratch,
output_gate_scratch, output_ptr_time, logger,
intermediate_tensor_indexes, subgraph_index, error_reporter);
}
} else {
for (int b = 0; b < n_batch; b++) {
const int input_step = n_input;
const int output_step = output_batch_leading_dim;
for (int t = 0; t < max_time; t++) {
// If this is the forward_sequence, step forward, otherwise step
// backwards.
const int t_rel = forward_sequence ? t : max_time - t - 1;
const int time_offset = b * max_time + t_rel;
const float* input_ptr =
GetTensorData<float>(input) + time_offset * input_step;
const float* aux_input_ptr = nullptr;
if (aux_input) {
aux_input_ptr =
GetTensorData<float>(aux_input) + time_offset * input_step;
}
float* output_ptr = GetTensorData<float>(output) +
time_offset * output_step + output_offset;
// Offset the {output,cell}_state pointers to the right batch.
float* output_state_ptr =
GetTensorData<float>(output_state) + b * output_batch_leading_dim;
float* cell_state_ptr = GetTensorData<float>(cell_state) + b * n_cell;
// Offset the scratch pointers to the right batch.
float* input_gate_scratch_ptr =
input_gate_scratch ? input_gate_scratch + b * n_cell : nullptr;
float* forget_gate_scratch_ptr = forget_gate_scratch + b * n_cell;
float* cell_gate_scratch_ptr = cell_gate_scratch + b * n_cell;
float* output_gate_scratch_ptr = output_gate_scratch + b * n_cell;
LstmStepCalibration(
input_ptr, GetTensorData<float>(input_to_input_weights),
GetTensorData<float>(input_to_forget_weights),
GetTensorData<float>(input_to_cell_weights),
GetTensorData<float>(input_to_output_weights), aux_input_ptr,
GetTensorData<float>(aux_input_to_input_weights),
GetTensorData<float>(aux_input_to_forget_weights),
GetTensorData<float>(aux_input_to_cell_weights),
GetTensorData<float>(aux_input_to_output_weights),
GetTensorData<float>(recurrent_to_input_weights),
GetTensorData<float>(recurrent_to_forget_weights),
GetTensorData<float>(recurrent_to_cell_weights),
GetTensorData<float>(recurrent_to_output_weights),
GetTensorData<float>(cell_to_input_weights),
GetTensorData<float>(cell_to_forget_weights),
GetTensorData<float>(cell_to_output_weights),
GetTensorData<float>(input_layer_norm_coefficients),
GetTensorData<float>(forget_layer_norm_coefficients),
GetTensorData<float>(cell_layer_norm_coefficients),
GetTensorData<float>(output_layer_norm_coefficients),
GetTensorData<float>(input_gate_bias),
GetTensorData<float>(forget_gate_bias),
GetTensorData<float>(cell_gate_bias),
GetTensorData<float>(output_gate_bias),
GetTensorData<float>(projection_weights),
GetTensorData<float>(projection_bias), params, /*n_batch=*/1,
n_cell, n_input, aux_input_size, n_output, output_batch_leading_dim,
output_state_ptr, cell_state_ptr, input_gate_scratch_ptr,
forget_gate_scratch_ptr, cell_gate_scratch_ptr,
output_gate_scratch_ptr, output_ptr, logger,
intermediate_tensor_indexes, subgraph_index, error_reporter);
}
}
}
return kTfLiteOk;
}
struct OpData {
// Which kernel type to use. Full kernel (24 inputs) or basic kernel (5
// inputs).
// Please note the 20-input full kernel is deprecated and only kept
// here for backward compatibility.
TfLiteLSTMKernelType kernel_type;
// If the lstm is layer norm.
bool use_layer_norm;
// These fields are only used by full kernel.
int scratch_tensor_index;
};
// Resize the output, state tensors based on the sizes of the input tensors.
// Allocate a temporary scratch tensor. Also check that the sizes of the input
// tensors match each other.
TfLiteStatus lstm_eval(TfLiteContext* context, int subgraph_index,
TfLiteNode* node, LSTMType lstm_type, Logger* logger,
ErrorReporter* error_reporter) {
const TfLiteTensor* input;
TF_LITE_ENSURE_OK(
context, GetInputSafe(context, node,
ops::builtin::lstm::full::kInputTensor, &input));
const TfLiteTensor* input_to_input_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kInputToInputWeightsTensor);
const TfLiteTensor* input_to_forget_weights;
TF_LITE_ENSURE_OK(
context,
GetInputSafe(context, node,
ops::builtin::lstm::full::kInputToForgetWeightsTensor,
&input_to_forget_weights));
const TfLiteTensor* input_to_cell_weights;
TF_LITE_ENSURE_OK(
context, GetInputSafe(context, node,
ops::builtin::lstm::full::kInputToCellWeightsTensor,
&input_to_cell_weights));
const TfLiteTensor* input_to_output_weights;
TF_LITE_ENSURE_OK(
context,
GetInputSafe(context, node,
ops::builtin::lstm::full::kInputToOutputWeightsTensor,
&input_to_output_weights));
const TfLiteTensor* recurrent_to_input_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kRecurrentToInputWeightsTensor);
const TfLiteTensor* recurrent_to_forget_weights;
TF_LITE_ENSURE_OK(
context,
GetInputSafe(context, node,
ops::builtin::lstm::full::kRecurrentToForgetWeightsTensor,
&recurrent_to_forget_weights));
const TfLiteTensor* recurrent_to_cell_weights;
TF_LITE_ENSURE_OK(
context,
GetInputSafe(context, node,
ops::builtin::lstm::full::kRecurrentToCellWeightsTensor,
&recurrent_to_cell_weights));
const TfLiteTensor* recurrent_to_output_weights;
TF_LITE_ENSURE_OK(
context,
GetInputSafe(context, node,
ops::builtin::lstm::full::kRecurrentToOutputWeightsTensor,
&recurrent_to_output_weights));
const TfLiteTensor* cell_to_input_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kCellToInputWeightsTensor);
const TfLiteTensor* cell_to_forget_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kCellToForgetWeightsTensor);
const TfLiteTensor* cell_to_output_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kCellToOutputWeightsTensor);
const TfLiteTensor* input_layer_norm_coefficients = GetOptionalInputTensor(
context, node,
ops::builtin::lstm::full::kInputLayerNormCoefficientsTensor);
const TfLiteTensor* forget_layer_norm_coefficients = GetOptionalInputTensor(
context, node,
ops::builtin::lstm::full::kForgetLayerNormCoefficientsTensor);
const TfLiteTensor* cell_layer_norm_coefficients = GetOptionalInputTensor(
context, node,
ops::builtin::lstm::full::kCellLayerNormCoefficientsTensor);
const TfLiteTensor* output_layer_norm_coefficients = GetOptionalInputTensor(
context, node,
ops::builtin::lstm::full::kOutputLayerNormCoefficientsTensor);
const TfLiteTensor* input_gate_bias = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kInputGateBiasTensor);
const TfLiteTensor* forget_gate_bias;
TF_LITE_ENSURE_OK(
context, GetInputSafe(context, node,
ops::builtin::lstm::full::kForgetGateBiasTensor,
&forget_gate_bias));
const TfLiteTensor* cell_gate_bias;
TF_LITE_ENSURE_OK(
context,
GetInputSafe(context, node, ops::builtin::lstm::full::kCellGateBiasTensor,
&cell_gate_bias));
const TfLiteTensor* output_gate_bias;
TF_LITE_ENSURE_OK(
context, GetInputSafe(context, node,
ops::builtin::lstm::full::kOutputGateBiasTensor,
&output_gate_bias));
const TfLiteTensor* projection_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kProjectionWeightsTensor);
const TfLiteTensor* projection_bias = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kProjectionBiasTensor);
// Index the scratch buffers pointers to the global scratch buffer.
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(
context, GetTemporarySafe(context, node, /*index=*/0, &scratch_buffer));
TfLiteTensor* output_state = GetVariableInput(
context, node, ops::builtin::lstm::full::kOutputStateTensor);
TF_LITE_ENSURE(context, output_state != nullptr);
TfLiteTensor* cell_state = GetVariableInput(
context, node, ops::builtin::lstm::full::kCellStateTensor);
TF_LITE_ENSURE(context, cell_state != nullptr);
TfLiteTensor* output;
TF_LITE_ENSURE_OK(
context, GetOutputSafe(context, node,
ops::builtin::lstm::full::kOutputTensor, &output));
std::vector<int> intermediate_tensor_indexes(node->intermediates->size);
// LSTM expect 5 intermediate tensors.
TF_LITE_ENSURE_EQ(context, node->intermediates->size, 5);
for (int i = 0; i < node->intermediates->size; ++i) {
intermediate_tensor_indexes[i] = node->intermediates->data[i];
}
TfLiteLSTMParams lstm_params;
bool time_major = true;
switch (lstm_type) {
case LSTMType::kLSTM: {
lstm_params = *(static_cast<TfLiteLSTMParams*>(node->builtin_data));
time_major = true;
break;
}
case LSTMType::kUnidirectionalSequenceLSTM: {
const auto* params = static_cast<TfLiteUnidirectionalSequenceLSTMParams*>(
node->builtin_data);
// Copy out the LSTM specific params so they can be passed in the
// function.
lstm_params.activation = params->activation;
lstm_params.cell_clip = params->cell_clip;
lstm_params.proj_clip = params->proj_clip;
lstm_params.asymmetric_quantize_inputs =
params->asymmetric_quantize_inputs;
time_major = params->time_major;
break;
}
default:
return kTfLiteError;
}
switch (input_to_output_weights->type) {
case kTfLiteFloat32: {
return EvalCalibration(
input, input_to_input_weights, input_to_forget_weights,
input_to_cell_weights, input_to_output_weights,
recurrent_to_input_weights, recurrent_to_forget_weights,
recurrent_to_cell_weights, recurrent_to_output_weights,
cell_to_input_weights, cell_to_forget_weights, cell_to_output_weights,
input_layer_norm_coefficients, forget_layer_norm_coefficients,
cell_layer_norm_coefficients, output_layer_norm_coefficients,
/*aux_input=*/nullptr,
/*aux_input_to_input_weights=*/nullptr,
/*aux_input_to_forget_weights=*/nullptr,
/*aux_input_to_cell_weights=*/nullptr,
/*aux_input_to_output_weights=*/nullptr, input_gate_bias,
forget_gate_bias, cell_gate_bias, output_gate_bias,
projection_weights, projection_bias, &lstm_params,
/*forward_sequence=*/true,
/*time_major=*/time_major,
/*output_offset=*/0, scratch_buffer, output_state, cell_state, output,
logger, intermediate_tensor_indexes, subgraph_index, error_reporter);
}
case kTfLiteUInt8:
case kTfLiteInt8:
default:
printf("Error. Only float model can be calibrated\n");
return kTfLiteError;
}
return kTfLiteOk;
}
} // namespace
TfLiteStatus lstm_logging_kernel(TfLiteContext* context,
const int subgraph_index, TfLiteNode* node,
Logger* logger,
ErrorReporter* error_reporter) {
return lstm_eval(context, subgraph_index, node, LSTMType::kLSTM, logger,
error_reporter);
}
TfLiteStatus unidirectional_sequence_lstm_logging_kernel(
TfLiteContext* context, const int subgraph_index, TfLiteNode* node,
Logger* logger, ErrorReporter* error_reporter) {
return lstm_eval(context, subgraph_index, node,
LSTMType::kUnidirectionalSequenceLSTM, logger,
error_reporter);
}
} // namespace builtin
} // namespace calibration
} // namespace optimize
} // namespace tflite
@@ -0,0 +1,45 @@
/* Copyright 2018 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_BUILTIN_LOGGING_OPS_LSTM_H_
#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_BUILTIN_LOGGING_OPS_LSTM_H_
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_logger.h"
namespace tflite {
namespace optimize {
namespace calibration {
namespace builtin {
enum class LSTMType {
kLSTM,
kUnidirectionalSequenceLSTM,
};
TfLiteStatus lstm_logging_kernel(TfLiteContext* context, int subgraph_index,
TfLiteNode* node, Logger* logger,
ErrorReporter* error_reporter);
TfLiteStatus unidirectional_sequence_lstm_logging_kernel(
TfLiteContext* context, int subgraph_index, TfLiteNode* node,
Logger* logger, ErrorReporter* error_reporter);
} // namespace builtin
} // namespace calibration
} // namespace optimize
} // namespace tflite
#endif // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_BUILTIN_LOGGING_OPS_LSTM_H_
@@ -0,0 +1,76 @@
/* Copyright 2018 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATION_COMMON_H_
#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATION_COMMON_H_
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "tensorflow/lite/mutable_op_resolver.h"
namespace tflite {
namespace optimize {
namespace calibration {
using BuiltinOperatorKey = std::pair<BuiltinOperator, int>;
using CustomOperatorKey = std::pair<std::string, int>;
using BuiltinOpsSet = std::unordered_set<
BuiltinOperatorKey,
op_resolver_hasher::OperatorKeyHasher<BuiltinOperatorKey>>;
using CustomOpsSet = std::unordered_set<
CustomOperatorKey,
op_resolver_hasher::OperatorKeyHasher<CustomOperatorKey>>;
template <typename T>
class BuiltinOpsMap
: public std::unordered_map<
BuiltinOperatorKey, T,
op_resolver_hasher::OperatorKeyHasher<BuiltinOperatorKey>> {};
template <typename T>
class CustomOpsMap
: public std::unordered_map<
CustomOperatorKey, T,
op_resolver_hasher::OperatorKeyHasher<CustomOperatorKey>> {};
// An alias for |TfLiteRegistration.invoke|.
using KernelEvalFuncPtr = TfLiteStatus (*)(TfLiteContext*, TfLiteNode*);
enum class OperatorTensorType { kNone, kInput, kOutput, kIntermediate };
// Information about an operator in the TfLite graph.
struct OperatorInfo {
int subgraph_index;
int node_index;
std::string name;
BuiltinOperator builtin_op_code;
bool is_custom_op;
std::vector<int> inputs;
std::vector<int> outputs;
// Inputs that need to be logged.
std::vector<int> loggable_inputs;
// Outputs that need to be logged.
std::vector<int> loggable_outputs;
const TfLiteRegistration* registration;
int version;
};
} // namespace calibration
} // namespace optimize
} // namespace tflite
#endif // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATION_COMMON_H_
@@ -0,0 +1,57 @@
/* 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 "tensorflow/lite/tools/optimize/calibration/calibration_logger.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
namespace tflite {
namespace optimize {
namespace calibration {
TfLiteStatus MinMax::Update(const float* values, size_t tensor_size,
ErrorReporter* error_reporter) {
if (tensor_size <= 0) return kTfLiteOk;
// TODO(shashishekhar): Make it possible to use weighted/moving average.
bool has_nan_value = false;
for (size_t i = 0; i < tensor_size; ++i) {
const float value = values[i];
if (std::isnan(value)) {
has_nan_value = true;
continue;
}
has_values_ = true;
min_ = std::min<float>(min_, value);
max_ = std::max<float>(max_, value);
}
if (has_nan_value) {
TFLITE_LOG(TFLITE_LOG_WARNING,
"Model resulted in Nan value during calibration. Please "
"consider making sure that model results in all real-values "
"during inference with provided dataset.");
}
return kTfLiteOk;
}
} // namespace calibration
} // namespace optimize
} // namespace tflite
@@ -0,0 +1,74 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATION_LOGGER_H_
#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATION_LOGGER_H_
#include <limits>
#include "absl/container/flat_hash_map.h"
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/c/common.h"
namespace tflite {
namespace optimize {
namespace calibration {
class MinMax {
public:
TfLiteStatus Update(const float* values, size_t tensor_size,
ErrorReporter* error_reporter);
bool HasValues() const { return has_values_; }
TfLiteStatus Get(float* min_val, float* max_val) const {
if (!has_values_) return kTfLiteError;
*min_val = min_;
*max_val = max_;
return kTfLiteOk;
}
private:
bool has_values_ = false;
float min_ = std::numeric_limits<float>::max();
float max_ = std::numeric_limits<float>::min();
};
// Captures min max values for tensors.
class Logger {
public:
// Log the value for tensor at |tensor_index| which has |tensor_values|
TfLiteStatus LogTensorValue(int subgraph_index, int tensor_index,
const float* tensor_values, size_t tensor_size,
ErrorReporter* error_reporter) {
std::tuple<int, int> key{subgraph_index, tensor_index};
return tensor_id_to_stats_map_[key].Update(tensor_values, tensor_size,
error_reporter);
}
// Returns a map from tensor_index -> observed min max values.
const absl::flat_hash_map<std::tuple<int, int>, MinMax>&
GetCalibrationValues() const {
return tensor_id_to_stats_map_;
}
private:
absl::flat_hash_map<std::tuple<int, int>, MinMax> tensor_id_to_stats_map_;
};
} // namespace calibration
} // namespace optimize
} // namespace tflite
#endif // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATION_LOGGER_H_
@@ -0,0 +1,80 @@
/* Copyright 2018 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 "tensorflow/lite/tools/optimize/calibration/calibration_reader.h"
#include <memory>
#include <tuple>
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace optimize {
namespace calibration {
TfLiteStatus CalibrationReader::GetTensorStatsAsMap(
absl::flat_hash_map<std::tuple<int, int>, CalibrationStats>*
tensor_id_to_stats_map) const {
tensor_id_to_stats_map->clear();
for (const auto& tensorid_stat : logger_->GetCalibrationValues()) {
auto minmax = tensorid_stat.second;
CalibrationReader::CalibrationStats stats;
TF_LITE_ENSURE_STATUS(minmax.Get(&stats.min, &stats.max));
tensor_id_to_stats_map->insert({tensorid_stat.first, stats});
}
return kTfLiteOk;
}
TfLiteStatus CalibrationReader::AddCalibrationToModel(ModelT* model,
const bool update) const {
if (!model || model->subgraphs.empty()) {
return kTfLiteError;
}
for (const auto& tensorid_stat : logger_->GetCalibrationValues()) {
int subgraph_index, tensor_index;
std::tie(subgraph_index, tensor_index) = tensorid_stat.first;
const auto& subgraph = model->subgraphs[subgraph_index];
auto minmax = tensorid_stat.second;
float min, max;
TfLiteStatus status = minmax.Get(&min, &max);
if (status != kTfLiteOk) continue;
if (update) {
auto tensor = subgraph->tensors[tensor_index].get();
if (tensor->quantization) {
if (!tensor->quantization->min.empty()) {
const float existing_min = tensor->quantization->min[0];
min = min < existing_min ? min : existing_min;
}
if (!tensor->quantization->max.empty()) {
const float existing_max = tensor->quantization->max[0];
max = max > existing_max ? max : existing_max;
}
}
}
auto quant_params = std::make_unique<tflite::QuantizationParametersT>();
quant_params->min.push_back(min);
quant_params->max.push_back(max);
subgraph->tensors[tensor_index]->quantization = std::move(quant_params);
}
return kTfLiteOk;
}
} // namespace calibration
} // namespace optimize
} // namespace tflite
@@ -0,0 +1,62 @@
/* Copyright 2018 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATION_READER_H_
#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATION_READER_H_
#include <tuple>
#include "absl/container/flat_hash_map.h"
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/core/model.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_logger.h"
namespace tflite {
namespace optimize {
namespace calibration {
// Warning: This is not a public API and subject to change.
//
// Reads calibrator data collected by running the interpreter through
// a calibration set.
class CalibrationReader {
public:
struct CalibrationStats {
float min;
float max;
};
explicit CalibrationReader(const Logger* logger) : logger_(logger) {}
// Gets a map from tensor index to recorded calibration values.
virtual TfLiteStatus GetTensorStatsAsMap(
absl::flat_hash_map<std::tuple<int, int>, CalibrationStats>*
tensor_id_to_stats_map) const;
// Annotates the tensors in the given model with statistics captured during
// calibration.
// "update" is a flag: when set to true, the min/max are updated, instead of
// being overwritten.
virtual TfLiteStatus AddCalibrationToModel(ModelT* model, bool update) const;
virtual ~CalibrationReader() {}
private:
const Logger* logger_;
};
} // namespace calibration
} // namespace optimize
} // namespace tflite
#endif // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATION_READER_H_
@@ -0,0 +1,497 @@
/* Copyright 2018 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 "tensorflow/lite/tools/optimize/calibration/calibrator.h"
#include <cstddef>
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/vector.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/allocation.h"
#include "tensorflow/compiler/mlir/lite/schema/schema_utils.h"
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/api/op_resolver.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
#include "tensorflow/lite/model_builder.h"
#include "tensorflow/lite/op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/stderr_reporter.h"
#include "tensorflow/lite/string_type.h"
#include "tensorflow/lite/tools/optimize/calibration/builtin_logging_ops/lstm.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_common.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_logger.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_reader.h"
#include "tensorflow/lite/tools/optimize/calibration/custom_logging_ops/lstm.h"
#include "tensorflow/lite/tools/optimize/calibration/logging_op.h"
#include "tensorflow/lite/tools/optimize/calibration/logging_op_resolver.h"
namespace tflite {
namespace optimize {
namespace calibration {
namespace {
// Calibrator is used to hold information that can be accessed during kernel
// invocations.
// TfLite kernel invocations are C functions and cannot look at the global
// structure of the graph. Calibrator allows the kernel invoke functions to
// access the global structure of graph and know which node is currently being
// executed. This also allows us to write a simple kernel invoke wrapper
// (see LoggingEval) that can work for most builtin ops.
class Calibrator {
public:
Calibrator(const std::unordered_map<const TfLiteNode*, OperatorInfo>&
node_ptr_opinfo_map,
std::unique_ptr<LoggingOpResolver> logging_op_resolver,
ErrorReporter* error_reporter)
: node_ptr_opinfo_map_(node_ptr_opinfo_map),
logging_op_resolver_(std::move(logging_op_resolver)),
error_reporter_(error_reporter) {
logger_ = std::make_unique<Logger>();
}
// Returns the wrapped kernel invoke function |TfLiteRegistration.invoke|.
KernelEvalFuncPtr GetKernelInvoke(const TfLiteNode* node) const;
// Gets the instance of logger associated with the current context.
Logger* GetLogger() const { return logger_.get(); }
// Gets the error reporter.
ErrorReporter* GetErrorReporter() const { return error_reporter_; }
// Gets the operator information about the given TfLiteNode.
const OperatorInfo& GetOpInfo(const TfLiteNode* node) const {
return node_ptr_opinfo_map_.at(node);
}
std::vector<const TfLiteNode*> GetNodesUnderCalibration() {
std::vector<const TfLiteNode*> nodes;
nodes.reserve(node_ptr_opinfo_map_.size());
for (const auto& entry : node_ptr_opinfo_map_) {
nodes.push_back(entry.first);
}
return nodes;
}
private:
std::unordered_map<const TfLiteNode*, OperatorInfo> node_ptr_opinfo_map_;
std::unique_ptr<LoggingOpResolver> logging_op_resolver_;
const std::unordered_map<int, OperatorInfo> index_opinfo_;
std::unique_ptr<Logger> logger_;
ErrorReporter* error_reporter_;
};
KernelEvalFuncPtr Calibrator::GetKernelInvoke(const TfLiteNode* node) const {
auto op_info = node_ptr_opinfo_map_.at(node);
if (op_info.is_custom_op) {
return logging_op_resolver_->GetWrappedKernelInvoke(op_info.name.c_str(),
op_info.version);
}
return logging_op_resolver_->GetWrappedKernelInvoke(op_info.builtin_op_code,
op_info.version);
}
// A registry of |Calibrator| objects per |TfLiteContext|.
// This global registry is needed to access |Calibrator| objects in the kernel
// invoke functions i.e. |TfLiteRegistration.invoke|.
// Kernel invoke functions are C functions that have limited access to
// |TfLiteContext|. Kernel invoke functions don't have access to global state of
// graph. That means during a kernel invocation, the function cannot know which
// node it was invoked for. E.g. in case of a model with |Conv| op at two
// locations, there is no easy way for the Conv.invoke function to disambiguate
// the calls.
//
// For calibration we solve this problem by creating a map of calibrators
// per |TfLiteContext|. This map is |GlobalCalibrationRegistry|.
//
// This registry is then accessed using a global getter function:
// |GetCalibratorRegistry|.
// E.g.
// TfLiteStatus SomeKernelInvokeFn(TfLiteContext* context, TfLiteNode* node) {
// .... code ....
// auto registry = GetCalibratorRegistry();
// auto calibrator = registry->GetCalibrator(context);
// ..... code ....
// }
//
// This way the kernel invoke functions can get the access to the Calibrator
// object associated with the |TfLiteContext|.
class GlobalCalibratorRegistry {
public:
// Get the |Calibrator| associated with given context, returns null if no
// calibrator is associated with the given context.
Calibrator* GetCalibrator(const TfLiteNode* node) const {
if (node_to_calibrator_.find(node) == node_to_calibrator_.cend()) {
return nullptr;
}
return node_to_calibrator_.at(node);
}
// Removes the association between calibrator and context.
// Note: This deletes the calibrator as well.
void RemoveCalibrator(const TfLiteContext* context) {
Calibrator* calibrator = calibrator_registry_.at(context).get();
auto nodes = calibrator->GetNodesUnderCalibration();
for (auto node : nodes) {
node_to_calibrator_.erase(node);
}
calibrator_registry_.erase(context);
}
// Creates an instance of |Calibrator|.
// Registry owns the |Calibrator| object which can be deleted by calling
// |RemoveCalibrator|.
TfLiteStatus CreateCalibrator(
const TfLiteContext* context,
const std::unordered_map<const TfLiteNode*, OperatorInfo>& node_to_opinfo,
std::unique_ptr<LoggingOpResolver> logging_op_resolver,
Calibrator** calibrator_ptr, ErrorReporter* reporter) {
if (calibrator_registry_.find(context) != calibrator_registry_.cend()) {
reporter->Report(
"Failed to create calibrator, context already registered.");
return kTfLiteError;
}
auto calibrator = std::make_unique<Calibrator>(
node_to_opinfo, std::move(logging_op_resolver), reporter);
calibrator_registry_[context] = std::move(calibrator);
*calibrator_ptr = calibrator_registry_.at(context).get();
for (const auto& entry : node_to_opinfo) {
node_to_calibrator_[entry.first] = *calibrator_ptr;
}
return kTfLiteOk;
}
private:
absl::flat_hash_map<const TfLiteContext*, std::unique_ptr<Calibrator>>
calibrator_registry_;
absl::flat_hash_map<const TfLiteNode*, Calibrator*> node_to_calibrator_;
};
GlobalCalibratorRegistry* GetCalibratorRegistry() {
static GlobalCalibratorRegistry* registry = new GlobalCalibratorRegistry();
return registry;
}
// Get the logging kernel if there are any.
// TODO(jianlijianli): extend this to support multiple recipe for the same
// model.
logging_kernel_func_ptr GetLoggingEvalFunc(TfLiteContext* context,
TfLiteNode* node,
int builtin_op_code) {
switch (builtin_op_code) {
case BuiltinOperator_LSTM: {
if (node->intermediates->size == 12) {
return tflite::optimize::calibration::custom::lstm_logging_kernel;
}
return tflite::optimize::calibration::builtin::lstm_logging_kernel;
}
case BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_LSTM:
return tflite::optimize::calibration::builtin::
unidirectional_sequence_lstm_logging_kernel;
default:
return nullptr;
}
}
// A wrapper implementation for |TfLiteRegistration.invoke| that logs inputs,
// invokes the wrapped implementation and then logs the outputs.
TfLiteStatus LoggingEval(TfLiteContext* context, TfLiteNode* node) {
Calibrator* calibrator = GetCalibratorRegistry()->GetCalibrator(node);
if (!calibrator) {
TF_LITE_KERNEL_LOG(context, "No calibrator found for context.");
return kTfLiteError;
}
auto kernel_invoke = calibrator->GetKernelInvoke(node);
auto logger = calibrator->GetLogger();
auto op_info = calibrator->GetOpInfo(node);
auto error_reporter = calibrator->GetErrorReporter();
for (int i : op_info.loggable_inputs) {
auto tensor = context->tensors[i];
TF_LITE_ENSURE_STATUS(
logger->LogTensorValue(op_info.subgraph_index, i, tensor.data.f,
tensor.bytes / sizeof(float), error_reporter));
}
auto builtin_op_code = calibrator->GetOpInfo(node).builtin_op_code;
auto kernel_invoke_intermediate =
GetLoggingEvalFunc(context, node, builtin_op_code);
if (kernel_invoke_intermediate == nullptr) {
TF_LITE_ENSURE_STATUS(kernel_invoke(context, node));
} else {
TF_LITE_ENSURE_STATUS(
kernel_invoke_intermediate(context, op_info.subgraph_index, node,
calibrator->GetLogger(), error_reporter));
}
// TODO(shashishekhar): An intermediate tensor in graph will get logged twice
// once as an input and second time as output. This doesn't change the min max
// values but is inefficient.
// Using moving average will also break this.
// Log input again to make sure the state tensors are captured after lstm
// cell.
for (int i : op_info.loggable_inputs) {
auto tensor = context->tensors[i];
TF_LITE_ENSURE_STATUS(
logger->LogTensorValue(op_info.subgraph_index, i, tensor.data.f,
tensor.bytes / sizeof(float), error_reporter));
}
for (int i : op_info.loggable_outputs) {
auto tensor = context->tensors[i];
TF_LITE_ENSURE_STATUS(
logger->LogTensorValue(op_info.subgraph_index, i, tensor.data.f,
tensor.bytes / sizeof(float), error_reporter));
}
return kTfLiteOk;
}
// Returns the loggable tensors. Not all inputs and outputs need to be logged.
// For example, const weight tensors which have buffers associated with them
// don't need to be logged.
std::vector<int> GetLoggableTensorIndices(
const std::vector<int>& tensor_indices,
const flatbuffers::Vector<flatbuffers::Offset<Tensor>>* tensors,
const flatbuffers::Vector<flatbuffers::Offset<Buffer>>* tensor_buffers) {
std::vector<int> loggable;
for (auto tensor_index : tensor_indices) {
if (tensor_index == kTfLiteOptionalTensor) {
continue;
}
auto tensor = tensors->Get(tensor_index);
auto buffer_index = tensor->buffer();
const bool has_no_buffer =
(tensor_buffers->Get(buffer_index) == nullptr) ||
(tensor_buffers->Get(buffer_index)->data() == nullptr) ||
(tensor_buffers->Get(buffer_index)->data()->size() == 0);
if (has_no_buffer && tensor->type() == tflite::TensorType_FLOAT32) {
loggable.push_back(tensor_index);
}
}
return loggable;
}
// Creates a mapping between the static model graph and the runtime TfLiteNode*
// nodes in the graph for the given context.
// This is done by querying the TfLiteContext for node and registrations using
// the |NodeInfoDelegateObserver|.
TfLiteStatus GetNodeOpInfoMapAndContext(
const absl::flat_hash_map<std::tuple<int, int>, OperatorInfo>&
node_to_opinfo,
tflite::Interpreter* const interpreter,
std::unordered_map<const TfLiteNode*, OperatorInfo>* node_ptr_opinfo_map,
TfLiteContext** context) {
*context = interpreter->primary_subgraph().context();
// Since we only consider the primary subgraph while populating
// node_to_opinfo, do the same here.
// Because Flex delegate can merge multiple op nodes into one Delegate node if
// they are located in a row, the size of the execution plan can be lesser
// than the size of the graph's op nodes.
TF_LITE_ENSURE(*context,
interpreter->execution_plan().size() <= node_to_opinfo.size());
for (const auto& entry : node_to_opinfo) {
auto op_info = entry.second;
int subgraph_index, op_index;
std::tie(subgraph_index, op_index) = entry.first;
const auto* node_and_reg =
interpreter->node_and_registration(subgraph_index, op_index);
op_info.registration = &node_and_reg->second;
node_ptr_opinfo_map->insert({&node_and_reg->first, op_info});
}
return kTfLiteOk;
}
string GetOpName(const tflite::OperatorCode& opcode) {
if (opcode.custom_code() != nullptr) {
return opcode.custom_code()->str();
}
return tflite::EnumNamesBuiltinOperator()[GetBuiltinCode(&opcode)];
}
// A |CalibrationReader| that owns the Calibrator.
class Reader : public CalibrationReader {
public:
Reader(const TfLiteContext* context, const Logger* logger)
: CalibrationReader(logger), context_(context) {}
~Reader() override { GetCalibratorRegistry()->RemoveCalibrator(context_); }
private:
const TfLiteContext* context_;
};
bool HasInputs(BuiltinOperator code) {
switch (code) {
case BuiltinOperator_CALL_ONCE:
case BuiltinOperator_VAR_HANDLE:
// Custom ops, including Flex ops, might not have inputs.
case BuiltinOperator_CUSTOM:
return false;
default:
return true;
}
}
bool HasOutputs(BuiltinOperator code) {
switch (code) {
case BuiltinOperator_ASSIGN_VARIABLE:
case BuiltinOperator_CALL_ONCE:
// Custom ops, including Flex ops, might not have outputs.
case BuiltinOperator_CUSTOM:
return false;
default:
return true;
}
}
} // namespace
TfLiteStatus BuildLoggingInterpreter(
const FlatBufferModel& model, const OpResolver& op_resolver,
std::unique_ptr<Interpreter>* interpreter,
std::unique_ptr<CalibrationReader>* calibration_reader) {
return BuildLoggingInterpreter(model.GetModel(), model.error_reporter(),
op_resolver, interpreter, calibration_reader,
model.allocation());
}
TfLiteStatus BuildLoggingInterpreter(
const tflite::Model* tflite_model, ErrorReporter* error_reporter,
const OpResolver& op_resolver, std::unique_ptr<Interpreter>* interpreter,
std::unique_ptr<CalibrationReader>* calibration_reader,
const Allocation* allocation) {
if (error_reporter == nullptr) {
// Make sure error_reporter is valid.
error_reporter = DefaultErrorReporter();
}
auto subgraphs = tflite_model->subgraphs();
auto tensor_buffers = tflite_model->buffers();
// Populate the node index to operator info map.
// We want to collect this information so we can use it during runtime to
// log details of which inputs and outputs.
// At runtime TFLite kernel invoke functions can only look into their
// own node in the graph (TFLiteNode*) and some limited context information.
absl::flat_hash_map<std::tuple<int, int>, OperatorInfo> node_to_opinfo;
BuiltinOpsSet builtin_op_and_versions;
CustomOpsSet custom_op_and_versions;
for (size_t subgraph_index = 0; subgraph_index < subgraphs->size();
subgraph_index++) {
auto subgraph = subgraphs->Get(subgraph_index);
auto operator_codes = tflite_model->operator_codes();
auto operators = subgraph->operators();
auto tensors = subgraph->tensors();
if (!operators) {
continue;
}
for (size_t i = 0; i < operators->size(); i++) {
OperatorInfo op_info;
op_info.subgraph_index = subgraph_index;
op_info.node_index = i;
auto op = operators->Get(i);
auto operator_code = operator_codes->Get(op->opcode_index());
op_info.builtin_op_code = GetBuiltinCode(operator_code);
op_info.name = GetOpName(*operator_code);
op_info.is_custom_op = operator_code->custom_code() != nullptr;
op_info.version = operator_code->version();
auto op_inputs = op->inputs();
auto op_outputs = op->outputs();
if (op_inputs) {
op_info.inputs = std::vector<int>(op_inputs->begin(), op_inputs->end());
} else if (HasInputs(op_info.builtin_op_code)) {
TFLITE_LOG(TFLITE_LOG_WARNING, "Op %s missing inputs",
op_info.name.c_str());
}
if (op_outputs) {
op_info.outputs =
std::vector<int>(op_outputs->begin(), op_outputs->end());
} else if (HasOutputs(op_info.builtin_op_code)) {
TFLITE_LOG(TFLITE_LOG_WARNING, "Op %s missing outputs",
op_info.name.c_str());
}
op_info.loggable_inputs =
GetLoggableTensorIndices(op_info.inputs, tensors, tensor_buffers);
op_info.loggable_outputs =
GetLoggableTensorIndices(op_info.outputs, tensors, tensor_buffers);
if (op_info.is_custom_op) {
op_info.registration =
op_resolver.FindOp(op_info.name.c_str(), operator_code->version());
custom_op_and_versions.insert(
{op_info.name.c_str(), operator_code->version()});
} else {
op_info.registration = op_resolver.FindOp(GetBuiltinCode(operator_code),
operator_code->version());
builtin_op_and_versions.insert(
{op_info.builtin_op_code, operator_code->version()});
}
std::tuple<int, int> key{subgraph_index, i};
node_to_opinfo[key] = op_info;
}
}
// Prepare the logging op resolver to use |LoggingEval| for kernel
// invocations.
auto logging_op_resolver = std::make_unique<LoggingOpResolver>(
builtin_op_and_versions, custom_op_and_versions, op_resolver, LoggingEval,
error_reporter);
tflite::InterpreterBuilder(tflite_model, *logging_op_resolver, error_reporter,
/*options_experimental=*/nullptr,
allocation)(interpreter);
if (!(*interpreter)) {
error_reporter->Report("Failed to construct interpreter");
return kTfLiteError;
}
// Compute the mapping between runtime and static graph structure, i.e.
// (TfLiteContext, TfLiteNode) -> OperatorInfo
std::unordered_map<const TfLiteNode*, OperatorInfo> node_ptr_opinfo_map;
TfLiteContext* context = nullptr;
TF_LITE_ENSURE_STATUS(GetNodeOpInfoMapAndContext(
node_to_opinfo, interpreter->get(), &node_ptr_opinfo_map, &context));
Calibrator* calibrator = nullptr;
// Register a calibrator object for the context. This can be accessed
// during invocations by the logging kernels.
TF_LITE_ENSURE_STATUS(GetCalibratorRegistry()->CreateCalibrator(
context, node_ptr_opinfo_map, std::move(logging_op_resolver), &calibrator,
error_reporter));
*calibration_reader = std::unique_ptr<CalibrationReader>(
new Reader(context, calibrator->GetLogger()));
return kTfLiteOk;
}
} // namespace calibration
} // namespace optimize
} // namespace tflite
@@ -0,0 +1,77 @@
/* Copyright 2018 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATOR_H_
#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATOR_H_
#include <memory>
#include "tensorflow/compiler/mlir/lite/allocation.h"
#include "tensorflow/lite/allocation.h"
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/api/op_resolver.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/core/model.h"
#include "tensorflow/lite/model_builder.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_reader.h"
namespace tflite {
namespace optimize {
namespace calibration {
// Warning: This is not a public API and subject to change.
// Builds a interpreter that logs the calibration data in memory.
// The calibration data can be recovered using |calibration_reader|.
//
// Sample usage:
// std::unique_ptr<Interpreter> interpreter;
// std::unique_ptr<CalibrationReader> calibration_reader;
// BuiltinOpResolver resolver = ...
// FlatBufferModel model = ..
//
// BuildLoggingInterpreter(model, resolver, &interpreter,
// &calibration_reader);
//
//
// * Allocate tensors...
// * Call interpreter->invoke on calibration dataset.
//
// Calibration data can be read either directly by calling
// std::unordered_map<int, CalibrationStats>> tensor_index_to_stats;
// calibration_reader->GetTensorStatsAsMap(&tensor_index_to_stats);
//
// or adding calibration data to model itself.
// ModelT * original_floating_point_model = ...
// calibration_reader->AddCalibrationToModel(original_floating_point_model,
// false);
//
TfLiteStatus BuildLoggingInterpreter(
const FlatBufferModel& model, const OpResolver& op_resolver,
std::unique_ptr<Interpreter>* interpreter,
std::unique_ptr<CalibrationReader>* calibration_reader);
// Same as above, except gets separate tflite::Model and ErrorReporter pointers.
TfLiteStatus BuildLoggingInterpreter(
const tflite::Model* model, ErrorReporter* error_reporter,
const OpResolver& op_resolver, std::unique_ptr<Interpreter>* interpreter,
std::unique_ptr<CalibrationReader>* calibration_reader,
const Allocation* allocation = nullptr);
} // namespace calibration
} // namespace optimize
} // namespace tflite
#endif // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CALIBRATOR_H_
@@ -0,0 +1,733 @@
/* Copyright 2018 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 "tensorflow/lite/tools/optimize/calibration/calibrator.h"
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "absl/container/flat_hash_map.h"
#include "tensorflow/core/platform/init_main.h"
#include "tensorflow/core/platform/path.h"
#include "tensorflow/core/platform/types.h"
#include "tensorflow/core/util/command_line_flags.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/model_builder.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/string_type.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_reader.h"
namespace {
std::string* g_test_model_dir = nullptr;
} // namespace
namespace tflite {
namespace optimize {
namespace calibration {
namespace {
std::unique_ptr<FlatBufferModel> ReadModel(const string& model_name) {
auto model_path = tensorflow::io::JoinPath(*g_test_model_dir, model_name);
return FlatBufferModel::BuildFromFile(model_path.c_str());
}
TEST(CalibratorTest, CalibrationStatsAreCollected) {
auto model = ReadModel("multi_add.bin");
ASSERT_TRUE(model);
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status = BuildLoggingInterpreter(
*model, ops::builtin::BuiltinOpResolver{}, &interpreter, &reader);
EXPECT_EQ(kTfLiteOk, status);
ASSERT_TRUE(interpreter);
ASSERT_TRUE(reader);
absl::flat_hash_map<std::tuple<int, int>, CalibrationReader::CalibrationStats>
stats;
status = reader->GetTensorStatsAsMap(&stats);
EXPECT_EQ(kTfLiteOk, status);
EXPECT_TRUE(stats.empty());
status = interpreter->AllocateTensors();
ASSERT_EQ(kTfLiteOk, status);
// Model does the following:
// 0 1 2 3
// | |__ ____| |
// | | |
// | Add(tensor:4) |
// |____ ______|______ ______|
// | |
// Add Add
// | |
// Output:5 Output:6
const size_t tensor_size = 1 * 8 * 8 * 3;
std::vector<float> ones(tensor_size, 1.0f);
// Fill input tensor i with i+1, i.e. input[0] = 1.0f, input[1] = 2.0f,
// input[2] = 3.0f
for (size_t i = 0; i < interpreter->inputs().size(); i++) {
int input_tensor_idx = interpreter->inputs()[i];
TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
ASSERT_EQ(tensor->bytes, tensor_size * sizeof(float));
for (size_t j = 0; j < tensor_size; j++) {
tensor->data.f[j] = i + 1;
}
}
status = interpreter->Invoke();
ASSERT_EQ(kTfLiteOk, status);
const float eps = 1e-6f;
// Verify that tensor 5: is 6
// Verify that tensor 6: is 9
TfLiteTensor* tensor = interpreter->tensor(interpreter->outputs()[0]);
for (size_t i = 0; i < tensor_size; i++) {
EXPECT_NEAR(tensor->data.f[i], 6.0f, eps);
}
tensor = interpreter->tensor(interpreter->outputs()[1]);
for (size_t i = 0; i < tensor_size; i++) {
EXPECT_NEAR(tensor->data.f[i], 9.0f, eps);
}
// Verify that min max of tensors.
status = reader->GetTensorStatsAsMap(&stats);
EXPECT_EQ(kTfLiteOk, status);
EXPECT_EQ(7, stats.size());
// Check inputs
for (int tensor_idx = 0; tensor_idx < 4; tensor_idx++) {
EXPECT_NEAR(stats.find({0, tensor_idx})->second.min, tensor_idx + 1, eps);
EXPECT_NEAR(stats.find({0, tensor_idx})->second.max, tensor_idx + 1, eps);
}
// Check tensor 4 max.
EXPECT_NEAR(stats.find({0, 4})->second.min, 5, eps);
EXPECT_NEAR(stats.find({0, 4})->second.max, 5, eps);
// Check outputs
EXPECT_NEAR(stats.find({0, 5})->second.min, 6, eps);
EXPECT_NEAR(stats.find({0, 5})->second.max, 6, eps);
EXPECT_NEAR(stats.find({0, 6})->second.min, 9, eps);
EXPECT_NEAR(stats.find({0, 6})->second.max, 9, eps);
}
TEST(CalibratorTest, MultipleInvokes) {
auto model = ReadModel("multi_add.bin");
ASSERT_TRUE(model);
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status = BuildLoggingInterpreter(
*model, ops::builtin::BuiltinOpResolver{}, &interpreter, &reader);
EXPECT_EQ(kTfLiteOk, status);
ASSERT_TRUE(interpreter);
ASSERT_TRUE(reader);
status = interpreter->AllocateTensors();
EXPECT_EQ(kTfLiteOk, status);
const size_t tensor_size = 1 * 8 * 8 * 3;
// Fill input tensor i with i+1, i.e. input[0] = 1.0f, input[1] = 2.0f,
// input[2] = 3.0f
for (size_t i = 0; i < interpreter->inputs().size(); i++) {
int input_tensor_idx = interpreter->inputs()[i];
TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
ASSERT_EQ(tensor->bytes, tensor_size * sizeof(float));
for (size_t j = 0; j < tensor_size; j++) {
tensor->data.f[j] = i + 1;
}
}
status = interpreter->Invoke();
ASSERT_EQ(kTfLiteOk, status);
const float eps = 1e-6f;
// Verify that min max of tensors.
absl::flat_hash_map<std::tuple<int, int>, CalibrationReader::CalibrationStats>
stats;
status = reader->GetTensorStatsAsMap(&stats);
EXPECT_EQ(kTfLiteOk, status);
EXPECT_EQ(7, stats.size());
const float expected_values[7] = {
1.0f, // input 0
2.0f, // input 1
3.0f, // input 2
4.0f, // input 3
5.0f, // Add(1, 2)
6.0f, // Output 5: Add(0, Add(1,2))
9.0f, // Output 6: Add(Add(1,2), 3)
};
for (int tensor_idx = 0; tensor_idx < 7; tensor_idx++) {
EXPECT_NEAR(stats.find({0, tensor_idx})->second.min,
expected_values[tensor_idx], eps);
EXPECT_NEAR(stats.find({0, tensor_idx})->second.max,
expected_values[tensor_idx], eps);
}
// Set input[0][0] = 1.5 and input[0][1] = 0.5 this should change the values
// only for input[0] and tensor 4 and outputs 5, 6.
TfLiteTensor* input0 = interpreter->tensor(0);
input0->data.f[0] = 1.5f;
input0->data.f[1] = 0.5f;
status = interpreter->Invoke();
ASSERT_EQ(kTfLiteOk, status);
status = reader->GetTensorStatsAsMap(&stats);
EXPECT_EQ(kTfLiteOk, status);
EXPECT_EQ(7, stats.size());
EXPECT_NEAR(stats.find({0, 0})->second.min, 0.5f, eps);
EXPECT_NEAR(stats.find({0, 0})->second.max, 1.5f, eps);
for (int tensor_idx = 1; tensor_idx < 5; tensor_idx++) {
EXPECT_NEAR(stats.find({0, tensor_idx})->second.min,
expected_values[tensor_idx], eps);
EXPECT_NEAR(stats.find({0, tensor_idx})->second.max,
expected_values[tensor_idx], eps);
}
EXPECT_NEAR(stats.find({0, 5})->second.min, 5.5f, eps);
EXPECT_NEAR(stats.find({0, 5})->second.max, 6.5f, eps);
EXPECT_NEAR(stats.find({0, 6})->second.min, 9.0f, eps);
EXPECT_NEAR(stats.find({0, 6})->second.max, 9.0f, eps);
}
TEST(CalibratorTest, UpdateMinMax) {
auto flatbuffer_model = ReadModel("multi_add.bin");
ASSERT_TRUE(flatbuffer_model);
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status = BuildLoggingInterpreter(*flatbuffer_model,
ops::builtin::BuiltinOpResolver{},
&interpreter, &reader);
EXPECT_EQ(kTfLiteOk, status);
auto readonly_model = flatbuffer_model->GetModel();
tflite::ModelT model;
readonly_model->UnPackTo(&model);
ASSERT_TRUE(interpreter);
ASSERT_TRUE(reader);
status = interpreter->AllocateTensors();
EXPECT_EQ(kTfLiteOk, status);
const size_t tensor_size = 1 * 8 * 8 * 3;
for (size_t i = 0; i < interpreter->inputs().size(); i++) {
int input_tensor_idx = interpreter->inputs()[i];
TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
ASSERT_EQ(tensor->bytes, tensor_size * sizeof(float));
for (size_t j = 0; j < tensor_size; j++) {
tensor->data.f[j] = i + 1;
}
}
auto input_0_quant_params =
std::make_unique<tflite::QuantizationParametersT>();
input_0_quant_params->min.push_back(0.5);
input_0_quant_params->max.push_back(1.5);
model.subgraphs[0]->tensors[0]->quantization =
std::move(input_0_quant_params);
// Invoke with update == true.
status = interpreter->Invoke();
ASSERT_EQ(kTfLiteOk, status);
const float eps = 1e-6f;
// Verify that min max of tensors.
const float expected_min[7] = {
0.5f, // input 0
2.0f, // input 1
3.0f, // input 2
4.0f, // input 3
5.0f, // Add(1, 2)
6.0f, // Output 5: Add(0, Add(1,2))
9.0f, // Output 6: Add(Add(1,2), 3)
};
const float expected_max[7] = {
1.5f, // input 0
2.0f, // input 1
3.0f, // input 2
4.0f, // input 3
5.0f, // Add(1, 2)
6.0f, // Output 5: Add(0, Add(1,2))
9.0f, // Output 6: Add(Add(1,2), 3)
};
status = reader->AddCalibrationToModel(&model, /*update=*/true);
for (int tensor_idx = 0; tensor_idx < 7; tensor_idx++) {
EXPECT_NEAR(model.subgraphs[0]->tensors[tensor_idx]->quantization->min[0],
expected_min[tensor_idx], eps);
EXPECT_NEAR(model.subgraphs[0]->tensors[tensor_idx]->quantization->max[0],
expected_max[tensor_idx], eps);
}
// Invoke with update == false;
// Verify that min max of tensors.
const float expected_value[7] = {
1.0f, // input 0
2.0f, // input 1
3.0f, // input 2
4.0f, // input 3
5.0f, // Add(1, 2)
6.0f, // Output 5: Add(0, Add(1,2))
9.0f, // Output 6: Add(Add(1,2), 3)
};
status = reader->AddCalibrationToModel(&model, /*update=*/false);
for (int tensor_idx = 0; tensor_idx < 7; tensor_idx++) {
EXPECT_NEAR(model.subgraphs[0]->tensors[tensor_idx]->quantization->min[0],
expected_value[tensor_idx], eps);
EXPECT_NEAR(model.subgraphs[0]->tensors[tensor_idx]->quantization->max[0],
expected_value[tensor_idx], eps);
}
}
TEST(CalibratorTest, HandleNanValues) {
auto flatbuffer_model = ReadModel("multi_add.bin");
ASSERT_TRUE(flatbuffer_model);
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status = BuildLoggingInterpreter(*flatbuffer_model,
ops::builtin::BuiltinOpResolver{},
&interpreter, &reader);
EXPECT_EQ(kTfLiteOk, status);
auto readonly_model = flatbuffer_model->GetModel();
tflite::ModelT model;
readonly_model->UnPackTo(&model);
ASSERT_TRUE(interpreter);
ASSERT_TRUE(reader);
status = interpreter->AllocateTensors();
EXPECT_EQ(kTfLiteOk, status);
const size_t tensor_size = 1 * 8 * 8 * 3;
for (size_t i = 0; i < interpreter->inputs().size(); i++) {
int input_tensor_idx = interpreter->inputs()[i];
TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
ASSERT_EQ(tensor->bytes, tensor_size * sizeof(float));
for (size_t j = 0; j < tensor_size; j++) {
if (j % 2 == 0) {
tensor->data.f[j] = NAN;
} else {
tensor->data.f[j] = i + 1;
}
}
}
auto input_0_quant_params =
std::make_unique<tflite::QuantizationParametersT>();
input_0_quant_params->min.push_back(0.5);
input_0_quant_params->max.push_back(1.5);
model.subgraphs[0]->tensors[0]->quantization =
std::move(input_0_quant_params);
// Invoke with update == true.
status = interpreter->Invoke();
ASSERT_EQ(kTfLiteOk, status);
const float eps = 1e-6f;
// Verify that min max of tensors.
const float expected_min[7] = {
0.5f, // input 0
2.0f, // input 1
3.0f, // input 2
4.0f, // input 3
5.0f, // Add(1, 2)
6.0f, // Output 5: Add(0, Add(1,2))
9.0f, // Output 6: Add(Add(1,2), 3)
};
const float expected_max[7] = {
1.5f, // input 0
2.0f, // input 1
3.0f, // input 2
4.0f, // input 3
5.0f, // Add(1, 2)
6.0f, // Output 5: Add(0, Add(1,2))
9.0f, // Output 6: Add(Add(1,2), 3)
};
status = reader->AddCalibrationToModel(&model, /*update=*/true);
for (int tensor_idx = 0; tensor_idx < 7; tensor_idx++) {
EXPECT_NEAR(model.subgraphs[0]->tensors[tensor_idx]->quantization->min[0],
expected_min[tensor_idx], eps);
EXPECT_NEAR(model.subgraphs[0]->tensors[tensor_idx]->quantization->max[0],
expected_max[tensor_idx], eps);
}
// Invoke with update == false;
// Verify that min max of tensors.
const float expected_value[7] = {
1.0f, // input 0
2.0f, // input 1
3.0f, // input 2
4.0f, // input 3
5.0f, // Add(1, 2)
6.0f, // Output 5: Add(0, Add(1,2))
9.0f, // Output 6: Add(Add(1,2), 3)
};
status = reader->AddCalibrationToModel(&model, /*update=*/false);
for (int tensor_idx = 0; tensor_idx < 7; tensor_idx++) {
EXPECT_NEAR(model.subgraphs[0]->tensors[tensor_idx]->quantization->min[0],
expected_value[tensor_idx], eps);
EXPECT_NEAR(model.subgraphs[0]->tensors[tensor_idx]->quantization->max[0],
expected_value[tensor_idx], eps);
}
}
TEST(CalibratorTest, LSTM) {
auto flatbuffer_model = ReadModel("lstm.bin");
ASSERT_TRUE(flatbuffer_model);
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status = BuildLoggingInterpreter(*flatbuffer_model,
ops::builtin::BuiltinOpResolver{},
&interpreter, &reader);
EXPECT_EQ(status, kTfLiteOk);
auto readonly_model = flatbuffer_model->GetModel();
tflite::ModelT model;
readonly_model->UnPackTo(&model);
ASSERT_TRUE(interpreter);
ASSERT_TRUE(reader);
status = interpreter->AllocateTensors();
EXPECT_EQ(kTfLiteOk, status);
const std::vector<float> lstm_input = {0.3, 0.2};
int input_tensor_idx = interpreter->inputs()[0];
TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
for (size_t j = 0; j < lstm_input.size(); j++) {
tensor->data.f[j] = lstm_input[j];
}
ASSERT_EQ(interpreter->Invoke(), kTfLiteOk);
absl::flat_hash_map<std::tuple<int, int>, CalibrationReader::CalibrationStats>
stats;
EXPECT_EQ(reader->GetTensorStatsAsMap(&stats), kTfLiteOk);
// Check the results.
const float eps = 1e-6f;
const absl::flat_hash_map<std::tuple<int, int>,
CalibrationReader::CalibrationStats>
expected_calibration_result = {
// Input.
{{0, 0}, {0.200000, 0.300000}},
// State.
{{0, 18}, {0.000000, 0.468415}},
// State.
{{0, 19}, {0.000000, 0.424350}},
// Output.
{{0, 24}, {0.265968, 0.468415}},
// Intemediate_0.
{{0, 25}, {0.080045, 0.170588}},
// Intemediate_1.
{{0, 26}, {0.080045, 0.170588}},
// Intemediate_2.
{{0, 27}, {0.080045, 0.170588}},
// Intemediate_3.
{{0, 28}, {0.080045, 0.170588}},
// Intemediate_4.
{{0, 29}, {0.000000, 0.270944}},
};
EXPECT_EQ(expected_calibration_result.size(), stats.size());
for (const auto& e : stats) {
auto expected_result = expected_calibration_result.find(e.first)->second;
EXPECT_NEAR(e.second.min, expected_result.min, eps);
EXPECT_NEAR(e.second.max, expected_result.max, eps);
}
}
TEST(CalibratorTest, UnidirectionalSequenceLSTM) {
auto flatbuffer_model = ReadModel("unidirectional_sequence_lstm.bin");
ASSERT_TRUE(flatbuffer_model);
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status = BuildLoggingInterpreter(*flatbuffer_model,
ops::builtin::BuiltinOpResolver{},
&interpreter, &reader);
EXPECT_EQ(kTfLiteOk, status);
auto readonly_model = flatbuffer_model->GetModel();
tflite::ModelT model;
readonly_model->UnPackTo(&model);
ASSERT_TRUE(interpreter);
ASSERT_TRUE(reader);
EXPECT_EQ(interpreter->AllocateTensors(), kTfLiteOk);
const std::vector<float> lstm_input = {0.3, 0.2, 0.9, 0.8};
int input_tensor_idx = interpreter->inputs()[0];
TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
for (size_t j = 0; j < lstm_input.size(); j++) {
tensor->data.f[j] = lstm_input[j];
}
ASSERT_EQ(interpreter->Invoke(), kTfLiteOk);
absl::flat_hash_map<std::tuple<int, int>, CalibrationReader::CalibrationStats>
stats;
EXPECT_EQ(reader->GetTensorStatsAsMap(&stats), kTfLiteOk);
// Check the results.
const float eps = 1e-6f;
const absl::flat_hash_map<std::tuple<int, int>,
CalibrationReader::CalibrationStats>
expected_calibration_result = {
// Input.
{{0, 0}, {0.200000, 0.900000}},
// State.
{{0, 18}, {0.000000, 0.520999}},
// State.
{{0, 19}, {0.000000, 0.711364}},
// Output.
{{0, 24}, {0.247992, 0.520999}},
// Intemediate_0.
{{0, 25}, {0.080045, 0.824241}},
// Intemediate_1.
{{0, 26}, {0.080045, 0.824241}},
// Intemediate_2.
{{0, 27}, {0.080045, 0.824241}},
// Intemediate_3.
{{0, 28}, {0.080045, 0.824241}},
// Intemediate_4.
{{0, 29}, {0.000000, 0.413618}},
};
EXPECT_EQ(expected_calibration_result.size(), stats.size());
for (const auto& e : stats) {
auto expected_result = expected_calibration_result.find(e.first)->second;
EXPECT_NEAR(e.second.min, expected_result.min, eps);
EXPECT_NEAR(e.second.max, expected_result.max, eps);
}
}
TEST(CalibratorTest, CustomLSTM) {
auto flatbuffer_model = ReadModel("custom_lstm.bin");
ASSERT_TRUE(flatbuffer_model);
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status = BuildLoggingInterpreter(*flatbuffer_model,
ops::builtin::BuiltinOpResolver{},
&interpreter, &reader);
EXPECT_EQ(kTfLiteOk, status);
auto readonly_model = flatbuffer_model->GetModel();
tflite::ModelT model;
readonly_model->UnPackTo(&model);
ASSERT_TRUE(interpreter);
ASSERT_TRUE(reader);
EXPECT_EQ(interpreter->AllocateTensors(), kTfLiteOk);
const std::vector<float> lstm_input = {0.3, 0.2, 0.9, 0.8};
int input_tensor_idx = interpreter->inputs()[0];
TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
for (size_t j = 0; j < lstm_input.size(); j++) {
tensor->data.f[j] = lstm_input[j];
}
ASSERT_EQ(interpreter->Invoke(), kTfLiteOk);
absl::flat_hash_map<std::tuple<int, int>, CalibrationReader::CalibrationStats>
stats;
EXPECT_EQ(reader->GetTensorStatsAsMap(&stats), kTfLiteOk);
// Check the results.
const float eps = 1e-6f;
const absl::flat_hash_map<std::tuple<int, int>,
CalibrationReader::CalibrationStats>
expected_calibration_result = {
// input.
{{0, 0}, {0.200000, 0.300000}},
// state.
{{0, 18}, {0.000000, 0.468415}},
// state.
{{0, 19}, {0.000000, 0.424349}},
// output.
{{0, 24}, {0.265968, 0.468415}},
// intermediate 0.
{{0, 25}, {0.080045, 0.170588}},
// intermediate 1.
{{0, 26}, {0.080045, 0.170588}},
// intermediate 2.
{{0, 27}, {0.000000, 0.000000}},
// intermediate 3.
{{0, 28}, {0.080045, 0.170588}},
// intermediate 4.
{{0, 29}, {0.080045, 0.170588}},
// intermediate 5.
{{0, 30}, {0.000000, 0.000000}},
// intermediate 6.
{{0, 31}, {0.080045, 0.170588}},
// intermediate 7.
{{0, 32}, {0.080045, 0.170588}},
// intermediate 8.
{{0, 33}, {0.000000, 0.000000}},
// intermediate 9.
{{0, 34}, {0.080045, 0.170588}},
// intermediate 10.
{{0, 35}, {0.080045, 0.170588}},
// intermediate 11.
{{0, 36}, {0.000000, 0.000000}},
};
EXPECT_EQ(expected_calibration_result.size(), stats.size());
for (const auto& e : stats) {
auto expected_result = expected_calibration_result.find(e.first)->second;
EXPECT_NEAR(e.second.min, expected_result.min, eps);
EXPECT_NEAR(e.second.max, expected_result.max, eps);
}
}
TEST(CalibratorTest, CalibrationWithMultipleSubgraphs) {
auto model = ReadModel("multi_subgraphs_while.bin");
ASSERT_TRUE(model);
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status = BuildLoggingInterpreter(
*model, ops::builtin::BuiltinOpResolver{}, &interpreter, &reader);
EXPECT_EQ(kTfLiteOk, status);
ASSERT_TRUE(interpreter);
ASSERT_TRUE(reader);
absl::flat_hash_map<std::tuple<int, int>, CalibrationReader::CalibrationStats>
stats;
status = reader->GetTensorStatsAsMap(&stats);
EXPECT_EQ(kTfLiteOk, status);
EXPECT_TRUE(stats.empty());
status = interpreter->AllocateTensors();
ASSERT_EQ(kTfLiteOk, status);
// The model accepts a float input, and multiply this by 2 for two times with
// while loop.
// Model does the following:
// serving_default_inp:0
// |
// While(cond: subgraph/1, body: subgraph/2)
// / \
// PartitionedCall:0 PartitionedCall:1
// loop variable input multiplier
const size_t tensor_size = 1;
for (size_t i = 0; i < interpreter->inputs().size(); i++) {
int input_tensor_idx = interpreter->inputs()[i];
TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
ASSERT_EQ(tensor->bytes, tensor_size * sizeof(int));
for (size_t j = 0; j < tensor_size; j++) {
tensor->data.f[j] = i + 1;
}
}
status = interpreter->Invoke();
ASSERT_EQ(kTfLiteOk, status);
// Verify that min max of tensors.
status = reader->GetTensorStatsAsMap(&stats);
EXPECT_EQ(kTfLiteOk, status);
EXPECT_EQ(4, stats.size());
// Check the results.
const float eps = 1e-6f;
const absl::flat_hash_map<std::tuple<int, int>,
CalibrationReader::CalibrationStats>
expected_calibration_result = {
// input.
{{0, 0}, {1.0, 1.0}},
// output.
{{0, 4}, {4.0, 4.0}},
// loop_multiply_input.
{{2, 2}, {1.0, 2.0}},
// loop_multiply_output.
{{2, 6}, {2.0, 4.0}},
};
EXPECT_EQ(expected_calibration_result.size(), stats.size());
for (const auto& e : stats) {
auto expected_result = expected_calibration_result.find(e.first)->second;
EXPECT_NEAR(e.second.min, expected_result.min, eps);
EXPECT_NEAR(e.second.max, expected_result.max, eps);
}
}
TEST(CalibratorTest, CalibrationWithCallOnce) {
auto model = ReadModel("call_once_mul.bin");
ASSERT_TRUE(model);
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status = BuildLoggingInterpreter(
*model, ops::builtin::BuiltinOpResolver{}, &interpreter, &reader);
EXPECT_EQ(kTfLiteOk, status);
ASSERT_TRUE(interpreter);
ASSERT_TRUE(reader);
absl::flat_hash_map<std::tuple<int, int>, CalibrationReader::CalibrationStats>
stats;
status = reader->GetTensorStatsAsMap(&stats);
EXPECT_EQ(kTfLiteOk, status);
EXPECT_TRUE(stats.empty());
status = interpreter->AllocateTensors();
ASSERT_EQ(kTfLiteOk, status);
// Model does the following:
// serving_default_inp:0 -> CallOnce() -> ReadVariableOp() -> Mul()
// | |
// AssignVariableOp() AssignVariableOp
const size_t tensor_size = 1;
for (size_t i = 0; i < interpreter->inputs().size(); i++) {
int input_tensor_idx = interpreter->inputs()[i];
TfLiteTensor* tensor = interpreter->tensor(input_tensor_idx);
ASSERT_EQ(tensor->bytes, tensor_size * sizeof(int));
for (size_t j = 0; j < tensor_size; j++) {
tensor->data.f[j] = i + 1;
}
}
status = interpreter->Invoke();
ASSERT_EQ(kTfLiteOk, status);
// Verify that min max of tensors.
status = reader->GetTensorStatsAsMap(&stats);
EXPECT_EQ(kTfLiteOk, status);
EXPECT_EQ(3, stats.size());
// Check the results.
const float eps = 1e-6f;
const absl::flat_hash_map<std::tuple<int, int>,
CalibrationReader::CalibrationStats>
expected_calibration_result = {// input.
{{0, 0}, {1.0, 1.0}},
// readvariableop.
{{0, 2}, {2.0, 2.0}},
// mul output.
{{0, 3}, {2.0, 2.0}}};
EXPECT_EQ(expected_calibration_result.size(), stats.size());
for (const auto& e : stats) {
auto expected_result = expected_calibration_result.find(e.first)->second;
EXPECT_NEAR(e.second.min, expected_result.min, eps);
EXPECT_NEAR(e.second.max, expected_result.max, eps);
}
}
} // namespace
} // namespace calibration
} // namespace optimize
} // namespace tflite
int main(int argc, char** argv) {
std::string model_file;
const std::vector<tensorflow::Flag> flag_list = {
tensorflow::Flag("test_model_file", &model_file,
"Path to test tflite model file."),
};
const bool parse_result = tensorflow::Flags::Parse(&argc, argv, flag_list);
if (!parse_result) {
std::cerr << "Required test_model_file\n";
std::abort();
}
g_test_model_dir = new std::string(tensorflow::io::Dirname(model_file));
::tensorflow::port::InitMain(argv[0], &argc, &argv);
return RUN_ALL_TESTS();
}
@@ -0,0 +1,662 @@
/* Copyright 2018 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 "tensorflow/lite/tools/optimize/calibration/custom_logging_ops/lstm.h"
#include <algorithm>
#include <cstdio>
#include <vector>
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/kernels/internal/portable_tensor_utils.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/internal/tensor_utils.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/kernels/lstm_shared.h"
#include "tensorflow/lite/kernels/op_macros.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_logger.h"
namespace tflite {
namespace optimize {
namespace calibration {
namespace custom {
namespace {
inline void LstmStepWithAuxInput(
const float* input_ptr, const float* input_to_input_weights_ptr,
const float* input_to_forget_weights_ptr,
const float* input_to_cell_weights_ptr,
const float* input_to_output_weights_ptr, const float* aux_input_ptr,
const float* aux_input_to_input_weights_ptr,
const float* aux_input_to_forget_weights_ptr,
const float* aux_input_to_cell_weights_ptr,
const float* aux_input_to_output_weights_ptr,
const float* recurrent_to_input_weights_ptr,
const float* recurrent_to_forget_weights_ptr,
const float* recurrent_to_cell_weights_ptr,
const float* recurrent_to_output_weights_ptr,
const float* cell_to_input_weights_ptr,
const float* cell_to_forget_weights_ptr,
const float* cell_to_output_weights_ptr,
const float* input_layer_norm_coefficients_ptr,
const float* forget_layer_norm_coefficients_ptr,
const float* cell_layer_norm_coefficients_ptr,
const float* output_layer_norm_coefficients_ptr,
const float* input_gate_bias_ptr, const float* forget_gate_bias_ptr,
const float* cell_bias_ptr, const float* output_gate_bias_ptr,
const float* projection_weights_ptr, const float* projection_bias_ptr,
const TfLiteLSTMParams* params, int n_batch, int n_cell, int n_input,
int n_aux_input, int n_output, int output_batch_leading_dim,
float* output_state_ptr, float* cell_state_ptr, float* input_gate_scratch,
float* forget_gate_scratch, float* cell_scratch, float* output_gate_scratch,
float* output_ptr, Logger* logger,
const std::vector<int>& intemediate_tensor_indexes,
const int subgraph_index, ErrorReporter* error_reporter) {
// Since we have already checked that weights are all there or none, we can
// check the existence of only one to the get the condition.
const bool use_cifg = (input_to_input_weights_ptr == nullptr);
const bool use_peephole = (cell_to_output_weights_ptr != nullptr);
const bool use_layer_norm = (forget_layer_norm_coefficients_ptr != nullptr);
// Initialize scratch buffers with bias for regular lstm or initialize with
// zero for layer norm lstm.
if (use_layer_norm) {
if (!use_cifg) {
std::fill_n(input_gate_scratch, n_cell * n_batch, 0.0f);
}
std::fill_n(forget_gate_scratch, n_cell * n_batch, 0.0f);
std::fill_n(cell_scratch, n_cell * n_batch, 0.0f);
std::fill_n(output_gate_scratch, n_cell * n_batch, 0.0f);
} else {
if (!use_cifg) {
tensor_utils::VectorBatchVectorAssign(input_gate_bias_ptr, n_cell,
n_batch, input_gate_scratch);
}
tensor_utils::VectorBatchVectorAssign(forget_gate_bias_ptr, n_cell, n_batch,
forget_gate_scratch);
tensor_utils::VectorBatchVectorAssign(cell_bias_ptr, n_cell, n_batch,
cell_scratch);
tensor_utils::VectorBatchVectorAssign(output_gate_bias_ptr, n_cell, n_batch,
output_gate_scratch);
}
// For each batch and cell: compute input_weight * input.
if (!use_cifg) {
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
input_to_input_weights_ptr, n_cell, n_input, input_ptr, n_batch,
input_gate_scratch);
}
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
input_to_forget_weights_ptr, n_cell, n_input, input_ptr, n_batch,
forget_gate_scratch);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(input_to_cell_weights_ptr,
n_cell, n_input, input_ptr,
n_batch, cell_scratch);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
input_to_output_weights_ptr, n_cell, n_input, input_ptr, n_batch,
output_gate_scratch);
{
// calibration.
if (!use_cifg) {
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[1],
input_gate_scratch, n_cell * n_batch,
error_reporter);
}
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[4],
forget_gate_scratch, n_cell * n_batch,
error_reporter);
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[7],
cell_scratch, n_cell * n_batch, error_reporter);
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[10],
output_gate_scratch, n_cell * n_batch,
error_reporter);
}
// If auxiliary input is available then compute aux_input_weight * aux_input
if (aux_input_ptr != nullptr) {
if (!use_cifg) {
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
aux_input_to_input_weights_ptr, n_cell, n_aux_input, aux_input_ptr,
n_batch, input_gate_scratch);
}
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
aux_input_to_forget_weights_ptr, n_cell, n_aux_input, aux_input_ptr,
n_batch, forget_gate_scratch);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
aux_input_to_cell_weights_ptr, n_cell, n_aux_input, aux_input_ptr,
n_batch, cell_scratch);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
aux_input_to_output_weights_ptr, n_cell, n_aux_input, aux_input_ptr,
n_batch, output_gate_scratch);
}
// For each batch and cell: compute recurrent_weight * output_state.
if (!use_cifg) {
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
recurrent_to_input_weights_ptr, n_cell, n_output, output_state_ptr,
n_batch, input_gate_scratch);
}
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
recurrent_to_forget_weights_ptr, n_cell, n_output, output_state_ptr,
n_batch, forget_gate_scratch);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
recurrent_to_cell_weights_ptr, n_cell, n_output, output_state_ptr,
n_batch, cell_scratch);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
recurrent_to_output_weights_ptr, n_cell, n_output, output_state_ptr,
n_batch, output_gate_scratch);
{
// calibrition.
if (!use_cifg) {
std::vector<float> temp_input(n_batch * n_cell);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
recurrent_to_input_weights_ptr, n_cell, n_output, output_state_ptr,
n_batch, temp_input.data());
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[2],
temp_input.data(), n_cell * n_batch,
error_reporter);
}
std::vector<float> temp_forget(n_batch * n_cell);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
recurrent_to_forget_weights_ptr, n_cell, n_output, output_state_ptr,
n_batch, temp_forget.data());
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[5],
temp_forget.data(), n_cell * n_batch,
error_reporter);
std::vector<float> temp_cell(n_batch * n_cell);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
recurrent_to_cell_weights_ptr, n_cell, n_output, output_state_ptr,
n_batch, temp_cell.data());
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[8],
temp_cell.data(), n_cell * n_batch, error_reporter);
std::vector<float> temp_output(n_batch * n_cell);
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
recurrent_to_output_weights_ptr, n_cell, n_output, output_state_ptr,
n_batch, temp_output.data());
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[11],
temp_output.data(), n_cell * n_batch,
error_reporter);
}
// For each batch and cell: update input gate.
if (!use_cifg) {
if (use_peephole) {
tensor_utils::VectorBatchVectorCwiseProductAccumulate(
cell_to_input_weights_ptr, n_cell, cell_state_ptr, n_batch,
input_gate_scratch);
}
if (use_layer_norm) {
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[0],
input_gate_scratch, n_cell * n_batch,
error_reporter);
tensor_utils::MeanStddevNormalization(
input_gate_scratch, input_gate_scratch, n_cell, n_batch);
tensor_utils::VectorBatchVectorCwiseProduct(
input_layer_norm_coefficients_ptr, n_cell, input_gate_scratch,
n_batch, input_gate_scratch);
tensor_utils::VectorBatchVectorAdd(input_gate_bias_ptr, n_cell, n_batch,
input_gate_scratch);
}
tensor_utils::ApplySigmoidToVector(input_gate_scratch, n_cell * n_batch,
input_gate_scratch);
}
// For each batch and cell: update forget gate.
if (use_peephole) {
tensor_utils::VectorBatchVectorCwiseProductAccumulate(
cell_to_forget_weights_ptr, n_cell, cell_state_ptr, n_batch,
forget_gate_scratch);
}
if (use_layer_norm) {
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[3],
forget_gate_scratch, n_cell * n_batch,
error_reporter);
tensor_utils::MeanStddevNormalization(forget_gate_scratch,
forget_gate_scratch, n_cell, n_batch);
tensor_utils::VectorBatchVectorCwiseProduct(
forget_layer_norm_coefficients_ptr, n_cell, forget_gate_scratch,
n_batch, forget_gate_scratch);
tensor_utils::VectorBatchVectorAdd(forget_gate_bias_ptr, n_cell, n_batch,
forget_gate_scratch);
}
tensor_utils::ApplySigmoidToVector(forget_gate_scratch, n_cell * n_batch,
forget_gate_scratch);
// For each batch and cell: update the cell.
tensor_utils::VectorVectorCwiseProduct(forget_gate_scratch, cell_state_ptr,
n_batch * n_cell, cell_state_ptr);
if (use_layer_norm) {
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[6],
cell_scratch, n_cell * n_batch, error_reporter);
tensor_utils::MeanStddevNormalization(cell_scratch, cell_scratch, n_cell,
n_batch);
tensor_utils::VectorBatchVectorCwiseProduct(
cell_layer_norm_coefficients_ptr, n_cell, cell_scratch, n_batch,
cell_scratch);
tensor_utils::VectorBatchVectorAdd(cell_bias_ptr, n_cell, n_batch,
cell_scratch);
}
tensor_utils::ApplyActivationToVector(cell_scratch, n_batch * n_cell,
params->activation, cell_scratch);
if (use_cifg) {
tensor_utils::Sub1Vector(forget_gate_scratch, n_batch * n_cell,
forget_gate_scratch);
tensor_utils::VectorVectorCwiseProductAccumulate(
cell_scratch, forget_gate_scratch, n_batch * n_cell, cell_state_ptr);
} else {
tensor_utils::VectorVectorCwiseProductAccumulate(
cell_scratch, input_gate_scratch, n_batch * n_cell, cell_state_ptr);
}
if (params->cell_clip > 0.0) {
tensor_utils::CwiseClipping(cell_state_ptr, n_batch * n_cell,
params->cell_clip);
}
// For each batch and cell: update the output gate.
if (use_peephole) {
tensor_utils::VectorBatchVectorCwiseProductAccumulate(
cell_to_output_weights_ptr, n_cell, cell_state_ptr, n_batch,
output_gate_scratch);
}
if (use_layer_norm) {
logger->LogTensorValue(subgraph_index, intemediate_tensor_indexes[9],
output_gate_scratch, n_cell * n_batch,
error_reporter);
tensor_utils::MeanStddevNormalization(output_gate_scratch,
output_gate_scratch, n_cell, n_batch);
tensor_utils::VectorBatchVectorCwiseProduct(
output_layer_norm_coefficients_ptr, n_cell, output_gate_scratch,
n_batch, output_gate_scratch);
tensor_utils::VectorBatchVectorAdd(output_gate_bias_ptr, n_cell, n_batch,
output_gate_scratch);
}
tensor_utils::ApplySigmoidToVector(output_gate_scratch, n_batch * n_cell,
output_gate_scratch);
tensor_utils::ApplyActivationToVector(cell_state_ptr, n_batch * n_cell,
params->activation, cell_scratch);
tensor_utils::VectorVectorCwiseProduct(output_gate_scratch, cell_scratch,
n_batch * n_cell, output_gate_scratch);
const bool use_projection_weight = (projection_weights_ptr != nullptr);
const bool use_projection_bias = (projection_bias_ptr != nullptr);
// For each batch: update the projection and output_state. Note that since
// the output batch rows may not be contiguous (output_batch_leading_dim !=
// n_output), we unroll batched operations.
if (use_projection_weight) {
if (use_projection_bias) {
for (int k = 0; k < n_batch; k++) {
std::copy_n(projection_bias_ptr, n_output,
output_ptr + k * output_batch_leading_dim);
}
} else {
for (int k = 0; k < n_batch; k++) {
std::fill_n(output_ptr + k * output_batch_leading_dim, n_output, 0.0f);
}
}
for (int k = 0; k < n_batch; k++) {
tensor_utils::MatrixBatchVectorMultiplyAccumulate(
projection_weights_ptr, n_output, n_cell,
output_gate_scratch + k * n_cell,
/*n_batch=*/1, output_ptr + k * output_batch_leading_dim);
if (params->proj_clip > 0.0) {
tensor_utils::CwiseClipping(output_ptr + k * output_batch_leading_dim,
n_output, params->proj_clip);
}
}
} else {
for (int k = 0; k < n_batch; k++) {
std::copy_n(output_gate_scratch + k * n_output, n_output,
output_ptr + k * output_batch_leading_dim);
}
}
for (int k = 0; k < n_batch; k++) {
std::copy_n(output_ptr + k * output_batch_leading_dim, n_output,
output_state_ptr + k * n_output);
}
}
TfLiteStatus EvalFloat(
const TfLiteTensor* input, const TfLiteTensor* input_to_input_weights,
const TfLiteTensor* input_to_forget_weights,
const TfLiteTensor* input_to_cell_weights,
const TfLiteTensor* input_to_output_weights,
const TfLiteTensor* recurrent_to_input_weights,
const TfLiteTensor* recurrent_to_forget_weights,
const TfLiteTensor* recurrent_to_cell_weights,
const TfLiteTensor* recurrent_to_output_weights,
const TfLiteTensor* cell_to_input_weights,
const TfLiteTensor* cell_to_forget_weights,
const TfLiteTensor* cell_to_output_weights,
const TfLiteTensor* input_layer_norm_coefficients,
const TfLiteTensor* forget_layer_norm_coefficients,
const TfLiteTensor* cell_layer_norm_coefficients,
const TfLiteTensor* output_layer_norm_coefficients,
const TfLiteTensor* aux_input,
const TfLiteTensor* aux_input_to_input_weights,
const TfLiteTensor* aux_input_to_forget_weights,
const TfLiteTensor* aux_input_to_cell_weights,
const TfLiteTensor* aux_input_to_output_weights,
const TfLiteTensor* input_gate_bias, const TfLiteTensor* forget_gate_bias,
const TfLiteTensor* cell_bias, const TfLiteTensor* output_gate_bias,
const TfLiteTensor* projection_weights, const TfLiteTensor* projection_bias,
const TfLiteLSTMParams* params, bool forward_sequence, bool time_major,
int output_offset, TfLiteTensor* scratch_buffer,
TfLiteTensor* activation_state, TfLiteTensor* cell_state,
TfLiteTensor* output, Logger* logger,
const std::vector<int>& intemediate_tensor_indexes,
const int subgraph_index, ErrorReporter* error_reporter) {
TF_LITE_ASSERT(input->dims->size >= 2 && input->dims->size <= 3);
int max_time, n_batch;
if (input->dims->size == 3) {
max_time = (time_major) ? input->dims->data[0] : input->dims->data[1];
n_batch = (time_major) ? input->dims->data[1] : input->dims->data[0];
} else {
max_time = 1;
n_batch = input->dims->data[0];
}
const int n_input = input->dims->data[input->dims->size - 1];
const int aux_input_size =
(aux_input) ? aux_input->dims->data[aux_input->dims->size - 1] : 0;
// n_cell and n_output will be the same size when there is no projection.
const int n_cell = input_to_output_weights->dims->data[0];
const int n_output = recurrent_to_output_weights->dims->data[1];
// Since we have already checked that weights are all there or none, we can
// check the existence of only one to the get the condition.
const bool use_cifg = (input_to_input_weights == nullptr);
// Index the scratch buffers pointers to the global scratch buffer.
float* scratch_buffer_ptr = GetTensorData<float>(scratch_buffer);
float* input_gate_scratch = nullptr;
float* cell_scratch = nullptr;
float* forget_gate_scratch = nullptr;
float* output_gate_scratch = nullptr;
if (use_cifg) {
cell_scratch = scratch_buffer_ptr;
forget_gate_scratch = scratch_buffer_ptr + n_cell * n_batch;
output_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch;
} else {
input_gate_scratch = scratch_buffer_ptr;
cell_scratch = scratch_buffer_ptr + n_cell * n_batch;
forget_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch;
output_gate_scratch = scratch_buffer_ptr + 3 * n_cell * n_batch;
}
const int output_batch_leading_dim =
output->dims->data[output->dims->size - 1];
if (time_major) {
// Loop through the sequence.
const int input_step = n_batch * n_input;
const int output_step = n_batch * output_batch_leading_dim;
for (int t = 0; t < max_time; t++) {
// If this is the forward_sequence, step forward, otherwise step
// backwards.
const int t_rel = forward_sequence ? t : max_time - t - 1;
const float* input_ptr = GetTensorData<float>(input) + t_rel * input_step;
const float* aux_input_ptr = nullptr;
if (aux_input) {
aux_input_ptr = GetTensorData<float>(aux_input) + t_rel * input_step;
}
float* output_ptr_time =
GetTensorData<float>(output) + t_rel * output_step + output_offset;
LstmStepWithAuxInput(
input_ptr, GetTensorData<float>(input_to_input_weights),
GetTensorData<float>(input_to_forget_weights),
GetTensorData<float>(input_to_cell_weights),
GetTensorData<float>(input_to_output_weights), aux_input_ptr,
GetTensorData<float>(aux_input_to_input_weights),
GetTensorData<float>(aux_input_to_forget_weights),
GetTensorData<float>(aux_input_to_cell_weights),
GetTensorData<float>(aux_input_to_output_weights),
GetTensorData<float>(recurrent_to_input_weights),
GetTensorData<float>(recurrent_to_forget_weights),
GetTensorData<float>(recurrent_to_cell_weights),
GetTensorData<float>(recurrent_to_output_weights),
GetTensorData<float>(cell_to_input_weights),
GetTensorData<float>(cell_to_forget_weights),
GetTensorData<float>(cell_to_output_weights),
GetTensorData<float>(input_layer_norm_coefficients),
GetTensorData<float>(forget_layer_norm_coefficients),
GetTensorData<float>(cell_layer_norm_coefficients),
GetTensorData<float>(output_layer_norm_coefficients),
GetTensorData<float>(input_gate_bias),
GetTensorData<float>(forget_gate_bias),
GetTensorData<float>(cell_bias),
GetTensorData<float>(output_gate_bias),
GetTensorData<float>(projection_weights),
GetTensorData<float>(projection_bias), params, n_batch, n_cell,
n_input, aux_input_size, n_output, output_batch_leading_dim,
GetTensorData<float>(activation_state),
GetTensorData<float>(cell_state), input_gate_scratch,
forget_gate_scratch, cell_scratch, output_gate_scratch,
output_ptr_time, logger, intemediate_tensor_indexes, subgraph_index,
error_reporter);
}
} else {
for (int b = 0; b < n_batch; b++) {
const int input_step = n_input;
const int output_step = output_batch_leading_dim;
for (int t = 0; t < max_time; t++) {
// If this is the forward_sequence, step forward, otherwise step
// backwards.
const int t_rel = forward_sequence ? t : max_time - t - 1;
const int time_offset = b * max_time + t_rel;
const float* input_ptr =
GetTensorData<float>(input) + time_offset * input_step;
const float* aux_input_ptr = nullptr;
if (aux_input) {
aux_input_ptr =
GetTensorData<float>(aux_input) + time_offset * input_step;
}
float* output_ptr = GetTensorData<float>(output) +
time_offset * output_step + output_offset;
// Offset the {activation,cell}_state pointers to the right batch.
float* activation_state_ptr = GetTensorData<float>(activation_state) +
b * output_batch_leading_dim;
float* cell_state_ptr = GetTensorData<float>(cell_state) + b * n_cell;
// Offset the scratch pointers to the right batch.
float* input_gate_scratch_ptr =
input_gate_scratch ? input_gate_scratch + b * n_cell : nullptr;
float* forget_gate_scratch_ptr = forget_gate_scratch + b * n_cell;
float* cell_scratch_ptr = cell_scratch + b * n_cell;
float* output_gate_scratch_ptr = output_gate_scratch + b * n_cell;
LstmStepWithAuxInput(
input_ptr, GetTensorData<float>(input_to_input_weights),
GetTensorData<float>(input_to_forget_weights),
GetTensorData<float>(input_to_cell_weights),
GetTensorData<float>(input_to_output_weights), aux_input_ptr,
GetTensorData<float>(aux_input_to_input_weights),
GetTensorData<float>(aux_input_to_forget_weights),
GetTensorData<float>(aux_input_to_cell_weights),
GetTensorData<float>(aux_input_to_output_weights),
GetTensorData<float>(recurrent_to_input_weights),
GetTensorData<float>(recurrent_to_forget_weights),
GetTensorData<float>(recurrent_to_cell_weights),
GetTensorData<float>(recurrent_to_output_weights),
GetTensorData<float>(cell_to_input_weights),
GetTensorData<float>(cell_to_forget_weights),
GetTensorData<float>(cell_to_output_weights),
GetTensorData<float>(input_layer_norm_coefficients),
GetTensorData<float>(forget_layer_norm_coefficients),
GetTensorData<float>(cell_layer_norm_coefficients),
GetTensorData<float>(output_layer_norm_coefficients),
GetTensorData<float>(input_gate_bias),
GetTensorData<float>(forget_gate_bias),
GetTensorData<float>(cell_bias),
GetTensorData<float>(output_gate_bias),
GetTensorData<float>(projection_weights),
GetTensorData<float>(projection_bias), params, /*n_batch=*/1,
n_cell, n_input, aux_input_size, n_output, output_batch_leading_dim,
activation_state_ptr, cell_state_ptr, input_gate_scratch_ptr,
forget_gate_scratch_ptr, cell_scratch_ptr, output_gate_scratch_ptr,
output_ptr, logger, intemediate_tensor_indexes, subgraph_index,
error_reporter);
}
}
}
return kTfLiteOk;
}
struct OpData {
// Which kernel type to use. Full kernel (24 inputs) or basic kernel (5
// inputs).
// Please note the 20-input full kernel is deprecated and only kept
// here for backward compatibility.
TfLiteLSTMKernelType kernel_type;
// If the lstm is layer norm.
bool use_layer_norm;
// These fields are only used by full kernel.
int scratch_tensor_index;
};
// Resize the output, state tensors based on the sizes of the input tensors.
// Allocate a temporary scratch tensor. Also check that the sizes of the input
// tensors match each other.
TfLiteStatus lstm_eval(TfLiteContext* context, const int subgraph_index,
TfLiteNode* node, Logger* logger,
ErrorReporter* error_reporter) {
const auto* params = static_cast<TfLiteLSTMParams*>(node->builtin_data);
const TfLiteTensor* input =
GetInput(context, node, ops::builtin::lstm::full::kInputTensor);
const TfLiteTensor* input_to_input_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kInputToInputWeightsTensor);
const TfLiteTensor* input_to_forget_weights = GetInput(
context, node, ops::builtin::lstm::full::kInputToForgetWeightsTensor);
const TfLiteTensor* input_to_cell_weights = GetInput(
context, node, ops::builtin::lstm::full::kInputToCellWeightsTensor);
const TfLiteTensor* input_to_output_weights = GetInput(
context, node, ops::builtin::lstm::full::kInputToOutputWeightsTensor);
const TfLiteTensor* recurrent_to_input_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kRecurrentToInputWeightsTensor);
const TfLiteTensor* recurrent_to_forget_weights = GetInput(
context, node, ops::builtin::lstm::full::kRecurrentToForgetWeightsTensor);
const TfLiteTensor* recurrent_to_cell_weights = GetInput(
context, node, ops::builtin::lstm::full::kRecurrentToCellWeightsTensor);
const TfLiteTensor* recurrent_to_output_weights = GetInput(
context, node, ops::builtin::lstm::full::kRecurrentToOutputWeightsTensor);
const TfLiteTensor* cell_to_input_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kCellToInputWeightsTensor);
const TfLiteTensor* cell_to_forget_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kCellToForgetWeightsTensor);
const TfLiteTensor* cell_to_output_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kCellToOutputWeightsTensor);
const TfLiteTensor* input_layer_norm_coefficients = GetOptionalInputTensor(
context, node,
ops::builtin::lstm::full::kInputLayerNormCoefficientsTensor);
const TfLiteTensor* forget_layer_norm_coefficients = GetOptionalInputTensor(
context, node,
ops::builtin::lstm::full::kForgetLayerNormCoefficientsTensor);
const TfLiteTensor* cell_layer_norm_coefficients = GetOptionalInputTensor(
context, node,
ops::builtin::lstm::full::kCellLayerNormCoefficientsTensor);
const TfLiteTensor* output_layer_norm_coefficients = GetOptionalInputTensor(
context, node,
ops::builtin::lstm::full::kOutputLayerNormCoefficientsTensor);
const TfLiteTensor* input_gate_bias = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kInputGateBiasTensor);
const TfLiteTensor* forget_gate_bias =
GetInput(context, node, ops::builtin::lstm::full::kForgetGateBiasTensor);
const TfLiteTensor* cell_bias =
GetInput(context, node, ops::builtin::lstm::full::kCellGateBiasTensor);
const TfLiteTensor* output_gate_bias =
GetInput(context, node, ops::builtin::lstm::full::kOutputGateBiasTensor);
const TfLiteTensor* projection_weights = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kProjectionWeightsTensor);
const TfLiteTensor* projection_bias = GetOptionalInputTensor(
context, node, ops::builtin::lstm::full::kProjectionBiasTensor);
// Index the scratch buffers pointers to the global scratch buffer.
TfLiteTensor* scratch_buffer = GetTemporary(context, node, /*index=*/0);
TfLiteTensor* activation_state = GetVariableInput(
context, node, ops::builtin::lstm::full::kOutputStateTensor);
TF_LITE_ENSURE(context, activation_state != nullptr);
TfLiteTensor* cell_state = GetVariableInput(
context, node, ops::builtin::lstm::full::kCellStateTensor);
TF_LITE_ENSURE(context, cell_state != nullptr);
TfLiteTensor* output =
GetOutput(context, node, ops::builtin::lstm::full::kOutputTensor);
std::vector<int> intemediate_tensor_indexes(node->intermediates->size);
for (int i = 0; i < node->intermediates->size; ++i) {
intemediate_tensor_indexes[i] = node->intermediates->data[i];
}
switch (input_to_output_weights->type) {
case kTfLiteFloat32: {
return EvalFloat(
input, input_to_input_weights, input_to_forget_weights,
input_to_cell_weights, input_to_output_weights,
recurrent_to_input_weights, recurrent_to_forget_weights,
recurrent_to_cell_weights, recurrent_to_output_weights,
cell_to_input_weights, cell_to_forget_weights, cell_to_output_weights,
input_layer_norm_coefficients, forget_layer_norm_coefficients,
cell_layer_norm_coefficients, output_layer_norm_coefficients,
/*aux_input=*/nullptr,
/*aux_input_to_input_weights=*/nullptr,
/*aux_input_to_forget_weights=*/nullptr,
/*aux_input_to_cell_weights=*/nullptr,
/*aux_input_to_output_weights=*/nullptr, input_gate_bias,
forget_gate_bias, cell_bias, output_gate_bias, projection_weights,
projection_bias, params, /*forward_sequence=*/true,
/*time_major=*/true,
/*output_offset=*/0, scratch_buffer, activation_state, cell_state,
output, logger, intemediate_tensor_indexes, subgraph_index,
error_reporter);
}
case kTfLiteUInt8:
case kTfLiteInt8:
default:
printf("Error. Only float model can be calibrated\n");
return kTfLiteError;
}
return kTfLiteOk;
}
} // namespace
TfLiteStatus lstm_logging_kernel(TfLiteContext* context,
const int subgraph_index, TfLiteNode* node,
Logger* logger,
ErrorReporter* error_reporter) {
return lstm_eval(context, subgraph_index, node, logger, error_reporter);
}
} // namespace custom
} // namespace calibration
} // namespace optimize
} // namespace tflite
@@ -0,0 +1,36 @@
/* Copyright 2018 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CUSTOM_LOGGING_OPS_LSTM_H_
#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CUSTOM_LOGGING_OPS_LSTM_H_
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_logger.h"
namespace tflite {
namespace optimize {
namespace calibration {
namespace custom {
TfLiteStatus lstm_logging_kernel(TfLiteContext* context,
const int subgraph_index, TfLiteNode* node,
Logger* logger, ErrorReporter* error_reporter);
} // namespace custom
} // namespace calibration
} // namespace optimize
} // namespace tflite
#endif // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_CUSTOM_LOGGING_OPS_LSTM_H_
@@ -0,0 +1,35 @@
/* Copyright 2018 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_LOGGING_OP_H_
#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_LOGGING_OP_H_
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_logger.h"
namespace tflite {
namespace optimize {
namespace calibration {
typedef TfLiteStatus (*logging_kernel_func_ptr)(TfLiteContext* context,
const int subgraph_index,
TfLiteNode* node,
Logger* logger,
ErrorReporter* error_reporter);
} // namespace calibration
} // namespace optimize
} // namespace tflite
#endif // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_LOGGING_OP_H_
@@ -0,0 +1,122 @@
/* Copyright 2018 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 "tensorflow/lite/tools/optimize/calibration/logging_op_resolver.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/api/op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_common.h"
#include "tensorflow/lite/util.h"
namespace tflite {
namespace optimize {
namespace calibration {
LoggingOpResolver::LoggingOpResolver(
const BuiltinOpsSet& builtin_ops_to_replace,
const CustomOpsSet& custom_ops_to_replace, const OpResolver& base_resolver,
KernelEvalFuncPtr logging_eval_fn, ErrorReporter* error_reporter) {
std::vector<std::string> unresolved_builtin_ops;
std::vector<std::string> unresolved_custom_ops;
for (const auto& op_and_version : builtin_ops_to_replace) {
const TfLiteRegistration* base_registration =
base_resolver.FindOp(op_and_version.first, op_and_version.second);
if (!base_registration) {
unresolved_builtin_ops.push_back(
EnumNameBuiltinOperator(op_and_version.first));
continue;
}
BuiltinOperatorKey key = op_and_version;
builtin_op_evalfn_map_[key] = base_registration->invoke;
auto logging_registration =
std::make_unique<TfLiteRegistration>(*base_registration);
logging_registration->invoke = logging_eval_fn;
builtin_op_registration_map_[key] = std::move(logging_registration);
}
for (const auto& op_and_version : custom_ops_to_replace) {
const TfLiteRegistration* base_registration = base_resolver.FindOp(
op_and_version.first.c_str(), op_and_version.second);
if (!base_registration) {
if (!IsFlexOp(op_and_version.first.c_str()))
unresolved_custom_ops.push_back(op_and_version.first.c_str());
continue;
}
CustomOperatorKey key = op_and_version;
custom_op_evalfn_map_[key] = base_registration->invoke;
auto logging_registration =
std::make_unique<TfLiteRegistration>(*base_registration);
logging_registration->invoke = logging_eval_fn;
custom_op_registration_map_[key] = std::move(logging_registration);
}
if (!unresolved_builtin_ops.empty() || !unresolved_custom_ops.empty()) {
if (!error_reporter) return;
std::string error_message =
"Failed to initialize op resolver for calibration:";
if (!unresolved_builtin_ops.empty())
absl::StrAppend(&error_message, "\nThere are unresolved builtin ops: [",
absl::StrJoin(unresolved_builtin_ops, ", "), "]");
if (!unresolved_custom_ops.empty()) {
absl::StrAppend(&error_message, "\nThere are unresolved custom ops: [",
absl::StrJoin(unresolved_custom_ops, ", "), "]");
}
TF_LITE_REPORT_ERROR(error_reporter, error_message.c_str());
}
}
const TfLiteRegistration* LoggingOpResolver::FindOp(BuiltinOperator op,
int version) const {
BuiltinOperatorKey key = {op, version};
if (builtin_op_registration_map_.find(key) !=
builtin_op_registration_map_.end()) {
return builtin_op_registration_map_.at(key).get();
}
return nullptr;
}
KernelEvalFuncPtr LoggingOpResolver::GetWrappedKernelInvoke(BuiltinOperator op,
int version) const {
return builtin_op_evalfn_map_.at({op, version});
}
const TfLiteRegistration* LoggingOpResolver::FindOp(const char* op,
int version) const {
CustomOperatorKey key = {op, version};
if (custom_op_registration_map_.find(key) !=
custom_op_registration_map_.end()) {
return custom_op_registration_map_.at(key).get();
}
return nullptr;
}
KernelEvalFuncPtr LoggingOpResolver::GetWrappedKernelInvoke(const char* op,
int version) const {
return custom_op_evalfn_map_.at({op, version});
}
} // namespace calibration
} // namespace optimize
} // namespace tflite
@@ -0,0 +1,68 @@
/* Copyright 2018 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_LOGGING_OP_RESOLVER_H_
#define TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_LOGGING_OP_RESOLVER_H_
#include <set>
#include <unordered_map>
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/core/api/error_reporter.h"
#include "tensorflow/lite/core/api/op_resolver.h"
#include "tensorflow/lite/mutable_op_resolver.h"
#include "tensorflow/lite/op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_common.h"
namespace tflite {
namespace optimize {
namespace calibration {
// A resolver that replaces the kernel invocations with a wrapper
// eval function.
class LoggingOpResolver : public OpResolver {
public:
// Creates an instance of |LoggingOpResolver|.
// All |TfLiteRegistration.invoke| functions are replaced by
// |logging_eval_fn|.
// TODO(shashishekhar): This interface needs to change for
// BuiltinOps that need special logging implementations.
LoggingOpResolver(const BuiltinOpsSet& builtin_ops_to_replace,
const CustomOpsSet& custom_ops_to_replace,
const OpResolver& base_resolver,
KernelEvalFuncPtr logging_eval_fn,
ErrorReporter* error_reporter);
const TfLiteRegistration* FindOp(BuiltinOperator op,
int version) const override;
KernelEvalFuncPtr GetWrappedKernelInvoke(BuiltinOperator op,
int version) const;
const TfLiteRegistration* FindOp(const char* op, int version) const override;
KernelEvalFuncPtr GetWrappedKernelInvoke(const char* op, int version) const;
private:
BuiltinOpsMap<std::unique_ptr<TfLiteRegistration>>
builtin_op_registration_map_;
BuiltinOpsMap<KernelEvalFuncPtr> builtin_op_evalfn_map_;
CustomOpsMap<std::unique_ptr<TfLiteRegistration>> custom_op_registration_map_;
CustomOpsMap<KernelEvalFuncPtr> custom_op_evalfn_map_;
};
} // namespace calibration
} // namespace optimize
} // namespace tflite
#endif // TENSORFLOW_LITE_TOOLS_OPTIMIZE_CALIBRATION_LOGGING_OP_RESOLVER_H_
@@ -0,0 +1,223 @@
/* Copyright 2018 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 "tensorflow/lite/tools/optimize/calibration/logging_op_resolver.h"
#include <string>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/mutable_op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_common.h"
namespace tflite {
namespace optimize {
namespace calibration {
namespace {
TfLiteStatus ConvPrepare(TfLiteContext* context, TfLiteNode* node) {
return kTfLiteOk;
}
TfLiteStatus ConvEval(TfLiteContext* context, TfLiteNode* node) {
return kTfLiteOk;
}
TfLiteStatus AddPrepare(TfLiteContext* context, TfLiteNode* node) {
return kTfLiteOk;
}
TfLiteStatus AddEval(TfLiteContext* context, TfLiteNode* node) {
return kTfLiteOk;
}
TfLiteStatus CustomPrepare(TfLiteContext* context, TfLiteNode* node) {
return kTfLiteOk;
}
TfLiteStatus CustomEval(TfLiteContext* context, TfLiteNode* node) {
return kTfLiteOk;
}
TfLiteStatus WrappingInvoke(TfLiteContext* context, TfLiteNode* node) {
return kTfLiteOk;
}
TEST(LoggingOpResolverTest, KernelInvokesAreReplaced) {
MutableOpResolver base_resolver;
TfLiteRegistration conv_registration = {};
conv_registration.prepare = ConvPrepare;
conv_registration.invoke = ConvEval;
base_resolver.AddBuiltin(BuiltinOperator_CONV_2D, &conv_registration);
TfLiteRegistration add_registration = {};
add_registration.prepare = AddPrepare;
add_registration.invoke = AddEval;
base_resolver.AddBuiltin(BuiltinOperator_ADD, &add_registration);
BuiltinOpsSet ops_to_replace = {
{BuiltinOperator_CONV_2D, /*version*/ 1},
{BuiltinOperator_ADD, /*version*/ 1},
};
LoggingOpResolver resolver(ops_to_replace, CustomOpsSet(), base_resolver,
WrappingInvoke, /*error_reporter=*/nullptr);
auto reg = resolver.FindOp(BuiltinOperator_CONV_2D, 1);
EXPECT_EQ(reg->builtin_code, BuiltinOperator_CONV_2D);
EXPECT_TRUE(reg->prepare == ConvPrepare);
EXPECT_TRUE(reg->invoke == WrappingInvoke);
reg = resolver.FindOp(BuiltinOperator_ADD, 1);
EXPECT_EQ(reg->builtin_code, BuiltinOperator_ADD);
EXPECT_TRUE(reg->prepare == AddPrepare);
EXPECT_TRUE(reg->invoke == WrappingInvoke);
}
TEST(LoggingOpResolverTest, OriginalKernelInvokesAreRetained) {
MutableOpResolver base_resolver;
TfLiteRegistration conv_registration = {};
conv_registration.prepare = ConvPrepare;
conv_registration.invoke = ConvEval;
base_resolver.AddBuiltin(BuiltinOperator_CONV_2D, &conv_registration);
TfLiteRegistration add_registration = {};
add_registration.prepare = AddPrepare;
add_registration.invoke = AddEval;
base_resolver.AddBuiltin(BuiltinOperator_ADD, &add_registration);
BuiltinOpsSet ops_to_replace = {
{BuiltinOperator_CONV_2D, /*version*/ 1},
{BuiltinOperator_ADD, /*version*/ 1},
};
LoggingOpResolver resolver(ops_to_replace, CustomOpsSet(), base_resolver,
WrappingInvoke, /*error_reporter=*/nullptr);
auto kernel_invoke =
resolver.GetWrappedKernelInvoke(BuiltinOperator_CONV_2D, 1);
EXPECT_TRUE(kernel_invoke == ConvEval);
kernel_invoke = resolver.GetWrappedKernelInvoke(BuiltinOperator_ADD, 1);
EXPECT_TRUE(kernel_invoke == AddEval);
}
TEST(LoggingOpResolverTest, OnlyOpsInReplacementSetAreReplaces) {
MutableOpResolver base_resolver;
TfLiteRegistration conv_registration = {};
conv_registration.prepare = ConvPrepare;
conv_registration.invoke = ConvEval;
base_resolver.AddBuiltin(BuiltinOperator_CONV_2D, &conv_registration);
TfLiteRegistration add_registration = {};
add_registration.prepare = AddPrepare;
add_registration.invoke = AddEval;
base_resolver.AddBuiltin(BuiltinOperator_ADD, &add_registration);
// Only replace conv2d
BuiltinOpsSet ops_to_replace = {
{BuiltinOperator_CONV_2D, /*version*/ 1},
};
LoggingOpResolver resolver(ops_to_replace, CustomOpsSet(), base_resolver,
WrappingInvoke, /*error_reporter=*/nullptr);
auto reg = resolver.FindOp(BuiltinOperator_CONV_2D, 1);
EXPECT_EQ(reg->builtin_code, BuiltinOperator_CONV_2D);
EXPECT_TRUE(reg->prepare == ConvPrepare);
EXPECT_TRUE(reg->invoke == WrappingInvoke);
reg = resolver.FindOp(BuiltinOperator_ADD, 1);
EXPECT_EQ(nullptr, reg);
}
TEST(LoggingOpResolverTest, CustomOps) {
MutableOpResolver base_resolver;
TfLiteRegistration custom_registration = {};
custom_registration.prepare = CustomPrepare;
custom_registration.invoke = CustomEval;
std::string custom_op_name = "custom";
base_resolver.AddCustom(custom_op_name.c_str(), &custom_registration);
CustomOpsSet ops_to_replace = {
{custom_op_name, /*version*/ 1},
};
LoggingOpResolver resolver(BuiltinOpsSet(), ops_to_replace, base_resolver,
WrappingInvoke, /*error_reporter=*/nullptr);
auto reg = resolver.FindOp(custom_op_name.c_str(), 1);
EXPECT_EQ(reg->builtin_code, BuiltinOperator_CUSTOM);
EXPECT_EQ(reg->custom_name, custom_op_name.c_str());
EXPECT_TRUE(reg->prepare == CustomPrepare);
EXPECT_TRUE(reg->invoke == WrappingInvoke);
}
TEST(LoggingOpResolverTest, UnresolvedCustomOps) {
// No custom op registration.
MutableOpResolver base_resolver;
std::string custom_op_name = "unresolved_custom_op";
CustomOpsSet ops_to_replace = {
{custom_op_name, /*version*/ 1},
};
// Expect no death.
LoggingOpResolver(BuiltinOpsSet(), ops_to_replace, base_resolver,
WrappingInvoke, /*error_reporter=*/nullptr);
}
TEST(LoggingOpResolverTest, UnresolvedBuiltinOps) {
// No builtin op registration.
MutableOpResolver base_resolver;
BuiltinOpsSet ops_to_replace = {
{BuiltinOperator_CONV_2D, /*version*/ 1},
{BuiltinOperator_ADD, /*version*/ 1},
};
// Expect no death.
LoggingOpResolver resolver(ops_to_replace, CustomOpsSet(), base_resolver,
WrappingInvoke, /*error_reporter=*/nullptr);
}
TEST(LoggingOpResolverTest, FlexOps) {
// No flex op registration.
MutableOpResolver base_resolver;
std::string custom_op_name = "FlexAdd";
CustomOpsSet ops_to_replace = {
{custom_op_name, /*version*/ 1},
};
LoggingOpResolver resolver(BuiltinOpsSet(), ops_to_replace, base_resolver,
WrappingInvoke, /*error_reporter=*/nullptr);
auto reg = resolver.FindOp(custom_op_name.c_str(), 1);
EXPECT_TRUE(!reg);
}
} // namespace
} // namespace calibration
} // namespace optimize
} // namespace tflite