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:
python -m pip install helia-aotFor a complete runnable script, jump to A complete example.
The surface
Section titled “The surface”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.
Building a configuration
Section titled “Building a configuration”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.
Running a conversion
Section titled “Running a conversion”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.
A complete example
Section titled “A complete example”This converts a model, checks the conversion succeeded, and reads the plan back out of the report it wrote.
import jsonfrom pathlib import Path
from pydantic import ValidationError
from helia_aot import AotConverter, ConvertArgsfrom 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.namereport = 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.
Extending the compiler
Section titled “Extending the compiler”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.
Reading the plan
Section titled “Reading the plan”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.