Imported Upstream version 1.18.0
[platform/core/ml/nnfw.git] / compiler / one-cmds / one-quantize
1 #!/usr/bin/env bash
2 ''''export SCRIPT_PATH="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" # '''
3 ''''export PY_PATH=${SCRIPT_PATH}/venv/bin/python                                       # '''
4 ''''test -f ${PY_PATH} && exec ${PY_PATH} "$0" "$@"                                     # '''
5 ''''echo "Error: Virtual environment not found. Please run 'one-prepare-venv' command." # '''
6 ''''exit 255                                                                            # '''
7
8 # Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
9 #
10 # Licensed under the Apache License, Version 2.0 (the "License");
11 # you may not use this file except in compliance with the License.
12 # You may obtain a copy of the License at
13 #
14 #    http://www.apache.org/licenses/LICENSE-2.0
15 #
16 # Unless required by applicable law or agreed to in writing, software
17 # distributed under the License is distributed on an "AS IS" BASIS,
18 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 # See the License for the specific language governing permissions and
20 # limitations under the License.
21
22 import argparse
23 import os
24 import subprocess
25 import sys
26 import tempfile
27
28 import utils as _utils
29
30 # TODO Find better way to suppress trackback on error
31 sys.tracebacklimit = 0
32
33
34 def _get_parser():
35     parser = argparse.ArgumentParser(
36         description='command line tool to quantize circle model')
37
38     _utils._add_default_arg(parser)
39
40     # input and output path.
41     parser.add_argument(
42         '-i', '--input_path', type=str, help='full filepath of the input file')
43     parser.add_argument(
44         '-d',
45         '--input_data',
46         type=str,
47         help=
48         'full filepath of the input data file. if not specified, run with random input data.'
49     )
50     parser.add_argument(
51         '-f',
52         '--input_data_format',
53         type=str,
54         help=
55         'file format of input data. h5/hdf5 (default), list/filelist (a text file where a file path of input data is written in each line), or dir/directory (a directory where input data are saved)'
56     )
57     parser.add_argument(
58         '-o', '--output_path', type=str, help='full filepath of the output file')
59
60     # argument for profiling
61     parser.add_argument(
62         '-p',
63         '--generate_profile_data',
64         action='store_true',
65         help='generate profiling data')
66
67     ## arguments for quantization
68     quantization_group = parser.add_argument_group('arguments for quantization')
69
70     quantization_group.add_argument(
71         '--input_dtype',
72         type=str,
73         help='input data type (supported: float32, default=float32)')
74     quantization_group.add_argument(
75         '--quantized_dtype',
76         type=str,
77         help='output quantized data type (supported: uint8, int16, default=uint8)')
78     quantization_group.add_argument(
79         '--granularity',
80         type=str,
81         help='quantize granularity (supported: layer, channel, default=layer)')
82     quantization_group.add_argument(
83         '--min_percentile', type=str, help='minimum percentile (0.0~100.0, default=1.0)')
84     quantization_group.add_argument(
85         '--max_percentile', type=str, help='maximum percentile (0.0~100.0, default=99.0)')
86     quantization_group.add_argument(
87         '--mode',
88         type=str,
89         help='record mode (supported: percentile/moving_average, default=percentile)')
90
91     # arguments for force_quantparam
92     parser.add_argument(
93         '--force_quantparam',
94         action='store_true',
95         help='write quantparam to the specified tensor')
96     parser.add_argument(
97         '--tensor_name', type=str, action='append', help='tensor name (string)')
98     parser.add_argument('--scale', type=float, action='append', help='scale (float)')
99     parser.add_argument(
100         '--zero_point', type=int, action='append', help='zero point (int)')
101
102     return parser
103
104
105 def _set_default_values(args):
106     if not _utils._is_valid_attr(args, 'input_dtype'):
107         setattr(args, 'input_dtype', 'float32')
108     if not _utils._is_valid_attr(args, 'quantized_dtype'):
109         setattr(args, 'quantized_dtype', 'uint8')
110     if not _utils._is_valid_attr(args, 'granularity'):
111         setattr(args, 'granularity', 'layer')
112     if not _utils._is_valid_attr(args, 'mode'):
113         setattr(args, 'mode', 'percentile')
114     if not _utils._is_valid_attr(args, 'min_percentile'):
115         setattr(args, 'min_percentile', '1.0')
116     if not _utils._is_valid_attr(args, 'max_percentile'):
117         setattr(args, 'max_percentile', '99.0')
118
119
120 def _verify_arg(parser, args):
121     """verify given arguments"""
122     # check if required arguments is given
123     missing = []
124     if not _utils._is_valid_attr(args, 'input_path'):
125         missing.append('-i/--input_path')
126     if not _utils._is_valid_attr(args, 'output_path'):
127         missing.append('-o/--output_path')
128     if _utils._is_valid_attr(args, 'force_quantparam'):
129         if not _utils._is_valid_attr(args, 'tensor_name'):
130             missing.append('--tensor_name')
131         if not _utils._is_valid_attr(args, 'scale'):
132             missing.append('--scale')
133         if not _utils._is_valid_attr(args, 'zero_point'):
134             missing.append('--zero_point')
135     if len(missing):
136         parser.error('the following arguments are required: ' + ' '.join(missing))
137     if _utils._is_valid_attr(args, 'force_quantparam'):
138         tensors = getattr(args, 'tensor_name')
139         scales = getattr(args, 'scale')
140         zerops = getattr(args, 'zero_point')
141         if len(tensors) != len(scales) or len(tensors) != len(zerops):
142             parser.error(
143                 'The same number of tensor_name, scale, and zero_point should be given.')
144
145
146 def _parse_arg(parser):
147     args = parser.parse_args()
148     # print version
149     if args.version:
150         _utils._print_version_and_exit(__file__)
151
152     return args
153
154
155 def _quantize(args):
156     if _utils._is_valid_attr(args, 'force_quantparam'):
157         # write quantization parameters
158         _write_qparam(args)
159         return
160
161     # get file path to log
162     dir_path = os.path.dirname(os.path.realpath(__file__))
163     logfile_path = os.path.realpath(args.output_path) + '.log'
164
165     with open(logfile_path, 'wb') as f, tempfile.TemporaryDirectory() as tmpdir:
166         # get driver path
167         circle_quantizer_path = os.path.join(dir_path, 'circle-quantizer')
168         record_minmax_path = os.path.join(dir_path, 'record-minmax')
169
170         ## make a command to quantize and dequantize the weights of the model
171         circle_quantizer_cmd = [circle_quantizer_path]
172         # verbose
173         if _utils._is_valid_attr(args, 'verbose'):
174             circle_quantizer_cmd.append('--verbose')
175         # quantize_dequantize_weights
176         circle_quantizer_cmd.append('--quantize_dequantize_weights')
177         if _utils._is_valid_attr(args, 'input_dtype'):
178             circle_quantizer_cmd.append(getattr(args, 'input_dtype'))
179         if _utils._is_valid_attr(args, 'quantized_dtype'):
180             circle_quantizer_cmd.append(getattr(args, 'quantized_dtype'))
181         if _utils._is_valid_attr(args, 'granularity'):
182             circle_quantizer_cmd.append(getattr(args, 'granularity'))
183         # input and output path
184         if _utils._is_valid_attr(args, 'input_path'):
185             circle_quantizer_cmd.append(getattr(args, 'input_path'))
186         tmp_output_path_1 = os.path.join(
187             tmpdir,
188             os.path.splitext(os.path.basename(args.input_path))[0]) + '1.circle'
189         circle_quantizer_cmd.append(tmp_output_path_1)
190         # profiling
191         if _utils._is_valid_attr(args, 'generate_profile_data'):
192             circle_quantizer_cmd.append('--generate_profile_data')
193
194         f.write((' '.join(circle_quantizer_cmd) + '\n').encode())
195
196         # run circle-quantizer
197         _utils._run(circle_quantizer_cmd, err_prefix="circle_quantizer", logfile=f)
198
199         ## make a command to record min-max value of each tensor while running the representative dataset
200         circle_record_minmax_cmd = [record_minmax_path]
201         # verbose
202         if _utils._is_valid_attr(args, 'verbose'):
203             circle_record_minmax_cmd.append('--verbose')
204         # input and output path
205         circle_record_minmax_cmd.append('--input_model')
206         circle_record_minmax_cmd.append(tmp_output_path_1)
207         tmp_output_path_2 = os.path.join(
208             tmpdir,
209             os.path.splitext(os.path.basename(args.input_path))[0]) + '2.circle'
210         circle_record_minmax_cmd.append('--output_model')
211         circle_record_minmax_cmd.append(tmp_output_path_2)
212         # input data
213         if _utils._is_valid_attr(args, 'input_data'):
214             circle_record_minmax_cmd.append('--input_data')
215             circle_record_minmax_cmd.append(getattr(args, 'input_data'))
216         if _utils._is_valid_attr(args, 'input_data_format'):
217             circle_record_minmax_cmd.append('--input_data_format')
218             circle_record_minmax_cmd.append(getattr(args, 'input_data_format'))
219         # min and max percentile
220         if _utils._is_valid_attr(args, 'min_percentile'):
221             circle_record_minmax_cmd.append('--min_percentile')
222             circle_record_minmax_cmd.append(getattr(args, 'min_percentile'))
223         if _utils._is_valid_attr(args, 'max_percentile'):
224             circle_record_minmax_cmd.append('--max_percentile')
225             circle_record_minmax_cmd.append(getattr(args, 'max_percentile'))
226         # mode
227         if _utils._is_valid_attr(args, 'mode'):
228             circle_record_minmax_cmd.append('--mode')
229             circle_record_minmax_cmd.append(getattr(args, 'mode'))
230         # profiling
231         if _utils._is_valid_attr(args, 'generate_profile_data'):
232             circle_record_minmax_cmd.append('--generate_profile_data')
233
234         f.write((' '.join(circle_record_minmax_cmd) + '\n').encode())
235
236         # run record-minmax
237         _utils._run(circle_record_minmax_cmd, err_prefix="record_minmax", logfile=f)
238
239         ## make a second command to quantize the model using the embedded information
240         circle_quantizer_cmd = [circle_quantizer_path]
241         # verbose
242         if _utils._is_valid_attr(args, 'verbose'):
243             circle_quantizer_cmd.append('--verbose')
244         # quantize_dequantize_weights
245         circle_quantizer_cmd.append('--quantize_with_minmax')
246         if _utils._is_valid_attr(args, 'input_dtype'):
247             circle_quantizer_cmd.append(getattr(args, 'input_dtype'))
248         if _utils._is_valid_attr(args, 'quantized_dtype'):
249             circle_quantizer_cmd.append(getattr(args, 'quantized_dtype'))
250         if _utils._is_valid_attr(args, 'granularity'):
251             circle_quantizer_cmd.append(getattr(args, 'granularity'))
252         # input and output path
253         circle_quantizer_cmd.append(tmp_output_path_2)
254         if _utils._is_valid_attr(args, 'output_path'):
255             circle_quantizer_cmd.append(getattr(args, 'output_path'))
256         # profiling
257         if _utils._is_valid_attr(args, 'generate_profile_data'):
258             circle_quantizer_cmd.append('--generate_profile_data')
259
260         f.write((' '.join(circle_quantizer_cmd) + '\n').encode())
261
262         # run circle-quantizer
263         _utils._run(circle_quantizer_cmd, err_prefix="circle_quantizer", logfile=f)
264
265
266 def _write_qparam(args):
267     # get file path to log
268     dir_path = os.path.dirname(os.path.realpath(__file__))
269     logfile_path = os.path.realpath(args.output_path) + '.log'
270
271     with open(logfile_path, 'wb') as f:
272         # get driver path
273         circle_quantizer_path = os.path.join(dir_path, 'circle-quantizer')
274
275         # make a command to write qparams to the tensors
276         circle_quantizer_cmd = [circle_quantizer_path]
277         # verbose
278         if _utils._is_valid_attr(args, 'verbose'):
279             circle_quantizer_cmd.append('--verbose')
280         if _utils._is_valid_attr(args, 'tensor_name'):
281             tensor_name = getattr(args, 'tensor_name')
282         if _utils._is_valid_attr(args, 'scale'):
283             scale = getattr(args, 'scale')
284         if _utils._is_valid_attr(args, 'zero_point'):
285             zero_point = getattr(args, 'zero_point')
286         for (t, s, zp) in zip(tensor_name, scale, zero_point):
287             circle_quantizer_cmd.append('--force_quantparam')
288             circle_quantizer_cmd.append(t)
289             circle_quantizer_cmd.append(str(s))
290             circle_quantizer_cmd.append(str(zp))
291         # input and output path
292         if _utils._is_valid_attr(args, 'input_path'):
293             circle_quantizer_cmd.append(getattr(args, 'input_path'))
294         if _utils._is_valid_attr(args, 'output_path'):
295             circle_quantizer_cmd.append(getattr(args, 'output_path'))
296
297         f.write((' '.join(circle_quantizer_cmd) + '\n').encode())
298
299         # run circle-quantizer
300         _utils._run(circle_quantizer_cmd, err_prefix="circle_quantizer", logfile=f)
301
302
303 def main():
304     # parse arguments
305     parser = _get_parser()
306     args = _parse_arg(parser)
307
308     # parse configuration file
309     _utils._parse_cfg(args, 'one-quantize')
310
311     # set default values
312     _set_default_values(args)
313
314     # verify arguments
315     _verify_arg(parser, args)
316
317     # quantize
318     _quantize(args)
319
320
321 if __name__ == '__main__':
322     _utils._safemain(main, __file__)