3 # Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT 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.
27 __slots__ = () # This prevents access via __dict__.
29 # (OPTION_NAME, HELP_MESSAGE)
30 ('O1', 'enable O1 optimization pass'),
31 ('convert_nchw_to_nhwc',
32 'Experimental: This will convert NCHW operators to NHWC under the assumption that input model is NCHW.'
34 ('expand_broadcast_const', 'expand broadcastable constant node inputs'),
35 ('nchw_to_nhwc_input_shape',
36 'convert the input shape of the model (argument for convert_nchw_to_nhwc)'),
37 ('nchw_to_nhwc_output_shape',
38 'convert the output shape of the model (argument for convert_nchw_to_nhwc)'),
39 ('fold_add_v2', 'fold AddV2 op with constant inputs'),
40 ('fold_cast', 'fold Cast op with constant input'),
41 ('fold_dequantize', 'fold Dequantize op'),
42 ('fold_dwconv', 'fold Depthwise Convolution op with constant inputs'),
43 ('fold_sparse_to_dense', 'fold SparseToDense op'),
44 ('forward_reshape_to_unaryop', 'Forward Reshape op'),
45 ('fuse_add_with_tconv', 'fuse Add op to Transposed'),
46 ('fuse_add_with_fully_connected', 'fuse Add op to FullyConnected op'),
47 ('fuse_batchnorm_with_conv', 'fuse BatchNorm op to Convolution op'),
48 ('fuse_batchnorm_with_dwconv', 'fuse BatchNorm op to Depthwise Convolution op'),
49 ('fuse_batchnorm_with_tconv', 'fuse BatchNorm op to Transposed Convolution op'),
50 ('fuse_bcq', 'apply Binary Coded Quantization'),
51 ('fuse_preactivation_batchnorm',
52 'fuse BatchNorm operators of pre-activations to Convolution op'),
53 ('fuse_mean_with_mean', 'fuse two consecutive Mean ops'),
54 ('fuse_transpose_with_mean',
55 'fuse Mean with a preceding Transpose under certain conditions'),
56 ('make_batchnorm_gamma_positive',
57 'make negative gamma of BatchNorm to a small positive value (1e-10).'
58 ' Note that this pass can change the execution result of the model.'
59 ' So, use it only when the impact is known to be acceptable.'),
60 ('fuse_activation_function', 'fuse Activation function to a preceding operator'),
61 ('fuse_instnorm', 'fuse ops to InstanceNorm operator'),
62 ('replace_cw_mul_add_with_depthwise_conv',
63 'replace channel-wise Mul/Add with DepthwiseConv2D'),
64 ('remove_fakequant', 'remove FakeQuant ops'),
65 ('remove_quantdequant', 'remove Quantize-Dequantize sequence'),
66 ('remove_redundant_reshape', 'fuse or remove subsequent Reshape ops'),
67 ('remove_redundant_transpose', 'fuse or remove subsequent Transpose ops'),
68 ('remove_unnecessary_reshape', 'remove unnecessary reshape ops'),
69 ('remove_unnecessary_slice', 'remove unnecessary slice ops'),
70 ('remove_unnecessary_strided_slice', 'remove unnecessary strided slice ops'),
71 ('remove_unnecessary_split', 'remove unnecessary split ops'),
72 ('resolve_customop_add', 'convert Custom(Add) op to Add op'),
73 ('resolve_customop_batchmatmul',
74 'convert Custom(BatchMatmul) op to BatchMatmul op'),
75 ('resolve_customop_matmul', 'convert Custom(Matmul) op to Matmul op'),
76 ('resolve_customop_max_pool_with_argmax',
77 'convert Custom(MaxPoolWithArgmax) to net of builtin operators'),
78 ('shuffle_weight_to_16x1float32',
79 'convert weight format of FullyConnected op to SHUFFLED16x1FLOAT32.'
80 ' Note that it only converts weights whose row is a multiple of 16'),
81 ('substitute_pack_to_reshape', 'convert single input Pack op to Reshape op'),
82 ('substitute_padv2_to_pad', 'convert certain condition PadV2 to Pad'),
83 ('substitute_splitv_to_split', 'convert certain condition SplitV to Split'),
84 ('substitute_squeeze_to_reshape', 'convert certain condition Squeeze to Reshape'),
85 ('substitute_strided_slice_to_reshape',
86 'convert certain condition StridedSlice to Reshape'),
87 ('substitute_transpose_to_reshape',
88 'convert certain condition Transpose to Reshape'),
89 ('transform_min_max_to_relu6', 'transform Minimum-Maximum pattern to Relu6 op'),
90 ('transform_min_relu_to_relu6', 'transform Minimum(6)-Relu pattern to Relu6 op'))
93 _CONSTANT = _CONSTANT()
96 def _add_default_arg(parser):
102 help='show program\'s version number and exit')
109 help='output additional information to stdout or stderr')
112 parser.add_argument('-C', '--config', type=str, help='run with configuation file')
113 # section name that you want to run in configuration file
114 parser.add_argument('-S', '--section', type=str, help=argparse.SUPPRESS)
117 def is_accumulated_arg(arg, driver):
118 if driver == "one-quantize":
119 if arg == "tensor_name" or arg == "scale" or arg == "zero_point":
125 def _is_valid_attr(args, attr):
126 return hasattr(args, attr) and getattr(args, attr)
129 def _parse_cfg_and_overwrite(config_path, section, args):
131 parse given section of configuration file and set the values of args.
132 Even if the values parsed from the configuration file already exist in args,
133 the values are overwritten.
135 if config_path == None:
138 config = configparser.ConfigParser()
139 # make option names case sensitive
140 config.optionxform = str
141 parsed = config.read(config_path)
143 raise FileNotFoundError('Not found given configuration file')
144 if not config.has_section(section):
145 raise AssertionError('configuration file doesn\'t have \'' + section +
147 for key in config[section]:
148 setattr(args, key, config[section][key])
149 # TODO support accumulated arguments
152 def _parse_cfg(args, driver_name):
153 """parse configuration file. If the option is directly given to the command line,
154 the option is processed prior to the configuration file.
155 That is, if the values parsed from the configuration file already exist in args,
156 the values are ignored."""
157 if _is_valid_attr(args, 'config'):
158 config = configparser.ConfigParser()
159 config.optionxform = str
160 config.read(args.config)
161 # if section is given, verify given section
162 if _is_valid_attr(args, 'section'):
163 if not config.has_section(args.section):
164 raise AssertionError('configuration file must have \'' + driver_name +
166 for key in config[args.section]:
167 if is_accumulated_arg(key, driver_name):
168 if not _is_valid_attr(args, key):
169 setattr(args, key, [config[args.section][key]])
171 getattr(args, key).append(config[args.section][key])
173 if not _is_valid_attr(args, key):
174 setattr(args, key, config[args.section][key])
175 # if section is not given, section name is same with its driver name
177 if not config.has_section(driver_name):
178 raise AssertionError('configuration file must have \'' + driver_name +
180 secton_to_run = driver_name
181 for key in config[secton_to_run]:
182 if is_accumulated_arg(key, driver_name):
183 if not _is_valid_attr(args, key):
184 setattr(args, key, [config[secton_to_run][key]])
186 getattr(args, key).append(config[secton_to_run][key])
188 if not _is_valid_attr(args, key):
189 setattr(args, key, config[secton_to_run][key])
192 def _make_tf2tfliteV2_cmd(args, driver_path, input_path, output_path):
193 """make a command for running tf2tfliteV2.py"""
194 cmd = [sys.executable, os.path.expanduser(driver_path)]
196 if _is_valid_attr(args, 'verbose'):
197 cmd.append('--verbose')
199 if _is_valid_attr(args, 'model_format_cmd'):
200 cmd.append(getattr(args, 'model_format_cmd'))
201 elif _is_valid_attr(args, 'model_format'):
202 cmd.append('--' + getattr(args, 'model_format'))
204 cmd.append('--graph_def') # default value
206 if _is_valid_attr(args, 'converter_version_cmd'):
207 cmd.append(getattr(args, 'converter_version_cmd'))
208 elif _is_valid_attr(args, 'converter_version'):
209 cmd.append('--' + getattr(args, 'converter_version'))
211 cmd.append('--v1') # default value
213 if _is_valid_attr(args, 'input_path'):
214 cmd.append('--input_path')
215 cmd.append(os.path.expanduser(input_path))
217 if _is_valid_attr(args, 'output_path'):
218 cmd.append('--output_path')
219 cmd.append(os.path.expanduser(output_path))
221 if _is_valid_attr(args, 'input_arrays'):
222 cmd.append('--input_arrays')
223 cmd.append(getattr(args, 'input_arrays'))
225 if _is_valid_attr(args, 'input_shapes'):
226 cmd.append('--input_shapes')
227 cmd.append(getattr(args, 'input_shapes'))
229 if _is_valid_attr(args, 'output_arrays'):
230 cmd.append('--output_arrays')
231 cmd.append(getattr(args, 'output_arrays'))
236 def _make_tflite2circle_cmd(driver_path, input_path, output_path):
237 """make a command for running tflite2circle"""
238 cmd = [driver_path, input_path, output_path]
239 return [os.path.expanduser(c) for c in cmd]
242 def _make_circle2circle_cmd(args, driver_path, input_path, output_path):
243 """make a command for running circle2circle"""
244 cmd = [os.path.expanduser(c) for c in [driver_path, input_path, output_path]]
246 if _is_valid_attr(args, 'generate_profile_data'):
247 cmd.append('--generate_profile_data')
248 # optimization pass(only true/false options)
249 # TODO support options whose number of arguments is more than zero
250 for opt in _CONSTANT.OPTIMIZATION_OPTS:
251 if _is_valid_attr(args, opt[0]):
253 if type(getattr(args, opt[0])) is bool:
254 cmd.append('--' + opt[0])
256 This condition check is for config file interface, usually would be
258 but user can write as follows while development
260 instead of removing SomeOption option
262 if type(getattr(args, opt[0])) is str and not getattr(
263 args, opt[0]).lower() in ['false', '0', 'n']:
264 cmd.append('--' + opt[0])
269 def _print_version_and_exit(file_path):
270 """print version of the file located in the file_path"""
271 script_path = os.path.realpath(file_path)
272 dir_path = os.path.dirname(script_path)
273 script_name = os.path.splitext(os.path.basename(script_path))[0]
275 subprocess.call([os.path.join(dir_path, 'one-version'), script_name])
279 def _safemain(main, mainpath):
280 """execute given method and print with program name for all uncaught exceptions"""
283 except Exception as e:
284 prog_name = os.path.basename(mainpath)
285 print(f"{prog_name}: {type(e).__name__}: " + str(e), file=sys.stderr)
289 def _run(cmd, err_prefix=None, logfile=None):
290 """Execute command in subprocess
293 cmd: command to be executed in subprocess
294 err_prefix: prefix to be put before every stderr lines
295 logfile: file stream to which both of stdout and stderr lines will be written
297 with subprocess.Popen(
298 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1) as p:
300 inputs = set([p.stdout, p.stderr])
302 readable, _, _ = select.select(inputs, [], [])
313 line = f"{err_prefix}: ".encode() + line
314 out.buffer.write(line)
318 if p.returncode != 0:
319 sys.exit(p.returncode)
322 def _remove_prefix(str, prefix):
323 if str.startswith(prefix):
324 return str[len(prefix):]
328 def _remove_suffix(str, suffix):
329 if str.endswith(suffix):
330 return str[:-len(suffix)]
334 def _get_optimization_list(get_name=False):
336 returns a list of optimization. If `get_name` is True,
337 only basename without extension is returned rather than full file path.
349 Optimization options must be placed in `optimization` folder
351 dir_path = os.path.dirname(os.path.realpath(__file__))
353 # optimization folder
354 files = [f for f in glob.glob(dir_path + '/../optimization/O*.cfg', recursive=True)]
355 # exclude if the name has space
356 files = [s for s in files if not ' ' in s]
360 base = ntpath.basename(cand)
361 if os.path.isfile(cand) and os.access(cand, os.R_OK):
362 opt_list.append(cand)
365 # NOTE the name includes prefix 'O'
366 # e.g. O1, O2, ONCHW not just 1, 2, NCHW
367 opt_list = [ntpath.basename(f) for f in opt_list]
368 opt_list = [_remove_suffix(s, '.cfg') for s in opt_list]