|
|
@@ -57,6 +57,13 @@ LICENSE = """/*
|
|
|
*/
|
|
|
"""
|
|
|
|
|
|
+INT32_MAX = 2147483647
|
|
|
+INT32_MIN = -2147483648
|
|
|
+INT16_MAX = 32767
|
|
|
+INT16_MIN = -32768
|
|
|
+INT8_MAX = 127
|
|
|
+INT8_MIN = -128
|
|
|
+
|
|
|
|
|
|
def parse_args():
|
|
|
parser = argparse.ArgumentParser(description="Generate input and refererence output data for unittests."
|
|
|
@@ -85,22 +92,13 @@ class TestSettings(ABC):
|
|
|
|
|
|
# This is input to the data generation. If everything or something is regenerated then it is overwritten.
|
|
|
# So it always has the same data as the OUTDIR.
|
|
|
- # The purpose of the pregen is primarily for debugging, as it enables to change a single parameter and see how the
|
|
|
- # output changes, without regenerating all input data.
|
|
|
+ # The purpose of the pregen is primarily for debugging, as it is enabling to change a single parameter and see how
|
|
|
+ # output changes (or not changes), without regenerating all input data.
|
|
|
# It also convinient when tesing changes in the script, to be able to run all test sets again.
|
|
|
PREGEN = 'PregeneratedData/'
|
|
|
|
|
|
- INT32_MAX = 2147483647
|
|
|
- INT32_MIN = -2147483648
|
|
|
- INT16_MAX = 32767
|
|
|
- INT16_MIN = -32767
|
|
|
- INT8_MAX = 127
|
|
|
- INT8_MIN = -128
|
|
|
- UINT8_MAX = 255
|
|
|
- UINT8_MIN = 0
|
|
|
-
|
|
|
def __init__(self, dataset, testtype, args, in_ch, out_ch, x_in, y_in, w_x, w_y, stride_x, stride_y, pad, randmin,
|
|
|
- randmax, outminrange=-128, outmaxrange=127, batches=1, generate_bias=True, relu6=False,
|
|
|
+ randmax, outminrange=INT8_MIN, outmaxrange=INT8_MAX, batches=1, generate_bias=True, relu6=False,
|
|
|
out_activation_min=None, out_activation_max=None):
|
|
|
|
|
|
self.tensor_flow_reference_version = ("// Generated by {} using TFL version {} as reference.\n".
|
|
|
@@ -109,6 +107,8 @@ class TestSettings(ABC):
|
|
|
# Randomization interval
|
|
|
self.mins = randmin
|
|
|
self.maxs = randmax
|
|
|
+ self.bias_mins = randmin
|
|
|
+ self.bias_maxs = randmax
|
|
|
|
|
|
self.relu6 = relu6
|
|
|
self.input_ch = in_ch
|
|
|
@@ -126,11 +126,11 @@ class TestSettings(ABC):
|
|
|
if out_activation_min:
|
|
|
self.out_activation_min = out_activation_min
|
|
|
else:
|
|
|
- self.out_activation_min = self.INT8_MIN
|
|
|
+ self.out_activation_min = INT8_MIN
|
|
|
if out_activation_max:
|
|
|
self.out_activation_max = out_activation_max
|
|
|
else:
|
|
|
- self.out_activation_max = self.INT8_MAX
|
|
|
+ self.out_activation_max = INT8_MAX
|
|
|
|
|
|
minrange = randmin - 1
|
|
|
maxrange = randmax + 1
|
|
|
@@ -174,18 +174,18 @@ class TestSettings(ABC):
|
|
|
def clamp(self, result, smallest, largest):
|
|
|
return max(smallest, min(result, largest))
|
|
|
|
|
|
- def quantize_input(self, value):
|
|
|
+ def quantize_input(self, value, quant_min_value=INT8_MIN, quant_max_value=INT8_MAX):
|
|
|
result = round(value / self.input_scale) + self.input_zero_point
|
|
|
- return self.clamp(result, self.INT8_MIN, self.INT8_MAX)
|
|
|
+ return self.clamp(result, quant_min_value, quant_max_value)
|
|
|
|
|
|
- def derive_scale_from_min_max(self, minrange, maxrange):
|
|
|
- scale = (maxrange - minrange) / ((self.INT8_MAX * 1.0) - self.INT8_MIN)
|
|
|
+ def derive_scale_from_min_max(self, minrange, maxrange, quant_min_value=INT8_MIN, quant_max_value=INT8_MAX):
|
|
|
+ scale = (maxrange - minrange) / ((quant_max_value * 1.0) - quant_min_value)
|
|
|
return scale
|
|
|
|
|
|
def derive_scale_and_zeropoint_from_min_max(self, minrange, maxrange):
|
|
|
scale = self.derive_scale_from_min_max(minrange, maxrange)
|
|
|
- zeropoint = self.INT8_MIN + int(-minrange / scale + 0.5)
|
|
|
- zeropoint = max(self.INT8_MIN, min(zeropoint, -self.INT8_MIN))
|
|
|
+ zeropoint = INT8_MIN + int(-minrange / scale + 0.5)
|
|
|
+ zeropoint = max(INT8_MIN, min(zeropoint, -INT8_MIN))
|
|
|
return (scale, zeropoint)
|
|
|
|
|
|
def save_multiple_dim_array_in_txt(self, file, data):
|
|
|
@@ -213,15 +213,18 @@ class TestSettings(ABC):
|
|
|
self.stride_x, self.stride_y, self.pad_x, self.pad_y, self.batches, self.has_padding) = \
|
|
|
(map(lambda x: x, params))
|
|
|
|
|
|
- def convert_tensor_np(self, tensor_in, converter):
|
|
|
+ def convert_tensor_np(self, tensor_in, converter, *qminmax):
|
|
|
w = tensor_in.numpy()
|
|
|
shape = w.shape
|
|
|
w = w.ravel()
|
|
|
- fw = converter(w)
|
|
|
+ if len(qminmax) == 2:
|
|
|
+ fw = converter(w, qminmax[0], qminmax[1])
|
|
|
+ else:
|
|
|
+ fw = converter(w)
|
|
|
fw.shape = shape
|
|
|
return tf.convert_to_tensor(fw)
|
|
|
|
|
|
- def convert_tensor(self, tensor_in, converter, params=None):
|
|
|
+ def convert_tensor(self, tensor_in, converter, *qminmax):
|
|
|
w = tensor_in.numpy()
|
|
|
shape = w.shape
|
|
|
w = w.ravel()
|
|
|
@@ -229,8 +232,8 @@ class TestSettings(ABC):
|
|
|
float_normal = []
|
|
|
|
|
|
for i in normal:
|
|
|
- if params:
|
|
|
- float_normal.append(converter(i, params))
|
|
|
+ if len(qminmax) == 2:
|
|
|
+ float_normal.append(converter(i, qminmax[0], qminmax[1]))
|
|
|
else:
|
|
|
float_normal.append(converter(i))
|
|
|
|
|
|
@@ -249,7 +252,7 @@ class TestSettings(ABC):
|
|
|
if not os.path.exists(regendir):
|
|
|
os.makedirs(regendir)
|
|
|
if decimals == 0:
|
|
|
- data = tf.Variable(tf.random.uniform(dims, minval=minrange, maxval=maxrange, dtype=tf.dtypes.int32))
|
|
|
+ data = tf.Variable(tf.random.uniform(dims, minval=minrange, maxval=maxrange, dtype=tf.dtypes.int64))
|
|
|
data = tf.cast(data, dtype=tf.float32)
|
|
|
else:
|
|
|
data = tf.Variable(tf.random.uniform(dims, minval=minrange, maxval=maxrange, dtype=tf.dtypes.float32))
|
|
|
@@ -282,7 +285,9 @@ class TestSettings(ABC):
|
|
|
else:
|
|
|
biases = self.get_randomized_data([self.output_ch],
|
|
|
self.bias_table_file,
|
|
|
- regenerate=self.regenerate_new_bias)
|
|
|
+ regenerate=self.regenerate_new_bias,
|
|
|
+ minrange=self.bias_mins,
|
|
|
+ maxrange=self.bias_maxs)
|
|
|
return biases
|
|
|
|
|
|
def format_output_file(self, file):
|
|
|
@@ -457,13 +462,36 @@ class TestSettings(ABC):
|
|
|
class ConvSettings(TestSettings):
|
|
|
|
|
|
def __init__(self, dataset, testtype, args, in_ch=1, out_ch=1, x_in=7, y_in=7, w_x=3, w_y=3, stride_x=2, stride_y=2,
|
|
|
- pad=True, randmin=-7, randmax=7, outminrange=-128, outmaxrange=127, batches=1, generate_bias=True,
|
|
|
- relu6=False, out_activation_min=None, out_activation_max=None):
|
|
|
+ pad=True, randmin=-7, randmax=7, outminrange=INT8_MIN, outmaxrange=INT8_MAX, batches=1,
|
|
|
+ generate_bias=True, relu6=False, out_activation_min=None, out_activation_max=None,
|
|
|
+ int16xint8=False, input_scale=None, bias_min=None, bias_max=None):
|
|
|
super().__init__(dataset, testtype, args, in_ch, out_ch, x_in, y_in, w_x, w_y, stride_x, stride_y, pad,
|
|
|
randmin, randmax, outminrange, outmaxrange, batches, generate_bias=generate_bias, relu6=relu6,
|
|
|
out_activation_min=out_activation_min, out_activation_max=out_activation_max)
|
|
|
|
|
|
self.scaling_factors = []
|
|
|
+ self.is_int16xint8 = int16xint8
|
|
|
+
|
|
|
+ if bias_min:
|
|
|
+ self.bias_mins = bias_min
|
|
|
+ if bias_max:
|
|
|
+ self.bias_maxs = bias_max
|
|
|
+ if self.is_int16xint8:
|
|
|
+ if input_scale:
|
|
|
+ self.input_scale = input_scale
|
|
|
+ else:
|
|
|
+ self.input_scale = self.derive_scale_from_min_max(self.mins, self.maxs, INT16_MIN, INT16_MAX)
|
|
|
+ self.input_zero_point = 0
|
|
|
+ self.output_scale = self.derive_scale_from_min_max(outminrange, outmaxrange, INT16_MIN, INT16_MAX)
|
|
|
+ self.output_zero_point = 0
|
|
|
+ if out_activation_min:
|
|
|
+ self.out_activation_min = out_activation_min
|
|
|
+ else:
|
|
|
+ self.out_activation_min = INT16_MIN
|
|
|
+ if out_activation_max:
|
|
|
+ self.out_activation_max = out_activation_max
|
|
|
+ else:
|
|
|
+ self.out_activation_max = INT16_MAX
|
|
|
|
|
|
if self.test_type == 'conv':
|
|
|
self.quantized_dimension = 0
|
|
|
@@ -489,7 +517,7 @@ class ConvSettings(TestSettings):
|
|
|
if self.test_type == 'depthwise_conv':
|
|
|
f.write("#define {}_CH_MULT {}\n".format(prefix, self.channel_multiplier))
|
|
|
|
|
|
- def quantize_bias(self, nparray):
|
|
|
+ def quantize_bias(self, nparray, quant_min_value=INT16_MIN, quant_max_value=INT16_MAX):
|
|
|
num_channels = self.output_ch
|
|
|
quantized_values = []
|
|
|
|
|
|
@@ -500,10 +528,10 @@ class ConvSettings(TestSettings):
|
|
|
print("WARNING: scale is 0")
|
|
|
scale = 0.0000001
|
|
|
quantized = round(value / scale)
|
|
|
- if quantized > self.INT16_MAX:
|
|
|
- quantized = self.INT16_MAX
|
|
|
- elif quantized < self.INT16_MIN:
|
|
|
- quantized = self.INT16_MIN
|
|
|
+ if quantized > quant_max_value:
|
|
|
+ quantized = quant_max_value
|
|
|
+ elif quantized < quant_min_value:
|
|
|
+ quantized = quant_min_value
|
|
|
return quantized
|
|
|
|
|
|
for x in range(num_channels):
|
|
|
@@ -539,7 +567,7 @@ class ConvSettings(TestSettings):
|
|
|
fmin = min(fmin, values[idx])
|
|
|
fmax = max(fmax, values[idx])
|
|
|
|
|
|
- self.scaling_factors.append(max(abs(fmin), abs(fmax)) / self.INT8_MAX)
|
|
|
+ self.scaling_factors.append(max(abs(fmin), abs(fmax)) / INT8_MAX)
|
|
|
|
|
|
for x in range(per_channel_size):
|
|
|
chs = channel * channel_stride + x * stride
|
|
|
@@ -591,6 +619,21 @@ class ConvSettings(TestSettings):
|
|
|
# Tensorflow Lite has a different kernel format compared to Tensorflow
|
|
|
reshaped_weights = None
|
|
|
|
|
|
+ if self.is_int16xint8:
|
|
|
+ quant_max_value = INT16_MAX
|
|
|
+ quant_min_value = INT16_MIN
|
|
|
+ quant_bias_max_value = INT32_MAX
|
|
|
+ quant_bias_min_value = INT32_MIN
|
|
|
+ datatype = "q15_t"
|
|
|
+ bias_datatype = "int64_t"
|
|
|
+ else:
|
|
|
+ quant_bias_max_value = INT16_MAX
|
|
|
+ quant_bias_min_value = INT16_MIN
|
|
|
+ quant_max_value = INT8_MAX
|
|
|
+ quant_min_value = INT8_MIN
|
|
|
+ datatype = "q7_t"
|
|
|
+ bias_datatype = "int32_t"
|
|
|
+
|
|
|
input_data = self.get_randomized_input_data(input_data)
|
|
|
|
|
|
if self.test_type == 'conv':
|
|
|
@@ -619,11 +662,19 @@ class ConvSettings(TestSettings):
|
|
|
conv = self.depthwise_conv2d(input_data, reshaped_weights, biases)
|
|
|
|
|
|
# Quantize and write to C headers
|
|
|
- self.generate_c_array("input", self.convert_tensor(input_data, self.quantize_input))
|
|
|
+ self.generate_c_array("input",
|
|
|
+ self.convert_tensor(input_data,
|
|
|
+ self.quantize_input,
|
|
|
+ quant_min_value,
|
|
|
+ quant_max_value),
|
|
|
+ datatype=datatype)
|
|
|
self.generate_c_array("weights", self.convert_tensor_np(weights, self.quantize_filter))
|
|
|
- self.generate_c_array("biases", self.convert_tensor_np(biases, self.quantize_bias), "int32_t")
|
|
|
+ self.generate_c_array("biases", self.convert_tensor_np(biases,
|
|
|
+ self.quantize_bias,
|
|
|
+ quant_bias_min_value,
|
|
|
+ quant_bias_max_value), bias_datatype)
|
|
|
self.generate_quantize_per_channel_multiplier()
|
|
|
- self.generate_c_array("output_ref", self.convert_tensor(conv, self.quantize_output))
|
|
|
+ self.generate_c_array("output_ref", self.convert_tensor(conv, self.quantize_output), datatype=datatype)
|
|
|
|
|
|
self.write_c_config_header()
|
|
|
self.write_c_header_wrapper()
|
|
|
@@ -692,7 +743,7 @@ class PoolingSettings(TestSettings):
|
|
|
class FullyConnectedSettings(TestSettings):
|
|
|
|
|
|
def __init__(self, dataset, testtype, args, in_ch=1, out_ch=1, x_in=1, y_in=1, w_x=1, w_y=1, stride_x=1, stride_y=1,
|
|
|
- pad=False, randmin=-4, randmax=4, outminrange=-128, outmaxrange=127, batches=1, input_scale=1.0,
|
|
|
+ pad=False, randmin=-4, randmax=4, outminrange=INT8_MIN, outmaxrange=INT8_MAX, batches=1, input_scale=1.0,
|
|
|
input_zero_point=0, weights_scale=1.0, bias_scale=1.0, output_scale=1.0,
|
|
|
output_zero_point=0, generate_bias=True, out_activation_min=None, out_activation_max=None):
|
|
|
super().__init__(dataset, testtype, args, in_ch, out_ch, x_in, y_in, w_x, w_y, stride_x, stride_y, pad, randmin,
|
|
|
@@ -733,16 +784,16 @@ class FullyConnectedSettings(TestSettings):
|
|
|
|
|
|
def derive_filter_scale_and_zeropoint_from_min_max(self, mini, maxi):
|
|
|
scale = self.derive_scale_from_min_max(mini, maxi)
|
|
|
- zero = int(self.INT8_MIN + (-mini/scale + 0.5))
|
|
|
+ zero = int(INT8_MIN + (-mini/scale + 0.5))
|
|
|
return (scale, zero)
|
|
|
|
|
|
def quantize_bias(self, value):
|
|
|
result = int(value / self.bias_scale)
|
|
|
- return self.clamp(result, self.INT32_MIN, self.INT32_MAX)
|
|
|
+ return self.clamp(result, INT32_MIN, INT32_MAX)
|
|
|
|
|
|
def quantize_weights(self, value):
|
|
|
result = round(value / self.weights_scale)
|
|
|
- return self.clamp(result, self.INT8_MIN, self.INT8_MAX)
|
|
|
+ return self.clamp(result, INT8_MIN, INT8_MAX)
|
|
|
|
|
|
def generate_data(self, input_data=None, weights=None, biases=None):
|
|
|
input_data = self.get_randomized_input_data(input_data)
|
|
|
@@ -776,7 +827,7 @@ class SoftmaxSettings(TestSettings):
|
|
|
super().__init__(dataset, testtype, args, 1, 1, x_in, y_in, 1, 1, 1, 1, False, randmin,
|
|
|
randmax)
|
|
|
self.output_scale = 1 / 256
|
|
|
- self.output_zero_point = -128
|
|
|
+ self.output_zero_point = INT8_MIN
|
|
|
self.x_input = self.x_output = x_in
|
|
|
self.y_input = self.y_output = y_in
|
|
|
|
|
|
@@ -859,8 +910,8 @@ class SVDFSettings(TestSettings):
|
|
|
(self.multiplier_in, self.shift_1) = self.quantize_scale(effective_scale_1)
|
|
|
(self.multiplier_out, self.shift_2) = self.quantize_scale(effective_scale_2)
|
|
|
|
|
|
- self.in_activation_max = self.INT16_MAX
|
|
|
- self.in_activation_min = self.INT16_MIN
|
|
|
+ self.in_activation_max = INT16_MAX
|
|
|
+ self.in_activation_min = INT16_MIN
|
|
|
|
|
|
def write_c_config_header(self):
|
|
|
super().write_c_config_header(write_common_parameters=False)
|
|
|
@@ -888,15 +939,15 @@ class SVDFSettings(TestSettings):
|
|
|
|
|
|
def quantize_weights_feature(self, value):
|
|
|
result = round(value / self.weights_feature_scale)
|
|
|
- return self.clamp(result, self.INT8_MIN, self.INT8_MAX)
|
|
|
+ return self.clamp(result, INT8_MIN, INT8_MAX)
|
|
|
|
|
|
def quantize_weights_time(self, value):
|
|
|
result = round(value / self.weights_time_scale)
|
|
|
- return self.clamp(result, self.INT16_MIN, self.INT16_MAX)
|
|
|
+ return self.clamp(result, INT16_MIN, INT16_MAX)
|
|
|
|
|
|
def quantize_state(self, value):
|
|
|
result = round(value / self.state_scale)
|
|
|
- return self.clamp(result, self.INT16_MIN, self.INT16_MAX)
|
|
|
+ return self.clamp(result, INT16_MIN, INT16_MAX)
|
|
|
|
|
|
def quantize_bias(self, value):
|
|
|
result = round(value / self.bias_scale)
|
|
|
@@ -1080,6 +1131,16 @@ def load_all_testdatasets():
|
|
|
ALL_TESTDATA_SETS[dataset] = ConvSettings(dataset, type_of_test, args, in_ch=2, out_ch=2, x_in=3, y_in=3, w_x=3,
|
|
|
w_y=3, stride_x=1, stride_y=1, pad=True, randmin=-5, randmax=5,
|
|
|
out_activation_min=-55, out_activation_max=55)
|
|
|
+ dataset = 'int16xint8'
|
|
|
+ ALL_TESTDATA_SETS[dataset] = ConvSettings(dataset, type_of_test, args, in_ch=3, out_ch=4, x_in=7,
|
|
|
+ y_in=8, w_x=2, w_y=4, stride_x=2, stride_y=3, pad=True,
|
|
|
+ randmin=-4, randmax=4, outminrange=-32766, outmaxrange=32767,
|
|
|
+ int16xint8=True)
|
|
|
+ dataset = 'requantize_s64'
|
|
|
+ ALL_TESTDATA_SETS[dataset] = ConvSettings(dataset, type_of_test, args, in_ch=2, out_ch=2, x_in=3,
|
|
|
+ y_in=2, w_x=2, w_y=2, stride_x=1, stride_y=1, pad=False,
|
|
|
+ randmin=-7, randmax=8, outminrange=INT16_MIN, outmaxrange=INT16_MAX,
|
|
|
+ int16xint8=True, input_scale=0.5, bias_min=-0x300, bias_max=0x9fffffff)
|
|
|
|
|
|
type_of_test = 'depthwise_conv'
|
|
|
dataset = 'depthwise_2'
|