hpx profiles LiteRT (.tflite) and ExecuTorch (.pte) models on real
Ambiq Apollo silicon.
One command resolves an inference engine, builds temporary NSX firmware,
flashes the target, captures measurements, and writes a portable result bundle.
Alpha. heliaPROFILER is pre-1.0. Breaking changes may land on minor versions until v1.0. Pin an exact version in production pipelines.
📖 Full docs: https://ambiqai.github.io/helia-profiler/
- Per-layer PMU breakdowns — cycles, instructions, cache, and (on Cortex-M55 boards) MVE and memory counter groups, one row per layer.
- Power & energy per inference — GPIO-gated Joulescope capture (JS110/JS220/JS320) uses a dedicated transport-free firmware image to isolate the inference window from setup/teardown and capture traffic.
- Four engines, one per run —
tflm(vanilla TFLM baseline),helia-rt(Ambiq's optimized TFLM interpreter),helia-aot(Ambiq's ahead-of-time model compiler), andexecutorch(Cortex-M ExecuTorch programs, on thearmornsCMSIS-NN provider). Selected explicitly — never auto-detected. - Multiple toolchains —
arm-none-eabi-gcc,armclang, and ATfE, so you can compare build/runtime trade-offs without changing your model. - Memory placement control — place the heliaRT tensor arena in TCM, SRAM, or PSRAM and weights in TCM, SRAM, MRAM, or PSRAM; heliaAOT exposes per-tensor placement through its engine configuration, and ExecuTorch places each of its five runtime buffers independently.
- Four capture transports — lossless RTT by default, plus USB CDC, UART, and diagnostic SWO, with probe, port, and target-reset helpers for bring-up.
- Host-only model analysis — inspect MACs, parameters, tensor sizes, and heliaAOT graph transforms without connecting hardware.
- Run comparison and regression policy — compare two result bundles, inspect the largest layer deltas, and apply versioned metric thresholds.
- Model Explorer overlays — export per-layer metrics as JSON overlays for Model Explorer.
- Config-file driven — a frozen, immutable
hpx.ymlschema merges with CLI flags, with strict validation and did-you-mean suggestions for typos. - Multi-board — Apollo3, Apollo4, and Apollo5-family EVBs. Run
hpx boardsfor the exact list your install supports. - Verifiable result bundles — summaries, per-layer data, provenance, validity/comparability issues, and a SHA-256 manifest designed for scripts as well as people.
- Hardware-in-the-loop validation —
hpx validateruns canonical MLPerf Tiny models end-to-end across engines, toolchains, transports, and memory placements, and emits a portable bundle two runs can be compared from. - Typed Python API — call
profile()directly or use immutable, branchableSessionworkflows in notebooks and automation.
See the Quick install for Nix, uv, and pip setup.
hpx doctor # check toolchain + dependencies
hpx profile model.tflite # profile with defaults
hpx profile model.tflite --power # add Joulescope power capture
hpx profile --config hpx.yml # reproducible, config-driven run
hpx analyze model.tflite # inspect the model without hardware
hpx compare results/baseline results/change # compare two completed runs
hpx validate --suite smoke # prove the whole bench workshpx profile defaults to heliaRT, GNU Arm, RTT, apollo510_evb, per-layer
CPU counters, 100 measured iterations, and 5 warmups. Select a different
engine, board, toolchain, transport, memory plan, PMU selection, or output
directory explicitly on the command line or in YAML.
Layer Op ARM_PMU_CPU_CYCLES ARM_PMU_INST_RETIRED
0 CONV_2D 123,456 98,765
1 DEPTHWISE_CONV_2D 45,678 34,567
...
Power: 1.234 mA avg 12.345 mW avg x.xxx µJ / inference
(Illustrative sample only — see Getting Started for a real walkthrough.)
uv sync --locked --all-groups --extra aot --extra analysis
uv run ruff check .
uv run ty check src/helia_profiler tests
uv run pytest -qpytest deselects the hardware marker by default, so the whole unit suite
runs with no board attached. Hardware cases run through hpx validate (or
pytest -m hardware).
For software-only capture tests or standalone review probes, install the device guard before importing HPX:
uv run python tools/software_only.py pytest tests/test_power_drivers.py -q
uv run python tools/software_only.py python /path/to/probe.pyStandalone probes can import sibling helpers; the checkout src directory
retains precedence over the probe directory.
The launcher blocks Joulescope, J-Link and USB driver imports and provides inert serial constructors and port discovery that raise until replaced with fakes. Callbacks copied after startup retain those inert operations even if a later monkeypatch changes the original module. Installation rejects already imported HPX/device modules. Selecting a pytest marker alone does not provide this guard.
Python children must use sys.executable and retain the launcher's bootstrap and
checkout src prefix in PYTHONPATH, plus PYTHONSAFEPATH=1;
a sitecustomize hook guards children and grandchildren before
their scripts run. Stripped environments, startup flags that suppress the hook,
shells, preexec_fn callbacks and external commands are rejected. Mock external
tool calls in these tests. Tests importing vendor helpers such as pyjoulescope_driver.time64 also
fail explicitly; this launcher is not a drop-in replacement for the whole suite.
This is an accidental-access guard for trusted Python tests, not an OS sandbox: trusted interpreter startup/site files are assumed, and direct native device I/O or deliberate removal of the guard is outside its boundary. Run hardware tests separately through the existing explicit hardware workflow with board ownership. The guard does not alter production capture behavior or claim hardware validation.
The launcher's own tests run fresh interpreters against inert vendor sentinels, including unguarded negative controls; run them outside the launcher:
uv run pytest --confcutdir=tools/tests tools/tests/test_software_only.py -qOne-time setup after cloning:
uv tool install pre-commit==4.6.0
pre-commit installNix contributors can run nix develop .#contrib to get pre-commit 4.6.0
alongside the HPX tools, then run pre-commit install once. The default
nix develop environment is unchanged; entering either shell does not install hooks.
This installs both hook stages the repo uses: pre-commit (formatting,
whitespace/YAML/JSON/TOML checks, ruff, and a gate requiring every
TODO(...)/FIXME(...)/HACK(...) marker to carry a reference) and
prepare-commit-msg (strips AI-tool attribution trailers from the commit
message). pre-push is left untouched, so git-lfs's own pre-push hook
keeps working. Secret scanning is server-side: GitHub secret scanning and
push protection are enabled on the repository, so a pushed secret is
rejected regardless of local hooks.
Run every hook against the whole tree with pre-commit run --all-files.
CI runs the identical .pre-commit-config.yaml, so a clean local run means
a clean CI run. Hook revisions are bumped deliberately via pre-commit autoupdate in its own reviewed PR, not ad hoc.
The docs site lives in astro-site/ (Astro and Starlight on @ambiqai/helia-ui);
npm ci && npm run build there builds it, npm run dev serves it, and the
check chain in .github/workflows/docs.yml is what CI runs. It deploys to
GitHub Pages on a release and on docs-only pushes to main.
See Documentation release checks for browser
validation, manual publishing and post-deployment verification.
Repository workflows use the committed uv.lock for reproducibility. PyPI
installations continue to resolve the compatible dependency ranges published in
pyproject.toml.
Architectural rules, module responsibilities, and the repo workflows that
should stay stable are documented in AGENTS.md — worth reading
before a first change, whether you are a person or an agent.
Apache-2.0
The fixture API supports SRAM or DTCM working memory with PSRAM weights for heliaAOT and upstream LiteRT Micro on Apollo510 EVB. AOT uses externally bound arenas; Micro constructs its interpreter from the PSRAM flatbuffer. Both load complete weights once from MRAM staging before model initialization and timing. MRAM staging remains part of the firmware image. Internal state policy and input restoration are unchanged.
Pass the compiled build's psram_weights descriptor to
FixtureCaptureRequest.psram_weights. The descriptor binds expected payloads
and PSRAM clock to the exact image. Capture requires the bounded placement
terminal and verifies ready/XIP status, active ranges and payload checksums.
A linker map alone cannot establish runtime PSRAM placement. A build rendered
without compilation has an unbound descriptor and cannot authorize capture.
This additive interface is fixture API 3.5; it does not include a streaming or
numerical qualification claim. Placement validation uses the canonical target
bank extents and implemented backing paths rather than benchmark recipe pairs.
Static working arenas currently use SRAM or DTCM; dynamic PSRAM working arenas,
other fixture reset targets, and NOR/RRAM loader declarations are separate
adapter gaps. External AOT fixture binding currently requires PSRAM weights.
Operator timing binding compares the effective working and weight banks recorded
in the build receipt; legacy receipts without those banks are refused.
Ordinary profile operator timings cannot be bound to PSRAM fixture builds: their
active payload, external arena layout and loader configuration are not recorded
by that profile binding.