blob: a3987bc1002413c596fd80c758c5ef062b384296 [file] [log] [blame]
alexanderf4e2c472021-05-14 13:14:21 +01001#!/usr/bin/env python3
Alex Tawsedaba3cf2023-09-29 15:55:38 +01002# SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
Isabella Gottardi2181d0a2021-04-07 09:27:38 +01003# SPDX-License-Identifier: Apache-2.0
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.
Alex Tawsedaba3cf2023-09-29 15:55:38 +010016"""
17Script to set up default resources for ML Embedded Evaluation Kit
18"""
19import dataclasses
Isabella Gottardief2b9dd2022-02-16 14:24:03 +000020import errno
Isabella Gottardi2181d0a2021-04-07 09:27:38 +010021import fnmatch
Isabella Gottardief2b9dd2022-02-16 14:24:03 +000022import json
Isabella Gottardi2181d0a2021-04-07 09:27:38 +010023import logging
Isabella Gottardief2b9dd2022-02-16 14:24:03 +000024import os
25import re
26import shutil
27import subprocess
Isabella Gottardi2181d0a2021-04-07 09:27:38 +010028import sys
Alex Tawsedaba3cf2023-09-29 15:55:38 +010029import typing
Isabella Gottardief2b9dd2022-02-16 14:24:03 +000030import urllib.request
Kshitij Sisodia36b5b132023-06-09 11:58:26 +010031import venv
Isabella Gottardief2b9dd2022-02-16 14:24:03 +000032from argparse import ArgumentParser
33from argparse import ArgumentTypeError
Kshitij Sisodia3be26232021-10-29 12:29:06 +010034from collections import namedtuple
Alex Tawsedaba3cf2023-09-29 15:55:38 +010035from dataclasses import dataclass
Richard Burton17069622022-03-17 10:54:26 +000036from pathlib import Path
Alex Tawsedaba3cf2023-09-29 15:55:38 +010037from urllib.error import URLError
Isabella Gottardi6c2ea452022-03-11 13:25:08 +000038
Kshitij Sisodia6a2ac462022-03-01 17:36:06 +000039from scripts.py.check_update_resources_downloaded import get_md5sum_for_file
Kshitij Sisodia3be26232021-10-29 12:29:06 +010040
Alex Tawsedaba3cf2023-09-29 15:55:38 +010041# Supported version of Python and Vela
42VELA_VERSION = "3.9.0"
43py3_version_minimum = (3, 9)
Isabella Gottardi2181d0a2021-04-07 09:27:38 +010044
Kshitij Sisodia3be26232021-10-29 12:29:06 +010045# Valid NPU configurations:
46valid_npu_config_names = [
Isabella Gottardief2b9dd2022-02-16 14:24:03 +000047 "ethos-u55-32",
48 "ethos-u55-64",
49 "ethos-u55-128",
50 "ethos-u55-256",
51 "ethos-u65-256",
52 "ethos-u65-512",
53]
Kshitij Sisodia3be26232021-10-29 12:29:06 +010054
55# Default NPU configurations (these are always run when the models are optimised)
56default_npu_config_names = [valid_npu_config_names[2], valid_npu_config_names[4]]
57
58# NPU config named tuple
Isabella Gottardief2b9dd2022-02-16 14:24:03 +000059NPUConfig = namedtuple(
60 "NPUConfig",
61 [
62 "config_name",
63 "memory_mode",
64 "system_config",
65 "ethos_u_npu_id",
66 "ethos_u_config_id",
67 "arena_cache_size",
68 ],
69)
Kshitij Sisodia3be26232021-10-29 12:29:06 +010070
Alex Tawsedaba3cf2023-09-29 15:55:38 +010071
72@dataclass(frozen=True)
73class UseCaseResource:
74 """
75 Represent a use case's resource
76 """
77 name: str
78 url: str
79 sub_folder: typing.Optional[str] = None
80
81
82@dataclass(frozen=True)
83class UseCase:
84 """
85 Represent a use case
86 """
87 name: str
88 url_prefix: str
89 resources: typing.List[UseCaseResource]
90
91
Kshitij Sisodia661959c2021-11-24 10:39:52 +000092# The internal SRAM size for Corstone-300 implementation on MPS3 specified by AN552
Kshitij Sisodia8c61c0a2022-05-17 11:16:22 +010093# The internal SRAM size for Corstone-310 implementation on MPS3 specified by AN555
94# is 4MB, but we are content with the 2MB specified below.
Alex Tawsedaba3cf2023-09-29 15:55:38 +010095MPS3_MAX_SRAM_SZ = 2 * 1024 * 1024 # 2 MiB (2 banks of 1 MiB each)
96
97
98def load_use_case_resources(current_file_dir: Path) -> typing.List[UseCase]:
99 """
100 Load use case metadata resources
101
102 Parameters
103 ----------
104 current_file_dir: Directory of the current script
105
106 Returns
107 -------
108 The use cases resources object parsed to a dict
109 """
110
111 resources_path = current_file_dir / "scripts" / "py" / "use_case_resources.json"
112 with open(resources_path, encoding="utf8") as f:
113 use_cases = json.load(f)
114 return [
115 UseCase(
116 name=u["name"],
117 url_prefix=u["url_prefix"],
118 resources=[UseCaseResource(**r) for r in u["resources"]],
119 )
120 for u in use_cases
121 ]
Liam Barryb52b5852021-11-15 11:41:40 +0000122
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100123
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000124def call_command(command: str, verbose: bool = True) -> str:
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100125 """
126 Helpers function that call subprocess and return the output.
127
128 Parameters:
129 ----------
130 command (string): Specifies the command to run.
131 """
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000132 if verbose:
133 logging.info(command)
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100134 try:
135 proc = subprocess.run(
136 command, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True
137 )
138 log = proc.stdout.decode("utf-8")
alexander50a06502021-05-12 19:06:02 +0100139 logging.info(log)
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100140 return log
141 except subprocess.CalledProcessError as err:
142 log = err.stdout.decode("utf-8")
alexander50a06502021-05-12 19:06:02 +0100143 logging.error(log)
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100144 raise err
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100145
146
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000147def get_default_npu_config_from_name(
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100148 config_name: str, arena_cache_size: int = 0
149) -> typing.Optional[NPUConfig]:
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100150 """
Richard Burton17069622022-03-17 10:54:26 +0000151 Gets the file suffix for the TFLite file from the
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100152 `accelerator_config` string.
153
154 Parameters:
155 ----------
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000156 config_name (str): Ethos-U NPU configuration from valid_npu_config_names
157
158 arena_cache_size (int): Specifies arena cache size in bytes. If a value
159 greater than 0 is provided, this will be taken
160 as the cache size. If 0, the default values, as per
161 the NPU config requirements, are used.
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100162
163 Returns:
164 -------
165 NPUConfig: An NPU config named tuple populated with defaults for the given
166 config name
167 """
168 if config_name not in valid_npu_config_names:
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000169 raise ValueError(
170 f"""
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100171 Invalid Ethos-U NPU configuration.
172 Select one from {valid_npu_config_names}.
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000173 """
174 )
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100175
176 strings_ids = ["ethos-u55-", "ethos-u65-"]
177 processor_ids = ["U55", "U65"]
178 prefix_ids = ["H", "Y"]
179 memory_modes = ["Shared_Sram", "Dedicated_Sram"]
180 system_configs = ["Ethos_U55_High_End_Embedded", "Ethos_U65_High_End"]
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000181 memory_modes_arena = {
Richard Burton17069622022-03-17 10:54:26 +0000182 # For shared SRAM memory mode, we use the MPS3 SRAM size by default.
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100183 "Shared_Sram": MPS3_MAX_SRAM_SZ if arena_cache_size <= 0 else arena_cache_size,
Richard Burton17069622022-03-17 10:54:26 +0000184 # For dedicated SRAM memory mode, we do not override the arena size. This is expected to
185 # be defined in the Vela configuration file instead.
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000186 "Dedicated_Sram": None if arena_cache_size <= 0 else arena_cache_size,
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000187 }
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100188
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100189 for i, string_id in enumerate(strings_ids):
190 if config_name.startswith(string_id):
191 npu_config_id = config_name.replace(string_id, prefix_ids[i])
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000192 return NPUConfig(
193 config_name=config_name,
194 memory_mode=memory_modes[i],
195 system_config=system_configs[i],
196 ethos_u_npu_id=processor_ids[i],
197 ethos_u_config_id=npu_config_id,
198 arena_cache_size=memory_modes_arena[memory_modes[i]],
199 )
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100200
201 return None
202
203
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100204def remove_tree_dir(dir_path: Path):
205 """
206 Delete and re-create a directory
207
208 Parameters
209 ----------
210 dir_path : The directory path
211 """
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000212 try:
Richard Burton17069622022-03-17 10:54:26 +0000213 # Remove the full directory.
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000214 shutil.rmtree(dir_path)
Richard Burton17069622022-03-17 10:54:26 +0000215 # Re-create an empty one.
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000216 os.mkdir(dir_path)
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100217 except OSError:
218 logging.error("Failed to delete %s.", dir_path)
219
220
221def initialize_use_case_resources_directory(
222 use_case: UseCase,
223 metadata: typing.Dict,
224 download_dir: Path,
225 check_clean_folder: bool,
226 setup_script_hash_verified: bool,
227):
228 """
229 Initialize the resources_downloaded directory for a use case
230
231 @param use_case: The use case
232 @param metadata: The metadata
233 @param download_dir: The parent directory
234 @param check_clean_folder: Whether to clean the folder
235 @param setup_script_hash_verified: Whether the hash of this script is verified
236 """
237 try:
238 # Does the usecase_name download dir exist?
239 (download_dir / use_case.name).mkdir()
240 except OSError as err:
241 if err.errno == errno.EEXIST:
242 # The usecase_name download dir exist.
243 if check_clean_folder and not setup_script_hash_verified:
244 for idx, metadata_uc_url_prefix in enumerate(
245 [
246 f
247 for f in metadata["resources_info"]
248 if f["name"] == use_case.name
249 ][0]["url_prefix"]
250 ):
251 if metadata_uc_url_prefix != use_case.url_prefix[idx]:
252 logging.info("Removing %s resources.", use_case.name)
253 remove_tree_dir(download_dir / use_case.name)
254 break
255 elif err.errno != errno.EEXIST:
256 logging.error("Error creating %s directory.", use_case.name)
257 raise
258
259
260def download_file(url: str, dest: Path):
261 """
262 Download a file
263
264 @param url: The URL of the file to download
265 @param dest: The destination of downloaded file
266 """
267 try:
268 with urllib.request.urlopen(url) as g:
269 with open(dest, "b+w") as f:
270 f.write(g.read())
271 logging.info("- Downloaded %s to %s.", url, dest)
272 except URLError:
273 logging.error("URLError while downloading %s.", url)
274 raise
275
276
277def download_resources(
278 use_case: UseCase,
279 metadata: typing.Dict,
280 download_dir: Path,
281 check_clean_folder: bool,
282 setup_script_hash_verified: bool,
283):
284 """
285 Download the resources associated with a use case
286
287 @param use_case: The use case
288 @param metadata: The metadata
289 @param download_dir: The parent directory
290 @param check_clean_folder: Whether to clean the folder
291 @param setup_script_hash_verified: Whether the hash is already verified
292 """
293 initialize_use_case_resources_directory(
294 use_case,
295 metadata,
296 download_dir,
297 check_clean_folder,
298 setup_script_hash_verified
299 )
300
301 reg_expr_str = r"{url_prefix:(.*\d)}"
302 reg_expr_pattern = re.compile(reg_expr_str)
303 for res in use_case.resources:
304 res_name = res.name
305 url_prefix_idx = int(reg_expr_pattern.search(res.url).group(1))
306 res_url = use_case.url_prefix[url_prefix_idx] + re.sub(
307 reg_expr_str, "", res.url
308 )
309
310 sub_folder = ""
311 if res.sub_folder is not None:
312 try:
313 # Does the usecase_name/sub_folder download dir exist?
314 (download_dir / use_case.name / res.sub_folder).mkdir()
315 except OSError as err:
316 if err.errno != errno.EEXIST:
317 logging.error(
318 "Error creating %s/%s directory.",
319 use_case.name,
320 res.sub_folder
321 )
322 raise
323 sub_folder = res.sub_folder
324
325 res_dst = download_dir / use_case.name / sub_folder / res_name
326
327 if res_dst.is_file():
328 logging.info("File %s exists, skipping download.", res_dst)
329 else:
330 download_file(res_url, res_dst)
331
332
333def run_vela(
334 config: NPUConfig,
335 env_activate_cmd: str,
336 model: Path,
337 config_file: Path,
338 output_dir: Path
339) -> bool:
340 """
341 Run vela on the specified model
342 @param config: The NPU configuration
343 @param env_activate_cmd: The Python venv activation command
344 @param model: The model
345 @param config_file: The vela config file
346 @param output_dir: The output directory
347 @return: True if the optimisation was skipped, false otherwise
348 """
349 # model name after compiling with vela is an initial model name + _vela suffix
350 vela_optimised_model_path = model.parent / (model.stem + "_vela.tflite")
351
352 vela_command_arena_cache_size = ""
353
354 if config.arena_cache_size:
355 vela_command_arena_cache_size = (
356 f"--arena-cache-size={config.arena_cache_size}"
357 )
358
359 vela_command = (
360 f"{env_activate_cmd} && vela {model} "
361 + f"--accelerator-config={config.config_name} "
362 + "--optimise Performance "
363 + f"--config {config_file} "
364 + f"--memory-mode={config.memory_mode} "
365 + f"--system-config={config.system_config} "
366 + f"--output-dir={output_dir} "
367 + f"{vela_command_arena_cache_size}"
368 )
369
370 # We want the name to include the configuration suffix. For example: vela_H128,
371 # vela_Y512 etc.
372 new_suffix = "_vela_" + config.ethos_u_config_id + ".tflite"
373 new_vela_optimised_model_path = model.parent / (model.stem + new_suffix)
374
375 skip_optimisation = new_vela_optimised_model_path.is_file()
376
377 if skip_optimisation:
378 logging.info(
379 "File %s exists, skipping optimisation.",
380 new_vela_optimised_model_path
381 )
382 else:
383 call_command(vela_command)
384
385 # Rename default vela model.
386 vela_optimised_model_path.rename(new_vela_optimised_model_path)
387 logging.info(
388 "Renaming %s to %s.",
389 vela_optimised_model_path,
390 new_vela_optimised_model_path
391 )
392
393 return skip_optimisation
394
395
396def run_vela_on_all_models(
397 current_file_dir: Path,
398 download_dir: Path,
399 env_activate_cmd: str,
400 arena_cache_size: int,
401 npu_config_names: typing.List[str]
402):
403 """
404 Run vela on downloaded models for the specified NPU configurations
405
406 @param current_file_dir: Path to the current directory
407 @param download_dir: Path to the downloaded resources directory
408 @param env_activate_cmd: Command used to activate Python venv
409 @param npu_config_names: Names of NPU configurations for which to run Vela
410 @param arena_cache_size: The arena cache size
411 """
412 config_file = current_file_dir / "scripts" / "vela" / "default_vela.ini"
413 models = [
414 Path(dirpath) / f
415 for dirpath, dirnames, files in os.walk(download_dir)
416 for f in fnmatch.filter(files, "*.tflite")
417 if "vela" not in f
418 ]
419
420 # Get npu config tuple for each config name in a list:
421 npu_configs = [
422 get_default_npu_config_from_name(name, arena_cache_size)
423 for name in npu_config_names
424 ]
425
426 logging.info("All models will be optimised for these configs:")
427 for config in npu_configs:
428 logging.info(config)
429
430 optimisation_skipped = False
431
432 for model in models:
433 for config in npu_configs:
434 optimisation_skipped = run_vela(
435 config,
436 env_activate_cmd,
437 model,
438 config_file,
439 output_dir=model.parent
440 ) or optimisation_skipped
441
442 # If any optimisation was skipped, show how to regenerate:
443 if optimisation_skipped:
444 logging.warning("One or more optimisations were skipped.")
445 logging.warning(
446 "To optimise all the models, please remove the directory %s.",
447 download_dir
448 )
449
450
451def initialize_resources_directory(
452 download_dir: Path,
453 check_clean_folder: bool,
454 metadata_file_path: Path,
455 setup_script_hash: str
456) -> typing.Tuple[typing.Dict, bool]:
457 """
458 Sets up the resources_downloaded directory and checks to see if this script
459 has been modified since the last time resources were downloaded
460
461 @param download_dir: Path to the resources_downloaded directory
462 @param check_clean_folder: Determines whether to clean the downloads directory
463 @param metadata_file_path: Path to the metadata file
464 @param setup_script_hash: The md5 hash of this script
465 @return: The metadata and a boolean to indicate whether this
466 script has changed since it was last run
467 """
468 metadata_dict = {}
469 setup_script_hash_verified = False
470
471 if download_dir.is_dir():
472 logging.info("'resources_downloaded' directory exists.")
473 # Check and clean?
474 if check_clean_folder and metadata_file_path.is_file():
475 with open(metadata_file_path, encoding="utf8") as metadata_file:
476 metadata_dict = json.load(metadata_file)
477
478 vela_in_metadata = metadata_dict["ethosu_vela_version"]
479 if vela_in_metadata != VELA_VERSION:
480 # Check if all the resources needs to be removed and regenerated.
481 # This can happen when the Vela version has changed.
482 logging.info(
483 ("Vela version in metadata is %s, current %s."
484 " Removing the resources and re-download them.",
485 vela_in_metadata,
486 VELA_VERSION
487 )
488 )
489 remove_tree_dir(download_dir)
490 metadata_dict = {}
491 else:
492 # Check if the set_up_default_resorces.py has changed from last setup
493 setup_script_hash_verified = (
494 metadata_dict.get("set_up_script_md5sum")
495 == setup_script_hash
496 )
497 else:
498 download_dir.mkdir()
499
500 return metadata_dict, setup_script_hash_verified
501
502
503def set_up_python_venv(
504 download_dir: Path,
505 additional_requirements_file: Path = ""
506):
507 """
508 Set up the Python environment with which to set up the resources
509
510 @param download_dir: Path to the resources_downloaded directory
511 @param additional_requirements_file: Optional additional requirements file
512 @return: Path to the venv Python binary + activate command
513 """
514 env_dirname = "env"
515 env_path = download_dir / env_dirname
516
517 venv_builder = venv.EnvBuilder(with_pip=True, upgrade_deps=True)
518 venv_context = venv_builder.ensure_directories(env_dir=env_path)
519
520 env_python = Path(venv_context.env_exe)
521
522 if not env_python.is_file():
523 # Create the virtual environment using current interpreter's venv
524 # (not necessarily the system's Python3)
525 venv_builder.create(env_dir=env_path)
526
527 if sys.platform == "win32":
528 env_activate = Path(f"{venv_context.bin_path}/activate.bat")
529 env_activate_cmd = str(env_activate)
530 else:
531 env_activate = Path(f"{venv_context.bin_path}/activate")
532 env_activate_cmd = f". {env_activate}"
533
534 if not env_activate.is_file():
535 venv_builder.install_scripts(venv_context, venv_context.bin_path)
536
537 # 1.3 Install additional requirements first, if a valid file has been provided
538 if additional_requirements_file and os.path.isfile(additional_requirements_file):
539 command = f"{env_python} -m pip install -r {additional_requirements_file}"
540 call_command(command)
541
542 # 1.4 Make sure to have all the main requirements
543 requirements = [f"ethos-u-vela=={VELA_VERSION}"]
544 command = f"{env_python} -m pip freeze"
545 packages = call_command(command)
546 for req in requirements:
547 if req not in packages:
548 command = f"{env_python} -m pip install {req}"
549 call_command(command)
550
551 return env_path, env_activate_cmd
552
553
554def update_metadata(
555 metadata_dict: typing.Dict,
556 setup_script_hash: str,
557 json_uc_res: typing.List[UseCase],
558 metadata_file_path: Path
559):
560 """
561 Update the metadata file
562
563 @param metadata_dict: The metadata dictionary to update
564 @param setup_script_hash: The setup script hash
565 @param json_uc_res: The use case resources metadata
566 @param metadata_file_path The metadata file path
567 """
568 metadata_dict["ethosu_vela_version"] = VELA_VERSION
569 metadata_dict["set_up_script_md5sum"] = setup_script_hash.strip("\n")
570 metadata_dict["resources_info"] = [dataclasses.asdict(uc) for uc in json_uc_res]
571
572 with open(metadata_file_path, "w", encoding="utf8") as metadata_file:
573 json.dump(metadata_dict, metadata_file, indent=4)
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000574
575
576def set_up_resources(
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100577 run_vela_on_models: bool = False,
578 additional_npu_config_names: tuple = (),
579 arena_cache_size: int = 0,
580 check_clean_folder: bool = False,
581 additional_requirements_file: Path = ""
582) -> Path:
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100583 """
584 Helpers function that retrieve the output from a command.
585
586 Parameters:
587 ----------
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000588 run_vela_on_models (bool): Specifies if run vela on downloaded models.
589 additional_npu_config_names(list): list of strings of Ethos-U NPU configs.
590 arena_cache_size (int): Specifies arena cache size in bytes. If a value
591 greater than 0 is provided, this will be taken
592 as the cache size. If 0, the default values, as per
593 the NPU config requirements, are used.
Kshitij Sisodia6a2ac462022-03-01 17:36:06 +0000594 check_clean_folder (bool): Indicates whether the resources folder needs to
595 be checked for updates and cleaned.
Kshitij Sisodiac22e80e2022-03-14 09:26:48 +0000596 additional_requirements_file (str): Path to a requirements.txt file if
597 additional packages need to be
598 installed.
Kshitij Sisodia9c6f9f82022-05-20 14:30:02 +0100599
600 Returns
601 -------
602
603 Tuple of pair of Paths: (download_directory_path, virtual_env_path)
604
605 download_directory_path: Root of the directory where the resources have been downloaded to.
606 virtual_env_path: Path to the root of virtual environment.
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100607 """
Richard Burton17069622022-03-17 10:54:26 +0000608 # Paths.
609 current_file_dir = Path(__file__).parent.resolve()
610 download_dir = current_file_dir / "resources_downloaded"
611 metadata_file_path = download_dir / "resources_downloaded_metadata.json"
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000612
Isabella Gottardi6c2ea452022-03-11 13:25:08 +0000613 # Is Python minimum requirement matched?
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100614 if sys.version_info < py3_version_minimum:
615 raise RuntimeError(
616 f"ERROR: Python{'.'.join(str(i) for i in py3_version_minimum)}+ is required,"
617 f" please see the documentation on how to update it."
Isabella Gottardi6c2ea452022-03-11 13:25:08 +0000618 )
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100619 logging.info("Using Python version: %s", sys.version_info)
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000620
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100621 json_uc_res = load_use_case_resources(current_file_dir)
Richard Burton17069622022-03-17 10:54:26 +0000622 setup_script_hash = get_md5sum_for_file(Path(__file__).resolve())
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100623
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100624 metadata_dict, setup_script_hash_verified = initialize_resources_directory(
625 download_dir,
626 check_clean_folder,
627 metadata_file_path,
628 setup_script_hash
629 )
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100630
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100631 env_path, env_activate = set_up_python_venv(
632 download_dir,
633 additional_requirements_file
634 )
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100635
636 # 2. Download models
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000637 logging.info("Downloading resources.")
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100638 for use_case in json_uc_res:
639 download_resources(
640 use_case,
641 metadata_dict,
642 download_dir,
643 check_clean_folder,
644 setup_script_hash_verified
645 )
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100646
647 # 3. Run vela on models in resources_downloaded
648 # New models will have same name with '_vela' appended.
649 # For example:
Kshitij Sisodia76a15802021-12-24 11:05:11 +0000650 # original model: kws_micronet_m.tflite
651 # after vela model: kws_micronet_m_vela_H128.tflite
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100652 #
653 # Note: To avoid to run vela twice on the same model, it's supposed that
654 # downloaded model names don't contain the 'vela' word.
655 if run_vela_on_models is True:
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100656 # Consolidate all config names while discarding duplicates:
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100657 run_vela_on_all_models(
658 current_file_dir,
659 download_dir,
660 env_activate,
661 arena_cache_size,
662 npu_config_names=list(set(default_npu_config_names + list(additional_npu_config_names)))
663 )
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000664
665 # 4. Collect and write metadata
666 logging.info("Collecting and write metadata.")
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100667 update_metadata(
668 metadata_dict,
669 setup_script_hash.strip("\n"),
670 json_uc_res,
671 metadata_file_path
672 )
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000673
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100674 return env_path
Kshitij Sisodia9c6f9f82022-05-20 14:30:02 +0100675
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000676
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000677if __name__ == "__main__":
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100678 parser = ArgumentParser()
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000679 parser.add_argument(
680 "--skip-vela",
681 help="Do not run Vela optimizer on downloaded models.",
682 action="store_true",
683 )
684 parser.add_argument(
685 "--additional-ethos-u-config-name",
686 help=f"""Additional (non-default) configurations for Vela:
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100687 {valid_npu_config_names}""",
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000688 default=[],
689 action="append",
690 )
691 parser.add_argument(
692 "--arena-cache-size",
693 help="Arena cache size in bytes (if overriding the defaults)",
694 type=int,
695 default=0,
696 )
697 parser.add_argument(
698 "--clean",
Richard Burton17069622022-03-17 10:54:26 +0000699 help="Clean the directory and optimize the downloaded resources",
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000700 action="store_true",
701 )
Kshitij Sisodiac22e80e2022-03-14 09:26:48 +0000702 parser.add_argument(
703 "--requirements-file",
704 help="Path to requirements.txt file to install additional packages",
705 type=str,
Richard Burton17069622022-03-17 10:54:26 +0000706 default=Path(__file__).parent.resolve() / 'scripts' / 'py' / 'requirements.txt'
Kshitij Sisodiac22e80e2022-03-14 09:26:48 +0000707 )
708
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100709 args = parser.parse_args()
Kshitij Sisodiab9e9c892021-05-27 13:57:35 +0100710
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000711 if args.arena_cache_size < 0:
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000712 raise ArgumentTypeError("Arena cache size cannot not be less than 0")
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000713
Richard Burton17069622022-03-17 10:54:26 +0000714 if not Path(args.requirements_file).is_file():
Kshitij Sisodiac22e80e2022-03-14 09:26:48 +0000715 raise ArgumentTypeError(f"Invalid requirements file: {args.requirements_file}")
716
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000717 logging.basicConfig(filename="log_build_default.log", level=logging.DEBUG)
Kshitij Sisodiab9e9c892021-05-27 13:57:35 +0100718 logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
719
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000720 set_up_resources(
721 not args.skip_vela,
722 args.additional_ethos_u_config_name,
723 args.arena_cache_size,
724 args.clean,
Isabella Gottardi6c2ea452022-03-11 13:25:08 +0000725 args.requirements_file,
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000726 )