Analysis and Run Comparison#
Profiling answers what happened on a board. Two related workflows help before and after that measurement:
hpx analyzeinspects a model on the host without hardware.hpx comparecompares two completed, compatible profile result bundles.
Analyze a model without hardware#
Install the analysis dependencies once:
Then inspect the raw LiteRT graph:
The report includes per-operator MACs, parameter counts, and tensor sizes.
--engine helia-rt evaluates the original graph as the interpreter sees it.
With the AOT extra installed, --engine helia-aot compiles and analyzes the
transformed graph; add --compare to show original and transformed graphs
side by side:
This is useful for checking operator coverage and graph transforms before
starting a firmware build. See the hpx analyze reference
for every option.
Compare two profile runs#
Keep each run in a separate output directory:
hpx profile model.tflite --engine helia-rt \
--output-dir results/rt
hpx profile model.tflite --engine helia-aot \
--output-dir results/aot
hpx compare results/rt results/aot \
--output-dir results/rt-vs-aot
The power.duration_s comparison row reads power.capture_duration_s from
summary.json. It is the duration of the power measurement identified by
power.measurement_scope: the gated measurement for GPIO-gated runs, or the
capture measurement for free-form runs. It does not use the host timing field
latency.capture_duration_s or the separate whole-capture diagnostics.
HPX verifies declared result-manifest paths, sizes, and SHA-256 digests before reading a bundle. It then applies typed comparability rules:
- invalid results or different model hashes block the comparison;
- different layer topology suppresses only per-layer deltas;
- incompatible power scope, mode, firmware, monitor presence, lock-step, integrity, clean-window probe, or (same platform only) the measured firmware code fingerprint suppresses only power deltas;
- a different link family suppresses only the per-region measured-memory rows (see below);
- engine (type and measured runtime version), toolchain, board, clock,
transport, and placement differences remain visible as informative
dimensions. The engine version is the measured identity
(
run_metadata.engine.version, e.g. a heliaRT promotion) — runs recorded before the dimension existed, and tflm/executorch runs (no resolved version), are skipped like any other absent dimension.
The clean-window probe is what ran inside the measured window. A
busy_loop window measures a calibrated CPU spin rather than a model
inference, so comparing one against an infer window reports the difference
between two different physical quantities — hpx compare omits power deltas
and says which probe each side used.
Two things it deliberately does not do. It is recorded only for runs that actually measured power, so comparing a plain profiling run against a power-instrumented one is unaffected. And it says nothing about the board, so two SoCs running the same probe stay power-comparable, as the dimension list above intends.
Baselines recorded before this dimension existed carry no value and are skipped, so stored comparisons do not flip to failing.
Power firmware fingerprint. Every power run also records a code hash of
the measured target's rendered C sources — the main source plus the PMU
profiler translation unit compiled into the same binary
(summary.power.firmware_code_fingerprint; comments and whitespace are
normalized away, so documentation-only firmware changes leave it
untouched — rendered build configuration and external module sources are
deliberately outside the hash). When two runs on the same board, SoC, and
firmware mode carry different fingerprints, the measured binaries ran
different code, and hpx cannot vouch that their power numbers answer the same
question — power deltas are omitted and the comparison says so. This closes
the failure where a firmware-semantics fix produced a +678% "regression"
against a stored baseline that every other dimension called fully comparable.
The fingerprint is consulted only on a matching platform: cross-board
comparisons keep the behavior documented above, and baselines predating the
fingerprint are skipped like any other absent dimension.
Link family and per-region memory. Every run records which linker family
built its firmware (run_metadata.platform.link_family: gnu for gcc and
ATfE, armlink for armclang), and hpx compare shows the measured
per-region used/free figures (memory_regions.DTCM.used, .free, and so
on for ITCM/SRAM/MRAM) as run-level rows — the "does it fit, how much
headroom" question, A/B'd. Those figures are only the same quantity within a
family: GNU ld counts the floating stack inside the app extent while
armlink's fixed reservations sit outside it, so a cross-family compare would
show a large, meaningless delta. When the families differ, the per-region
rows are omitted and the comparison says why; binary section sizes (text,
data, bss) remain comparable across toolchains. Canonical regions render
first (ITCM, MRAM, DTCM, SRAM), any other region the summary lists after
them. A region present on one side only (ITCM exists on the Apollo510
family alone) renders with a dash rather than being hidden — that is an
SoC-axis change worth seeing. Runs that predate the platform record fall
back to the family the memory measurer wrote into
summary.memory_regions.link_family, so the gate holds on existing bundles;
only runs with no family anywhere are skipped like any other absent
dimension. Link family appears in the Config table so a comparison profile
can require it to match.
The terminal highlights totals and the largest layer deltas.
--output-dir also writes compare_summary.json and layer_diff.csv.
Add a regression policy#
A versioned comparison profile turns selected metrics into a deterministic pass, warning, or failure:
{
"schema": "hpx.comparison-profile",
"schema_version": 1,
"name": "apollo510-runtime",
"required_dimensions": ["board", "cpu_clock"],
"metrics": {
"total_cycles": {
"direction": "smaller",
"unit": "cycles",
"max_regression_pct": 3.0
},
"power.energy_per_inference_j": {
"direction": "smaller",
"unit": "J",
"max_regression_pct": 5.0,
"missing": "warn"
}
}
}
hpx compare results/baseline results/change \
--profile regression-profile.json \
--output-dir results/regression
Profiles support smaller, larger, and equal directions, percentage and
absolute tolerances, required configuration dimensions, and explicit policy
for missing metrics. A failed regression returns a non-zero exit status. See
the hpx compare reference for the full contract.