blob: a4288ed89677641dc61a2f54e8c355cd8cc0ac5d [file] [log] [blame]
/* 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/versioning/op_version.h"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "absl/memory/memory.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_split.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/kernels/internal/quantization_util.h"
namespace tflite {
namespace {
// Get the number of dimensions of a tensor with idx of an operator op.
inline int GetNumDims(const SubGraph* subgraph, const Operator* op, int idx) {
return subgraph->tensors()->Get(op->inputs()->Get(idx))->shape()->size();
}
// Compare shape of two tensors with idx1 and idx2 of an operator op, return
// true if they have the same shape.
inline bool HaveSameShapes(const SubGraph* subgraph, const Operator* op,
int idx1, int idx2) {
const flatbuffers::Vector<int32_t>* shape1 =
subgraph->tensors()->Get(op->inputs()->Get(idx1))->shape();
const flatbuffers::Vector<int32_t>* shape2 =
subgraph->tensors()->Get(op->inputs()->Get(idx2))->shape();
if (shape1->size() != shape2->size()) {
return false;
}
return std::equal(shape1->begin(), shape1->end(), shape2->begin());
}
} // namespace
int GetBuiltinOperatorVersion(const OpSignature& op_sig) {
switch (op_sig.op) {
case BuiltinOperator_CONV_2D:
// If the op has signed int8 op_sig.inputs and op_sig.outputs, its
// version 3.
if (op_sig.input_types.at(0) == TensorType_INT8 &&
op_sig.input_types.at(1) == TensorType_INT8 &&
op_sig.output_types.at(0) == TensorType_INT8) {
return 3;
}
// If the op is a signed int8 hybrid operation, we need to return
// version 2.
if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
op_sig.input_types.at(1) == TensorType_INT8 &&
op_sig.output_types.at(0) == TensorType_FLOAT32) {
return 2;
}
return 1;
case BuiltinOperator_DEPTHWISE_CONV_2D:
// If the op is a signed int8 hybrid operation, we need to return
// version 4.
if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
op_sig.input_types.at(1) == TensorType_INT8 &&
op_sig.output_types.at(0) == TensorType_FLOAT32) {
return 4;
}
// If the op has signed int8 op_sig.inputs and op_sig.outputs, its
// version 3.
if (op_sig.input_types.at(0) == TensorType_INT8 &&
op_sig.input_types.at(1) == TensorType_INT8 &&
op_sig.output_types.at(0) == TensorType_INT8) {
return 3;
}
if (op_sig.options.depthwise_conv_2d.dilation_w_factor != 1 ||
op_sig.options.depthwise_conv_2d.dilation_h_factor != 1) {
return 2;
}
return 1;
case BuiltinOperator_FAKE_QUANT:
if (op_sig.options.fakequant.narrow_range) {
return 2;
}
return 1;
case BuiltinOperator_FULLY_CONNECTED:
// +-----------------+--------------------+--------------------------+
// | | Weight::Default | Weight::Shuffled4x16Int8 |
// +-----------------+--------------------+--------------------------+
// | Float | 1 | 2 |
// | Quantized Uint8 | 1 | 2 |
// | Hybrid | 3 | 3 |
// | Quantized Int8 | 4 | 4 |
// +-----------------+--------------------+--------------------------+
// 2 op_sig.inputs (no bias) use case is supported starting from
// version 6.
if (op_sig.input_types.size() == 2) {
return 6;
}
// `keep_num_dims` is supported at verison 5.
if (op_sig.options.fully_connected.keep_num_dims) {
return 5;
}
// Int8 fully fixed point kernel is at version 4.
if (op_sig.input_types.at(0) == TensorType_INT8 &&
op_sig.input_types.at(1) == TensorType_INT8 &&
op_sig.output_types.at(0) == TensorType_INT8) {
return 4;
}
// If the op is a signed int8 hybrid operation, we need to return
// version 3.
if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
op_sig.input_types.at(1) == TensorType_INT8 &&
op_sig.output_types.at(0) == TensorType_FLOAT32) {
return 3;
}
// For float and uint8 fixed point kernels, if the weight is
// Shuffled4x16Int8, is is version 2.
if (op_sig.options.fully_connected.weights_format ==
FullyConnectedOptionsWeightsFormat_SHUFFLED4x16INT8) {
return 2;
}
// Otherwise (weight is default), the version is 1.
return 1;
case BuiltinOperator_GATHER:
// If the op takes bool input, it is version 3.
if (op_sig.input_types.at(0) == TensorType_BOOL) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_SVDF:
// Fully integer SVDF has int8 as input and is of version 3.
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 3;
}
// If the op is a signed int8 hybrid operation, we need to return
// version 2.
if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
op_sig.input_types.at(1) == TensorType_INT8 &&
op_sig.output_types.at(0) == TensorType_FLOAT32) {
return 2;
}
return 1;
case BuiltinOperator_MUL:
// Version 3 supports have a rescale value greater than or equal to 1.
if (op_sig.options.mul.input1_scale != 0 &&
op_sig.options.mul.input2_scale != 0 &&
op_sig.options.mul.output_scale != 0 &&
(op_sig.options.mul.input1_scale * op_sig.options.mul.input2_scale /
op_sig.options.mul.output_scale) >= 1.0) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_TRANSPOSE:
// If the op takes bool input, it is version 3.
if (op_sig.input_types.at(0) == TensorType_BOOL) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_TRANSPOSE_CONV:
// If the op takes int8 input, it is version 2.
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_LSTM:
// If the input tensor is float and a weight is int8, this is a version
// 3 hybrid operation.
if (op_sig.options.lstm.kernel_type == LSTMKernelType_FULL &&
op_sig.input_types.at(0) == TensorType_FLOAT32 &&
op_sig.input_types.at(2) == TensorType_INT8 &&
op_sig.output_types.at(0) == TensorType_FLOAT32) {
return 3;
}
// KERNEL_BASIC was added in version 2.
if (op_sig.options.lstm.kernel_type == LSTMKernelType_BASIC) {
return 2;
}
return 1;
case BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_LSTM:
// If the input tensor is float and a weight is int8, this is a version
// 2 hybrid operation.
if (op_sig.input_types.at(0) == TensorType_FLOAT32 &&
op_sig.input_types.at(2) == TensorType_INT8 &&
op_sig.output_types.at(0) == TensorType_FLOAT32) {
return 2;
}
return 1;
case BuiltinOperator_SPLIT:
// If the op take int8 input, it is version 2, for int32 it's version 3.
if (op_sig.input_types.at(0) == TensorType_INT32) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_SPARSE_TO_DENSE:
// Version 3 supports Int8 and Uint8 type.
if (op_sig.input_types.at(2) == TensorType_INT8 ||
op_sig.input_types.at(2) == TensorType_UINT8) {
return 3;
}
// Version 2 supports Int64 value type.
if (op_sig.input_types.at(2) == TensorType_INT64) {
return 2;
}
return 1;
case BuiltinOperator_SLICE:
// Version 3 supports string input types.
if (op_sig.input_types.at(0) == TensorType_STRING) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_UNPACK:
// If the op take int8/uint8 input, it is version 2.
if (op_sig.input_types.at(0) == TensorType_INT8 ||
op_sig.input_types.at(0) == TensorType_UINT8) {
return 2;
}
// If the op take bool input, it is version 3.
if (op_sig.input_types.at(0) == TensorType_BOOL) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT16 &&
op_sig.output_types.at(0) == TensorType_INT16) {
return 4;
}
return 1;
case BuiltinOperator_DEQUANTIZE:
// Version 3 supports signed int16 input types.
if (op_sig.input_types.at(0) == TensorType_INT16 ||
op_sig.input_types.at(0) == TensorType_FLOAT16) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_FLOOR_DIV:
if (op_sig.input_types.at(0) == TensorType_FLOAT32) {
return 2;
}
return 1;
case BuiltinOperator_L2_NORMALIZATION:
if (op_sig.output_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_RELU:
if (op_sig.input_types.at(0) == TensorType_INT8 ||
op_sig.input_types.at(0) == TensorType_UINT8) {
return 2;
}
return 1;
case BuiltinOperator_STRIDED_SLICE:
if (op_sig.options.strided_slice.num_dims > 4) {
return 4;
}
// If the op takes bool input, it is version 3.
if (op_sig.input_types.at(0) == TensorType_BOOL) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_REVERSE_V2:
if (op_sig.input_types.at(0) == TensorType_BOOL) {
return 2;
}
return 1;
case BuiltinOperator_RESIZE_BILINEAR:
if (op_sig.options.resize_bilinear.half_pixel_centers) {
return 3;
} else if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_MAXIMUM:
case BuiltinOperator_MINIMUM:
if (op_sig.input_types.at(0) == TensorType_INT16 &&
op_sig.output_types.at(0) == TensorType_INT16) {
return 4;
}
if (op_sig.options.broadcast.need_broadcast &&
op_sig.options.broadcast.num_dims > 4) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_PACK:
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
if (op_sig.input_types.at(0) == TensorType_INT16 &&
op_sig.output_types.at(0) == TensorType_INT16) {
return 3;
}
return 1;
case BuiltinOperator_TILE:
if (op_sig.input_types.at(0) == TensorType_STRING) {
return 2;
}
return 1;
case BuiltinOperator_SPACE_TO_BATCH_ND:
case BuiltinOperator_BATCH_TO_SPACE_ND:
if (op_sig.options.space_batch.num_dims != 4) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_ADD:
if (op_sig.input_types.at(0) == TensorType_INT16 &&
op_sig.output_types.at(0) == TensorType_INT16) {
if (op_sig.options.addsub.pot_scale_int16) {
return 4;
} else {
return 3;
}
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_SUB:
if (op_sig.input_types.at(0) == TensorType_INT16 &&
op_sig.output_types.at(0) == TensorType_INT16) {
if (op_sig.options.addsub.pot_scale_int16) {
return 5;
} else {
return 4;
}
}
if (op_sig.options.broadcast.need_broadcast &&
op_sig.options.broadcast.num_dims > 4) {
return 3;
}
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
case BuiltinOperator_AVERAGE_POOL_2D:
case BuiltinOperator_CONCATENATION:
case BuiltinOperator_MAX_POOL_2D:
case BuiltinOperator_PAD:
case BuiltinOperator_PADV2:
case BuiltinOperator_SOFTMAX:
case BuiltinOperator_SPACE_TO_DEPTH:
case BuiltinOperator_SPLIT_V:
case BuiltinOperator_MEAN:
case BuiltinOperator_SUM:
case BuiltinOperator_REDUCE_MAX:
case BuiltinOperator_REDUCE_MIN:
case BuiltinOperator_RELU6:
case BuiltinOperator_RESIZE_NEAREST_NEIGHBOR:
case BuiltinOperator_TANH:
case BuiltinOperator_LOGISTIC:
case BuiltinOperator_LOG_SOFTMAX:
case BuiltinOperator_TOPK_V2:
case BuiltinOperator_ARG_MAX:
case BuiltinOperator_ARG_MIN:
case BuiltinOperator_EQUAL:
case BuiltinOperator_NOT_EQUAL:
case BuiltinOperator_GREATER:
case BuiltinOperator_GREATER_EQUAL:
case BuiltinOperator_LESS:
case BuiltinOperator_LESS_EQUAL:
case BuiltinOperator_SELECT:
if (op_sig.input_types.at(0) == TensorType_INT8) {
return 2;
}
return 1;
default:
return 1;
}
}
TensorType GetTensorType(int32_t idx, const SubGraph* subgraph) {
const auto& none_type = static_cast<::tflite::TensorType>(-1);
if (idx == -1)
// For optional input/output, return none type directly.
return none_type;
// Some tests have a graph with invalid tensor index.
TFLITE_DCHECK_GE(idx, 0);
if (subgraph->tensors() && idx < subgraph->tensors()->Length()) {
return subgraph->tensors()->Get(idx)->type();
}
LOG(ERROR) << "Can't access tenor " << idx;
return none_type;
}
// Generate OpSignature with the given OperatorCode, Operator and Tensors (from
// SubGraph). The OpSignature will be used by GetBuiltinOperatorVersion() and
// mostly input and output tensor types are enough to figure out op version.
// But some ops (DEPTHWISE_CONV_2D, FULLY_CONNECTED, ...) require to pass their
// options to decide op version.
OpSignature GetOpSignature(const OperatorCode* op_code, const Operator* op,
const SubGraph* subgraph) {
OpSignature op_sig = {op_code->builtin_code()};
switch (op_code->builtin_code()) {
case BuiltinOperator_DEPTHWISE_CONV_2D: {
auto conv_option = op->builtin_options_as_DepthwiseConv2DOptions();
if (conv_option) {
op_sig.options.depthwise_conv_2d.dilation_w_factor =
conv_option->dilation_w_factor();
op_sig.options.depthwise_conv_2d.dilation_h_factor =
conv_option->dilation_h_factor();
}
} break;
case BuiltinOperator_FAKE_QUANT: {
auto fakequant_option = op->builtin_options_as_FakeQuantOptions();
if (fakequant_option) {
op_sig.options.fakequant.narrow_range =
fakequant_option->narrow_range();
}
} break;
case BuiltinOperator_FULLY_CONNECTED: {
auto fully_connected_option =
op->builtin_options_as_FullyConnectedOptions();
if (fully_connected_option) {
op_sig.options.fully_connected.keep_num_dims =
fully_connected_option->keep_num_dims();
op_sig.options.fully_connected.weights_format =
fully_connected_option->weights_format();
}
} break;
case BuiltinOperator_MUL: {
if (op->inputs()->Length() < 2 || op->outputs()->Length() < 1) {
break;
}
const Tensor* input1_tensor =
subgraph->tensors()->Get(op->inputs()->Get(0));
const Tensor* input2_tensor =
subgraph->tensors()->Get(op->inputs()->Get(1));
const Tensor* output_tensor =
subgraph->tensors()->Get(op->outputs()->Get(0));
const QuantizationParameters* input1_quant =
input1_tensor->quantization();
const QuantizationParameters* input2_qunt = input2_tensor->quantization();
const QuantizationParameters* output_quant =
output_tensor->quantization();
if (input1_quant && input1_quant->scale() &&
input1_quant->scale()->Length() && input2_qunt &&
input2_qunt->scale() && input2_qunt->scale()->Length() &&
output_quant && output_quant->scale() &&
output_quant->scale()->Length()) {
op_sig.options.mul.input1_scale = input1_quant->scale()->Get(0);
op_sig.options.mul.input2_scale = input2_qunt->scale()->Get(0);
op_sig.options.mul.output_scale = output_quant->scale()->Get(0);
}
} break;
case BuiltinOperator_ADD:
case BuiltinOperator_SUB: {
op_sig.options.addsub.pot_scale_int16 = false;
const Tensor* input1_tensor =
subgraph->tensors()->Get(op->inputs()->Get(0));
const Tensor* input2_tensor =
subgraph->tensors()->Get(op->inputs()->Get(1));
const Tensor* output_tensor =
subgraph->tensors()->Get(op->outputs()->Get(0));
const QuantizationParameters* input1_quant =
input1_tensor->quantization();
const QuantizationParameters* input2_quant =
input2_tensor->quantization();
const QuantizationParameters* output_quant =
output_tensor->quantization();
if (input1_quant && input1_quant->scale() &&
input1_quant->scale()->Length() && input2_quant &&
input2_quant->scale() && input2_quant->scale()->Length() &&
output_quant && output_quant->scale() &&
output_quant->scale()->Length()) {
float input1_scale = input1_quant->scale()->Get(0);
float input2_scale = input2_quant->scale()->Get(0);
float output_scale = output_quant->scale()->Get(0);
int scale_log2_rounded = 0;
bool input1_scale_is_pot =
CheckedLog2(input1_scale, &scale_log2_rounded);
bool input2_scale_is_pot =
CheckedLog2(input2_scale, &scale_log2_rounded);
bool output_scale_is_pot =
CheckedLog2(output_scale, &scale_log2_rounded);
op_sig.options.addsub.pot_scale_int16 =
input1_scale_is_pot && input2_scale_is_pot && output_scale_is_pot;
}
if (op_code->builtin_code() == BuiltinOperator_SUB) {
op_sig.options.broadcast.need_broadcast =
!HaveSameShapes(subgraph, op, 0, 1);
op_sig.options.broadcast.num_dims =
std::max(GetNumDims(subgraph, op, 0), GetNumDims(subgraph, op, 1));
}
} break;
case BuiltinOperator_LSTM: {
auto lstm_option = op->builtin_options_as_LSTMOptions();
if (lstm_option) {
op_sig.options.lstm.kernel_type = lstm_option->kernel_type();
}
} break;
case BuiltinOperator_RESIZE_BILINEAR: {
auto resize_bilinear_option =
op->builtin_options_as_ResizeBilinearOptions();
if (resize_bilinear_option) {
op_sig.options.resize_bilinear.half_pixel_centers =
resize_bilinear_option->half_pixel_centers();
}
} break;
// TODO(b/150176627): Add tests for GetOpSignature.
case BuiltinOperator_STRIDED_SLICE: {
op_sig.options.strided_slice.num_dims = GetNumDims(subgraph, op, 0);
} break;
case BuiltinOperator_SPACE_TO_BATCH_ND:
case BuiltinOperator_BATCH_TO_SPACE_ND: {
op_sig.options.space_batch.num_dims = GetNumDims(subgraph, op, 0);
} break;
case BuiltinOperator_MAXIMUM:
case BuiltinOperator_MINIMUM: {
op_sig.options.broadcast.need_broadcast =
!HaveSameShapes(subgraph, op, 0, 1);
op_sig.options.broadcast.num_dims =
std::max(GetNumDims(subgraph, op, 0), GetNumDims(subgraph, op, 1));
} break;
default:
break;
}
for (int32_t i = 0; i < op->inputs()->Length(); ++i) {
TensorType tensor_type = GetTensorType(op->inputs()->Get(i), subgraph);
op_sig.input_types.push_back(tensor_type);
}
for (int32_t i = 0; i < op->outputs()->Length(); ++i) {
TensorType tensor_type = GetTensorType(op->outputs()->Get(i), subgraph);
op_sig.output_types.push_back(tensor_type);
}
return op_sig;
}
void UpdateOpVersion(uint8_t* model_buffer_pointer) {
auto model = GetMutableModel(model_buffer_pointer);
auto subgraphs = model->subgraphs();
for (int i = 0; i < subgraphs->Length(); ++i) {
const SubGraph* subgraph = subgraphs->Get(i);
for (int j = 0; j < subgraph->operators()->Length(); ++j) {
const Operator* op = subgraph->operators()->Get(j);
OperatorCode* op_code =
model->mutable_operator_codes()->GetMutableObject(op->opcode_index());
if (op_code->builtin_code() != BuiltinOperator_CUSTOM) {
OpSignature op_sig = GetOpSignature(op_code, op, subgraph);
// Update builtin operator version.
int32_t op_ver = GetBuiltinOperatorVersion(op_sig);
if (!op_code->mutate_version(op_ver)) {
LOG(ERROR) << "Can't set operator "
<< EnumNameBuiltinOperator(op_code->builtin_code())
<< " to version " << op_ver;
}
}
}
}
}
} // namespace tflite