blob: 7ed9e97fe135167969757f2614f89a5741781838 [file] [log] [blame]
alexanderf4e2c472021-05-14 13:14:21 +01001#!/usr/bin/env python3
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +00002# SPDX-FileCopyrightText: Copyright 2021-2024 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
Alex Tawsedaba3cf2023-09-29 15:55:38 +010034from dataclasses import dataclass
Richard Burton17069622022-03-17 10:54:26 +000035from pathlib import Path
Alex Tawsedaba3cf2023-09-29 15:55:38 +010036from urllib.error import URLError
Isabella Gottardi6c2ea452022-03-11 13:25:08 +000037
Kshitij Sisodia6a2ac462022-03-01 17:36:06 +000038from scripts.py.check_update_resources_downloaded import get_md5sum_for_file
Kshitij Sisodia3be26232021-10-29 12:29:06 +010039
Alex Tawsedaba3cf2023-09-29 15:55:38 +010040# Supported version of Python and Vela
Richard Burton49482d52023-11-30 11:38:45 +000041
42VELA_VERSION = "3.10.0"
43py3_version_minimum = (3, 10)
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
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +000058# The internal SRAM size for Corstone-300 implementation on MPS3 specified by AN552
59# The internal SRAM size for Corstone-310 implementation on MPS3 specified by AN555
60# is 4MB, but we are content with the 2MB specified below.
61MPS3_MAX_SRAM_SZ = 2 * 1024 * 1024 # 2 MiB (2 banks of 1 MiB each)
62
63default_use_case_resources_path = (Path(__file__).parent.resolve()
64 / 'scripts' / 'py' / 'use_case_resources.json')
65
66default_requirements_path = (Path(__file__).parent.resolve()
67 / 'scripts' / 'py' / 'requirements.txt')
68
69
70@dataclass(frozen=True)
71class NpuConfig:
72 """
73 Represent an NPU configuration for Vela
74 """
75 config_name: str
76 memory_mode: str
77 system_config: str
78 ethos_u_npu_id: str
79 ethos_u_config_id: str
80 arena_cache_size: str
Kshitij Sisodia3be26232021-10-29 12:29:06 +010081
Alex Tawsedaba3cf2023-09-29 15:55:38 +010082
83@dataclass(frozen=True)
84class UseCaseResource:
85 """
86 Represent a use case's resource
87 """
88 name: str
89 url: str
90 sub_folder: typing.Optional[str] = None
91
92
93@dataclass(frozen=True)
94class UseCase:
95 """
96 Represent a use case
97 """
98 name: str
99 url_prefix: str
100 resources: typing.List[UseCaseResource]
101
102
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000103@dataclass(frozen=True)
104class SetupArgs:
105 """
106 Args used to set up the project.
107
108 Attributes:
109 run_vela_on_models (bool) : Whether to run Vela on the downloaded models
110 additional_npu_config_names (list) : List of strings of Ethos-U NPU configs.
111 use_case_names (list) : List of names of use cases to set up resources for
112 (default is all).
113 arena_cache_size (int) : Specifies arena cache size in bytes. If a value
114 greater than 0 is provided, this will be taken
115 as the cache size. If 0, the default values, as per
116 the NPU config requirements, are used.
117 check_clean_folder (bool) : Indicates whether the resources folder needs to
118 be checked for updates and cleaned.
119 additional_requirements_file (str) : Path to a requirements.txt file if
120 additional packages need to be
121 installed.
122 use_case_resources_file (str) : Path to a JSON file containing the use case
123 metadata resources.
124 """
125 run_vela_on_models: bool = False
126 additional_npu_config_names: typing.List[str] = ()
127 use_case_names: typing.List[str] = ()
128 arena_cache_size: int = 0
129 check_clean_folder: bool = False
130 additional_requirements_file: Path = ""
131 use_case_resources_file: Path = ""
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100132
133
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000134def load_use_case_resources(
135 use_case_resources_file: Path,
136 use_case_names: typing.List[str] = ()
137) -> typing.List[UseCase]:
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100138 """
139 Load use case metadata resources
140
141 Parameters
142 ----------
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000143 use_case_resources_file : Path to a JSON file containing the use case
144 metadata resources.
145 use_case_names : List of named use cases to restrict
146 resource loading to.
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100147 Returns
148 -------
149 The use cases resources object parsed to a dict
150 """
151
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000152 with open(use_case_resources_file, encoding="utf8") as f:
153 parsed_use_cases = json.load(f)
154 use_cases = (
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100155 UseCase(
156 name=u["name"],
157 url_prefix=u["url_prefix"],
158 resources=[UseCaseResource(**r) for r in u["resources"]],
159 )
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000160 for u in parsed_use_cases
161 )
162
163 if len(use_case_names) == 0:
164 return list(use_cases)
165
166 return [uc for uc in use_cases if uc.name in use_case_names]
Liam Barryb52b5852021-11-15 11:41:40 +0000167
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100168
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000169def call_command(command: str, verbose: bool = True) -> str:
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100170 """
171 Helpers function that call subprocess and return the output.
172
173 Parameters:
174 ----------
175 command (string): Specifies the command to run.
176 """
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000177 if verbose:
178 logging.info(command)
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100179 try:
180 proc = subprocess.run(
181 command, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True
182 )
183 log = proc.stdout.decode("utf-8")
alexander50a06502021-05-12 19:06:02 +0100184 logging.info(log)
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100185 return log
186 except subprocess.CalledProcessError as err:
187 log = err.stdout.decode("utf-8")
alexander50a06502021-05-12 19:06:02 +0100188 logging.error(log)
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100189 raise err
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100190
191
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000192def get_default_npu_config_from_name(
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100193 config_name: str, arena_cache_size: int = 0
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000194) -> typing.Optional[NpuConfig]:
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100195 """
Richard Burton17069622022-03-17 10:54:26 +0000196 Gets the file suffix for the TFLite file from the
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100197 `accelerator_config` string.
198
199 Parameters:
200 ----------
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000201 config_name (str): Ethos-U NPU configuration from valid_npu_config_names
202
203 arena_cache_size (int): Specifies arena cache size in bytes. If a value
204 greater than 0 is provided, this will be taken
205 as the cache size. If 0, the default values, as per
206 the NPU config requirements, are used.
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100207
208 Returns:
209 -------
210 NPUConfig: An NPU config named tuple populated with defaults for the given
211 config name
212 """
213 if config_name not in valid_npu_config_names:
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000214 raise ValueError(
215 f"""
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100216 Invalid Ethos-U NPU configuration.
217 Select one from {valid_npu_config_names}.
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000218 """
219 )
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100220
221 strings_ids = ["ethos-u55-", "ethos-u65-"]
222 processor_ids = ["U55", "U65"]
223 prefix_ids = ["H", "Y"]
224 memory_modes = ["Shared_Sram", "Dedicated_Sram"]
225 system_configs = ["Ethos_U55_High_End_Embedded", "Ethos_U65_High_End"]
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000226 memory_modes_arena = {
Richard Burton17069622022-03-17 10:54:26 +0000227 # For shared SRAM memory mode, we use the MPS3 SRAM size by default.
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100228 "Shared_Sram": MPS3_MAX_SRAM_SZ if arena_cache_size <= 0 else arena_cache_size,
Richard Burton17069622022-03-17 10:54:26 +0000229 # For dedicated SRAM memory mode, we do not override the arena size. This is expected to
230 # be defined in the Vela configuration file instead.
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000231 "Dedicated_Sram": None if arena_cache_size <= 0 else arena_cache_size,
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000232 }
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100233
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100234 for i, string_id in enumerate(strings_ids):
235 if config_name.startswith(string_id):
236 npu_config_id = config_name.replace(string_id, prefix_ids[i])
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000237 return NpuConfig(
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000238 config_name=config_name,
239 memory_mode=memory_modes[i],
240 system_config=system_configs[i],
241 ethos_u_npu_id=processor_ids[i],
242 ethos_u_config_id=npu_config_id,
243 arena_cache_size=memory_modes_arena[memory_modes[i]],
244 )
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100245
246 return None
247
248
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100249def remove_tree_dir(dir_path: Path):
250 """
251 Delete and re-create a directory
252
253 Parameters
254 ----------
255 dir_path : The directory path
256 """
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000257 try:
Richard Burton17069622022-03-17 10:54:26 +0000258 # Remove the full directory.
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000259 shutil.rmtree(dir_path)
Richard Burton17069622022-03-17 10:54:26 +0000260 # Re-create an empty one.
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000261 os.mkdir(dir_path)
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100262 except OSError:
263 logging.error("Failed to delete %s.", dir_path)
264
265
266def initialize_use_case_resources_directory(
267 use_case: UseCase,
268 metadata: typing.Dict,
269 download_dir: Path,
270 check_clean_folder: bool,
271 setup_script_hash_verified: bool,
272):
273 """
274 Initialize the resources_downloaded directory for a use case
275
276 @param use_case: The use case
277 @param metadata: The metadata
278 @param download_dir: The parent directory
279 @param check_clean_folder: Whether to clean the folder
280 @param setup_script_hash_verified: Whether the hash of this script is verified
281 """
282 try:
283 # Does the usecase_name download dir exist?
284 (download_dir / use_case.name).mkdir()
285 except OSError as err:
286 if err.errno == errno.EEXIST:
287 # The usecase_name download dir exist.
288 if check_clean_folder and not setup_script_hash_verified:
289 for idx, metadata_uc_url_prefix in enumerate(
290 [
291 f
292 for f in metadata["resources_info"]
293 if f["name"] == use_case.name
294 ][0]["url_prefix"]
295 ):
296 if metadata_uc_url_prefix != use_case.url_prefix[idx]:
297 logging.info("Removing %s resources.", use_case.name)
298 remove_tree_dir(download_dir / use_case.name)
299 break
300 elif err.errno != errno.EEXIST:
301 logging.error("Error creating %s directory.", use_case.name)
302 raise
303
304
305def download_file(url: str, dest: Path):
306 """
307 Download a file
308
309 @param url: The URL of the file to download
310 @param dest: The destination of downloaded file
311 """
312 try:
313 with urllib.request.urlopen(url) as g:
314 with open(dest, "b+w") as f:
315 f.write(g.read())
316 logging.info("- Downloaded %s to %s.", url, dest)
317 except URLError:
318 logging.error("URLError while downloading %s.", url)
319 raise
320
321
322def download_resources(
323 use_case: UseCase,
324 metadata: typing.Dict,
325 download_dir: Path,
326 check_clean_folder: bool,
327 setup_script_hash_verified: bool,
328):
329 """
330 Download the resources associated with a use case
331
332 @param use_case: The use case
333 @param metadata: The metadata
334 @param download_dir: The parent directory
335 @param check_clean_folder: Whether to clean the folder
336 @param setup_script_hash_verified: Whether the hash is already verified
337 """
338 initialize_use_case_resources_directory(
339 use_case,
340 metadata,
341 download_dir,
342 check_clean_folder,
343 setup_script_hash_verified
344 )
345
346 reg_expr_str = r"{url_prefix:(.*\d)}"
347 reg_expr_pattern = re.compile(reg_expr_str)
348 for res in use_case.resources:
349 res_name = res.name
350 url_prefix_idx = int(reg_expr_pattern.search(res.url).group(1))
351 res_url = use_case.url_prefix[url_prefix_idx] + re.sub(
352 reg_expr_str, "", res.url
353 )
354
355 sub_folder = ""
356 if res.sub_folder is not None:
357 try:
358 # Does the usecase_name/sub_folder download dir exist?
359 (download_dir / use_case.name / res.sub_folder).mkdir()
360 except OSError as err:
361 if err.errno != errno.EEXIST:
362 logging.error(
363 "Error creating %s/%s directory.",
364 use_case.name,
365 res.sub_folder
366 )
367 raise
368 sub_folder = res.sub_folder
369
370 res_dst = download_dir / use_case.name / sub_folder / res_name
371
372 if res_dst.is_file():
373 logging.info("File %s exists, skipping download.", res_dst)
374 else:
375 download_file(res_url, res_dst)
376
377
378def run_vela(
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000379 config: NpuConfig,
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100380 env_activate_cmd: str,
381 model: Path,
382 config_file: Path,
383 output_dir: Path
384) -> bool:
385 """
386 Run vela on the specified model
387 @param config: The NPU configuration
388 @param env_activate_cmd: The Python venv activation command
389 @param model: The model
390 @param config_file: The vela config file
391 @param output_dir: The output directory
392 @return: True if the optimisation was skipped, false otherwise
393 """
394 # model name after compiling with vela is an initial model name + _vela suffix
395 vela_optimised_model_path = model.parent / (model.stem + "_vela.tflite")
396
397 vela_command_arena_cache_size = ""
398
399 if config.arena_cache_size:
400 vela_command_arena_cache_size = (
401 f"--arena-cache-size={config.arena_cache_size}"
402 )
403
404 vela_command = (
405 f"{env_activate_cmd} && vela {model} "
406 + f"--accelerator-config={config.config_name} "
407 + "--optimise Performance "
408 + f"--config {config_file} "
409 + f"--memory-mode={config.memory_mode} "
410 + f"--system-config={config.system_config} "
411 + f"--output-dir={output_dir} "
412 + f"{vela_command_arena_cache_size}"
413 )
414
415 # We want the name to include the configuration suffix. For example: vela_H128,
416 # vela_Y512 etc.
417 new_suffix = "_vela_" + config.ethos_u_config_id + ".tflite"
418 new_vela_optimised_model_path = model.parent / (model.stem + new_suffix)
419
420 skip_optimisation = new_vela_optimised_model_path.is_file()
421
422 if skip_optimisation:
423 logging.info(
424 "File %s exists, skipping optimisation.",
425 new_vela_optimised_model_path
426 )
427 else:
428 call_command(vela_command)
429
430 # Rename default vela model.
431 vela_optimised_model_path.rename(new_vela_optimised_model_path)
432 logging.info(
433 "Renaming %s to %s.",
434 vela_optimised_model_path,
435 new_vela_optimised_model_path
436 )
437
438 return skip_optimisation
439
440
441def run_vela_on_all_models(
442 current_file_dir: Path,
443 download_dir: Path,
444 env_activate_cmd: str,
445 arena_cache_size: int,
446 npu_config_names: typing.List[str]
447):
448 """
449 Run vela on downloaded models for the specified NPU configurations
450
451 @param current_file_dir: Path to the current directory
452 @param download_dir: Path to the downloaded resources directory
453 @param env_activate_cmd: Command used to activate Python venv
454 @param npu_config_names: Names of NPU configurations for which to run Vela
455 @param arena_cache_size: The arena cache size
456 """
457 config_file = current_file_dir / "scripts" / "vela" / "default_vela.ini"
458 models = [
459 Path(dirpath) / f
460 for dirpath, dirnames, files in os.walk(download_dir)
461 for f in fnmatch.filter(files, "*.tflite")
462 if "vela" not in f
463 ]
464
465 # Get npu config tuple for each config name in a list:
466 npu_configs = [
467 get_default_npu_config_from_name(name, arena_cache_size)
468 for name in npu_config_names
469 ]
470
471 logging.info("All models will be optimised for these configs:")
472 for config in npu_configs:
473 logging.info(config)
474
475 optimisation_skipped = False
476
477 for model in models:
478 for config in npu_configs:
479 optimisation_skipped = run_vela(
480 config,
481 env_activate_cmd,
482 model,
483 config_file,
484 output_dir=model.parent
485 ) or optimisation_skipped
486
487 # If any optimisation was skipped, show how to regenerate:
488 if optimisation_skipped:
489 logging.warning("One or more optimisations were skipped.")
490 logging.warning(
491 "To optimise all the models, please remove the directory %s.",
492 download_dir
493 )
494
495
496def initialize_resources_directory(
497 download_dir: Path,
498 check_clean_folder: bool,
499 metadata_file_path: Path,
500 setup_script_hash: str
501) -> typing.Tuple[typing.Dict, bool]:
502 """
503 Sets up the resources_downloaded directory and checks to see if this script
504 has been modified since the last time resources were downloaded
505
506 @param download_dir: Path to the resources_downloaded directory
507 @param check_clean_folder: Determines whether to clean the downloads directory
508 @param metadata_file_path: Path to the metadata file
509 @param setup_script_hash: The md5 hash of this script
510 @return: The metadata and a boolean to indicate whether this
511 script has changed since it was last run
512 """
513 metadata_dict = {}
514 setup_script_hash_verified = False
515
516 if download_dir.is_dir():
517 logging.info("'resources_downloaded' directory exists.")
518 # Check and clean?
519 if check_clean_folder and metadata_file_path.is_file():
520 with open(metadata_file_path, encoding="utf8") as metadata_file:
521 metadata_dict = json.load(metadata_file)
522
523 vela_in_metadata = metadata_dict["ethosu_vela_version"]
524 if vela_in_metadata != VELA_VERSION:
525 # Check if all the resources needs to be removed and regenerated.
526 # This can happen when the Vela version has changed.
527 logging.info(
528 ("Vela version in metadata is %s, current %s."
529 " Removing the resources and re-download them.",
530 vela_in_metadata,
531 VELA_VERSION
532 )
533 )
534 remove_tree_dir(download_dir)
535 metadata_dict = {}
536 else:
537 # Check if the set_up_default_resorces.py has changed from last setup
538 setup_script_hash_verified = (
539 metadata_dict.get("set_up_script_md5sum")
540 == setup_script_hash
541 )
542 else:
543 download_dir.mkdir()
544
545 return metadata_dict, setup_script_hash_verified
546
547
548def set_up_python_venv(
549 download_dir: Path,
550 additional_requirements_file: Path = ""
551):
552 """
553 Set up the Python environment with which to set up the resources
554
555 @param download_dir: Path to the resources_downloaded directory
556 @param additional_requirements_file: Optional additional requirements file
557 @return: Path to the venv Python binary + activate command
558 """
559 env_dirname = "env"
560 env_path = download_dir / env_dirname
561
562 venv_builder = venv.EnvBuilder(with_pip=True, upgrade_deps=True)
563 venv_context = venv_builder.ensure_directories(env_dir=env_path)
564
565 env_python = Path(venv_context.env_exe)
566
567 if not env_python.is_file():
568 # Create the virtual environment using current interpreter's venv
569 # (not necessarily the system's Python3)
570 venv_builder.create(env_dir=env_path)
571
572 if sys.platform == "win32":
573 env_activate = Path(f"{venv_context.bin_path}/activate.bat")
574 env_activate_cmd = str(env_activate)
575 else:
576 env_activate = Path(f"{venv_context.bin_path}/activate")
577 env_activate_cmd = f". {env_activate}"
578
579 if not env_activate.is_file():
580 venv_builder.install_scripts(venv_context, venv_context.bin_path)
581
582 # 1.3 Install additional requirements first, if a valid file has been provided
583 if additional_requirements_file and os.path.isfile(additional_requirements_file):
584 command = f"{env_python} -m pip install -r {additional_requirements_file}"
585 call_command(command)
586
587 # 1.4 Make sure to have all the main requirements
588 requirements = [f"ethos-u-vela=={VELA_VERSION}"]
589 command = f"{env_python} -m pip freeze"
590 packages = call_command(command)
591 for req in requirements:
592 if req not in packages:
593 command = f"{env_python} -m pip install {req}"
594 call_command(command)
595
596 return env_path, env_activate_cmd
597
598
599def update_metadata(
600 metadata_dict: typing.Dict,
601 setup_script_hash: str,
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000602 use_case_resources: typing.List[UseCase],
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100603 metadata_file_path: Path
604):
605 """
606 Update the metadata file
607
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000608 @param metadata_dict : The metadata dictionary to update
609 @param setup_script_hash : The setup script hash
610 @param use_case_resources : The use case resources metadata
611 @param metadata_file_path : The metadata file path
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100612 """
613 metadata_dict["ethosu_vela_version"] = VELA_VERSION
614 metadata_dict["set_up_script_md5sum"] = setup_script_hash.strip("\n")
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000615 metadata_dict["resources_info"] = [dataclasses.asdict(uc) for uc in use_case_resources]
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100616
617 with open(metadata_file_path, "w", encoding="utf8") as metadata_file:
618 json.dump(metadata_dict, metadata_file, indent=4)
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000619
620
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000621def get_default_use_cases_names() -> typing.List[str]:
622 """
623 Get the names of the default use cases
624
625 :return : List of use case names as strings
626 """
627 use_case_resources = load_use_case_resources(default_use_case_resources_path)
628 return [uc.name for uc in use_case_resources]
629
630
631def set_up_resources(args: SetupArgs) -> Path:
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100632 """
633 Helpers function that retrieve the output from a command.
634
635 Parameters:
636 ----------
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000637 args (SetupArgs) : Arguments used to set up the project.
Kshitij Sisodia9c6f9f82022-05-20 14:30:02 +0100638
639 Returns
640 -------
641
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000642 Tuple of pairs of Paths: (download_directory_path, virtual_env_path)
Kshitij Sisodia9c6f9f82022-05-20 14:30:02 +0100643
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000644 download_directory_path : Root of the directory where the resources have been downloaded to.
645 virtual_env_path : Path to the root of virtual environment.
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100646 """
Richard Burton17069622022-03-17 10:54:26 +0000647 # Paths.
648 current_file_dir = Path(__file__).parent.resolve()
649 download_dir = current_file_dir / "resources_downloaded"
650 metadata_file_path = download_dir / "resources_downloaded_metadata.json"
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000651
Isabella Gottardi6c2ea452022-03-11 13:25:08 +0000652 # Is Python minimum requirement matched?
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100653 if sys.version_info < py3_version_minimum:
654 raise RuntimeError(
655 f"ERROR: Python{'.'.join(str(i) for i in py3_version_minimum)}+ is required,"
656 f" please see the documentation on how to update it."
Isabella Gottardi6c2ea452022-03-11 13:25:08 +0000657 )
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100658 logging.info("Using Python version: %s", sys.version_info)
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000659
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000660 use_case_resources = load_use_case_resources(
661 args.use_case_resources_file,
662 args.use_case_names
663 )
Richard Burton17069622022-03-17 10:54:26 +0000664 setup_script_hash = get_md5sum_for_file(Path(__file__).resolve())
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100665
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100666 metadata_dict, setup_script_hash_verified = initialize_resources_directory(
667 download_dir,
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000668 args.check_clean_folder,
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100669 metadata_file_path,
670 setup_script_hash
671 )
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100672
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100673 env_path, env_activate = set_up_python_venv(
674 download_dir,
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000675 args.additional_requirements_file
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100676 )
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100677
678 # 2. Download models
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000679 logging.info("Downloading resources.")
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000680 for use_case in use_case_resources:
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100681 download_resources(
682 use_case,
683 metadata_dict,
684 download_dir,
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000685 args.check_clean_folder,
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100686 setup_script_hash_verified
687 )
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100688
689 # 3. Run vela on models in resources_downloaded
690 # New models will have same name with '_vela' appended.
691 # For example:
Kshitij Sisodia76a15802021-12-24 11:05:11 +0000692 # original model: kws_micronet_m.tflite
693 # after vela model: kws_micronet_m_vela_H128.tflite
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100694 #
695 # Note: To avoid to run vela twice on the same model, it's supposed that
696 # downloaded model names don't contain the 'vela' word.
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000697 if args.run_vela_on_models is True:
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100698 # Consolidate all config names while discarding duplicates:
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100699 run_vela_on_all_models(
700 current_file_dir,
701 download_dir,
702 env_activate,
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000703 args.arena_cache_size,
704 npu_config_names=list(
705 set(default_npu_config_names + list(args.additional_npu_config_names))
706 )
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100707 )
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000708
709 # 4. Collect and write metadata
710 logging.info("Collecting and write metadata.")
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100711 update_metadata(
712 metadata_dict,
713 setup_script_hash.strip("\n"),
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000714 use_case_resources,
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100715 metadata_file_path
716 )
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000717
Alex Tawsedaba3cf2023-09-29 15:55:38 +0100718 return env_path
Kshitij Sisodia9c6f9f82022-05-20 14:30:02 +0100719
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000720
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000721if __name__ == "__main__":
Isabella Gottardi2181d0a2021-04-07 09:27:38 +0100722 parser = ArgumentParser()
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000723 parser.add_argument(
724 "--skip-vela",
725 help="Do not run Vela optimizer on downloaded models.",
726 action="store_true",
727 )
728 parser.add_argument(
729 "--additional-ethos-u-config-name",
730 help=f"""Additional (non-default) configurations for Vela:
Kshitij Sisodia3be26232021-10-29 12:29:06 +0100731 {valid_npu_config_names}""",
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000732 default=[],
733 action="append",
734 )
735 parser.add_argument(
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000736 "--use-case",
737 help=f"""Only set up resources for the specified use case (can specify multiple times).
738 Valid values are: {get_default_use_cases_names()}
739 """,
740 default=[],
741 action="append",
742 )
743 parser.add_argument(
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000744 "--arena-cache-size",
745 help="Arena cache size in bytes (if overriding the defaults)",
746 type=int,
747 default=0,
748 )
749 parser.add_argument(
750 "--clean",
Richard Burton17069622022-03-17 10:54:26 +0000751 help="Clean the directory and optimize the downloaded resources",
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000752 action="store_true",
753 )
Kshitij Sisodiac22e80e2022-03-14 09:26:48 +0000754 parser.add_argument(
755 "--requirements-file",
756 help="Path to requirements.txt file to install additional packages",
757 type=str,
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000758 default=default_requirements_path
759 )
760 parser.add_argument(
761 "--use-case-resources-file",
762 help="Path to the use case resources file",
763 type=str,
764 default=default_use_case_resources_path
Kshitij Sisodiac22e80e2022-03-14 09:26:48 +0000765 )
766
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000767 parsed_args = parser.parse_args()
Kshitij Sisodiab9e9c892021-05-27 13:57:35 +0100768
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000769 if parsed_args.arena_cache_size < 0:
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000770 raise ArgumentTypeError("Arena cache size cannot not be less than 0")
Isabella Gottardi3acaaee2021-11-30 12:33:27 +0000771
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000772 if not Path(parsed_args.requirements_file).is_file():
773 raise ArgumentTypeError(f"Invalid requirements file: {parsed_args.requirements_file}")
Kshitij Sisodiac22e80e2022-03-14 09:26:48 +0000774
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000775 logging.basicConfig(filename="log_build_default.log", level=logging.DEBUG)
Kshitij Sisodiab9e9c892021-05-27 13:57:35 +0100776 logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
777
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000778 setup_args = SetupArgs(
779 run_vela_on_models=not parsed_args.skip_vela,
780 additional_npu_config_names=parsed_args.additional_ethos_u_config_name,
781 use_case_names=parsed_args.use_case,
782 arena_cache_size=parsed_args.arena_cache_size,
783 check_clean_folder=parsed_args.clean,
784 additional_requirements_file=parsed_args.requirements_file,
785 use_case_resources_file=parsed_args.use_case_resources_file,
Isabella Gottardief2b9dd2022-02-16 14:24:03 +0000786 )
Hugues Kamba-Mpianafea932f2024-03-04 16:01:55 +0000787
788 set_up_resources(setup_args)