Deploy Structure and User Guide ================================ This guide provides a comprehensive overview of ``aidge_deploy``, the dedicated Eclipse Aidge module responsible for target-specific compilation, artifact packaging, transport, flashing, target execution, log parsing, and Hardware-in-the-Loop (HIL) benchmarking and validation. Introduction ------------ In the Aidge framework, model code generation (**export**) is cleanly decoupled from target-specific deployment orchestration (**deploy**). Once a model has been exported to standard C++ or device-optimized libraries, ``aidge_deploy`` automates the end-to-end pipeline required to compile, transfer, flash, execute, and evaluate the model on physical microcontrollers (bare-metal MCUs) or Single Board Computers (Linux SBCs). Key Features: - **Target Abstraction**: Unified pipeline across diverse hardware targets (ARM Cortex-M, STMicroelectronics STM32, Alif Ensemble, Raspberry Pi, NVIDIA Jetson, custom SBCs). - **Flexible Connection Backends**: Run compilations and executions locally, over SSH, inside Docker containers, or remotely through a Board-Farm API. - **Self-contained Export Input**: Consumes buildable projects whose exporter already resolved the required BSP. - **Hardware-in-the-Loop (HIL) Benchmarking & Validation**: Cycle counting, latency measurements, RAM/Flash profiling, and target prediction accuracy checks. System Architecture & Lifecycle Pipeline ---------------------------------------- The deployment workflow processes a finalized Aidge export artifact through a multi-stage lifecycle pipeline. .. code-block:: text ┌───────────────────────────┐ │ Aidge Model Export │ │ (Generated Code Folder) │ └─────────────┬─────────────┘ │ ▼ aidge_deploy.deploy() │ ▼ DeployTarget Registry (target, backend) ──► Target Instance │ ┌─────────────────────────────┴─────────────────────────────┐ │ │ │ 1. prepare() Read manifest and runtime settings │ │ 2. build() Local, Docker, or Remote C++ compile │ │ 3. transfer() [Optional] SSH/SCP upload to SBC │ │ 4. flash() [Optional] Program MCU (pyOCD / J-Link)│ │ 5. execute() Run binary on target & capture log │ │ 6. parse_output() Extract timings, cycles & accuracy │ │ │ └─────────────────────────────┬─────────────────────────────┘ │ ▼ DeployResult & Metrics Dict Ownership Across the Three Layers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``aidge_deploy`` is the consumer of an export contract, not an extension of code generation. Responsibilities are divided as follows: .. list-table:: Architectural ownership :header-rows: 1 :widths: 22 38 40 * - Layer - Owns - Must not own * - ``aidge_core`` - Hardware targets, execution domains, memories, processing elements, architecture, build system, BSP coordinates, shared registries, export orchestration, and artifact schema - Transport credentials, flashing sessions, or exporter templates * - Exporter package - Kernels, graph-to-code mappings, target-specific export assets, project composition, BSP integration, generated entry points, and output protocol selection - SSH/serial connections, flashing, or result collection * - ``aidge_deploy`` - Build location, transfer, flashing, execution, log capture, protocol parsing, and result aggregation - Graph transformations, kernel generation, BSP selection, or duplicate hardware profiles The hardware model remains authoritative throughout the flow. Both ``TargetExport`` and ``DeployTarget`` resolve an existing hardware target class from ``aidge_core.hw_model``. The manifest records the identity and properties of the generated artifact, but it does not replace the hardware model. For the producer side of this contract, including ``ExportContext``, ``TargetExport``, composers, preprocessors, and target strategies, see :doc:`aidge_export_structure`. The Export/Deploy Boundary ^^^^^^^^^^^^^^^^^^^^^^^^^^ Export returns an ``ExportArtifact`` with two members: ``path`` Directory containing the complete project produced by the exporter. ``manifest`` An immutable ``ExportManifest`` also serialized as ``.aidge-export.json`` in that directory. The manifest contains the generated target, execution domain, environment, build system, application kind, expected artifact path, output protocol, input/output descriptions, profiling information, and serialized ``ExportApplication`` settings. These are artifact facts: they describe what was generated and how its output must be interpreted. ``deploy()`` calls ``load_export_artifact()`` for either an artifact object or a directory. Loading validates the manifest schema. Deployment then rejects a configuration whose target differs from ``manifest.target``. Runtime defaults for benchmark, validation, TinyML Benchmark, and heterogeneous execution are hydrated from ``manifest.application_config`` when the caller did not provide explicit deploy sub-configurations. This boundary provides two useful properties: * export and deploy can run independently, in different processes or on different machines; * deploy never needs the original Aidge graph, exporter configuration, or template-selection logic. Target Capabilities & Mixins ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Every target implementation inherits from the abstract base class ``DeployTarget`` and can selectively implement mixin interfaces depending on its capabilities: - **``DeployTarget``**: Core interface defining ``prepare()``, ``build()``, ``execute()``, and ``parse_output()``. - **``TransferableTarget``**: Adds ``transfer()`` for targets requiring file transfers to a remote host (e.g., via SSH). - **``FlashableTarget``**: Adds ``flash()`` for bare-metal targets requiring firmware programming. - **``RemoteBuildTarget``**: Replaces local ``build()`` with ``build_remote()`` for builds executed on a remote server or API. Heterogeneous graph partitioning and multi-program generation are exporter responsibilities. The resulting manifest tells deploy which build, flash, and runtime path to use; no deploy target generates model sources. ``DeployTarget`` and the Hardware Model ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``@register_deploy_target(target, backend)`` first resolves ``target`` through ``get_target_class()``. The decorator attaches that class as ``hardware_target_class`` to the deploy implementation. Each instance exposes ``hardware_target`` as a cached hardware-model object. Consequently, deploy code reads target facts such as the target name, build system, HAL family, or UART handle from ``self.hardware_target`` rather than declaring them again. The deploy registry key has two dimensions because the same hardware may be reached in different ways: .. code-block:: text ("stm32h7a3", "local") -> local build, probe flash, UART capture ("stm32h7a3", "api") -> board-farm implementation ("raspberrypi", "ssh") -> remote transfer and execution ("raspberrypi", "ssh_docker") -> remote container implementation The target dimension selects hardware identity; the backend dimension selects deployment mechanics. Neither dimension selects export templates. Public Interfaces & API Reference --------------------------------- The ``deploy()`` Function ^^^^^^^^^^^^^^^^^^^^^^^^^ The primary entry point for deployment orchestration is ``aidge_deploy.deploy()``. .. code-block:: python import aidge_deploy result: aidge_deploy.DeployResult = aidge_deploy.deploy(export_dir, config) Parameters: - **``export_dir``**: An ``ExportArtifact`` returned by ``export()``, or a path to its directory containing ``.aidge-export.json``. - **``config``** (``DeployConfig``): Configuration object specifying target hardware, connection backend, flashing parameters, serial settings, and optional benchmarking/validation parameters. Returns: - A **``DeployResult``** object containing output dictionaries, performance metrics, peak memory usage, inference timing lists, and accuracy. Configuration Data Structures ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DeployConfig ~~~~~~~~~~~~ The ``DeployConfig`` dataclass encapsulates all configuration flags required by ``aidge_deploy``: .. code-block:: python from pathlib import Path from aidge_deploy import DeployConfig config = DeployConfig( target="stm32h7a3", # Name of target device backend="local", # Backend: 'local', 'ssh', 'ssh_docker', 'api' build_dir=Path("./build"), # Optional build workspace path keep_build_dir=False, # Retain build directory after completion # API Backend Options (for remote board farm) api_url="http://127.0.0.1:8000", api_username="admin", api_password="password", api_board_name="stm32h7_lab_01", # SSH / SBC Options ssh_host="192.168.1.100", ssh_user="pi", ssh_port=22, ssh_key_path=Path("~/.ssh/id_ed25519"), # Serial / UART Options serial_port="/dev/ttyACM0", serial_baudrate=115200, uart_timeout=0.1, uart_capture_duration=300.0, end_keyword="END DEMO", # Flashing & Docker Options docker_image="aidge:latest", flash_method="pyocd", probe_unique_id="0001A", pyocd_target="stm32h7a3zi", # Optional Sub-configurations validation=valid_config, # ValidationConfig benchmark=bench_config # BenchmarkConfig ) Alternatively, construct ``DeployConfig`` from a CLI connection namespace using the helper method: .. code-block:: python deploy_config = DeployConfig.from_connection_config( conn_cfg=conn_cfg, target="stm32h7a3", validation=valid_cfg, benchmark=bench_cfg ) Execution & Result Data Structures ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - **``DeployContext``**: Passed across pipeline stages; holds ``export_dir``, ``target``, ``backend``, ``build_dir``, and temporary attributes. - **``BuildResult``**: Produced by ``build()``; contains ``artifact_path``, ``mem_peaks``, and compilation status. - **``ExecutionResult``**: Produced by ``execute()``; captures raw ``stdout`` or console logs from the target. - **``DeployResult``**: Final result object with fields: - ``output`` (``dict | None``): Parsed log output containing layer timings or prediction arrays. - ``metrics`` (``dict``): Collected hardware metrics (e.g. CPU cycles). - ``memory_peak_bytes`` (``int``): Peak RAM usage during execution. - ``timings`` (``list[float] | None``): Measured inference latencies in milliseconds. - ``accuracy`` (``float | None``): Top-1 accuracy score when target validation is enabled. Dynamic Selection Registry -------------------------- Targets and connection backends are registered dynamically using decorators. This eliminates hardcoded conditional logic and allows third-party target modules to register seamlessly. Registering a Target ^^^^^^^^^^^^^^^^^^^^ Target classes decorate themselves with ``@register_deploy_target(target_name, backend_name)``: .. code-block:: python from aidge_deploy.registry import register_deploy_target, DeployTarget from aidge_deploy.boards.baremetal.stm32.base import BaseSTM32LocalDeployTarget @register_deploy_target("stm32h7a3", "local") class STM32H7A3LocalTarget(BaseSTM32LocalDeployTarget): """Only local deployment mechanics belong here.""" # Hardware metadata is available through self.hardware_target. pass Resolving a Target ^^^^^^^^^^^^^^^^^^ To retrieve an instantiated target: .. code-block:: python from aidge_deploy.registry import get_deploy_target target_instance = get_deploy_target("stm32h7a3", "local") Board Support Package (BSP) Asset Management -------------------------------------------- Static hardware support files (linker scripts ``.ld``, vendor HAL headers, assembly startup code, and Makefiles) are stored separately as **Board Support Packages (BSPs)**. BSP resolution happens during export, never during deploy. Ownership and resolution ^^^^^^^^^^^^^^^^^^^^^^^^ The generic manager is ``aidge_core.export_utils.bsp_manager``. The hardware model declares the complete ``BSPPackage`` for each target: package name, version, and archive name. The relevant exporter reads this package from its resolved ``Device`` and incorporates it into the generated project. No package coordinate is declared by a deploy target or duplicated in an exporter profile. ``ensure_bsp_package()`` first accepts a non-empty exporter-owned local BSP, then checks the package/version/archive-specific cache, and finally downloads the exact GitLab Generic Package asset. The exporter copies the result into the generated project. ``aidge_deploy`` receives that complete project and does not download or copy BSP content. Authentication & Environment Variables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When downloading or uploading BSP assets, authentication token lookup follows this priority: .. code-block:: bash export GITLAB_TOKEN="" # or export GITLAB_PRIVATE_TOKEN="" # or (automatically set in GitLab CI pipelines) export CI_JOB_TOKEN="" Environment Overrides: - ``AIDGE_BSP_REGISTRY_URL``: Custom GitLab package registry base URL (default: Eclipse GitLab repository). - ``AIDGE_BSP_CACHE_DIR``: Custom directory for storing downloaded BSP archives. Publishing BSP Packages to GitLab ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When introducing a new target board, publish its BSP assets using either the Python API or ``curl``. Using Python: .. code-block:: python from pathlib import Path from aidge_core.export_utils import BSPPackage, publish_bsp_package upload_url = publish_bsp_package( Path("./stm32f413_bsp_staging"), BSPPackage( name="aidge_deploy_bsp_stm32f413", version="v1.2.5", archive_name="stm32f413.tar.gz", ), token="your_gitlab_token" ) print(f"BSP asset published to: {upload_url}") Using Terminal ``curl``: .. code-block:: bash # 1. Compress BSP staging directory tar -czvf stm32f413.tar.gz -C ./stm32f413_bsp_staging . # 2. Upload to GitLab Generic Package Registry curl --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \ --upload-file stm32f413.tar.gz \ "https://gitlab.eclipse.org/api/v4/projects/5138/packages/generic/aidge_deploy_bsp_stm32f413/v1.2.5/stm32f413.tar.gz" Usage Examples -------------- Example 1: Local STM32 Bare-metal Deployment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This example demonstrates exporting a model, flashing an STM32 board locally using pyOCD, and capturing inference timings over UART. .. code-block:: python import aidge_core from aidge_core.export_utils import ExportApplication, export from aidge_core.export_utils.config import ExportConfig import aidge_deploy # 1. Generate a complete, buildable project export_cfg = ExportConfig( target="stm32h7a3", export_folder="export_stm32h7", dtype="int8", cmsis=True, application=ExportApplication( kind="benchmark", nb_warmup=10, nb_iterations=100, profiling=True ), ) artifact = export(model, export_cfg, input_tensors=[sample_tensor]) # 2. Configure Local Deployment deploy_cfg = aidge_deploy.DeployConfig( target="stm32h7a3", backend="local", serial_port="/dev/ttyACM0", serial_baudrate=115200, ) # 3. Execute Deployment result = aidge_deploy.deploy(artifact, deploy_cfg) print(f"Peak Memory: {result.memory_peak_bytes} bytes") print(f"Mean Inference Latency: {sum(result.timings)/len(result.timings):.2f} ms") Example 2: Remote SBC Deployment via SSH & Docker ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Deploy and benchmark an exported model on a remote Raspberry Pi board running inside a Docker container. .. code-block:: python from pathlib import Path import aidge_deploy deploy_cfg = aidge_deploy.DeployConfig( target="raspberrypi", backend="ssh_docker", ssh_host="192.168.1.50", ssh_user="pi", ssh_key_path=Path("~/.ssh/id_ed25519"), docker_image="aidge:latest", benchmark=BenchmarkConfig(nb_iterations=50) ) result = aidge_deploy.deploy("export_raspberrypi", deploy_cfg) print("Execution output:", result.output) Example 3: Remote Board-Farm API Deployment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Submit a model build to a remote Board-Farm server queue. .. code-block:: python import aidge_deploy deploy_cfg = aidge_deploy.DeployConfig( target="stm32h7a3", backend="api", api_url="http://board-farm.example.com/api", api_board_name="stm32h7_node_01", api_username="ci_user", api_password="ci_password" ) result = aidge_deploy.deploy("export_stm32h7", deploy_cfg) print(f"Board Farm Result Accuracy: {result.accuracy}") Adding a New Target End to End ------------------------------ Adding hardware support normally crosses all three layers, but each piece of information must have only one owner. 1. **Register the hardware model in core.** Define the ``Device``, processing elements, memories, execution domain, architecture, compatible export libraries, and build system. Put hardware-owned information such as ``BSPPackage``, HAL family, UART handle, and electrical reference values on this model. 2. **Add exporter integration.** Under the exporter, create ``targets//__init__.py`` and register a ``TargetExport``. Store only exporter-owned assets there, such as a target-specific main or Makefile template. Put reusable composition in ``targets/common.py``. 3. **Select an export hook only if necessary.** Use a project composer for environment-wide project assembly, a target preprocessor for target graph transformation, or a target export strategy when the target must replace generic generation entirely. 4. **Make export independently buildable.** If the target needs a BSP, publish it and reference its coordinates from the hardware model. The exporter must resolve and copy it into the project. Verify export without invoking deploy. 5. **Add deployment mechanics.** Create the appropriate module under ``aidge_deploy/boards////``. Subclass a common deploy implementation and register each supported connection backend with ``@register_deploy_target(target, backend)``. 6. **Implement only required capabilities.** Add ``TransferableTarget``, ``FlashableTarget``, or ``RemoteBuildTarget`` only when the backend needs that stage. Reuse ``self.hardware_target`` for hardware metadata. 7. **Test the boundary.** Test export generation and its manifest separately, then test deploy with an existing manifested directory. Also test rejection of mismatched targets and parsing for the declared protocol. Before adding a value to an exporter or deploy class, ask whether it describes the physical/logical hardware, the generated project, or the deployment session. Hardware facts belong to the hardware model; generated-project facts belong to exporter metadata or the manifest; connection and execution facts belong to ``DeployConfig`` and ``DeployTarget``. For detailed developer instructions, see the internal guide in ``aidge_deploy/aidge_deploy/README.md``.