519 lines
21 KiB
Python
519 lines
21 KiB
Python
# 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.
|
|
# ==============================================================================
|
|
"""Tests for util.py."""
|
|
|
|
import os
|
|
|
|
from absl.testing import parameterized
|
|
import numpy as np
|
|
import tensorflow as tf
|
|
|
|
from tensorflow.lite.python import lite
|
|
from tensorflow.lite.python import util
|
|
from tensorflow.lite.tools.flatbuffer_utils import read_model as _read_model
|
|
from tensorflow.python.client import session
|
|
from tensorflow.python.framework import convert_to_constants
|
|
from tensorflow.python.framework import dtypes
|
|
from tensorflow.python.framework import ops
|
|
from tensorflow.python.framework import test_util
|
|
from tensorflow.python.ops import array_ops
|
|
from tensorflow.python.ops import math_ops
|
|
from tensorflow.python.ops import while_loop
|
|
from tensorflow.python.platform import test
|
|
|
|
|
|
# TODO(nupurgarg): Add test for Grappler and frozen graph related functions.
|
|
class UtilTest(test_util.TensorFlowTestCase):
|
|
|
|
def testConvertEnumToDtype(self):
|
|
self.assertEqual(
|
|
util._convert_tflite_enum_type_to_tf_type(0), dtypes.float32)
|
|
self.assertEqual(
|
|
util._convert_tflite_enum_type_to_tf_type(1), dtypes.float16)
|
|
self.assertEqual(util._convert_tflite_enum_type_to_tf_type(2), dtypes.int32)
|
|
self.assertEqual(util._convert_tflite_enum_type_to_tf_type(3), dtypes.uint8)
|
|
self.assertEqual(util._convert_tflite_enum_type_to_tf_type(4), dtypes.int64)
|
|
self.assertEqual(
|
|
util._convert_tflite_enum_type_to_tf_type(5), dtypes.string)
|
|
self.assertEqual(util._convert_tflite_enum_type_to_tf_type(6), dtypes.bool)
|
|
self.assertEqual(util._convert_tflite_enum_type_to_tf_type(7), dtypes.int16)
|
|
self.assertEqual(
|
|
util._convert_tflite_enum_type_to_tf_type(8), dtypes.complex64)
|
|
self.assertEqual(util._convert_tflite_enum_type_to_tf_type(9), dtypes.int8)
|
|
self.assertEqual(
|
|
util._convert_tflite_enum_type_to_tf_type(10), dtypes.float64)
|
|
self.assertEqual(
|
|
util._convert_tflite_enum_type_to_tf_type(11), dtypes.complex128)
|
|
self.assertEqual(
|
|
util._convert_tflite_enum_type_to_tf_type(16), dtypes.uint32)
|
|
with self.assertRaises(ValueError) as error:
|
|
util._convert_tflite_enum_type_to_tf_type(20)
|
|
self.assertEqual(
|
|
"Unsupported enum 20. The valid map of enum to tf types is : "
|
|
"{0: tf.float32, 1: tf.float16, 2: tf.int32, 3: tf.uint8, 4: tf.int64, "
|
|
"5: tf.string, 6: tf.bool, 7: tf.int16, 8: tf.complex64, 9: tf.int8, "
|
|
"10: tf.float64, 11: tf.complex128, 16: tf.uint32}",
|
|
str(error.exception))
|
|
|
|
def testTensorName(self):
|
|
with ops.Graph().as_default():
|
|
in_tensor = array_ops.placeholder(dtype=dtypes.float32, shape=[4])
|
|
out_tensors = array_ops.split(
|
|
value=in_tensor, num_or_size_splits=[1, 1, 1, 1], axis=0)
|
|
|
|
expect_names = ["split", "split:1", "split:2", "split:3"]
|
|
for i in range(len(expect_names)):
|
|
got_name = util.get_tensor_name(out_tensors[i])
|
|
self.assertEqual(got_name, expect_names[i])
|
|
|
|
def testUint32PassThrough(self):
|
|
model = tf.keras.Sequential([
|
|
tf.keras.layers.InputLayer(input_shape=(4,), dtype=tf.uint32),
|
|
tf.keras.layers.Reshape(target_shape=(2, 2))
|
|
])
|
|
converter = lite.TFLiteConverterV2.from_keras_model(model)
|
|
tflite_model = converter.convert()
|
|
interpreter = lite.Interpreter(model_content=tflite_model)
|
|
interpreter.allocate_tensors()
|
|
input_details = interpreter.get_input_details()[0]
|
|
output_details = interpreter.get_output_details()[0]
|
|
|
|
self.assertEqual(input_details["dtype"], np.uint32)
|
|
self.assertEqual(output_details["dtype"], np.uint32)
|
|
|
|
in_array = np.array([[1, 1, 1, 1]], dtype="uint32") * ((1 << 32) - 1)
|
|
expected_out = np.reshape(in_array, (2, 2))
|
|
|
|
interpreter.set_tensor(input_details["index"], in_array)
|
|
interpreter.invoke()
|
|
|
|
output_data = interpreter.get_tensor(output_details["index"])[0]
|
|
self.assertAllEqual(expected_out, output_data)
|
|
|
|
@test_util.enable_control_flow_v2
|
|
def testRemoveLowerUsingSwitchMerge(self):
|
|
with ops.Graph().as_default():
|
|
i = array_ops.placeholder(dtype=dtypes.int32, shape=())
|
|
c = lambda i: math_ops.less(i, 10)
|
|
b = lambda i: math_ops.add(i, 1)
|
|
while_loop.while_loop(c, b, [i])
|
|
sess = session.Session()
|
|
|
|
new_graph_def = convert_to_constants.disable_lower_using_switch_merge(
|
|
sess.graph_def)
|
|
lower_using_switch_merge_is_removed = False
|
|
for node in new_graph_def.node:
|
|
if node.op == "While" or node.op == "StatelessWhile":
|
|
if not node.attr["_lower_using_switch_merge"].b:
|
|
lower_using_switch_merge_is_removed = True
|
|
self.assertTrue(lower_using_switch_merge_is_removed)
|
|
|
|
def testConvertBytes(self):
|
|
source, header = util.convert_bytes_to_c_source(
|
|
b"\x00\x01\x02\x23", "foo", 16, use_tensorflow_license=False)
|
|
self.assertTrue(
|
|
source.find("const unsigned char foo[] DATA_ALIGN_ATTRIBUTE = {"))
|
|
self.assertTrue(source.find(""" 0x00, 0x01,
|
|
0x02, 0x23,"""))
|
|
self.assertNotEqual(-1, source.find("const int foo_len = 4;"))
|
|
self.assertEqual(-1, source.find("/* Copyright"))
|
|
self.assertEqual(-1, source.find("#include " ""))
|
|
self.assertNotEqual(-1, header.find("extern const unsigned char foo[];"))
|
|
self.assertNotEqual(-1, header.find("extern const int foo_len;"))
|
|
self.assertEqual(-1, header.find("/* Copyright"))
|
|
|
|
source, header = util.convert_bytes_to_c_source(
|
|
b"\xff\xfe\xfd\xfc",
|
|
"bar",
|
|
80,
|
|
include_guard="MY_GUARD",
|
|
include_path="my/guard.h",
|
|
use_tensorflow_license=True)
|
|
self.assertNotEqual(
|
|
-1, source.find("const unsigned char bar[] DATA_ALIGN_ATTRIBUTE = {"))
|
|
self.assertNotEqual(-1, source.find(""" 0xff, 0xfe, 0xfd, 0xfc,"""))
|
|
self.assertNotEqual(-1, source.find("/* Copyright"))
|
|
self.assertNotEqual(-1, source.find("#include \"my/guard.h\""))
|
|
self.assertNotEqual(-1, header.find("#ifndef MY_GUARD"))
|
|
self.assertNotEqual(-1, header.find("#define MY_GUARD"))
|
|
self.assertNotEqual(-1, header.find("/* Copyright"))
|
|
|
|
|
|
class TensorFunctionsTest(test_util.TensorFlowTestCase):
|
|
|
|
def testGetTensorsValid(self):
|
|
with ops.Graph().as_default():
|
|
in_tensor = array_ops.placeholder(
|
|
dtype=dtypes.float32, shape=[1, 16, 16, 3])
|
|
_ = in_tensor + in_tensor
|
|
sess = session.Session()
|
|
|
|
tensors = util.get_tensors_from_tensor_names(sess.graph, ["Placeholder"])
|
|
self.assertEqual("Placeholder:0", tensors[0].name)
|
|
|
|
def testGetTensorsInvalid(self):
|
|
with ops.Graph().as_default():
|
|
in_tensor = array_ops.placeholder(
|
|
dtype=dtypes.float32, shape=[1, 16, 16, 3])
|
|
_ = in_tensor + in_tensor
|
|
sess = session.Session()
|
|
|
|
with self.assertRaises(ValueError) as error:
|
|
util.get_tensors_from_tensor_names(sess.graph, ["invalid-input"])
|
|
self.assertEqual("Invalid tensors 'invalid-input' were found.",
|
|
str(error.exception))
|
|
|
|
def testSetTensorShapeValid(self):
|
|
with ops.Graph().as_default():
|
|
tensor = array_ops.placeholder(dtype=dtypes.float32, shape=[None, 3, 5])
|
|
self.assertAllEqual([None, 3, 5], tensor.shape)
|
|
|
|
util.set_tensor_shapes([tensor], {"Placeholder": [5, 3, 5]})
|
|
self.assertAllEqual([5, 3, 5], tensor.shape)
|
|
|
|
def testSetTensorShapeNoneValid(self):
|
|
with ops.Graph().as_default():
|
|
tensor = array_ops.placeholder(dtype=dtypes.float32)
|
|
|
|
util.set_tensor_shapes([tensor], {"Placeholder": [1, 3, 5]})
|
|
self.assertAllEqual([1, 3, 5], tensor.shape)
|
|
|
|
def testSetTensorShapeArrayInvalid(self):
|
|
# Tests set_tensor_shape where the tensor name passed in doesn't exist.
|
|
with ops.Graph().as_default():
|
|
tensor = array_ops.placeholder(dtype=dtypes.float32, shape=[None, 3, 5])
|
|
self.assertAllEqual([None, 3, 5], tensor.shape)
|
|
|
|
with self.assertRaises(ValueError) as error:
|
|
util.set_tensor_shapes([tensor], {"invalid-input": [5, 3, 5]})
|
|
self.assertEqual(
|
|
"Invalid tensor 'invalid-input' found in tensor shapes map.",
|
|
str(error.exception))
|
|
self.assertAllEqual([None, 3, 5], tensor.shape)
|
|
|
|
def testSetTensorShapeDimensionInvalid(self):
|
|
# Tests set_tensor_shape where the shape passed in is incompatible.
|
|
with ops.Graph().as_default():
|
|
tensor = array_ops.placeholder(dtype=dtypes.float32, shape=[None, 3, 5])
|
|
self.assertAllEqual([None, 3, 5], tensor.shape)
|
|
|
|
with self.assertRaises(ValueError) as error:
|
|
util.set_tensor_shapes([tensor], {"Placeholder": [1, 5, 5]})
|
|
self.assertIn("The shape of tensor 'Placeholder' cannot be changed",
|
|
str(error.exception))
|
|
self.assertAllEqual([None, 3, 5], tensor.shape)
|
|
|
|
def testSetTensorShapeEmpty(self):
|
|
with ops.Graph().as_default():
|
|
tensor = array_ops.placeholder(dtype=dtypes.float32, shape=[None, 3, 5])
|
|
self.assertAllEqual([None, 3, 5], tensor.shape)
|
|
|
|
util.set_tensor_shapes([tensor], {})
|
|
self.assertAllEqual([None, 3, 5], tensor.shape)
|
|
|
|
|
|
def _get_keras_model(add_unquantizable_layer=False):
|
|
"""Define Sample keras model and returns it."""
|
|
# Define a pseudo MNIST dataset (as downloading the dataset on-the-fly causes
|
|
# network connection failures)
|
|
n = 10 # Number of samples
|
|
images = np.random.randint(low=0, high=255, size=[n, 28, 28], dtype=np.uint8)
|
|
labels = np.random.randint(low=0, high=9, size=(n,), dtype=np.uint8)
|
|
|
|
# Normalize the input image so that each pixel value is between 0 to 1.
|
|
images = images / 255.0
|
|
|
|
# Define TF model
|
|
model = tf.keras.Sequential([
|
|
tf.keras.layers.InputLayer(input_shape=(28, 28)),
|
|
tf.keras.layers.Reshape(target_shape=(28, 28, 1)),
|
|
tf.keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation="relu"),
|
|
tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
|
|
tf.keras.layers.Flatten(),
|
|
tf.keras.layers.Dense(10)
|
|
])
|
|
if add_unquantizable_layer:
|
|
# This adds Neg op to the model which will remain as float.
|
|
model.add(tf.keras.layers.Lambda(lambda x: -x))
|
|
|
|
# Train
|
|
model.compile(
|
|
optimizer="adam",
|
|
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
|
|
metrics=["accuracy"])
|
|
|
|
model.fit(
|
|
images,
|
|
labels,
|
|
epochs=1,
|
|
validation_split=0.1,
|
|
)
|
|
|
|
return model
|
|
|
|
|
|
def _generate_integer_tflite_model(quantization_type=dtypes.int8,
|
|
use_saved_model=False,
|
|
saved_model_dir=None,
|
|
add_unquantizable_layer=False):
|
|
"""Define an integer post-training quantized tflite model."""
|
|
|
|
model = _get_keras_model(add_unquantizable_layer)
|
|
if not use_saved_model:
|
|
# Convert TF Model to an Integer Quantized TFLite Model
|
|
converter = lite.TFLiteConverterV2.from_keras_model(model)
|
|
else:
|
|
tf.saved_model.save(model, saved_model_dir)
|
|
converter = lite.TFLiteConverterV2.from_saved_model(saved_model_dir)
|
|
converter.optimizations = {lite.Optimize.DEFAULT}
|
|
|
|
def representative_dataset_gen():
|
|
for _ in range(2):
|
|
yield [
|
|
np.random.uniform(low=0, high=1, size=(1, 28, 28)).astype(np.float32)
|
|
]
|
|
|
|
converter.representative_dataset = representative_dataset_gen
|
|
if quantization_type == dtypes.int8:
|
|
converter.target_spec.supported_ops = {lite.OpsSet.TFLITE_BUILTINS_INT8}
|
|
else:
|
|
converter.target_spec.supported_ops = {
|
|
lite.OpsSet
|
|
.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8
|
|
}
|
|
tflite_model = converter.convert()
|
|
|
|
return tflite_model
|
|
|
|
|
|
def _test_param_modify_integer_model_io_type():
|
|
"""Function to generate parameterized inputs for testing."""
|
|
params = []
|
|
str_template = "_{}{}{}{}"
|
|
map_model_type = {
|
|
"PostTraining": True,
|
|
# "DuringTraining": False,
|
|
}
|
|
map_quantize_type_to_io_types = {
|
|
tf.int8: {tf.float32, tf.int8, tf.uint8},
|
|
tf.int16: {tf.float32, tf.int16}
|
|
}
|
|
for k1, v1 in map_model_type.items():
|
|
for qtype, v2 in map_quantize_type_to_io_types.items():
|
|
qstr = "_IntegerQuantize{}".format(qtype.name.capitalize())
|
|
for itype in v2:
|
|
istr = "_Input{}".format(itype.name.capitalize())
|
|
for otype in v2:
|
|
ostr = "_Output{}".format(otype.name.capitalize())
|
|
params.append((str_template.format(k1, qstr, istr,
|
|
ostr), v1, qtype, itype, otype))
|
|
return params
|
|
|
|
|
|
class UtilModifyIntegerQuantizedModelIOTypeTest(test_util.TensorFlowTestCase,
|
|
parameterized.TestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(UtilModifyIntegerQuantizedModelIOTypeTest, cls).setUpClass()
|
|
cls.post_train_int8_model = _generate_integer_tflite_model()
|
|
cls.post_train_int16_model = _generate_integer_tflite_model(
|
|
quantization_type=dtypes.int16)
|
|
|
|
@parameterized.named_parameters(_test_param_modify_integer_model_io_type())
|
|
def test(self, is_post_train, quantization_type, in_tftype, out_tftype):
|
|
"""Modify the float input/output type of an integer quantized model."""
|
|
|
|
def _run_tflite_inference(model, in_tftype, out_tftype):
|
|
"""Run inference on a model with a specific input/output type."""
|
|
# Load TFLite model and allocate tensors.
|
|
interpreter = lite.Interpreter(model_content=model)
|
|
interpreter.allocate_tensors()
|
|
input_details = interpreter.get_input_details()[0]
|
|
output_details = interpreter.get_output_details()[0]
|
|
|
|
# Validate TFLite model input and output types
|
|
self.assertEqual(input_details["dtype"], in_tftype.as_numpy_dtype)
|
|
self.assertEqual(output_details["dtype"], out_tftype.as_numpy_dtype)
|
|
|
|
# Define Input
|
|
np.random.seed(0)
|
|
input_data = np.random.uniform(low=0, high=1, size=(1, 28, 28))
|
|
input_data = input_data.astype(np.float32)
|
|
if input_details["dtype"] != np.float32:
|
|
# quantize float to int
|
|
scale, zero_point = input_details["quantization"]
|
|
input_data = input_data / scale + zero_point
|
|
input_data = input_data.astype(input_details["dtype"])
|
|
|
|
# Run Inference
|
|
interpreter.set_tensor(input_details["index"], input_data)
|
|
interpreter.invoke()
|
|
|
|
# Get output
|
|
output_data = interpreter.get_tensor(output_details["index"])[0]
|
|
if output_details["dtype"] != np.float32:
|
|
# dequantize int to float
|
|
scale, zero_point = output_details["quantization"]
|
|
output_data = output_data.astype(np.float32)
|
|
output_data = (output_data - zero_point) * scale
|
|
|
|
return output_data
|
|
|
|
if is_post_train and quantization_type == tf.int8:
|
|
model = self.__class__.post_train_int8_model
|
|
elif is_post_train and quantization_type == tf.int16:
|
|
model = self.__class__.post_train_int16_model
|
|
else:
|
|
model = None
|
|
# Run model inference with float input output type
|
|
output_data = _run_tflite_inference(model, tf.float32, tf.float32)
|
|
# Modify the model io types to the target input/output types.
|
|
model_io = util.modify_model_io_type(model, in_tftype, out_tftype)
|
|
# Run model inference with modified integer input output type
|
|
output_io_data = _run_tflite_inference(model_io, in_tftype, out_tftype)
|
|
# Validate that both the outputs are the same
|
|
self.assertAllClose(output_data, output_io_data, atol=1.0)
|
|
|
|
# Modify the model with the target input/output types should be a no op.
|
|
model_io = util.modify_model_io_type(model_io, in_tftype, out_tftype)
|
|
# Run model inference with modified integer input output type
|
|
output_io_data = _run_tflite_inference(model_io, in_tftype, out_tftype)
|
|
# Validate that both the outputs are the same
|
|
self.assertAllClose(output_data, output_io_data, atol=1.0)
|
|
|
|
|
|
class UtilModifyIntegerQuantizedModelIOTypeSignatureDefTest(
|
|
test_util.TensorFlowTestCase):
|
|
|
|
def _generate_integer_tflite_model_from_saved_model(self):
|
|
"""Define an integer post-training quantized model from saved model."""
|
|
|
|
saved_model_dir = os.path.join(self.get_temp_dir(), "simple_savedmodel")
|
|
return _generate_integer_tflite_model(
|
|
use_saved_model=True,
|
|
saved_model_dir=saved_model_dir,
|
|
add_unquantizable_layer=True)
|
|
|
|
def test(self):
|
|
"""Makes sure modifying IO types updates Signature correctly."""
|
|
post_train_int8_model = (
|
|
self._generate_integer_tflite_model_from_saved_model())
|
|
modified_model = util.modify_model_io_type(post_train_int8_model, tf.int8,
|
|
tf.float32)
|
|
interpreter = lite.Interpreter(model_content=modified_model)
|
|
input_details = interpreter.get_input_details()
|
|
output_details = interpreter.get_output_details()
|
|
signature = interpreter._get_full_signature_list()
|
|
input_ids = []
|
|
output_ids = []
|
|
for input_tensor in input_details:
|
|
input_ids.append(input_tensor["index"])
|
|
for output_tensor in output_details:
|
|
output_ids.append(output_tensor["index"])
|
|
for _, tensor_id in signature["serving_default"]["inputs"].items():
|
|
assert tensor_id in input_ids
|
|
for _, tensor_id in signature["serving_default"]["outputs"].items():
|
|
assert tensor_id in output_ids
|
|
|
|
|
|
class UtilModifyIntegerQuantizedConcatResidualModelIOTypeTest(
|
|
test_util.TensorFlowTestCase, parameterized.TestCase
|
|
):
|
|
|
|
def _generate_int8_f32io_concat_residual_tflite(self, number_of_inputs=3):
|
|
dtype = float
|
|
|
|
class ConcatNResidual(tf.keras.layers.Layer):
|
|
"""A simple concat and residual Keras Model."""
|
|
|
|
def __init__(self, number_of_inputs=3, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.number_of_inputs = number_of_inputs
|
|
self.conv = tf.keras.layers.Conv2D(2, (2, 2), padding="same")
|
|
self.mins = [-0.01 * (i + 1) for i in range(self.number_of_inputs)]
|
|
self.maxs = [0.01 * (i + 1) for i in range(self.number_of_inputs)]
|
|
|
|
def call(self, inputs):
|
|
xs = [
|
|
tf.quantization.fake_quant_with_min_max_args(
|
|
inputs[i], self.mins[i], self.maxs[i]
|
|
)
|
|
for i in range(self.number_of_inputs)
|
|
]
|
|
x = tf.keras.backend.concatenate(xs, 1)
|
|
x = x[:, : inputs[-1].shape[1]]
|
|
x = x + xs[-1]
|
|
x = tf.quantization.fake_quant_with_min_max_args(x, -2.242, 2.242)
|
|
return x
|
|
|
|
inputs = [
|
|
tf.keras.layers.Input(shape=(2, 2, 2), batch_size=1, dtype=dtype)
|
|
for _ in range(number_of_inputs)
|
|
]
|
|
outputs = ConcatNResidual(number_of_inputs)(inputs)
|
|
model = tf.keras.Model(inputs, outputs)
|
|
|
|
converter = lite.TFLiteConverterV2.from_keras_model(model)
|
|
converter.optimizations = [lite.Optimize.DEFAULT]
|
|
tflite_model = converter.convert()
|
|
return tflite_model
|
|
|
|
def _verify_tensor_connections(self, flatbuffer_model):
|
|
"""Verify that all the tensors have input and output ops except the tensors have buffer data."""
|
|
tflite_subgraph = flatbuffer_model.subgraphs[0]
|
|
tensors = tflite_subgraph.tensors
|
|
buffers = flatbuffer_model.buffers
|
|
tensors_used_as_inputs = set()
|
|
tensors_used_as_outputs = set()
|
|
for op in tflite_subgraph.operators:
|
|
tensors_used_as_inputs.update(
|
|
idx for idx in op.inputs if buffers[tensors[idx].buffer].data is None
|
|
)
|
|
tensors_used_as_outputs.update(idx for idx in op.outputs)
|
|
|
|
tensors_used_as_inputs.update(idx for idx in tflite_subgraph.outputs)
|
|
tensors_used_as_outputs.update(idx for idx in tflite_subgraph.inputs)
|
|
|
|
self.assertEqual(tensors_used_as_inputs, tensors_used_as_outputs)
|
|
|
|
@parameterized.named_parameters([
|
|
("_IntOnly_Float32InputOutput", tf.float32),
|
|
("_IntOnly_INT8InputOutput", tf.int8),
|
|
("_IntOnly_UINT8InputOutput", tf.uint8),
|
|
])
|
|
def test(self, inference_input_output_type):
|
|
"""Make sure modifying IO types removes tensors correctly."""
|
|
srqed_int8_f32io_model = self._generate_int8_f32io_concat_residual_tflite()
|
|
|
|
if inference_input_output_type != tf.float32:
|
|
target_model = util.modify_model_io_type(
|
|
srqed_int8_f32io_model,
|
|
inference_input_output_type,
|
|
inference_input_output_type,
|
|
)
|
|
else:
|
|
target_model = srqed_int8_f32io_model
|
|
|
|
tflite_path = os.path.join(self.get_temp_dir(), "concat_residual.tflite")
|
|
with tf.io.gfile.GFile(tflite_path, "wb") as writer:
|
|
writer.write(target_model)
|
|
flatbuffer_model = _read_model(tflite_path)
|
|
self._verify_tensor_connections(flatbuffer_model)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test.main()
|