ONE - On-device Neural Engine
Loading...
Searching...
No Matches
utils.py
Go to the documentation of this file.
1#!/usr/bin/env python
2
3# Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
4#
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
8#
9# http://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,
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.
16
17import argparse
18import configparser
19import glob
20import importlib.machinery
21import importlib.util
22import ntpath
23import os
24import subprocess
25import sys
26
27from typing import Union, Optional
28
29from onelib.argumentparse import ArgumentParser
30"""
31Support commands in the one-cmds.
32
33{
34 ${GROUP} : {
35 ${CMD} : EXPLANATIONS
36 }
37}
38"""
39ONE_CMD = {
40 'compile': {
41 'import': 'Convert given model to circle',
42 'optimize': 'Optimize circle model',
43 'quantize': 'Quantize circle model',
44 },
45 'package': {
46 'pack': 'Package circle and metadata into nnpackage',
47 },
48 'backend': {
49 'codegen': 'Code generation tool',
50 'profile': 'Profile backend model file',
51 'infer': 'Infer backend model file'
52 },
53}
54
55
57 return [cmd for group, cmds in ONE_CMD.items() for cmd in cmds.keys()]
58
59
60def add_default_arg(parser):
61 # version
62 parser.add_argument('-v',
63 '--version',
64 action='store_true',
65 help='show program\'s version number and exit')
66
67 # verbose
68 parser.add_argument('-V',
69 '--verbose',
70 action='store_true',
71 help='output additional information to stdout or stderr')
72
73 # configuration file
74 parser.add_argument('-C', '--config', type=str, help='run with configuation file')
75 # section name that you want to run in configuration file
76 parser.add_argument('-S', '--section', type=str, help=argparse.SUPPRESS)
77
78
80 """
81 This adds -v -V args only (no -C nor -S)
82 """
83 # version
84 parser.add_argument('-v',
85 '--version',
86 action='store_true',
87 help='show program\'s version number and exit')
88
89 # verbose
90 parser.add_argument('-V',
91 '--verbose',
92 action='store_true',
93 help='output additional information to stdout or stderr')
94
95
96def is_accumulated_arg(arg, driver):
97 if driver == "one-quantize":
98 accumulables = [
99 "tensor_name", "scale", "zero_point", "src_tensor_name", "dst_tensor_name"
100 ]
101 if arg in accumulables:
102 return True
103
104 return False
105
106
107def is_valid_attr(args, attr):
108 return hasattr(args, attr) and getattr(args, attr)
109
110
111def get_config_parser() -> configparser.ConfigParser:
112 """
113 Initialize configparser and set default option
114
115 This funciton has been introduced for all the one-cmds tools having same parsing option.
116 """
117 parser = configparser.ConfigParser(inline_comment_prefixes=('#', ';'))
118 parser.optionxform = str
119
120 return parser
121
122
123def parse_cfg(config_path: Union[str, None],
124 section_to_parse: str,
125 args,
126 quiet: bool = False):
127 """
128 parse configuration file and store the information to args
129
130 :param config_path: path to configuration file
131 :param section_to_parse: section name to parse
132 :param args: object to store the parsed information
133 :param quiet: raise no error when given section doesn't exist
134 """
135 if config_path is None:
136 return
137
138 parser = get_config_parser()
139 parser.read(config_path)
140
141 if not parser.has_section(section_to_parse) and quiet:
142 return
143
144 if not parser.has_section(section_to_parse):
145 raise AssertionError('configuration file must have \'' + section_to_parse +
146 '\' section')
147
148 # set environment
149 CFG_ENV_SECTION = 'Environment'
150 if parser.has_section(CFG_ENV_SECTION):
151 for key in parser[CFG_ENV_SECTION]:
152 os.environ[key] = parser[CFG_ENV_SECTION][key]
153
154 for key in parser[section_to_parse]:
155 if is_accumulated_arg(key, section_to_parse):
156 if not is_valid_attr(args, key):
157 setattr(args, key, [parser[section_to_parse][key]])
158 else:
159 getattr(args, key).append(parser[section_to_parse][key])
160 continue
161 if hasattr(args, key) and getattr(args, key):
162 continue
163 setattr(args, key, parser[section_to_parse][key])
164
165
167 """print version of the file located in the file_path"""
168 script_path = os.path.realpath(file_path)
169 dir_path = os.path.dirname(script_path)
170 script_name = os.path.splitext(os.path.basename(script_path))[0]
171 # run one-version
172 subprocess.call([os.path.join(dir_path, 'one-version'), script_name])
173 sys.exit()
174
175
176def safemain(main, mainpath):
177 """execute given method and print with program name for all uncaught exceptions"""
178 try:
179 main()
180 except Exception as e:
181 prog_name = os.path.basename(mainpath)
182 print(f"{prog_name}: {type(e).__name__}: " + str(e), file=sys.stderr)
183 sys.exit(255)
184
185
186def run_ret(cmd, *, one_cmd: str = None, err_prefix=None, logfile=None):
187 """Execute command in subprocess
188
189 Args:
190 one_cmd: subtool name to execute with given `cmd`
191 cmd: command to be executed in subprocess
192 err_prefix: prefix to be put before every stderr lines
193 logfile: file stream to which both of stdout and stderr lines will be written
194 Return:
195 Process execution return code; 0 if success and others for error.
196 """
197 if one_cmd:
198 assert one_cmd in one_cmd_list(), f'Invalid ONE COMMAND: {one_cmd}'
199 dir_path = os.path.dirname(os.path.dirname(
200 os.path.realpath(__file__))) # bin = onelib/../
201 driver_path = os.path.join(dir_path, f'one-{one_cmd}')
202 cmd = [driver_path] + cmd
203
204 with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p:
205 import select
206 inputs = set([p.stdout, p.stderr])
207 while inputs:
208 readable, _, _ = select.select(inputs, [], [])
209 for x in readable:
210 line = x.readline()
211 if len(line) == 0:
212 inputs.discard(x)
213 continue
214 if x == p.stdout:
215 out = sys.stdout
216 if x == p.stderr:
217 out = sys.stderr
218 if err_prefix:
219 line = f"{err_prefix}: ".encode() + line
220 out.buffer.write(line)
221 out.buffer.flush()
222 if logfile != None:
223 logfile.write(line)
224 return p.returncode
225
226
227# TODO make run call run_ret
228def run(cmd, *, one_cmd: str = None, err_prefix=None, logfile=None):
229 """Execute command in subprocess
230
231 Args:
232 one_cmd: subtool name to execute with given `cmd`
233 cmd: command to be executed in subprocess
234 err_prefix: prefix to be put before every stderr lines
235 logfile: file stream to which both of stdout and stderr lines will be written
236 """
237 if one_cmd:
238 assert one_cmd in one_cmd_list(), f'Invalid ONE COMMAND: {one_cmd}'
239 dir_path = os.path.dirname(os.path.dirname(
240 os.path.realpath(__file__))) # bin = onelib/../
241 driver_path = os.path.join(dir_path, f'one-{one_cmd}')
242 cmd = [driver_path] + cmd
243
244 with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p:
245 import select
246 inputs = set([p.stdout, p.stderr])
247 while inputs:
248 readable, _, _ = select.select(inputs, [], [])
249 for x in readable:
250 line = x.readline()
251 if len(line) == 0:
252 inputs.discard(x)
253 continue
254 if x == p.stdout:
255 out = sys.stdout
256 if x == p.stderr:
257 out = sys.stderr
258 if err_prefix:
259 line = f"{err_prefix}: ".encode() + line
260 out.buffer.write(line)
261 out.buffer.flush()
262 if logfile != None:
263 logfile.write(line)
264 if p.returncode != 0:
265 sys.exit(p.returncode)
266
267
268def remove_prefix(str, prefix):
269 if str.startswith(prefix):
270 return str[len(prefix):]
271 return str
272
273
274def remove_suffix(str, suffix):
275 if str.endswith(suffix):
276 return str[:-len(suffix)]
277 return str
278
279
280def get_optimization_list(get_name=False):
281 """
282 returns a list of optimization. If `get_name` is True,
283 only basename without extension is returned rather than full file path.
284
285 [one hierarchy]
286 one
287 ├── backends
288 ├── bin
289 ├── doc
290 ├── include
291 ├── lib
292 ├── optimization
293 └── test
294
295 Optimization options must be placed in `optimization` folder
296 """
297 dir_path = os.path.dirname(os.path.realpath(__file__))
298
299 # optimization folder
300 files = [
301 f for f in glob.glob(dir_path + '/../../optimization/O*.cfg', recursive=True)
302 ]
303 # exclude if the name has space
304 files = [s for s in files if not ' ' in s]
305
306 opt_list = []
307 for cand in files:
308 base = ntpath.basename(cand)
309 if os.path.isfile(cand) and os.access(cand, os.R_OK):
310 opt_list.append(cand)
311
312 if get_name == True:
313 # NOTE the name includes prefix 'O'
314 # e.g. O1, O2, ONCHW not just 1, 2, NCHW
315 opt_list = [ntpath.basename(f) for f in opt_list]
316 opt_list = [remove_suffix(s, '.cfg') for s in opt_list]
317
318 return opt_list
319
320
321def get_target_list(get_name=False):
322 """
323 returns a list of targets. If `get_name` is True,
324 only basename without extension is returned rather than full file path.
325
326 [one hierarchy]
327 one
328 ├── backends
329 ├── bin
330 ├── doc
331 ├── include
332 ├── lib
333 ├── optimization
334 ├── target
335 └── test
336
337 Target configuration files must be placed in `target` folder
338 """
339 dir_path = os.path.dirname(os.path.realpath(__file__))
340
341 # target folder
342 files = [f for f in glob.glob(dir_path + '/../../target/*.ini', recursive=True)]
343 # exclude if the name has space
344 files = [s for s in files if not ' ' in s]
345
346 target_list = []
347 for cand in files:
348 if os.path.isfile(cand) and os.access(cand, os.R_OK):
349 target_list.append(cand)
350
351 if get_name == True:
352 target_list = [ntpath.basename(f) for f in target_list]
353 target_list = [remove_suffix(s, '.ini') for s in target_list]
354
355 return target_list
356
357
358def get_arg_parser(backend: Optional[str], cmd: str,
359 target: Optional[str]) -> Optional[ArgumentParser]:
360 if not backend:
361 return None
362
363 dir_path = os.path.dirname(os.path.realpath(__file__))
364 # for python module naming convention
365 command_schema_path = dir_path + f'/../../backends/command/{backend}/{cmd}.py'
366 if not os.path.isfile(command_schema_path):
367 return None
368
369 # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
370 spec = importlib.util.spec_from_file_location(cmd, command_schema_path)
371 module = importlib.util.module_from_spec(spec)
372 sys.modules[cmd] = module
373 spec.loader.exec_module(module)
374
375 if not hasattr(module, "command_schema"):
376 raise RuntimeError('You must implement "command_schema" function')
377
378 parser: ArgumentParser = module.command_schema()
379 parser.target = target
380 return parser
381
382
384 """Looks for import drivers in given directory
385
386 Args:
387 search_path: path to the directory where to search import drivers
388
389 Returns:
390 dict: each entry is related to single detected driver,
391 key is a config section name, value is a driver name
392
393 """
394 import_drivers_dict = {}
395 for module_name in os.listdir(search_path):
396 full_path = os.path.join(search_path, module_name)
397 if not os.path.isfile(full_path):
398 continue
399 if module_name.find("one-import-") != 0:
400 continue
401 module_loader = importlib.machinery.SourceFileLoader(module_name, full_path)
402 module_spec = importlib.util.spec_from_loader(module_name, module_loader)
403 module = importlib.util.module_from_spec(module_spec)
404 try:
405 module_loader.exec_module(module)
406 if hasattr(module, "get_driver_cfg_section"):
407 section = module.get_driver_cfg_section()
408 import_drivers_dict[section] = module_name
409 except:
410 pass
411 return import_drivers_dict
int main(void)
Optional[ArgumentParser] get_arg_parser(Optional[str] backend, str cmd, Optional[str] target)
Definition utils.py:359
add_default_arg_no_CS(parser)
Definition utils.py:79
remove_suffix(str, suffix)
Definition utils.py:274
configparser.ConfigParser get_config_parser()
Definition utils.py:111
get_target_list(get_name=False)
Definition utils.py:321
run_ret(cmd, *str one_cmd=None, err_prefix=None, logfile=None)
Definition utils.py:186
one_cmd_list()
Definition utils.py:56
is_accumulated_arg(arg, driver)
Definition utils.py:96
get_optimization_list(get_name=False)
Definition utils.py:280
print_version_and_exit(file_path)
Definition utils.py:166
detect_one_import_drivers(search_path)
Definition utils.py:383
is_valid_attr(args, attr)
Definition utils.py:107
parse_cfg(Union[str, None] config_path, str section_to_parse, args, bool quiet=False)
Definition utils.py:126
remove_prefix(str, prefix)
Definition utils.py:268
run(cmd, *str one_cmd=None, err_prefix=None, logfile=None)
Definition utils.py:228
add_default_arg(parser)
Definition utils.py:60
safemain(main, mainpath)
Definition utils.py:176