Skip to content
heliaAOT
HELIA HUB

Python API in practice

helia-aot convert is a thin wrapper around a Python API. Everything the command line does, it does by building a ConvertArgs and handing it to an AotConverter, so anything you can express on the command line you can express in a script.

Use the Python API to compose conversions in a build script or notebook, sweep configuration values, or register custom parsers, operators and transforms. The CLI also supports scripted conversions, but registry customization requires Python; there is no CLI flag for loading a custom operator.

Install helia-aot into the environment running your script:

Terminal window
python -m pip install helia-aot

For a complete runnable script, jump to A complete example.

Only the symbols in the package’s API manifest are public. Everything else is an implementation detail and may change without a release note.

Symbol What it is Reference
ConvertArgs The whole configuration of one conversion, as a validated model helia_aot.cli.defines
AotConverter The converter; construct it with a config and call convert() helia_aot.converter
RegistryContext The registries a conversion dispatches through helia_aot.registry.context
build_default_registry_context Builds a context from the built-ins, with your customizers applied helia_aot.registry.context
HeliaAotError and its subclasses The typed failures a conversion raises helia_aot.errors
MemoryPlan The planned placement of every tensor helia_aot.memory.defines
SocPlatform A target’s cores, memories and capabilities helia_aot.platforms.defines
get_platform, list_platforms Look up or enumerate the registered targets helia_aot.platforms.platforms
AirModel The graph representation a conversion works in helia_aot.air.model
custom_code_to_op_key Derives the canonical operator key from a LiteRT customCode helia_aot.air.op_keys
create_interpreter Builds an interpreter for reference execution helia_aot.interpreters

The Python API reference is generated from the source and carries every signature, parameter and docstring. This page is about how the pieces fit together.

ConvertArgs is a validated model, which gives you two ways in.

From a YAML file, which is the same file the command line reads:

from helia_aot import ConvertArgs
config = ConvertArgs.from_yaml("kws.yaml")

from_yaml raises FileNotFoundError when the path does not exist and ValueError when the file is not a mapping or a field does not validate. The config also keeps the path it was loaded from, on its own path field.

From a dictionary, which is what you want when the configuration is computed:

config = ConvertArgs.model_validate(
{
"model": {"path": "kws_ref.tflite", "name": "kws_ref"},
"module": {"path": "./out", "name": "kws_ref", "type": "neuralspot"},
"platform": {"name": "apollo510_evb"},
"memory": {"dump_residency_json": True},
}
)

Configuration types and field constraints are checked at construction, so an unknown module type fails here. Target lookup happens during conversion: a plain platform.name string is not proof that a registered target exists. An unregistered name can instead request a custom platform when its required fields are supplied. The keys are the YAML keys; Configuration is the generated list of all of them with their defaults and their command-line spellings.

Unknown keys in a nested block warn with a suggestion rather than failing, which is worth watching for in a script that builds configurations programmatically: a typo will not stop you.

from helia_aot import AotConverter
converter = AotConverter(config)
context = converter.convert()

AotConverter(config, registry_context=None) takes the configuration and, optionally, the registries to dispatch through. Omit the context and the converter builds the default, frozen one, which is exactly what the command line does.

convert() runs the six stages in order (Load, Transform, Resolve, Plan, Emit and Export) and either writes the module or raises. The return value is a code generation context carrying the configuration, the resolved graph, the memory plan, the platform and the operator list.

This converts a model, checks the conversion succeeded, and reads the plan back out of the report it wrote.

convert_kws.py
import json
from pathlib import Path
from pydantic import ValidationError
from helia_aot import AotConverter, ConvertArgs
from helia_aot.errors import HeliaAotError
try:
config = ConvertArgs.model_validate(
{
"model": {"path": "kws_ref.tflite", "name": "kws_ref"},
"module": {"path": "./out", "name": "kws_ref", "type": "neuralspot"},
"platform": {"name": "apollo510_evb"},
"memory": {"dump_residency_json": True},
"force": True,
}
)
except ValidationError as error:
raise SystemExit(f"invalid configuration: {error}") from error
try:
AotConverter(config).convert()
except HeliaAotError as error:
raise SystemExit(f"conversion failed: {error}") from error
module_dir = Path(config.module.path) / config.module.name
report = json.loads((module_dir / f"{config.module.prefix}_residency.json").read_text())
if report["schema_version"] != 4:
raise ValueError("Review the residency schema before reading this report")
print("schema", report["schema_version"], "plan", report["plan_hash"])
for role, arenas in report["arenas"].items():
for arena in arenas:
print(role, arena["memory"], arena["used"], "of", arena["total_size"])

Run this with kws_ref.tflite available relative to the process’s working directory and heliaAOT installed in that Python environment. Success writes out/kws_ref/ and prints the report’s schema, plan hash and arena sizes. The separate tensor_layout_hash identifies the tensor layout; see memory reports. This example deliberately uses a directory output; a .zip or .pack export contains the report inside the archive instead of at this filesystem path.

force is set because a conversion refuses to write into a directory that already holds that module; in a script that reruns, say so deliberately rather than deleting the tree blindly.

HeliaAotError catches typed conversion failures, whose subclasses also inherit matching standard-library exception types. Configuration construction raises Pydantic validation errors separately. Some input paths still raise ordinary exceptions, such as FileNotFoundError for a missing golden archive and ValueError for an unknown transform name. Keep unexpected tracebacks visible rather than turning every exception into a successful script exit. Troubleshooting maps each class to what to do about it.

Every dispatch a conversion makes (the LiteRT parser for a node, the operator class for a key, the transforms that run, the model hooks that run before parsing) goes through a RegistryContext. Build one, apply your customizers, and hand it to the converter:

from helia_aot.registry import build_default_registry_context
registry_context = build_default_registry_context(
customizers=[customize_registry],
)
AotConverter(config, registry_context=registry_context).convert()

The three keyword arguments are the whole contract:

Argument Default What it controls
customizers None Callables invoked with the context, in order, after the built-ins are registered
allow_override False Whether a registration may replace a built-in entry
freeze True Whether the context is frozen once the customizers have run

Leave allow_override at its default when your customizer is only meant to add: a registration that collides then raises instead of quietly shadowing a built-in. Leave freeze at its default too, so nothing mutates the registries once a conversion is under way.

Custom operators is the full walkthrough, including the one canonical key that has to match across the parser, the AIR operator and the operator class.

The residency report is the supported way to read a conversion’s memory plan from a script. Set memory.dump_residency_json and the conversion writes <prefix>_residency.json next to the module, carrying a schema version, the arena envelope per role, every tensor with its role, memory, offset and size, and two hashes over the layout. A hash change says the layout changed; matching hashes do not check the model’s weight values or numerical behavior.

Gate on it the way you would gate on a linker map: fail when an arena exceeds what the board offers, and treat a hash change as something to review. Testing and automation covers using it in a pipeline and Memory reads one field by field.