conv_settings.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. # SPDX-FileCopyrightText: Copyright 2010-2024 Arm Limited and/or its affiliates <open-source-office@arm.com>
  2. #
  3. # SPDX-License-Identifier: Apache-2.0
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the License); you may
  6. # not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an AS IS BASIS, WITHOUT
  13. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. from test_settings import TestSettings
  18. import tensorflow as tf
  19. import numpy as np
  20. import math
  21. import tf_keras as keras
  22. class ConvSettings(TestSettings):
  23. def __init__(self,
  24. dataset,
  25. testtype,
  26. regenerate_weights,
  27. regenerate_input,
  28. regenerate_biases,
  29. schema_file,
  30. in_ch=1,
  31. out_ch=1,
  32. x_in=7,
  33. y_in=7,
  34. w_x=3,
  35. w_y=3,
  36. stride_x=2,
  37. stride_y=2,
  38. groups=1,
  39. pad=True,
  40. randmin=TestSettings.INT8_MIN,
  41. randmax=TestSettings.INT8_MAX,
  42. batches=1,
  43. generate_bias=True,
  44. relu6=False,
  45. out_activation_min=None,
  46. out_activation_max=None,
  47. int16xint8=False,
  48. bias_min=TestSettings.INT32_MIN,
  49. bias_max=TestSettings.INT32_MAX,
  50. dilation_x=1,
  51. dilation_y=1,
  52. interpreter="tensorflow",
  53. int4_weights=False):
  54. super().__init__(dataset,
  55. testtype,
  56. regenerate_weights,
  57. regenerate_input,
  58. regenerate_biases,
  59. schema_file,
  60. in_ch,
  61. out_ch,
  62. x_in,
  63. y_in,
  64. w_x,
  65. w_y,
  66. stride_x,
  67. stride_y,
  68. pad,
  69. randmin,
  70. randmax,
  71. batches,
  72. generate_bias=generate_bias,
  73. relu6=relu6,
  74. out_activation_min=out_activation_min,
  75. out_activation_max=out_activation_max,
  76. int16xint8=int16xint8,
  77. bias_min=bias_min,
  78. bias_max=bias_max,
  79. dilation_x=dilation_x,
  80. dilation_y=dilation_y,
  81. interpreter=interpreter,
  82. int4_weights=int4_weights)
  83. self.scaling_factors = []
  84. self.groups = groups
  85. if self.test_type == 'depthwise_conv':
  86. self.channel_multiplier = self.output_ch // self.input_ch
  87. if self.output_ch % self.input_ch != 0:
  88. raise RuntimeError("out channel ({}) is not multiple of in channel ({})".format(out_ch, in_ch))
  89. if groups != 1:
  90. raise RuntimeError("ERROR: Groups cannot be used for depthwise convolution")
  91. else:
  92. self.channel_multiplier = 0
  93. self.filter_ch = in_ch // groups
  94. if in_ch % groups != 0:
  95. raise RuntimeError("ERROR: Input channels {} must be an even multiple of groups {}".format(in_ch, groups))
  96. if out_ch % groups != 0:
  97. raise RuntimeError("ERROR: Output channels {} must be an even multiple of groups {}".format(out_ch, groups))
  98. if self.int4_weights:
  99. if self.test_type == 'conv':
  100. self.json_template = "TestCases/Common/conv2d_s4_weights_template.json"
  101. elif self.test_type == 'depthwise_conv':
  102. self.json_template = "TestCases/Common/dw_s4_weights_template.json"
  103. def write_c_config_header(self) -> None:
  104. super().write_c_config_header()
  105. filename = self.config_data
  106. filepath = self.headers_dir + filename
  107. prefix = self.testdataset.upper()
  108. with open(filepath, "a") as f:
  109. self.write_common_config(f, prefix)
  110. if self.test_type == 'depthwise_conv':
  111. f.write("#define {}_CH_MULT {}\n".format(prefix, self.channel_multiplier))
  112. f.write("#define {}_INPUT_OFFSET {}\n".format(prefix, -self.input_zero_point))
  113. f.write("#define {}_OUTPUT_OFFSET {}\n".format(prefix, self.output_zero_point))
  114. f.write("#define {}_DILATION_X {}\n".format(prefix, self.dilation_x))
  115. f.write("#define {}_DILATION_Y {}\n".format(prefix, self.dilation_y))
  116. if self.groups != 1:
  117. f.write("#define {}_FILTER_CH {}\n".format(prefix, self.filter_ch))
  118. if self.test_type == 'transpose_conv':
  119. f.write("#define {}_PAD_X_WITH_OFFSET {}\n".format(prefix, self.pad_x_with_offset))
  120. f.write("#define {}_PAD_Y_WITH_OFFSET {}\n".format(prefix, self.pad_y_with_offset))
  121. def generate_quantize_per_channel_multiplier(self):
  122. num_channels = self.output_ch
  123. per_channel_multiplier = []
  124. per_channel_shift = []
  125. if len(self.scaling_factors) != num_channels:
  126. raise RuntimeError("Missing scaling factors")
  127. for i in range(num_channels):
  128. effective_output_scale = self.input_scale * self.scaling_factors[i] / self.output_scale
  129. (quantized_multiplier, shift) = self.quantize_scale(effective_output_scale)
  130. per_channel_multiplier.append(quantized_multiplier)
  131. per_channel_shift.append(shift)
  132. return per_channel_multiplier, per_channel_shift
  133. def generate_int4_scale(self, scale, shift, input_scale):
  134. self.output_scale = scale
  135. self.output_zp = shift
  136. self.input_scale = input_scale
  137. self.scaling_factors = np.random.uniform(0.001, 0.01, [self.output_ch]).tolist()
  138. per_channel_multiplier, per_channel_shift = self.generate_quantize_per_channel_multiplier()
  139. while any((x > 31 or x < -31) for x in per_channel_shift):
  140. self.output_scale = self.output_scale / 10
  141. per_channel_multiplier, per_channel_shift = self.generate_quantize_per_channel_multiplier()
  142. return self.output_scale, self.output_zp
  143. # TODO
  144. def quantize_float_data(self, data=None, quantization_bit_range=8, quantization_type="affine", tf_tensor=False):
  145. if data is not None:
  146. if tf_tensor:
  147. data = data.numpy()
  148. data_max = np.amax(data)
  149. data_min = np.amin(data)
  150. if quantization_type.lower() == "affine":
  151. data_min = min(data_min, 0.0)
  152. data_max = max(data_max, 0.0)
  153. scale = (data_max - data_min) / (pow(2, quantization_bit_range) - 1)
  154. zero_point = -(round(data_max * scale)) - pow(2, quantization_bit_range - 1)
  155. zero_point = max(zero_point, pow(quantization_bit_range - 1) - 1)
  156. zero_point = min(zero_point, -pow(quantization_bit_range - 1))
  157. elif quantization_type.lower() == "symmetric":
  158. absolute_max = max(abs(data_min), abs(data_max))
  159. scale = absolute_max / (pow(2, quantization_bit_range - 1) - 1)
  160. zero_point = 0
  161. else:
  162. raise RuntimeError("Quantization scheme not supported")
  163. scale = 0.1 if scale == 0 else scale
  164. quantized_data = [(x // scale) + zero_point for x in data]
  165. return tf.convert_to_tensor(quantized_data), scale, zero_point
  166. def generate_data(self, input_data=None, weights=None, biases=None) -> None:
  167. if self.is_int16xint8:
  168. inttype = tf.int16
  169. datatype = "int16_t"
  170. bias_datatype = "int64_t"
  171. else:
  172. inttype = tf.int8
  173. datatype = "int8_t"
  174. bias_datatype = "int32_t"
  175. input_data = self.get_randomized_input_data(input_data)
  176. biases = self.get_randomized_bias_data(biases)
  177. if self.test_type == 'conv' or self.test_type == 'transpose_conv':
  178. out_channel = self.output_ch
  179. elif self.test_type == 'depthwise_conv':
  180. out_channel = self.channel_multiplier
  181. if self.int4_weights:
  182. w_shape = [self.filter_y * self.filter_x * self.input_ch * out_channel]
  183. if weights is not None:
  184. weights = tf.reshape(weights, w_shape)
  185. else:
  186. weights = self.get_randomized_data(w_shape,
  187. self.kernel_table_file,
  188. minrange=TestSettings.INT4_MIN,
  189. maxrange=TestSettings.INT4_MAX,
  190. decimals=1,
  191. regenerate=self.regenerate_new_weights)
  192. input_scale = 0.046774
  193. input_zp = -128
  194. if w_shape[0] % 2:
  195. weights = np.append(weights, [0])
  196. if self.test_type == 'depthwise_conv':
  197. bias_scale = [64751.269531] * self.output_ch
  198. bias_zp = [0] * self.output_ch
  199. if self.generate_bias:
  200. output_scale, output_zp = self.generate_int4_scale(4684910.0, -2, input_scale)
  201. else:
  202. output_scale = 0.525255
  203. output_zp = 2
  204. else:
  205. quant_bias, bias_scale, bias_zp = self.quantize_float_data(
  206. biases, quantization_bit_range=8, quantization_type="symmetric", tf_tensor=not self.generate_bias)
  207. bias_scale = [bias_scale] * self.output_ch
  208. bias_zp = [bias_zp] * self.output_ch
  209. output_scale = np.random.uniform(0.02, 0.06)
  210. output_zp = 0
  211. scaling_factors = np.random.uniform(0.001, 0.01, [self.output_ch]).tolist()
  212. w_zp = [0] * self.output_ch
  213. if self.has_padding:
  214. # TODO dilation with padding
  215. output_x = math.ceil(float(self.x_input) / float(self.stride_x))
  216. output_y = math.ceil(float(self.y_input) / float(self.stride_y))
  217. else:
  218. dilation_filter_x = (self.filter_x - 1) * (self.dilation_x - 1)
  219. dilation_filter_y = (self.filter_y - 1) * (self.dilation_y - 1)
  220. output_x = math.ceil(float(self.x_input - self.filter_x - dilation_filter_x + 1) / float(self.stride_x))
  221. output_y = math.ceil(float(self.y_input - self.filter_y - dilation_filter_y + 1) / float(self.stride_y))
  222. self.json_replacements = {
  223. "batches": self.batches,
  224. "input_ch": self.input_ch,
  225. "output_ch": self.output_ch,
  226. "input_x": self.x_input,
  227. "input_y": self.y_input,
  228. "weight_x": self.filter_x,
  229. "weight_y": self.filter_y,
  230. "output_x": output_x,
  231. "output_y": output_y,
  232. "input_scale": input_scale,
  233. "input_zp": input_zp,
  234. "w_scale": scaling_factors,
  235. "w_zp": w_zp,
  236. "bias_scale": bias_scale,
  237. "bias_zp": bias_zp,
  238. "output_scale": output_scale,
  239. "output_zp": output_zp,
  240. "stride_x": self.stride_x,
  241. "stride_y": self.stride_y,
  242. "dilation_x": self.dilation_x,
  243. "dilation_y": self.dilation_y,
  244. "type_pad": self.padding,
  245. "ch_mult": self.channel_multiplier
  246. }
  247. # Pack weights
  248. temp = np.reshape(weights, (len(weights) // 2, 2)).astype(np.uint8)
  249. temp = 0xff & ((0xf0 & (temp[:, 1] << 4)) | (temp[:, 0] & 0xf))
  250. weights = tf.convert_to_tensor(temp)
  251. # Generate tflite model
  252. if self.test_type == 'depthwise_conv':
  253. generated_json = self.generate_json_from_template(
  254. None, weights, int8_time_weights=True, bias_data=biases, bias_buffer=3)
  255. else:
  256. generated_json = self.generate_json_from_template(weights, int8_time_weights=False,
  257. bias_data=quant_bias, bias_buffer=2)
  258. self.flatc_generate_tflite(generated_json, self.schema_file)
  259. filter_index = 1
  260. bias_index = 2
  261. else:
  262. if self.test_type == 'transpose_conv':
  263. weight_shape = [self.filter_y, self.filter_x, out_channel, self.input_ch]
  264. else:
  265. weight_shape = [self.filter_y, self.filter_x, self.filter_ch, out_channel]
  266. if weights is not None:
  267. weights = tf.reshape(weights, weight_shape)
  268. else:
  269. weights = self.get_randomized_data(weight_shape,
  270. self.kernel_table_file,
  271. minrange=TestSettings.INT32_MIN,
  272. maxrange=TestSettings.INT32_MAX,
  273. decimals=1,
  274. regenerate=self.regenerate_new_weights)
  275. # Create a one layer Keras model.
  276. model = keras.models.Sequential()
  277. input_shape = (self.batches, self.y_input, self.x_input, self.input_ch)
  278. model.add(keras.layers.InputLayer(input_shape=input_shape[1:], batch_size=self.batches))
  279. if self.test_type == 'conv':
  280. conv_layer = keras.layers.Conv2D(self.output_ch,
  281. kernel_size=(self.filter_y, self.filter_x),
  282. strides=(self.stride_y, self.stride_x),
  283. padding=self.padding,
  284. input_shape=input_shape[1:],
  285. dilation_rate=(self.dilation_y, self.dilation_x),
  286. groups=self.groups)
  287. model.add(conv_layer)
  288. conv_layer.set_weights([weights, biases])
  289. elif self.test_type == 'depthwise_conv':
  290. depthwise_layer = keras.layers.DepthwiseConv2D(kernel_size=(self.filter_y, self.filter_x),
  291. strides=(self.stride_y, self.stride_x),
  292. padding=self.padding,
  293. depth_multiplier=self.channel_multiplier,
  294. input_shape=input_shape[1:],
  295. dilation_rate=(self.dilation_y, self.dilation_x))
  296. model.add(depthwise_layer)
  297. depthwise_layer.set_weights([weights, biases])
  298. elif self.test_type == 'transpose_conv':
  299. transposed_conv_layer = keras.layers.Conv2DTranspose(self.output_ch,
  300. kernel_size=(self.filter_y, self.filter_x),
  301. strides=(self.stride_y, self.stride_x),
  302. padding=self.padding,
  303. input_shape=input_shape[1:],
  304. dilation_rate=(self.dilation_y,
  305. self.dilation_x),
  306. use_bias=self.generate_bias)
  307. model.add(transposed_conv_layer)
  308. if self.generate_bias:
  309. transposed_conv_layer.set_weights([weights, biases])
  310. else:
  311. transposed_conv_layer.set_weights([weights])
  312. if self.test_type == 'transpose_conv' and self.generate_bias:
  313. filter_index = 3
  314. bias_index = 2
  315. elif self.is_int16xint8 and self.generate_bias:
  316. filter_index = 1
  317. bias_index = 2
  318. else:
  319. filter_index = 2
  320. bias_index = 1
  321. self.convert_model(model, inttype)
  322. interpreter = self.interpret_model(input_data, inttype)
  323. all_layers_details = interpreter.get_tensor_details()
  324. filter_layer = all_layers_details[filter_index]
  325. if self.test_type == 'transpose_conv' and not self.generate_bias:
  326. # TODO: real null bias for all operators and not only transpose conv.
  327. bias_layer = None
  328. biases = []
  329. else:
  330. bias_layer = all_layers_details[bias_index]
  331. if self.int4_weights:
  332. expected_weight_size = math.ceil(interpreter.get_tensor(filter_layer['index']).size / 2)
  333. else:
  334. expected_weight_size = interpreter.get_tensor(filter_layer['index']).size
  335. if weights.numpy().size != expected_weight_size or \
  336. (self.generate_bias and biases.numpy().size != interpreter.get_tensor(bias_layer['index']).size):
  337. raise RuntimeError(f"Dimension mismatch for {self.testdataset}")
  338. output_details = interpreter.get_output_details()
  339. self.x_output = output_details[0]['shape'][2]
  340. self.y_output = output_details[0]['shape'][1]
  341. if self.test_type == 'transpose_conv':
  342. self.calculate_padding(self.x_input, self.y_input, self.x_output, self.y_output)
  343. else:
  344. self.calculate_padding(self.x_output, self.y_output, self.x_input, self.y_input)
  345. self.generate_c_array(self.input_data_file_prefix, input_data, datatype=datatype)
  346. self.generate_c_array(
  347. self.weight_data_file_prefix, interpreter.get_tensor(filter_layer['index']), pack=self.int4_weights)
  348. self.scaling_factors = filter_layer['quantization_parameters']['scales']
  349. per_channel_multiplier, per_channel_shift = self.generate_quantize_per_channel_multiplier()
  350. self.generate_c_array("output_mult", per_channel_multiplier, datatype='int32_t')
  351. self.generate_c_array("output_shift", per_channel_shift, datatype='int32_t')
  352. if self.generate_bias:
  353. self.generate_c_array(
  354. self.bias_data_file_prefix, interpreter.get_tensor(bias_layer['index']), bias_datatype)
  355. else:
  356. self.generate_c_array(
  357. self.bias_data_file_prefix, biases, bias_datatype)
  358. # Generate reference
  359. interpreter.invoke()
  360. output_data = interpreter.get_tensor(output_details[0]["index"])
  361. self.generate_c_array(self.output_data_file_prefix,
  362. np.clip(output_data, self.out_activation_min, self.out_activation_max),
  363. datatype=datatype)
  364. self.write_c_config_header()
  365. self.write_c_header_wrapper()