Skip to content
heliaPROFILER
User guide
HELIA

Engine adapters

Show the seam that keeps the pipeline engine-agnostic. Everything an engine contributes to a run passes through one prepare() call and one typed artifact object; the rest of hpx reads those and never imports an engine package.

Engine dispatchFrom the engine.type key to the objects the rest of the pipeline reads. The shared helpers are called by more than one adapter; the consumers never import an adapter.
  • Registryengines/__init__.py
    • EngineType
    • adapter factories
    • get_adapter
  • Adapters
    • tflm
    • helia_rt
    • helia_aot
    • executorch
  • Shared helpers
    • cmsis_nn
    • ethos_u
  • Artifactsengines/base.py
    • EngineArtifacts
    • TflmArtifacts
    • HeliaRtArtifacts
    • HeliaAotArtifacts
    • ExecutorchArtifacts
  • Consumers
    • firmware render
    • memory planning
    • workspace fingerprint
    • model analysis
Text description of the diagram
  • Registry (engines/__init__.py): EngineType, adapter factories, get_adapter
  • Adapters: tflm, helia_rt, helia_aot, executorch
  • Shared helpers: cmsis_nn, ethos_u
  • Artifacts (engines/base.py): EngineArtifacts, TflmArtifacts, HeliaRtArtifacts, HeliaAotArtifacts, ExecutorchArtifacts
  • Consumers: firmware render, memory planning, workspace fingerprint, model analysis

EngineType is a StrEnum with four members, tflm, helia-rt, helia-aot and executorch, so YAML and Jinja compare against the hyphen form without unwrapping. _ADAPTER_FACTORIES maps each member to a deferred import, so registering an engine does not import its package until get_adapter(engine_type) asks for it.

EngineAdapter in engines/base.py has a name, an engine_type, and prepare(config, work_dir) returning that engine’s artifact subtype. Two hooks let an engine shape memory placement: apply_arena_placement_override() for engines that emit typed arena regions, and check_psram_placement() together with psram_weights_source, which is how the PSRAM preflight on Memory placement is enforced per engine.

Engine Module Steps Modules it adds
tflm engines/tflm.py resolves the nsx-tflite-micro module and the reference or CMSIS-NN backend nsx-tflite-micro (plus the CMSIS-NN provider for that backend)
helia-rt engines/helia_rt/ validates the variant and toolchain; with no override returns the pinned registry module; source_path installs a local tree as a module; dist_path or a release source verifies the matching core and toolchain archive and generates a wrapper (nsx_module.py, artifacts.py, download.py) nsx-helia-rt
helia-aot engines/helia_aot/ maps the board to a compiler platform and runs the helia-aot compiler (compile.py); extracts the operator manifest and memory plan from the compiler’s context (manifest.py); vendors the generated model as a local module named by engine.config.module_name (default hpx_model); resolves the CMSIS-NN provider nsx-cmsis-nn, the model module
executorch engines/executorch.py resolves the nsx-executorch checkout at the commit the compatibility baseline pins (auto-cloned into the hpx cache, or engine.config.source_path); reads the optional <model>.pte.json sidecar for the provider, op lists, planned arena and I/O sizes; resolves the region of each static RAM buffer; generates a wrapper module and emits the provider module before it the CMSIS-NN provider, the nsx-executorch wrapper

The shared helpers sit beside the adapters: engines/cmsis_nn.py resolves the nsx-cmsis-nn module reference and its CMake variables for every engine that links it, and engines/ethos_u.py holds the NSX module identity of the Ethos-U backend. Support for each engine and backend per SoC is a registry question, answered on Engines and Boards and targets, not here.

EngineArtifacts carries only what every engine produces: engine_type, engine_header (no default; each adapter states its own), extra_modules, cmake_vars and memory_plan. Engine-specific fields live on the subclass, so reading another engine’s field is an AttributeError at the access site rather than a silent None; each subclass pins its engine_type and raises ValueError if constructed with another. Four resolved_* properties (backend, version, variant, toolchain_tag) are how the dependency workspace fingerprint records engine identity without knowing which engine it holds. tests/contracts/test_engine_artifact_types.py asserts the adapter-to-artifact map is complete over EngineType.

Consumer Reads Module
firmware render engine_header, cmake_vars, extra_modules, the subtype’s fields (AOT operator manifest and arena regions, ExecuTorch buffer sizes) firmware/context.py, firmware/project.py
memory planning memory_plan, the adapter’s placement hooks stages/plan_memory.py, placement.py
workspace fingerprint the resolved_* properties deps/dependencies.py
model analysis the engine’s view of the graph for hpx analyze --engine evaluation/engine_analysis.py
Engine Main template Module graph on top of the board’s starter profile
tflm main.cc.j2 nsx-tflite-micro, with the CMSIS-NN provider first for that backend
helia-rt main.cc.j2 nsx-helia-rt
helia-aot main_aot.cc.j2 nsx-cmsis-nn, then the generated model module
executorch main_executorch.cc.j2 the CMSIS-NN provider, then nsx-executorch

The provider is ordered before nsx-executorch on purpose: NSX must configure exactly one CMSIS-NN provider before the runtime creates its bridge target. The binary section sizes in summary.json record what the engine choice cost in flash and RAM for the run at hand.

Block Path under src/helia_profiler/
registry engines/__init__.py
protocol and artifacts engines/base.py
adapters engines/tflm.py, engines/helia_rt/, engines/helia_aot/, engines/executorch.py
shared helpers engines/cmsis_nn.py, engines/ethos_u.py
stage stages/prepare_engine.py
contract test tests/contracts/test_engine_artifact_types.py

Firmware generation for what the render context does with the artifacts; Adding an engine for the touch points a fifth adapter has to cover.