|
|
@@ -63,7 +63,8 @@ def parse_args():
|
|
|
parser.add_argument('--regenerate-input', action='store_true', help="Regenerate and store new input.")
|
|
|
parser.add_argument('--regenerate-biases', action='store_true', help="Regenerate and store new biases.")
|
|
|
parser.add_argument('-a', '--regenerate-all', action='store_true', help="Regenerate and store all data.")
|
|
|
- parser.add_argument('-t', '--type', type=str, default='conv', choices=['conv', 'avgpool', 'maxpool'],
|
|
|
+ parser.add_argument('-t', '--type', type=str, default='conv', choices=['conv', 'depthwise_conv', 'avgpool',
|
|
|
+ 'maxpool'],
|
|
|
help='Type of test.')
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
@@ -97,6 +98,9 @@ class TestSettings(ABC):
|
|
|
self.stride_x = stride_x
|
|
|
self.stride_y = stride_y
|
|
|
self.batches = batches
|
|
|
+ self.channel_multiplier = out_ch // in_ch
|
|
|
+ if out_ch % in_ch != 0:
|
|
|
+ raise RuntimeError("out channel ({}) is not multiple of in channel ({})".format(out_ch, in_ch))
|
|
|
|
|
|
self.has_padding = pad
|
|
|
self.test_type = args.type
|
|
|
@@ -247,6 +251,8 @@ class TestSettings(ABC):
|
|
|
f.write("// Generated by {}\n".format(os.path.basename(__file__)))
|
|
|
f.write("#define {}_OUT_CH {}\n".format(prefix, self.output_ch))
|
|
|
f.write("#define {}_IN_CH {}\n".format(prefix, self.input_ch))
|
|
|
+ f.write("#define {}_CH_MULT {} "
|
|
|
+ "// (Only for depthwise conv)\n".format(prefix, self.channel_multiplier))
|
|
|
f.write("#define {}_INPUT_W {}\n".format(prefix, self.x_input))
|
|
|
f.write("#define {}_INPUT_H {}\n".format(prefix, self.y_input))
|
|
|
f.write("#define {}_FILTER_X {}\n".format(prefix, self.filter_x))
|
|
|
@@ -329,12 +335,23 @@ class ConvSettings(TestSettings):
|
|
|
super().__init__(args, in_ch, out_ch, x_in, y_in, w_x, w_y, stride_x, stride_y, pad, randmin, randmax,
|
|
|
outminrange, outmaxrange, batches)
|
|
|
|
|
|
+ if self.test_type == 'conv':
|
|
|
+ self.quantized_dimension = 0
|
|
|
+ elif self.test_type == 'depthwise_conv':
|
|
|
+ self.quantized_dimension = 3
|
|
|
+ else:
|
|
|
+ raise RuntimeError("Invalid test type {}".format(self.test_type))
|
|
|
+
|
|
|
def quantize_bias(self, nparray):
|
|
|
num_channels = self.output_ch
|
|
|
quantized_values = []
|
|
|
+
|
|
|
values = np.array(nparray)
|
|
|
|
|
|
def quantize_float_to_int(value, scale):
|
|
|
+ if scale == 0:
|
|
|
+ print("WARNING: scale is 0")
|
|
|
+ scale = 0.0000001
|
|
|
quantized = round(value / scale)
|
|
|
if quantized > self.INT_MAX:
|
|
|
quantized = self.INT_MAX
|
|
|
@@ -347,20 +364,46 @@ class ConvSettings(TestSettings):
|
|
|
|
|
|
return np.asarray(quantized_values)
|
|
|
|
|
|
- def reshape_kernel(self, kernel):
|
|
|
+ def reshape_conv_kernel(self, kernel):
|
|
|
+ """
|
|
|
+ TFL & TFLu conv weight format: kOHWI
|
|
|
+ Tensorflow conv weight format: kHWIO
|
|
|
+ """
|
|
|
kernel = tf.reshape(kernel, [self.output_ch, self.filter_y, self.filter_x, self.input_ch])
|
|
|
kernel = tf.transpose(kernel, (1, 2, 0, 3))
|
|
|
kernel = tf.transpose(kernel, (0, 1, 3, 2))
|
|
|
return kernel
|
|
|
|
|
|
+ def reshape_depthwise_conv_kernel(self, kernel):
|
|
|
+ """
|
|
|
+ TFL & TFLu depthwise conv weight format: k1HWO
|
|
|
+ Tensorflow depthwise conv weight format: kHWIM
|
|
|
+ """
|
|
|
+ kernel = tf.reshape(kernel, [1, self.filter_y, self.filter_x, self.output_ch])
|
|
|
+ kernel = tf.transpose(kernel, (1, 0, 2, 3))
|
|
|
+ kernel = tf.transpose(kernel, (0, 2, 1, 3))
|
|
|
+ kernel = tf.reshape(kernel, [self.filter_y, self.filter_x, self.input_ch, self.channel_multiplier])
|
|
|
+ return kernel
|
|
|
+
|
|
|
def quantize_filter(self, nparray):
|
|
|
- quantized_values = []
|
|
|
channel_count = self.output_ch
|
|
|
- input_size = self.filter_y * self.filter_x * self.input_ch * self.output_ch
|
|
|
+
|
|
|
+ if self.quantized_dimension == 0:
|
|
|
+ input_size = self.filter_y * self.filter_x * self.input_ch * self.output_ch
|
|
|
+ elif self.quantized_dimension == 3:
|
|
|
+ input_size = self.filter_y * self.filter_x * self.input_ch * self.channel_multiplier
|
|
|
+
|
|
|
per_channel_size = input_size // channel_count
|
|
|
+
|
|
|
+ if self.quantized_dimension == 0:
|
|
|
+ stride = 1
|
|
|
+ channel_stride = per_channel_size
|
|
|
+ elif self.quantized_dimension == 3:
|
|
|
+ stride = channel_count
|
|
|
+ channel_stride = 1
|
|
|
+
|
|
|
values = np.array(nparray)
|
|
|
- stride = 1
|
|
|
- channel_stride = per_channel_size
|
|
|
+ quantized_values = values.copy()
|
|
|
|
|
|
for channel in range(channel_count):
|
|
|
fmin = 0
|
|
|
@@ -378,7 +421,7 @@ class ConvSettings(TestSettings):
|
|
|
|
|
|
# Clamp
|
|
|
quantized_value = min(127, max(-127, quantized_value))
|
|
|
- quantized_values.append(quantized_value)
|
|
|
+ quantized_values[chs] = quantized_value
|
|
|
|
|
|
return np.asarray(quantized_values)
|
|
|
|
|
|
@@ -405,7 +448,7 @@ class ConvSettings(TestSettings):
|
|
|
self.generate_c_array("output_mult", per_channel_multiplier, datatype='int32_t')
|
|
|
self.generate_c_array("output_shift", per_channel_shift, datatype='int32_t')
|
|
|
|
|
|
- def convolution(self, indata, weights, bias=None):
|
|
|
+ def conv2d(self, indata, weights, bias=None):
|
|
|
indata = tf.cast(indata, dtype=tf.dtypes.float32)
|
|
|
weights = tf.cast(weights, dtype=tf.dtypes.float32)
|
|
|
bias = tf.cast(bias, dtype=tf.dtypes.float32)
|
|
|
@@ -421,6 +464,25 @@ class ConvSettings(TestSettings):
|
|
|
|
|
|
return out
|
|
|
|
|
|
+ def depthwise_conv2d(self, indata, weights, bias=None):
|
|
|
+ indata = tf.cast(indata, dtype=tf.dtypes.float32)
|
|
|
+ weights = tf.cast(weights, dtype=tf.dtypes.float32)
|
|
|
+ bias = tf.cast(bias, dtype=tf.dtypes.float32)
|
|
|
+
|
|
|
+ out = tf.nn.depthwise_conv2d(indata,
|
|
|
+ weights,
|
|
|
+ strides=[1, self.stride_y, self.stride_x, 1],
|
|
|
+ padding=self.padding)
|
|
|
+
|
|
|
+ if tf.TensorShape([self.batches, self.y_output, self.x_output, self.output_ch]) \
|
|
|
+ .as_list() != out.shape.as_list():
|
|
|
+ raise RuntimeError("Shape mismatch, regenerate data?")
|
|
|
+
|
|
|
+ out = tf.nn.bias_add(out, bias)
|
|
|
+ out = tf.clip_by_value(out, self.minrange, self.maxrange)
|
|
|
+
|
|
|
+ return out
|
|
|
+
|
|
|
def generate_data(self, input_data=None, weights=None, biases=None):
|
|
|
# Tensorflow Lite has a different kernel format compared to Tensorflow
|
|
|
reshaped_weights = None
|
|
|
@@ -432,13 +494,22 @@ class ConvSettings(TestSettings):
|
|
|
input_data = self.get_randomized_data([self.batches, self.y_input, self.x_input, self.input_ch],
|
|
|
self.inputs_table_file,
|
|
|
regenerate=self.regenerate_new_input)
|
|
|
+ if self.test_type == 'conv':
|
|
|
+ out_channel = self.output_ch
|
|
|
+ elif self.test_type == 'depthwise_conv':
|
|
|
+ out_channel = self.channel_multiplier
|
|
|
+
|
|
|
if weights is not None:
|
|
|
- weights = tf.reshape(weights, [self.filter_y, self.filter_x, self.input_ch, self.output_ch])
|
|
|
+ weights = tf.reshape(weights, [self.filter_y, self.filter_x, self.input_ch, out_channel])
|
|
|
else:
|
|
|
- weights = self.get_randomized_data([self.filter_y, self.filter_x, self.input_ch, self.output_ch],
|
|
|
+ weights = self.get_randomized_data([self.filter_y, self.filter_x, self.input_ch, out_channel],
|
|
|
self.kernel_table_file,
|
|
|
regenerate=self.regenerate_new_weights)
|
|
|
- reshaped_weights = self.reshape_kernel(weights)
|
|
|
+ if self.test_type == 'conv':
|
|
|
+ reshaped_weights = self.reshape_conv_kernel(weights)
|
|
|
+ elif self.test_type == 'depthwise_conv':
|
|
|
+ reshaped_weights = self.reshape_depthwise_conv_kernel(weights)
|
|
|
+
|
|
|
if biases is not None:
|
|
|
biases = tf.reshape(biases, [self.output_ch])
|
|
|
else:
|
|
|
@@ -446,8 +517,11 @@ class ConvSettings(TestSettings):
|
|
|
self.bias_table_file,
|
|
|
regenerate=self.regenerate_new_bias)
|
|
|
|
|
|
- # Generate conv reference
|
|
|
- conv = self.convolution(input_data, reshaped_weights, biases)
|
|
|
+ # Generate reference
|
|
|
+ if self.test_type == 'conv':
|
|
|
+ conv = self.conv2d(input_data, reshaped_weights, biases)
|
|
|
+ elif self.test_type == 'depthwise_conv':
|
|
|
+ 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))
|
|
|
@@ -513,22 +587,24 @@ if __name__ == '__main__':
|
|
|
|
|
|
args = parse_args()
|
|
|
|
|
|
- if args.type == 'conv':
|
|
|
- generator = ConvSettings(args, in_ch=4, out_ch=17, x_in=15, y_in=15, w_x=1, w_y=1, stride_x=1, stride_y=1,
|
|
|
- pad=False, randmin=1, randmax=4, outminrange=-126, outmaxrange=127)
|
|
|
- #conv_3
|
|
|
+ if args.type == 'conv' or args.type == 'depthwise_conv':
|
|
|
+ # kernel1x1
|
|
|
+ # generator = ConvSettings(args, in_ch=4, out_ch=17, x_in=15, y_in=15, w_x=1, w_y=1, stride_x=1, stride_y=1,
|
|
|
+ # pad=False, randmin=1, randmax=4, outminrange=-126, outmaxrange=127)
|
|
|
+
|
|
|
+ # conv_3
|
|
|
# generator = ConvSettings(args, in_ch=3, out_ch=1, x_in=10, y_in=49, w_x=4, w_y=10, stride_x=1, stride_y=2,
|
|
|
# pad=True, randmin=-2, randmax=2, outminrange=-127, outmaxrange=127)
|
|
|
|
|
|
- #conv_1_x_n_1
|
|
|
+ # conv_1_x_n_1
|
|
|
# generator = ConvSettings(args, in_ch=3, out_ch=3, x_in=5, y_in=5, w_x=2, w_y=1, stride_x=2, stride_y=1,
|
|
|
# pad=False, randmin=-2, randmax=2, outminrange=-127, outmaxrange=127, batches=2)
|
|
|
|
|
|
- #conv_1_x_n_2
|
|
|
+ # conv_1_x_n_2
|
|
|
# generator = ConvSettings(args, in_ch=3, out_ch=1, x_in=11, y_in=11, w_x=11, w_y=1, stride_x=1, stride_y=1,
|
|
|
# pad=True, randmin=-2, randmax=2, outminrange=-127, outmaxrange=127)
|
|
|
|
|
|
- #conv_1_x_n_3
|
|
|
+ # conv_1_x_n_3
|
|
|
# generator = ConvSettings(args, in_ch=1, out_ch=3, x_in=11, y_in=11, w_x=1, w_y=11, stride_x=1, stride_y=1,
|
|
|
# pad=True, randmin=-2, randmax=2, outminrange=-127, outmaxrange=127)
|
|
|
|
|
|
@@ -536,16 +612,23 @@ if __name__ == '__main__':
|
|
|
# generator = ConvSettings(args, in_ch=2, out_ch=4, x_in=6, y_in=3, w_x=3, w_y=3, stride_x=1, stride_y=1,
|
|
|
# pad=True, randmin=-2, randmax=2, outminrange=-126, outmaxrange=127)
|
|
|
|
|
|
- #conv_4 batches > 2
|
|
|
+ # conv_4 batches > 2
|
|
|
# generator = ConvSettings(args, in_ch=3, out_ch=3, x_in=5, y_in=5, w_x=2, w_y=3, stride_x=2, stride_y=2,
|
|
|
# pad=False, randmin=-2, randmax=2, outminrange=-127, outmaxrange=127, batches=3)
|
|
|
|
|
|
+ # depthwise_2
|
|
|
+ # generator = ConvSettings(args, in_ch=3, out_ch=9, x_in=6, y_in=5, w_x=3, w_y=4, stride_x=2, stride_y=2,
|
|
|
+ # pad=True, randmin=-2, randmax=2, outminrange=-126, outmaxrange=127)
|
|
|
|
|
|
+ # depthwise_kernel_3x3
|
|
|
+ # generator = ConvSettings(args, in_ch=5, out_ch=5, x_in=4, y_in=5, w_x=3, w_y=3, stride_x=2, stride_y=2,
|
|
|
+ # pad=True, randmin=-2, randmax=2, outminrange=-126, outmaxrange=127)
|
|
|
+
|
|
|
+ # depthwise_eq_in_out_ch
|
|
|
+ generator = ConvSettings(args, in_ch=6, out_ch=6, x_in=4, y_in=5, w_x=2, w_y=3, stride_x=1, stride_y=1,
|
|
|
+ pad=True, randmin=-2, randmax=2, outminrange=-126, outmaxrange=127)
|
|
|
+
|
|
|
+ # depthwise_eq_in_out_ch
|
|
|
elif args.type == 'avgpool' or args.type == 'maxpool':
|
|
|
generator = PoolingSettings(args, channels=8, x_in=22, y_in=12, stride_x=9, stride_y=5, w_x=6, w_y=5, pad=True)
|
|
|
-
|
|
|
generator.generate_data()
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|