Skip to content
heliaEstimator
User guide
HELIA HUB

Parse and visualize results

Share the published walkthrough with evaluators. It opens without GitHub repository access. GitHub source and release links require access while the repository is private; an unauthenticated GitHub 404 does not mean the documentation or release is missing.

Start with the bundle, then choose a table, a model graph or a timeline. This walkthrough uses the public keyword-spotting (KWS) examples. Commands assume a source checkout and its Python 3.12 or 3.13 environment; installed-wheel users can omit uv run --frozen when invoking helx.

Terminal window
uv run --frozen helx run -c examples/at110/kws_vela_only.yaml
uv run --frozen helx show results/kws_vela_only
uv run --frozen helx validate bundle results/kws_vela_only

run_summary.md is the readable report. run_summary.json is the native schema-version-4 contract. JSON measurements live under measurements.samples[i]. run_summary.csv contains one row per measured invocation, with dotted columns such as measurement.index and measurement.cycles.total. Without measured samples it retains one row of shared facts. Use helx export --fields statistics for aggregate rows. manifest.json binds the retained files to their sizes and SHA-256 hashes. Keep the whole bundle when sharing evidence, including input_config.yaml and artifacts/. The original source model can live outside the bundle.

Check fvp.stage, PMU sanity, platform revision, values status and metric basis before quoting numbers. A valid Vela-only result has no executed FVP counters. Energy, power and efficiency remain experimental estimates. Integrity verification does not establish numerical accuracy or silicon qualification.

For automation, the verified offline reader checks completion, schema, paths and retained hashes before returning typed fields:

from pathlib import Path
from helia_estimator.api import load_result, measurement_samples
result = load_result(Path("results/kws_vela_only"))
summary = result.summary
print(summary.run.model, summary.fvp.stage)
for sample in measurement_samples(result):
if sample.cycles is not None:
print("Measured sample", sample.index, "NPU total cycles:", sample.cycles.total)
for artifact in result.artifacts:
print(artifact.relative_path, artifact.sha256)

Run a saved script with uv run --frozen python your_script.py. Missing values are unavailable, not zero. To read a retained file with another integrity check, pass its ArtifactRef to result.read_artifact(artifact). To list saved Model Explorer/Perfetto files with their experiment meaning, use helx results artifacts results/kws_vela_only. The equivalent typed Python catalog is viewer_artifacts(result). Use results export-artifact or export_viewer_artifact to copy a retained visualization with another hash and completion check; see the saved-visualization API. See the Python API guide for rejected/incomplete bundles and native exports, and the result reference for every field.

For JSON and aggregate CSV:

Terminal window
uv run --frozen helx show results/kws_vela_only --json
uv run --frozen helx results inspect results/kws_vela_only --format json
uv run --frozen helx export results/ --format csv --fields all > runs.csv

show --json emits native schema 4. results inspect/show/export use a separate, draft portable mapping after native verification; that mapping is not a released wire API. Ordinary helx export makes a table; --fields all selects all native leaves rather than the default curated columns. Write derived files outside the bundle so they do not invalidate its manifest. Use --strict on table export when any rejected input should fail the export.

Explore the model in Google Model Explorer

Section titled “Explore the model in Google Model Explorer”

Model Explorer shows a graph with HELX’s per-original-operation overlays. Install the optional viewer dependencies in the checkout:

Terminal window
uv sync --frozen --extra viz

Load the original, uncompiled .tflite used by this run. Overlay keys are original output tensor names in graph main; the Vela-compiled or fenced model will not give the same graph. Find the source in run_summary.json under source.cached_path (or recover the exact original from source.given). A moved bundle does not carry the model cache with it. The manifest verifies retained artifacts, not an external cached source file.

For this example, copy the source into local/kws-original.tflite using a small script saved as local/copy_viewer_model.py (create local/ first):

from pathlib import Path
from shutil import copyfile
from helia_estimator.api import load_result
summary = load_result(Path("results/kws_vela_only")).summary
source = Path(summary.source.cached_path)
copyfile(source, Path("local/kws-original.tflite"))
Terminal window
uv run --frozen python local/copy_viewer_model.py
uv run --frozen --extra viz model-explorer local/kws-original.tflite --node_data_paths=results/kws_vela_only/artifacts/model_explorer_overlay_mac_count.json

Follow the local URL printed by Model Explorer. Expand groups to inspect operations; select MAC Count in the node data controls and use its eye icon to color the graph. You can also start model-explorer without arguments, select the original model, choose View selected models, then Add per-node data to load an overlay JSON. These are Model Explorer data files, not run_summary.json.

Overlay in artifacts/ Meaning
model_explorer_overlay_mac_count.json Vela-attributed MAC count
model_explorer_overlay_sram.json Vela-attributed SRAM accesses
model_explorer_overlay_cycles.json Vela cycle attribution scaled to the full-model FVP cycle total

In HELX 0.7.0, a Vela-only run’s Calibrated Cycles overlay contains zeros because there is no FVP total to scale to. Use MAC Count or SRAM Accesses for that run. To view calibrated cycles, use an executed FVP bundle, for example the bus-trace run below, and select its cycles overlay with the same original model. These are attributed cycles, not independent per-operation measurements. Raw compiler estimates and attribution are also available in per_layer_breakdown.csv and per_original_op_breakdown.csv.

For multi-subgraph models, or runs whose warmup excludes CALL_ONCE initialization, HELX currently omits calibrated layer breakdowns and overlays because Vela’s table may describe a different execution scope. The raw vela_verbose_performance.log remains available; per_layer.unavailable_reason, run_summary.md and helx show BUNDLE --layers 5 explain the omission. Full-model PMU counters remain available. An overlay is also omitted when no original operators can be mapped; consult the retained CSVs in that case.

Public KWS graph with MAC Count overlay in Model Explorer

The screenshot uses the public KWS example’s compiler output. Graph grouping and controls may vary with the viewer version. If the overlay does not color nodes, check the selected data series, graph main and original model identity first. The optional TFLite adapter is platform dependent; if unavailable on your host, run the viewer in a supported Linux environment. HELX’s normal CLI does not require Model Explorer. See Google’s user guide and command-line guide.

After the normal helx setup prerequisites and Arm licence acceptance, run:

Terminal window
uv run --frozen helx run -c examples/at110/kws_bus_trace.yaml
uv run --frozen helx validate bundle results/kws_bus_trace

The example enables defaults.fvp.bus_trace.enabled. Bus tracing requires the Corstone-320 / Ethos-U85 target; it is unavailable on Corstone-300 / U55 / U65.

Open ui.perfetto.dev, choose Open trace file, and select results/kws_bus_trace/artifacts/bus_trace_perfetto.json, or drag that file into the page. Use the path recorded in artifacts.bus_trace_perfetto_json for other bundles. No recording session or browser extension is needed. Zoom with W/S, pan with A/D, and click a track event to inspect its details.

Click Expand all in the track toolbar so SRAM0, SRAM1 and EXT are visible. Zoom into an active interval to inspect the three interfaces together. The screenshot below has the memory-interface group expanded.

Public KWS simulated bus timeline opened in Perfetto

This view bins FVP bus transactions into a timeline normalized to NPU cycles. It is a simulator diagnostic, not a silicon capture or a cycle-accurate CPU/bus schedule. Keep fvp.bus_trace checks, time scaling and the configured NPU clock with the interpretation. Companion bus_trace_timeline.csv and bus_trace_transfers.csv support programmatic analysis. keep_raw: false removes the raw transaction log after analysis, while retaining these outputs. See Perfetto’s UI guide.

The fenced Perfetto view focuses on PMU counters and verified bus traffic. Use these figures to decide what to inspect next:

Figure Meaning and useful follow-up
MAC active MAC-active cycles / NPU-active cycles. Low activity warrants checking memory pressure and the operator; it is not a percentage of peak MAC throughput.
SRAM0 busy, SRAM1 busy, EXT busy Read + write beats / beats allowed at the configured port rate. A busy interface can motivate a memory-placement or bandwidth experiment. Separate SRAM interfaces require a verified bus trace.
SRAM0 traffic share SRAM0’s fraction of all SRAM read + write beats; 50% is even. Compare the two SRAM busy tracks before trying a port-balance change. No traffic means unknown.
Mean SRAM read/write transfer Bytes / transfers, separately for each direction. Small transfers can motivate inspecting layout and burst splitting; a mean alone does not establish a cause. Requires a verified trace.
Reads/writes queued, outstanding limit Counted request-stall events / NPU-active cycles. Use these with port usage to investigate queue pressure; events can overlap and ratios can exceed 100%.

Uncounted metrics stay absent; a metric missing on some invocations has gaps rather than a misleading zero. The selected profiles do not measure cache hits/misses, so there is no reported hit rate. Vela cycle comparisons remain available in fenced_layers.csv for compiler diagnosis and are omitted from the timeline and selected-event details. Existing traces retain their original tracks; rerun with the updated version to produce the new view.

For the separate SRAM usage, traffic-share and transfer-size figures, combine defaults.fvp.bus_trace.enabled: true with per-operator fencing on U85, as described in the methodology. The bus-only example above does not enable per-operator fencing by itself. A PMU-only capture shows aggregate SRAM busy when its counters suffice, assuming an even split across interfaces.

The expanded view below uses a BC-ResNet-8 proxy model with port_stall counters and a verified bus trace. The top lanes show individual fenced NPU invocations; the graphs below show their measured activity and derived traffic figures. SRAM0/SRAM1 usage and traffic share include both reads and writes. The read-only stall profile does not count write stalls; their absence is unknown, rather than zero.

BC-ResNet proxy fenced invocations with expanded MAC activity, memory-interface usage, traffic balance, transfer-size and request-stall tracks

To generate this set of metrics for the public KWS model, copy examples/at110/kws_bus_trace.yaml within its directory, give run.results_dir a separate directory, add pmu_profile: port_stall under defaults.fvp, and add per_layer: {mode: per_op} inside the model entry. Run that config, then open its artifacts/fenced_layers_perfetto.json. Use Expand all to reveal both groups and all their tracks. In each counter’s Track options, choose Size → Medium (2x) for larger graphs, and drag the label-column divider to leave more room for the timeline. Model timings and operator counts will differ from the proxy screenshot.

The screenshot reuses a verified v0.8.0 simulation with the updated renderer; these metric tracks are available from helx 0.9.0. Capture and model identities are recorded in the source repository’s docs/images/README.md.

On the Ethos-U85, select defaults.fvp.pmu_profile: sram_stall to count SRAM read/write queue cycles, read/write beats and MAC-active cycles together. The complete example also enables per-operator fencing:

Terminal window
uv run --frozen helx run -c examples/at110/resnet_sram_stall.yaml
uv run --frozen helx show results/resnet_sram_stall

measurements.samples[i].cycles.sram_wr_tran_req_stalled counts write address requests held while the memory interface is not ready (AWVALID && !AWREADY); it does not count write-data backpressure. sram_wr_stall_limit counts stalls from the NPU’s outstanding-transaction limit. These are distinct events, not a count of outstanding transactions or proof of the downstream queue depth. The analogous sram_rd_* fields describe reads. Zero means counted zero; an absent field means uncounted. The profile uses all eight event counters, so EXT traffic and the complete measured energy estimate are unavailable. Existing profiles remain available for other counter selections. U55/U65 default and default_ext_rd_stall also leave EXT write beats uncounted; these runs cannot supply the complete counter-based energy estimate. A previous parser substituted zero for that missing counter; new results preserve it as unknown.

The fenced CSV and Perfetto timeline add SRAM writes queued and SRAM writes at the NPU’s outstanding limit. U85 SRAM stall events sum across AXI ports. The plotted ratio is the event count divided by NPU-active cycles, shown as a percentage; it can exceed 100% and is left unclipped. It is not the fraction of elapsed time a model stalled. Read and write events may overlap each other and useful NPU work: do not add them or interpret them as recoverable runtime. The SRAM queue pressure and NPU engine lanes are heuristics; missing counters do not establish an unconstrained memory path.

Open results/resnet_sram_stall/artifacts/fenced_layers_perfetto.json in Perfetto, click Expand all, and select an invocation to see its original operators, NPU-active cycles and traffic. Expand both layers (fenced run) and per-layer figures; the read and write request-stall tracks are separate. The screenshot uses the shipped ResNet example on helx 0.8.0.

ResNet read and write diagnostics with all Perfetto track groups expanded and an invocation selected

This profile does not count EXT traffic. Its absence in the details means unknown, rather than zero. A Not enough counted to tell lane is expected when the available counters cannot support a bottleneck classification.

Fencing changes scheduling and fusion. A percentage improvement in an operator-isolated run is not the same improvement in the normal model. Compare measurements.samples[i].cycles from matching unfenced runs for the model’s NPU work; these counters exclude CPU operators and are not end-to-end application latency. FVP total/CCNT values have simulator step granularity, so small differences are model sensitivity, not a precise silicon speedup; use per_segment artifacts to investigate the separate fenced experiment. Counter definitions: Arm Ethos-U85 TRM, issue 05, PMU events.

To collect counters between operations, use the existing examples/what-if/corstone_300_u55/kws_per_op.yaml example after helx setup --target corstone-300:

Terminal window
uv run --frozen helx run -c examples/what-if/corstone_300_u55/kws_per_op.yaml

Alternatively, enable fencing on a copy of your own config. Set this inside its model entry, alongside source and memory_placement:

per_layer:
mode: per_op

Fenced profiling supports LiteRT, heliaRT and heliaAOT (1.3.0 or later). It inserts ETHOS_U_STREAM_SPLIT boundaries, recompiles and reruns the model. Fences can change fusion and scheduling; compare with the unfenced run explicitly.

Open the file recorded in per_segment.layers_perfetto_json (normally artifacts/fenced_layers_perfetto.json) in Perfetto. Its time axis concatenates fenced NPU-active intervals at the configured NPU clock; it omits CPU gaps and is not application wall time. per_segment.layers_csv holds the segment table. A segment is a fenced NPU invocation, not a guarantee of one original operation. For additional stall diagnostics on U85 only, choose defaults.fvp.pmu_profile: port_stall. On U55/U65, keep the example’s default supported profile; port_stall is unavailable. That U85 profile does not count write beats, so absent write metrics remain unknown. Bus tracing can accompany it on U85, but is not required for the layer timeline.

In Perfetto, click Expand all in the track toolbar, or expand the chevrons beside layers (fenced run) and per-layer figures. The first group shows NPU invocation intervals; the second shows the retained diagnostic series. Click an invocation to inspect its duration and retained counter arguments.

Public KWS fenced-layer trace with both track groups expanded in Perfetto

This U55 example does not collect every counter needed to classify a layer’s likely bottleneck. Its Not enough counted to tell lane preserves that limitation; it is not a simulation failure. This retained v0.8.0 image includes the former Vela comparison track; new traces use the measured and derived view illustrated above.

If a visualization file is absent, check the summary’s artifact references and stage first: a compile-only run has no bus trace or fenced PMU timeline, and a run without per_layer fencing has no fenced timeline. The overlay graph and the fenced timeline answer different questions and need different inputs.

See Tensor inputs and numerical verification for fixed input bytes, per-sample output artifacts and explicit reference/tolerance policies.

On U85, defaults.fvp.pmu_profile: sram_transactions counts accepted SRAM read/write transactions with matching aggregate SRAM beats in the same capture:

Terminal window
uv run helx run -c examples/at110/resnet_sram_transactions.yaml
uv run helx show results/resnet_sram_transactions

The eight slots contain NPU activity, MAC activity, all four SRAM/EXT beat totals, and SRAM read/write accepted transactions. The dedicated cycle counter remains available. This preserves the traffic inputs for energy estimation, but omits the default profile’s per-interface read splits and does not count queue stalls. Existing profiles and the default are unchanged.

Each native measurements.samples[i].traffic_transactions block has sram_rd and sram_wr counts, in transactions. Zero is counted zero; null is unavailable. The portable export retains these as namespaced transaction observations and adds derived burst_average observations with source pointers.

For each direction, average beats per transaction is matching beats divided by accepted transactions; bytes per transaction multiplies by the platform’s bytes per beat. Ratios are unavailable when either count is missing, the transaction count is zero, or PMU overflow is nonzero or unknown. Byte ratios also require a known byte-aligned beat width. These are whole-window aggregate transfer averages, not a distribution of burst lengths, outstanding queue depth, or a prediction of performance gains. No counters from different profiles or invocations are combined.

The example also makes a separate per-operator fenced capture. Its segment CSV contains the raw transaction counts and matching beat/byte averages. Segment ratios use only that capture and are withheld if its totals do not reconcile. Fencing changes scheduling; do not combine those counters with the normal run.