|
|
@@ -57,8 +57,8 @@ def parse_args():
|
|
|
parser.add_argument('-a', '--regenerate-all', action='store_true', help="Regenerate and store all data.")
|
|
|
parser.add_argument('-t', '--testtype', type=str, default=None, choices=['conv', 'depthwise_conv', 'avgpool',
|
|
|
'maxpool', 'fully_connected', 'softmax',
|
|
|
- 'svdf'],
|
|
|
- help='Type of test.')
|
|
|
+ 'svdf', 'add', 'mul'],
|
|
|
+ help='Type of test. There are the operators that have unit tests.')
|
|
|
parser.add_argument('--run-all-testsets', action='store_true', help="Run the script for all existing test "
|
|
|
"sets. Regenerate all, partially all or no input data (output may still change, depending on"
|
|
|
" changes in script) depending on regenerate flags. If used together with the -t flag, only"
|
|
|
@@ -363,24 +363,31 @@ class TestSettings(ABC):
|
|
|
significand_q31 = round(significand * (1 << 31))
|
|
|
return significand_q31, shift
|
|
|
|
|
|
- def get_convolving_calib_data_func(self):
|
|
|
+ def get_convolving_calib_data_func(self, n_inputs):
|
|
|
def representative_data_gen():
|
|
|
- # testset = np.random.rand(self.batches, self.y_input, self.x_input, self.input_ch).astype('float32')
|
|
|
- testset = np.ones((self.batches, self.y_input, self.x_input, self.input_ch), dtype=np.float32)
|
|
|
- yield [testset]
|
|
|
+ representative_testsets = []
|
|
|
+ if n_inputs > 0:
|
|
|
+ for i in range(n_inputs):
|
|
|
+ representative_testsets.append(np.ones((self.batches, self.y_input, self.x_input, self.input_ch),
|
|
|
+ dtype=np.float32))
|
|
|
+ yield representative_testsets
|
|
|
+ else:
|
|
|
+ raise RuntimeError("Invalid number of representative test sets: {}. Must be more than 0".
|
|
|
+ format(self.test_type))
|
|
|
return representative_data_gen
|
|
|
|
|
|
- def convert_and_interpret(self, model, input_data, inttype):
|
|
|
+ def convert_and_interpret(self, model, inttype, input_data=None):
|
|
|
"""
|
|
|
Compile and convert a model to Tflite format, run interpreter and allocate tensors.
|
|
|
"""
|
|
|
model.compile(loss=tf.keras.losses.categorical_crossentropy,
|
|
|
optimizer=tf.keras.optimizers.Adam(),
|
|
|
metrics=['accuracy'])
|
|
|
+ n_inputs = len(model.inputs)
|
|
|
|
|
|
converter = tf.lite.TFLiteConverter.from_keras_model(model)
|
|
|
converter.optimizations = [tf.lite.Optimize.DEFAULT]
|
|
|
- converter.representative_dataset = self.get_convolving_calib_data_func()
|
|
|
+ converter.representative_dataset = self.get_convolving_calib_data_func(n_inputs)
|
|
|
if self.is_int16xint8:
|
|
|
converter.target_spec.supported_ops = [
|
|
|
tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8]
|
|
|
@@ -398,14 +405,15 @@ class TestSettings(ABC):
|
|
|
model_path=str(self.model_path_tflite), experimental_op_resolver_type=OpResolverType.BUILTIN_REF)
|
|
|
interpreter.allocate_tensors()
|
|
|
|
|
|
- input_details = interpreter.get_input_details()
|
|
|
output_details = interpreter.get_output_details()
|
|
|
-
|
|
|
- (self.input_scale, self.input_zero_point) = input_details[0]['quantization']
|
|
|
(self.output_scale, self.output_zero_point) = output_details[0]['quantization']
|
|
|
|
|
|
- # Set input tensors
|
|
|
- interpreter.set_tensor(input_details[0]["index"], tf.cast(input_data, inttype))
|
|
|
+ if input_data is not None:
|
|
|
+ input_details = interpreter.get_input_details()
|
|
|
+ (self.input_scale, self.input_zero_point) = input_details[0]['quantization']
|
|
|
+
|
|
|
+ # Set input tensors
|
|
|
+ interpreter.set_tensor(input_details[0]["index"], tf.cast(input_data, inttype))
|
|
|
|
|
|
return interpreter
|
|
|
|
|
|
@@ -511,8 +519,7 @@ class ConvSettings(TestSettings):
|
|
|
input_shape=input_shape[1:], dilation_rate=(self.dilation_y, self.dilation_x))
|
|
|
model.add(depthwise_layer)
|
|
|
depthwise_layer.set_weights([weights, biases])
|
|
|
-
|
|
|
- interpreter = self.convert_and_interpret(model, input_data, inttype)
|
|
|
+ interpreter = self.convert_and_interpret(model, inttype, input_data)
|
|
|
|
|
|
all_layers_details = interpreter.get_tensor_details()
|
|
|
filter_layer = all_layers_details[1]
|
|
|
@@ -580,7 +587,7 @@ class PoolingSettings(TestSettings):
|
|
|
else:
|
|
|
raise RuntimeError("Wrong test type")
|
|
|
|
|
|
- interpreter = self.convert_and_interpret(model, input_data, inttype)
|
|
|
+ interpreter = self.convert_and_interpret(model, inttype, input_data)
|
|
|
|
|
|
output_details = interpreter.get_output_details()
|
|
|
self.set_output_dims_and_padding(output_details[0]['shape'][2], output_details[0]['shape'][1])
|
|
|
@@ -671,7 +678,7 @@ class FullyConnectedSettings(TestSettings):
|
|
|
model.add(fully_connected_layer)
|
|
|
fully_connected_layer.set_weights([weights, biases])
|
|
|
|
|
|
- interpreter = self.convert_and_interpret(model, input_data, inttype)
|
|
|
+ interpreter = self.convert_and_interpret(model, inttype, input_data)
|
|
|
|
|
|
all_layers_details = interpreter.get_tensor_details()
|
|
|
if self.is_int16xint8:
|
|
|
@@ -766,7 +773,7 @@ class SoftmaxSettings(TestSettings):
|
|
|
input_shape = (self.y_input, self.x_input)
|
|
|
model.add(tf.keras.layers.Softmax(input_shape=input_shape[1:]))
|
|
|
|
|
|
- interpreter = self.convert_and_interpret(model, input_data, tf.int8)
|
|
|
+ interpreter = self.convert_and_interpret(model, tf.int8, input_data)
|
|
|
|
|
|
self.calc_softmax_params()
|
|
|
|
|
|
@@ -989,6 +996,110 @@ class SVDFSettings(TestSettings):
|
|
|
return generated_json_file
|
|
|
|
|
|
|
|
|
+class AddMulSettings(TestSettings):
|
|
|
+
|
|
|
+ def __init__(self, dataset, testtype, args, channels=1, x_in=4, y_in=4, decimal_input=6, randmin=INT8_MIN,
|
|
|
+ randmax=INT8_MAX, out_activation_min=INT8_MIN, out_activation_max=INT8_MAX, int16xint8=False):
|
|
|
+ super().__init__(dataset, testtype, args, in_ch=channels, out_ch=channels, x_in=x_in, y_in=y_in, w_x=1, w_y=1,
|
|
|
+ stride_x=1, stride_y=1, pad=False, randmin=randmin, randmax=randmax, batches=1,
|
|
|
+ generate_bias=False, relu6=False, out_activation_min=out_activation_min,
|
|
|
+ out_activation_max=out_activation_max, int16xint8=int16xint8)
|
|
|
+
|
|
|
+ self.x_input = self.x_output = x_in
|
|
|
+ self.y_input = self.y_output = y_in
|
|
|
+ self.decimal_input = decimal_input
|
|
|
+
|
|
|
+ self.left_shift = 15 if self.is_int16xint8 else 20
|
|
|
+
|
|
|
+ def generate_data(self, input_data1=None, input_data2=None):
|
|
|
+ input_shape = (1, self.y_input, self.x_input, self.input_ch)
|
|
|
+
|
|
|
+ input_data1 = self.get_randomized_data(list(input_shape),
|
|
|
+ self.inputs_table_file,
|
|
|
+ regenerate=self.regenerate_new_input,
|
|
|
+ decimals=self.decimal_input)
|
|
|
+ input_data2 = self.get_randomized_data(list(input_shape),
|
|
|
+ self.kernel_table_file,
|
|
|
+ regenerate=self.regenerate_new_weights,
|
|
|
+ decimals=self.decimal_input)
|
|
|
+
|
|
|
+ if self.is_int16xint8:
|
|
|
+ inttype = "int16_t"
|
|
|
+ inttype_tf = tf.int16
|
|
|
+ else:
|
|
|
+ inttype = "int8_t"
|
|
|
+ inttype_tf = tf.int8
|
|
|
+
|
|
|
+ # Create a one-layer functional Keras model as add/mul cannot use a sequntial Keras model.
|
|
|
+ input1 = tf.keras.layers.Input(shape=input_shape[1:])
|
|
|
+ input2 = tf.keras.layers.Input(shape=input_shape[1:])
|
|
|
+ if self.test_type == 'add':
|
|
|
+ layer = tf.keras.layers.Add()([input1, input2])
|
|
|
+ elif self.test_type == 'mul':
|
|
|
+ layer = tf.keras.layers.Multiply()([input1, input2])
|
|
|
+ else:
|
|
|
+ raise RuntimeError("Wrong test type")
|
|
|
+ out = tf.keras.layers.Lambda(function=lambda x: x)(layer)
|
|
|
+ model = tf.keras.models.Model(inputs=[input1, input2], outputs=out)
|
|
|
+
|
|
|
+ interpreter = self.convert_and_interpret(model, inttype_tf)
|
|
|
+
|
|
|
+ input_details = interpreter.get_input_details()
|
|
|
+ interpreter.set_tensor(input_details[0]["index"], tf.cast(input_data1, inttype_tf))
|
|
|
+ interpreter.set_tensor(input_details[1]["index"], tf.cast(input_data2, inttype_tf))
|
|
|
+
|
|
|
+ # Calculate multipliers, shifts and offsets.
|
|
|
+ (input1_scale, self.input1_zero_point) = input_details[0]['quantization']
|
|
|
+ (input2_scale, self.input2_zero_point) = input_details[1]['quantization']
|
|
|
+ self.input1_zero_point = -self.input1_zero_point
|
|
|
+ self.input2_zero_point = -self.input2_zero_point
|
|
|
+ double_max_input_scale = max(input1_scale, input2_scale) * 2
|
|
|
+ (self.input1_mult, self.input1_shift) = self.quantize_scale(input1_scale/double_max_input_scale)
|
|
|
+ (self.input2_mult, self.input2_shift) = self.quantize_scale(input2_scale/double_max_input_scale)
|
|
|
+
|
|
|
+ if self.test_type == 'add':
|
|
|
+ actual_output_scale = double_max_input_scale / ((1 << self.left_shift) * self.output_scale)
|
|
|
+ elif self.test_type == 'mul':
|
|
|
+ actual_output_scale = input1_scale * input2_scale / self.output_scale
|
|
|
+ (self.output_mult, self.output_shift) = self.quantize_scale(actual_output_scale)
|
|
|
+
|
|
|
+ # Generate reference.
|
|
|
+ interpreter.invoke()
|
|
|
+ output_details = interpreter.get_output_details()
|
|
|
+ output_data = interpreter.get_tensor(output_details[0]["index"])
|
|
|
+ self.generate_c_array("input1", input_data1, datatype=inttype)
|
|
|
+ self.generate_c_array("input2", input_data2, datatype=inttype)
|
|
|
+ self.generate_c_array("output_ref", np.clip(output_data, self.out_activation_min, self.out_activation_max),
|
|
|
+ datatype=inttype)
|
|
|
+
|
|
|
+ self.write_c_config_header()
|
|
|
+ self.write_c_header_wrapper()
|
|
|
+
|
|
|
+ def write_c_config_header(self):
|
|
|
+ super().write_c_config_header(write_common_parameters=False)
|
|
|
+
|
|
|
+ filename = self.config_data
|
|
|
+ filepath = self.headers_dir + filename
|
|
|
+ prefix = self.testdataset.upper()
|
|
|
+
|
|
|
+ with open(filepath, "a") as f:
|
|
|
+ f.write("#define {}_DST_SIZE {}\n".format(prefix,
|
|
|
+ self.batches * self.y_input * self.x_input * self.input_ch))
|
|
|
+ f.write("#define {}_OUT_ACTIVATION_MIN {}\n".format(prefix, self.out_activation_min))
|
|
|
+ f.write("#define {}_OUT_ACTIVATION_MAX {}\n".format(prefix, self.out_activation_max))
|
|
|
+ f.write("#define {}_INPUT1_OFFSET {}\n".format(prefix, self.input1_zero_point))
|
|
|
+ f.write("#define {}_INPUT2_OFFSET {}\n".format(prefix, self.input2_zero_point))
|
|
|
+ f.write("#define {}_OUTPUT_MULT {}\n".format(prefix, self.output_mult))
|
|
|
+ f.write("#define {}_OUTPUT_SHIFT {}\n".format(prefix, self.output_shift))
|
|
|
+ f.write("#define {}_OUTPUT_OFFSET {}\n".format(prefix, self.output_zero_point))
|
|
|
+ if self.test_type == 'add':
|
|
|
+ f.write("#define {}_LEFT_SHIFT {}\n".format(prefix, self.left_shift))
|
|
|
+ f.write("#define {}_INPUT1_SHIFT {}\n".format(prefix, self.input1_shift))
|
|
|
+ f.write("#define {}_INPUT2_SHIFT {}\n".format(prefix, self.input2_shift))
|
|
|
+ f.write("#define {}_INPUT1_MULT {}\n".format(prefix, self.input1_mult))
|
|
|
+ f.write("#define {}_INPUT2_MULT {}\n".format(prefix, self.input2_mult))
|
|
|
+
|
|
|
+
|
|
|
def load_all_testdatasets():
|
|
|
"""
|
|
|
Add all new testdata sets here
|
|
|
@@ -1235,21 +1346,34 @@ def load_all_testdatasets():
|
|
|
dataset = 'svdf'
|
|
|
ALL_TESTDATA_SETS[dataset] = SVDFSettings(dataset, type_of_test, args, batches=2, number_inputs=2, rank=8,
|
|
|
memory_size=8, input_size=3, number_units=3)
|
|
|
- type_of_test = 'svdf'
|
|
|
dataset = 'svdf_1'
|
|
|
ALL_TESTDATA_SETS[dataset] = SVDFSettings(dataset, type_of_test, args, batches=3, number_inputs=2, rank=1,
|
|
|
memory_size=2, input_size=7, number_units=5)
|
|
|
-
|
|
|
- type_of_test = 'svdf'
|
|
|
dataset = 'svdf_2'
|
|
|
ALL_TESTDATA_SETS[dataset] = SVDFSettings(dataset, type_of_test, args, batches=3, number_inputs=2, rank=2,
|
|
|
memory_size=2, input_size=7, number_units=5, generate_bias=False)
|
|
|
-
|
|
|
- type_of_test = 'svdf'
|
|
|
dataset = 'svdf_3'
|
|
|
ALL_TESTDATA_SETS[dataset] = SVDFSettings(dataset, type_of_test, args, batches=1, number_inputs=2, rank=1,
|
|
|
memory_size=2, input_size=20, number_units=12, generate_bias=False)
|
|
|
|
|
|
+ type_of_test = 'add'
|
|
|
+ dataset = 'add'
|
|
|
+ ALL_TESTDATA_SETS[dataset] = AddMulSettings(dataset, type_of_test, args, channels=8, x_in=4, y_in=4,
|
|
|
+ randmin=INT8_MIN, randmax=INT8_MAX)
|
|
|
+ dataset = 'add_s16'
|
|
|
+ ALL_TESTDATA_SETS[dataset] = AddMulSettings(dataset, type_of_test, args, channels=8, x_in=4, y_in=4,
|
|
|
+ randmin=INT16_MIN, randmax=INT16_MAX, out_activation_min=INT16_MIN,
|
|
|
+ out_activation_max=INT16_MAX, int16xint8=True)
|
|
|
+
|
|
|
+ type_of_test = 'mul'
|
|
|
+ dataset = 'mul'
|
|
|
+ ALL_TESTDATA_SETS[dataset] = AddMulSettings(dataset, type_of_test, args, channels=8, x_in=4, y_in=5,
|
|
|
+ randmin=INT8_MIN, randmax=INT8_MAX)
|
|
|
+ dataset = 'mul_s16'
|
|
|
+ ALL_TESTDATA_SETS[dataset] = AddMulSettings(dataset, type_of_test, args, channels=8, x_in=5, y_in=4,
|
|
|
+ randmin=INT16_MIN, randmax=INT16_MAX, out_activation_min=INT16_MIN,
|
|
|
+ out_activation_max=INT16_MAX, int16xint8=True)
|
|
|
+
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
if version.parse(tf.__version__) < REQUIRED_MINIMUM_TENSORFLOW_VERSION:
|
|
|
@@ -1295,6 +1419,8 @@ if __name__ == '__main__':
|
|
|
generator = SoftmaxSettings(testdataset, test_type, args)
|
|
|
elif args.testtype == 'svdf':
|
|
|
generator = SVDFSettings(testdataset, test_type, args)
|
|
|
+ elif args.testtype == 'add' or args.testtype == 'mul':
|
|
|
+ generator = AddMulSettings(testdataset, test_type, args)
|
|
|
else:
|
|
|
raise RuntimeError("Please specify type of test with -t")
|
|
|
generator.generate_data()
|