Export Code Structure#

This guide provides an overview of the code structure of Aidge export modules.

Introduction#

In Aidge, an export module refers to any module that generates a standalone program capable of running a neural network on specific hardware targets.

Several export modules are already available in the framework, such as:

Throughout this guide, we will refer primarily to the C++ Export, since all export modules share a similar structure. We will walk through the complete export process using a small model as an example, explaining each step and mechanism involved.

Core, Export, and Deploy Architecture#

The source-generation workflow is deliberately divided into three layers:

aidge_core                         exporter package                    aidge_deploy
───────────────────────────────    ─────────────────────────────────   ─────────────────────────────
Hardware model                    Kernel/operator registrations       Build and execution backends
Generic graph preparation         TargetExport registrations         Transfer and flashing
Export orchestration              Project composers                  Output/protocol parsing
Shared registries                 Target preprocessors/strategies     DeployResult construction
ExportArtifact + manifest         Templates and BSP integration

         hardware identity                  buildable project
target ─────────────────────────► export ────────────────────────────► deploy

The boundaries are important:

  • aidge_core owns the hardware description and the generic orchestration.

  • An exporter owns source generation and produces a complete, buildable project. It may specialize generation for a target, but it does not flash or execute that project.

  • aidge_deploy consumes the exported project and its manifest. It builds, transfers, flashes, executes, and parses results, but it never regenerates model source code.

This separation makes export() useful without aidge_deploy and lets deployment consume an export in a later process or on another machine.

The Hardware Model: the Authoritative Target Description#

Hardware targets are registered in aidge_core.hw_model.targets with @register_target. A target class derives from Device and describes the hardware itself: processing elements, memories, execution domains, architecture, supported export libraries, build system, and target-owned metadata such as BSP package coordinates, HAL family, and UART handle when applicable.

The hardware model is the single source of truth for facts about the device. Exporter and deploy registrations reference the registered hardware class; they must not copy those facts into parallel profile dictionaries.

The relevant lookup functions are:

  • get_target_class(name) returns the registered target class.

  • get_target_by_name(name) creates a target instance.

  • list_registered_targets() lists known names and aliases.

Device.default_execution_domain selects the default execution context. It determines properties used during export, including main_pe, lib, arch, and build_system. Heterogeneous devices can expose multiple execution domains and may use an export preprocessor or a complete target strategy to generate several programs.

Generic Core Export Objects#

ExportConfig#

ExportConfig is the user request. It selects the hardware target, output directory, precision and optional backend features, and contains an ExportApplication. It is configuration, not resolved state.

ExportApplication#

ExportApplication describes the entry point that must be generated with the model. Its kind can request a standalone application, output display, benchmarking, or validation. It also carries generation-time options such as warm-up and iteration counts, profiling, TinyML Benchmark protocol support, and heterogeneous deployment flags.

These values affect generated sources. They are serialized into the manifest so deploy can reconstruct matching runtime defaults, but deploy does not use them to choose or regenerate templates.

ExportContext#

prepare_export() resolves the request into an immutable ExportContext. The context contains the instantiated Device, adapted graph, scheduler, input tensors, destination path, data type, optional labels, and the remaining generation flags. It is the internal contract passed to project composers, preprocessors, and target strategies.

TargetExport#

TargetExport links exporter-specific assets to an existing hardware-model target. It contains only information owned by that exporter, for example:

  • the assets root for that exporter/target pair;

  • a target-specific main.cpp template;

  • an optional Makefile template.

Construction resolves hardware_target_class through the core hardware registry. An unknown target therefore fails immediately. The registry key is (export_backend, target) and is managed by register_target_export() and get_target_export().

Exporter packages organize these definitions consistently:

aidge_export_<backend>/
└── aidge_export_<backend>/
    └── targets/
        ├── common.py
        ├── host/
        │   └── __init__.py
        └── my_target/
            ├── __init__.py
            └── templates/

targets is intentionally broader than boards: a target may be a development host, an SBC, a microcontroller board, an accelerator, or a virtual execution target. common.py owns backend-wide behavior; targets/<target>/__init__.py registers only target-specific export assets and hooks.

Export Extension Points#

There are three distinct hooks. Choosing the narrowest hook keeps additions easy to understand:

ProjectComposer

Registered by execution environment with register_project_composer(environment, callable). It runs after the generic model sources have been emitted and assembles the application around them: entry point, BSP, Makefile, and protocol-specific files. It returns manifest overrides such as expected_artifact and protocol.

ExportPreprocessor

Registered by hardware target with register_export_preprocessor(target, callable). It replaces the prepared ExportContext before normal generation. Use it when a target must partition or transform the graph while the rest of the pipeline remains generic. For example, an NPU integration can partition the graph, emit accelerator regions, reschedule it, and return an updated context.

TargetExportStrategy

Registered by hardware target with register_target_export_strategy(target, callable). It replaces generic source writing and project composition for the target. Use it only when a target needs a fundamentally different or multi-project export, such as a heterogeneous system that emits coordinated programs.

Resolution order is deterministic:

prepare_export()
    │
    ├─ target preprocessor, when required
    │
    ├─ target strategy found? ── yes ─► strategy owns generation
    │
    └─ no
        ├─ write_export() emits model sources
        └─ environment composer found?
            ├─ yes ─► composer assembles the project
            └─ no  ─► generic main.cpp fallback

ExportManifest and ExportArtifact#

Every successful export writes .aidge-export.json and returns an ExportArtifact. The artifact contains the project path and its parsed ExportManifest. The manifest is the stable, serializable boundary between export and deploy; it is not a second hardware database.

The manifest records facts about this particular generated artifact:

  • target, execution domain, environment, architecture, and build system;

  • generated application and its configuration;

  • expected build artifact path and output protocol;

  • export libraries and input/output descriptions;

  • export-computed memory and profiling metadata;

  • a schema version used to reject incompatible formats.

This information lets deploy consume an existing directory without importing the original graph or repeating export decisions. load_export_artifact() accepts either an ExportArtifact or a path, loads the manifest, and checks its schema version.

End-to-End Export Sequence#

The public aidge_core.export_utils.export() function performs the following sequence:

  1. Select the host inference backend used for preparation and calibration.

  2. Optionally run sanity inference on the input graph.

  3. Quantize the graph and inputs when an int8 export was requested.

  4. Resolve the hardware model and adapt the graph to its supported kernels.

  5. Schedule the prepared graph and build an ExportContext.

  6. Apply the target preprocessor, strategy, or environment composer according to the resolution order above.

  7. Generate a complete project, create the ExportManifest, write .aidge-export.json, and return ExportArtifact.

The exporter-specific ExportLib and ExportNode objects described later in this guide operate inside steps 4 to 6. They map scheduled Aidge operators to kernel implementations and templates; they do not model hardware or deployment connections.

Export overview diagram

Graph Preparation#

Before generating the standalone export, the Aidge graph undergoes several transformations to fit the targeted export backend.

One key step in this process is the fusion of operators into MetaOperators, groups of operators designed to match specific kernel implementations.

Graph fusion process

For example, as shown above, the convolution kernel supports both padding and activation (e.g., ReLU), so these operations are fused together into a single convolution operation.

This transformation is achieved by applying a set of regular expression-based recipes to the graph. Each export module provides its own recipe set. For more details, refer to:

Additional transformations, such as setting data formats or data types, are applied within the export() function defined in export.py. Most helper functions used in this process are implemented in export_utils.py.

For a detailed walkthrough, see the “Quantized LeNet C++ Export” tutorial.

Standalone Export Structure#

The Standalone Export refers to the generated code that runs the exported model independently.

Before exploring the internal structure of the export module itself, let’s first review the structure of the generated export:

Standalone export structure diagram

In the generated export:

  • forward.cpp contains the function responsible for running inference.

  • The generated folders include:

    • kernels/ (red): Implementation code for each supported kernel.

    • utils/ (green): Utility source files.

    • layers/ (blue): Configuration files for each model layer. These define parameters such as kernel size, dilation, and memory offsets for outputs.

    • parameters/ (yellow): Serialized model parameters, such as weights and biases.

(Note: newer exports may support fallback mechanisms that allow partial reuse of existing export kernels.)

Export Module Structure#

The Export Module is the Aidge component that generates the Standalone Export.

Full export structure diagram

Copied and Generated Files#

In the diagram above, the export module structure includes two main categories of files:

  • Copied files: Files in kernels and static are copied directly into the generated export.

  • Generated files: Configuration and parameter files are dynamically generated based on the model’s layers.

Each kernel type has two template files:

  • A configuration template (in layers/)

  • A forward template (used for function calls in forward.cpp)

Parameters are generated using a third template, parameters.jinja.

Operators#

The operators/ folder is a crucial part of the export module. It contains Python scripts that connect Aidge’s intermediate representation (IR) to the actual implementation used by the export.

Export class hierarchy diagram

The main classes defining export behavior are:

Each export module defines its own ExportLib (in export_registry.py), which maintains a registry mapping Aidge operators to their:

ImplSpec defines the constraints and supported configurations for each kernel, for example, the C++ convolution kernel supports only NHWC data formats for inputs and weights.

ExportNode holds all information required to generate files for a specific operator, including:

  • config_template - Path to the layer configuration template

  • forward_template - Path to the kernel call template in forward.cpp

  • include_list - Required includes for forward.cpp

  • kernels_to_copy - List of kernel implementation files to include (one or more)

Example of Conv2D operator registration

Each operator’s ExportNode is defined in the corresponding file under operators/ (e.g., operators/Conv.py). In the example above, the Conv2D operator’s ImplSpec specifies that only NHWC input, weight, and output formats are supported.

The attributes dictionary defines the variables available to Jinja templates when generating configuration and forward files.

Because the convolution implementation supports multiple combinations (e.g., padding + activation), several variants such as Conv2D, PadConv, and ConvAct are registered as subclasses of the same base operator.

Convolution operator inheritance

For instance, PadConvAct inherits from previous convolution variants, extending or overriding parameters as needed. Base classes initialize defaults (e.g., padding = 0, activation = Linear), and derived classes modify them.

Finally, the Producer.py file manages parameter exports (corresponding to producers in the Aidge graph).

Adding a New Kernel#

Now that the export structure has been described, let’s go through the process of adding a new kernel to the export. The procedure is straightforward, simply follow these steps:

  1. Create the kernel implementation and place the file in the kernels folder.

  2. Create the configuration template file, defining all the parameters required to execute the kernel function.

  3. Add the forward template file, which generates the kernel call inside the forward.cpp file.

  4. Register the kernel in the ExportLib by creating a dedicated Python file within the operators folder.

  5. (Optional) If your kernel combines multiple operators, you can create a recipe to fuse them into a MetaOperator and register it as well. These recipes are typically defined in the export_utils.py file.

And that’s it, your new operator is now supported in the export system !

For more detailed instructions on adding a kernel, refer to the tutorial: “Add a Custom Operator to the CPP Export”.