Skip to content
heliaPROFILER
User guide
HELIA

Output files

Know what each file in a result bundle is, which numbers it holds, and what each number measures. The bundle is designed for progressive disclosure: a summary and a per-layer table by default, deeper projections on request, and a manifest that lets a consumer verify the whole set.

A results/ directory from First profile.

File Schema Role When present
result_manifest.json hpx.result-manifest v1 (results/manifest.py) the bundle envelope: identity, validity, issues, provenance, comparability, and every artifact’s path, media type, size and SHA-256 always; written last
summary.json hpx.run-summary v19 (results/run_summary.py) headline metrics and health signals; read this first always
profile_results.csv or .json hpx.profile-results v1 for the JSON form (report/contracts.py); the CSV carries the semantic name without a published schema one row per layer, one column per counter always; one of the two per output.format
run_metadata.json hpx.run-metadata v1 (report/contracts.py) the resolved configuration, platform, model, toolchain, build images always
nsx.lock neuralSPOT-X lock file the exact module lock the firmware was built from always
aot_operator_manifest.json engine extension the operators the AOT compiler emitted with tensor placement helia-aot runs
aot_memory_layers.csv engine extension the same placement data flattened for a spreadsheet helia-aot runs
model_explorer/me_overlay_<COUNTER>.json Model Explorer overlay format one overlay per counter unless output.model_explorer: false
detailed/memory.json, detailed/profile_<group>.csv, detailed/profile_<preset>.csv, detailed/power_summary.csv projections and diagnostics counters merged per compute-unit group and per PMU preset, memory breakdown, per-window power output.detailed: true
result.json in a run directory’s evidence-<N>/ hpx.run-result v1 (runs/record.py) one run-API capture, saved and verifiable; see A saved run result hpx run, RunResult.save

The manifest’s artifact entries carry role (core, projection, extension, export, diagnostic), name, producer, optional, and schema plus schema_version only where a published schema exists. A complete profile bundle requires the core artifacts summary.json, run_metadata.json and the selected primary result, plus nsx.lock when the run’s dependency provenance recorded one. load_result_manifest(path, verify=True) rejects missing, modified or path-escaping artifacts; verify defaults to False, so a loader that omits it reads the manifest without checking the files. The packaged JSON Schemas are under helia_profiler/data/ (result_manifest.schema.v1.json, run_summary.schema.v1.json, run_metadata.schema.v1.json, profile_results.schema.v1.json); they fix the stable root fields and stay open to additive extension, and the typed models in helia_profiler.results are the authoritative shape.

Written by report/summary.py from the RunSummary model. The file opens with its identity; abridged here, the full file also carries binary, latency, memory, compatibility, dependencies, identity, top_layers and the nested memory and analysis tables described below:

{
"schema": "hpx.run-summary",
"schema_version": 5,
"engine": "helia-rt",
"layers": 13,
"total_cycles": 2060239.0,
"overflow_detected": false,
"validity": "valid",
"issues": []
}

(the engine, layer and cycle values are from the shipped bundle examples/results/hardware-validation-2026-09-16/apollo510_evb-kws-rt-ns-arm-none-eabi-gcc-rtt-auto/, which retains schema version 5. New runs use schema version 19; use the typed loader to read historical bundles without rewriting them). Top-level fields in new runs:

Field Type Unit What it measures
schema, schema_version string, int none hpx.run-summary, version 19
engine string none the engine that ran
layers int count operators in the profiled graph
total_cycles number CPU cycles the sum of the per-layer instrumented cycle counts (report/summary.py), so it includes the per-layer counter reads; the clean window is latency.device_clean_infer_avg_cycles
overflow_detected bool none any counter saturated its 32-bit accumulator in any pass
top_layers[] op, cycles, pct cycles, percent of total_cycles the heaviest layers
validity valid, degraded, invalid none the run’s verdict from the same evaluation the manifest records
issues[] code, severity, message, context none the structured reasons; every code is on the issue codes page

memory (from MemorySection):

Field Unit What it measures
arena_size bytes the arena requested (model.arena_size or the engine default)
allocated_arena bytes what the runtime used of it; the number to size the next run from
model_size bytes the model file
num_tensors, input_size, output_size count, bytes, bytes graph tensors and the I/O buffers

binary (from BinarySection, using the profile ELF’s size-tool totals and section headers for reserved-memory reconciliation; the power-only binary is not measured):

Field Unit What it measures
text bytes size-tool code plus read-only data; not just the ELF section named .text
data, bss bytes initialised writable data and zero-initialised state, respectively
reserved bytes linker-reserved NOBITS capacity separated from statically allocated state (Apollo5 linker scripts fill the remaining DTCM as heap); omitted when zero
total bytes the size tool’s inclusive sum; text + data + bss + reserved reconciles to it

latency (from LatencySection; the boundary of each number is in its name):

Field Unit Boundary
capture_duration_s, hpx_start_latency_s, protocol_duration_s, boot_phases_s seconds host-side timing of the whole capture, not of inference
device_profiled_infer_count, device_profiled_infer_total_us, device_profiled_infer_avg_us count, microseconds the instrumented per-layer passes, on the device’s clock
device_clean_infer_count, device_clean_infer_total_cycles, device_clean_infer_avg_cycles, device_clean_infer_avg_us count, cycles, microseconds the clean window: warm, back to back, no per-layer instrumentation
device_clean_stalled_iters, device_clean_partial_iters, device_clean_ref_cycles, device_clean_dwt_rate_cyc, device_clean_dwt_rate_us, device_clean_attach_wait_us counts, cycles, microseconds the clean-window clock checks described on Verifying a capture
clean_workload string identifies what the clean window ran (for AOT, aot_raw_zero_refill_included_v1 means input restoration is inside the timing)

cache: the aggregated memory-group counters (ARM_PMU_L1D_CACHE, ARM_PMU_L1D_CACHE_RD, ARM_PMU_L1D_CACHE_REFILL, ARM_PMU_L1D_CACHE_MISS_RD, ARM_PMU_DTCM_ACCESS, ARM_PMU_MEM_ACCESS among others; report/memory.py holds the list) as event counts over the profiled passes, plus l1d_hit_rate_pct derived as on PMU counters.

power (from PowerSection, present only when power was captured):

Field Unit Boundary
measurement_scope gpio_gated_clean_window, on_device_gated_inference, free_form_capture, whole_capture_window what the numbers below were integrated over (Windows)
avg_current_a, median_current_a, p95_current_a, p99_current_a, peak_current_a amperes inside the scope; percentiles exist for the Joulescope only
avg_power_w, median_power_w watts inside the scope
energy_j, energy_per_inference_j, inferences_per_joule joules, joules, 1/joule the scope’s integrated energy and its division by the counted inferences
capture_duration_s, capture_window_s, gated_window_expected_duration_s, gated_window_duration_ratio seconds, seconds, seconds, ratio the measured window against the expectation (Verifying a capture)
gated_window_count, gated_window_duration_suspect, gated_window_reference_drift, per_inference_metrics_omitted, short_gate_pulses_ignored count, bool, string, string, count health signals
power_firmware, observation_mode, integrity, gate_failure, gating_method, sync, sync_timing_s, terminal, on_device_summary, window_clock_ceiling, gate_duration_integrity, firmware_code_fingerprint, clean_workload, power_plan, target_lifecycle mixed how the capture was produced; firmware_code_fingerprint and clean_workload are comparability inputs
active_window_estimated_* seconds, joules an estimate for the active portion, labelled by active_window_estimate_method

memory_plan and memory_regions answer two different questions and are kept apart on purpose:

  • memory_plan is the decision record computed before any compiler ran: per region, capacity, used (the sum of what hpx placed) and the named consumers. It carries no free or overflow, because only a measurement can answer those.
  • memory_regions is the measured truth from the linked ELF, classified into the per-SoC windows: link_family, linker_profile, and per region window, app_window, used, reserved, free, load_image, window_provenance, app_provenance, plus unattributed for sections outside every window. used includes the live stack on GNU links; free is app_window.length - used; GNU’s fill-to-end heap sits inside the app window (in both reserved and free), armlink’s fixed heap and stack sit outside it, so used and free compare only within a link family. The block is absent whenever it cannot be true: a custom SoC, a non-default linker_profile, a failed tool probe or a partial section inventory.
  • memory_reconciliation, when the symbol table is readable, holds the two against each other: per consumer planned_size, status (matched with measured_size and delta, missing, or unmatchable when no sized symbol can exist) and matched_symbols; per region plan used against measured used.

The schema version history matters for comparisons: v2 split reserved out of bss; v3 removed free and overflow from memory_plan; v4 re-sourced the gate-duration verdict (power.gate_duration_integrity with gated_window_reference_drift); v5 preferred the driver’s packet duration for gated power; v6 added clean_workload. hpx compare reports a version difference as informative and reads artifacts through load_run_summary, whose PowerSection.gate_duration_unarbitrated_failure property applies the v4 arbitration.

identity (from helia_profiler.identity.RunIdentity, built by profile_identity.py) states what ran in the vocabulary fixture builds use, so a profile run and a fixture build of the same firmware can be compared. A fact the run did not record is {"unknown": "<reason>"} rather than absent or guessed:

Field What it states
runtime engine (litert, helia-rt, helia-aot or executorch), repository, version and commit: the runtime module from the lock, the installed heliaAOT package, or the ExecuTorch runtime record
kernels library (reference, cmsis-nn or helia-core), repository, version, commit: the one CMSIS-NN module the lock resolved, never the backend label; reference only for LiteRT’s reference backend, which links none
build toolchain, toolchain_version, board, cpu_hz, clock_profile, arena_location, weights_location, arena_bytes
baseline_id, dependency_lock_sha256, producer the compatibility baseline, the lock’s sha256 and the HPX release, from the dependency workspace
model_sha256, inputs_hash the model file’s sha256, and an hpx.inputs semantic hash of each pinned input’s tensor name and data sha256; profile firmware feeds zero-filled inputs, so inputs_hash is unknown
run how it ran and how the clean window was timed: the fields below
run.lifecycle, run.instance_boundary carry with boot: the engine initializes once and keeps its state across invocations; single with invocation for ExecuTorch, whose firmware reloads and initializes the program on every call
run.iterations, run.warmups the configured counts; the clean window always runs at least one warmup
run.first_invoke_initializer, run.first_invoke_window whether the first invocation of an instance runs the model’s one-time initializer, and where it falls. LiteRT and heliaRT run a CALL_ONCE subgraph inside the first Invoke(), so this is whether the source model has one; heliaAOT runs it in model_init(), so false; unknown for ExecuTorch. The window is warmup, except measured for ExecuTorch, where every invocation is its instance’s first
run.timing_scope restore_and_invoke for heliaAOT’s clean window, else invoke_only
run.timing_clock, run.timing_clock_hz the clock that times the clean window and its configured rate: stimer at 32,768 Hz for the busy_loop probe; otherwise cpu cycles at the CPU clock for ExecuTorch (its execute-cycle count) on every SoC, stimer on Apollo5, and cpu (DWT) cycles elsewhere
run.timing_statistic mean: the clean window reports the mean per invocation (latency.device_clean_infer_avg_us); per-layer counters use profiling.aggregation
run.energy the method the run’s energy is measured with: null for a profile without power.enabled, and unknown for one with it, since a profile does not state its energy method. A run-API rail capture states its scope, rail, subtraction and completion_protocol here, also when the capture failed
build_hash an hpx.build semantic hash of the flashed .bin image, what the lock resolved, and the model; it ignores the HPX release and the lock’s timestamps, so it changes only when what runs changes
observation_hash an hpx.observation semantic hash of the PMU and device timing results, without host paths; two captures of the same firmware can differ
intent_hash the frozen plan’s hpx.intent semantic hash for a run built from one; null for an ordinary profile

build_hash, inputs_hash, observation_hash and intent_hash, and a compiled build’s binding, are semantic hashes: hpx.<kind>/<version>:sha256:<hex>, with 64 lowercase hex digits. The kind says what was hashed and the version says how. The identity’s *_sha256 fields stay bare hex: each is the sha256 of one file’s bytes (the model, the dependency lock, the prepared runtime archive).

The hashed bytes, the basis, are the value’s JSON as Python’s json.dumps writes it with sort_keys=True, separators=(",", ":") and ensure_ascii=True: keys sorted, no whitespace, every non-ASCII character escaped as \uXXXX (a surrogate pair beyond U+FFFF) so the bytes are ASCII, and each float in Python’s shortest round-trip repr (0.1, 1e-05). A non-finite float (NaN or an infinity) is refused, except in an observation basis, where a capture can report a NaN counter and it is written as the string "nan", "inf" or "-inf". Each kind has one basis:

Kind Basis
hpx.intent/1 what a frozen plan asks for: its settings without host paths, the model hash, baseline, qualification, overrides, local inputs, toolchain, environment and engine runtime
hpx.build/1 the flashed image’s sha256, what the dependency lock resolved, the linked runtime archive’s sha256 and the model’s sha256
hpx.observation/1 one capture’s measured facts, with files reduced to their sha256 and no host paths or error text
hpx.binding/1 a compiled build’s plan intent_hash, lifecycle, timing scope, state policy, power wiring, prepared runtime manifest and what it observes
hpx.inputs/1 each pinned input’s [tensor name, data sha256], in model order
hpx.target-config/1 retained board, probe device type, load address, configured clock profile/CPU rate and arena/weights placements; no workload or arena capacity

Changing what goes into a kind’s basis bumps that kind’s version. Compare two hashes only when their producer, kind and version all match. helia_profiler.SemanticHash.parse checks a string’s form and returns its producer, kind, version and hex; it refuses bare hex, uppercase hex, a missing version and any algorithm but sha256.

These examples pin the encoding:

Kind Basis bytes Hash
intent {"settings":{"engine":{"type":"helia-aot"}},"toolchain":["atfe","21.1.1"]} hpx.intent/1:sha256:a74a43acef82134a3774a67959b8da1232a38fb88d7ea6f93e55e3e7662786c8
build {"image":"ab12","lock":{},"model":"cd34","runtime_archive":null} hpx.build/1:sha256:6c416b863ba7e576075dca7ff677be4449aa90e879738cb4591dcb323d9583f5
observation {"cycles":[120,80],"error":false,"mean_us":"nan"} hpx.observation/1:sha256:8f2796b5d85db5099f58b011ce8cb5c0b7c38f22d36d462cf80e6e9b77eba605
binding {"lifecycle":"carry","power_sync":null} hpx.binding/1:sha256:7b9198891413a5779f01bb8aed3d0a68125d87cf983fddec759efbfcb58201ed
inputs [["input_1","cd34"],["input_2","ef56"]] hpx.inputs/1:sha256:4a9320c8ecb5b4deede7d46e4dd9f2ad11ecf27e7626385dc7e7e03d84f2c9cc
target-config {"arena_location":"sram","board":"apollo510_evb","clock_profile":"lp","cpu_hz":96000000,"device":"AP510NFA-CBR","load_address":4259840,"weights_location":"mram"} hpx.target-config/1:sha256:41d565d1ce07c353df4a6156c30885773b692ad5b6224fd58b5a918d26ad04e0
intent {"note":"\u00b5s \u2713","ratio":0.1,"scale":1e-05} hpx.intent/1:sha256:78888b62d415f66c54322c1aa3ea08858c0ffd0df737e142b8c13424825ca312

The last example is {"note": "µs ✓", "scale": 1e-05, "ratio": 0.1}: its non-ASCII characters are escaped and its floats keep their shortest form.

Four more top-level sections appear when the run produced them and are owned by other pages: compatibility and dependencies (the qualified baseline and the lock provenance, Toolchains), psram (Memory placement) and model_analysis (cycles_per_mac and cycles_per_op from model analysis, Analysis and comparison).

Version 12 uses compiled-graph costs for AOT profiles when the compiler supplies the transformed graph. model_analysis.engine identifies the analyzer and index_space is execution for compiled costs or source for original-graph costs. Original-graph fallback and older reports retain source-index joins.

Version 13 reports the planned MRAM capacity as the linker’s application window for the selected toolchain, which is smaller than the whole MRAM part (4,128,768 of 4,194,304 bytes on Apollo510), wherever the SoC has a linked memory map. Fusions can change totals and derived rates; compare like analysis bases.

Version 14 adds identity. The change is additive: no existing field changes meaning.

Version 16 adds identity.intent_hash and the run fields from instance_boundary to timing_statistic. An ExecuTorch run’s run.lifecycle becomes single; nothing else changes meaning.

Version 17 renames the engine tflm to litert in engine and wherever else a summary names the engine; nothing else changes. Read a summary that says tflm with the HPX release that wrote it.

Version 18 adds identity.run.energy; nothing else changes meaning.

Version 19 writes identity.build_hash, observation_hash and intent_hash as semantic hashes instead of bare hex, and renames identity.inputs_sha256 to inputs_hash, also a semantic hash; the hex part of each is unchanged. The verified result reader refuses a version 19 summary whose identity is not an object, omits one of these four, or states one that is neither a hash of its kind nor, where allowed, {"unknown": "<reason>"} or null. It reads no other summary version: one is refused as unsupported_schema, naming its version, and is read with the HPX release that wrote it.

One row per layer, written from the merged per-layer table:

Column Unit Meaning
id index the firmware’s execution position: the sequential layer index for the interpreter engines, the post-compilation position for AOT (which skips operators such as VAR_HANDLE without renumbering)
op string operator type; DEPTHWISE_CONV_2D:1 on AOT carries the original index
source_index index AOT only: the original operator index retained as provenance; compiled-graph costs join on execution position
kernel_calls role=symbol pairs AOT only, when the installed helia-aot records them: the CORE entries the generated operator calls directly, ;-separated and sorted by role. run calls execute inside the measured window; init calls run in the operator’s initialisation before it; other keys name shared helpers. none when the compiler recorded no CORE call; empty when it recorded nothing for that operator. Interpreter engines select kernels at run time and have no column. A symbol linked into the image is not evidence that it ran
<COUNTER> columns event counts each requested counter, aggregated across iterations by profiling.aggregation
cycles cycles the dedicated cycle counter
overflow bool any counter of this layer saturated; its values are a lower bound
macs, ops, cycles_per_mac count, count, cycles per MAC present when the model could be analysed (the analysis extra)

The JSON form (output.format: json) carries the measured rows under layers (id, op, counters, cycles, cycles_pct, overflow) without the attribution columns, with metadata, summary, presets, overflow_detected and power at the root, per profile_results.schema.v1.json. aot_operator_manifest.json keeps the per-operator provenance for either format.

The provenance of the run, from report/contracts.py:

Field Meaning
hpx_version, run_id, timestamp the tool and the run
config the fully resolved configuration after the merge (Configuration)
platform board, soc, core, link_family (gnu for GCC and ATfE, armlink for armclang), the resolved clock tier
model name, size in bytes, sha256
toolchain compiler, compiler_version, cmake_version as found on the host
engine type and the measured runtime version where one resolves (heliaRT, heliaAOT)
build_images[] one entry per built target (profile, and power on a power run): target_name, binary_name, sha256 of the linked ELF, flash_sha256 of the flat .bin image the probe writes (no host paths; null when the build left none), size_bytes, architecture_flags counted from the configured build tree’s compile_commands.json, translation_units
firmware arena_size, allocated_arena, model_size as the firmware reported them
dependency provenance the typed registry hash, baseline identity and fingerprint, workspace fingerprint, lock mode and update state, requested refs and tags, peeled commits, content hashes and explicit overrides

architecture_flags counts instruction-set flags such as -mcpu=cortex-m55 against -mcpu=cortex-m55+nomve across the configured tree; it is an inventory of the configured units, not proof each was linked, and precompiled libraries contribute nothing. hpx compare shows it as an informative dimension.

aot_operator_manifest.json records every operator the AOT compiler emitted after graph transforms, in execution order, each with inputs, outputs and local tensors (weights, weight sums, per-operator scratch). idx is the execution position. id is the operator’s .tflite index, or null when a transform created it or fused several (source_indices lists every index it came from); the firmware labels such a layer <OP_TYPE>:-1, where <OP_TYPE> is op_type upper-cased with any character outside A-Z, 0-9 and _ replaced by _. kernel_calls is the compiler’s record of the CORE entries the operator’s generated code calls, keyed by role or shared helper, or null when nothing was recorded for that operator or it was malformed. The compiler’s own <prefix>_report.json, beside the generated module under aot_output/ in the work directory, carries the module-wide list with its unrecorded operators. Tensor entries carry id, the model’s name when it has one, dtype, ctype, kind (input, output, constant, persistent or scratch), nbytes and shape. Placed tensors add memory (dtcm, itcm, sram, mram, psram), source_memory, staged, arena_role (scratch, persistent, constant), arena_region_id, offset and allocation_size, all in bytes where they are sizes. Manifests from hpx 0.2.x recorded model inputs and outputs as scratch and named tensors tensor_<id>, so hpx compare against such a run reports their placement as changed. aot_memory_layers.csv is the same data as one row per tensor with layer_idx, layer_id, op_type, op_name, tensor_role, tensor_id, tensor_name, tensor_kind, memory, source_memory, staged, arena_role, arena_region_id, offset, size, shape.

With output.detailed: true: memory.json (binary sections, the arena block, per_layer_memory with each layer’s memory-group counters, cache_totals, and memory_symbols: the top 32 sized symbols per region), profile_<group>.csv merged per compute-unit group and, when the run needed more than one PMU pass, profile_<preset>.csv per preset (presets are named <group>_<index>, for example cpu_0), and power_summary.csv with one row per gated window plus a whole_capture_window reference row.

Every run prints the arena, model and binary sizes, the layer count, total cycles, the top layers with percentages, the cache totals and the validity footer: VALID, DEGRADED with one code: message line per warning, or INVALID with the errors first. An invalid run still exits 0 and writes its bundle unless output.fail_on_invalid (--fail-on-invalid) is set, in which case it exits 3.

hpx run saves each capture as result.json in its evidence directory. From Python, RunResult.save(path, roots=..., build=...) writes one and helia_profiler.load_run(path, roots=...) reads it back as a RunResultRecord (path, size_bytes, sha256, hpx_version, result, document). Paths are {root, path} under named roots, as in a saved compiled build, so the run directory moves as a unit.

Field Holds
location where the result itself sits, under its root
compiled_build the saved build’s root, path, sha256 and size_bytes
capture the capture’s facts (state, timing, memory, rail_energy, error and the rest); every file it references (outputs, terminal snapshots, the rail trace) as root, path, sha256 and size_bytes
power null, or the request with kind rail or supply
verification null, or the reference origin, tolerance, matched, the per-tensor facts and reference_files, the reference outputs written beside the result as <stem>.reference_<index>.<publication>.bin
identity the run’s RunIdentity
hpx_version the heliaPROFILER that wrote the result

Loading checks the evidence: every pinned file matches its sha256 and size, the compiled build is the one recorded and verifies, the identity and the output comparison recompute from the build and the capture, and the power request matches the build’s GPIO wiring, protocol and supported power route. A successful capture must include timing whose iteration and warmup counts match the compiled build; both saving and loading enforce this check. Failed captures may retain no timing. Retained energy must agree with the request’s instrument and rail, the build’s iteration counts, and a valid batch completion record. Timing-only and supply-only runs cannot carry rail energy. An unknown identity fact matches an unknown fact whatever its reason text. Free text, such as a failed capture’s error, is recorded, not verified. The checks catch accidents, such as an edited or mixed-up file, not tampering. A failed capture saves and loads too; its outcome is data. A failed rail capture may have no energy. Error text may be null or a string; its wording is not verified.

Saving uses a new set of reference filenames for each publication. If a replacement save fails, the previous JSON and its reference files remain readable. Unreferenced files may remain after an interrupted save.

A refusal is a helia_profiler.RunResultError, a ConfigError whose code names the failed check:

Code Meaning
invalid_json not strict JSON: unparsable, a repeated key or a non-finite number
unsupported_schema another schema, or another hpx.run-result version
invalid_path unusable roots, or a result that is not where it records itself
artifact_changed the build, an evidence file or a reference output is missing or changed
runtime_missing the build’s prepared runtime is not in the HPX cache; the hint names the hpx runtimes prepare command
identity_mismatch the identity or the output comparison does not recompute
invalid_evidence anything else: an unreadable or malformed result, a build that does not verify, or a power request the build cannot take

helia_profiler.api.load_result takes a result.json or its evidence directory, dispatches on the file’s schema, and verifies it under the root it records itself under and this host’s HPX cache, raising a ResultReadError with the same codes. It adds publication_changed for a result that changed while it was read, and ambiguous_location for a directory holding both result.json and result_manifest.json; name the file instead. observations(record) maps it to the portable result view (1.0-draft, mapping hpx-run-2); hpx results show and hpx results export print and write it.

Metric Unit Scope Statistic Basis
latency.invoke.v1 or latency.restore_invoke.v1 ms the run’s timing_scope mean over the measured iterations (sample_count) ticks / timer_hz / iterations * 1000; clocks are the configured timing_clock_hz and the reported timer_hz, domain timer for the STIMER
energy.per_inference.v1 uJ gross_rail_restore_and_invoke mean per measured iteration energy_per_iteration_j * 1e6; domain system, since the MCU rail feeds more than the CPU
power.mean.v1 mW gross_rail_restore_and_invoke time mean over the gated window mean_power_w * 1e3
memory.arena_capacity.v1 B arena_capacity size the capture’s memory terminal
memory.arena_used_after_invoke.v1 B allocator_after_invoke size the capture’s memory terminal

A timing-only or supply-only run states energy unavailable (energy_not_measured_timing_only, energy_not_measured_supply_only), and a failed capture states every measurement unavailable (capture_failed). producer.version is the hpx_version that wrote the result. workload holds the pinned model file’s name, its sha256, the inputs_hash and the lifecycle. verification.checks names the output comparison only when the run compared its outputs; otherwise a limitation says they were not compared. engine.version follows the engine, and extensions.helia-profiler names its basis:

Engine engine.version engine_version_basis
helia-aot the heliaAOT release that generated the module aot_provider_release
helia-rt, litert, executorch the linked runtime release linked_runtime_release

An unknown version stays null, with its reason in engine_version_unavailable_reason. extensions.helia-profiler also holds the build-time producer (build_producer), the output comparison, the semantic hashes and the run facts (instance_boundary, first_invoke_initializer, first_invoke_window, timing_statistic, warmups). The view does not qualify the engine numerically or authenticate the result. examples/results/run-api-synthetic/ holds a synthetic saved result and its portable.json.

A bundle whose manifest verifies, whose summary says valid, and whose per-layer table has one row per operator and one column per counter you asked for. Parsing outputs reads all of it from Python and jq.

Key Flag Effect
output.dir --output-dir the bundle directory (results by default; a second run into it overwrites in place)
output.format --output-format csv or json the primary per-layer artifact
output.model_explorer --no-model-explorer skip the overlays
output.detailed --detailed write detailed/
output.fail_on_invalid --fail-on-invalid exit 3 on an invalid run

Parsing outputs for scripts against these files, Analysis and comparison for two bundles side by side, Model Explorer overlays for the graph view.

Schema v7 uses the INA228 accumulation interval for internal-mode average power and current. Energy and energy per inference are unchanged. The GPIO gate interval is power.terminal.gate_elapsed_us; the whole firmware interval remains power.terminal.elapsed_us. Comparing v6 and v7 reports an informative schema difference.

Schema v8 corrects MAC counts for TRANSPOSE_CONV, non-flat FULLY_CONNECTED, and BATCH_MATMUL. Models using these operators can have changed total MACs, total operations, TOPS, and cycles per MAC.

Schema v9 publishes power.energy_per_inference_j and power.inferences_per_joule for on-device gated-inference summaries when the probe ran inferences and the run has no power error; power.per_inference_metrics_omitted names the errors otherwise. Earlier summaries lacked these fields for that scope, so comparing v8 and v9 reports an informative schema difference.

Schema v10 counts SQRT, RSQRT, ABS, and SQUARED_DIFFERENCE as one operation per output element in the heliaAOT analysis, matching the TFLite analysis. helia-aot models using these operators can have changed total operations and TOPS; MACs are unchanged.

Schema v11 renames the firmware gate interval in the power.window_observer_mismatch and power.window_clock_mismatch issue contexts from elapsed_us / elapsed_s to gate_elapsed_us / gate_elapsed_s, so one key name no longer means two intervals. elapsed_us in power.window_clock_ceiling and power.window_clock_frozen remains the whole firmware window. Measured values are unchanged.

Retained target configuration and CPU clocks

Section titled “Retained target configuration and CPU clocks”

Saved-run mapping hpx-run-2 exposes target.identity as hpx.target-config/1:sha256:<hex> when all seven retained facts are known: board, device, load_address, clock_profile, cpu_hz, arena_location and weights_location. The producer extension target_config exposes that flat hash basis, per-field artifact/JSON-pointer sources and unavailable reasons. Any unknown or invalid included fact makes the identity null; known fields stay visible, and reason text is never hashed. Values come from the verified saved result and compiled build, never current board definitions or installed pins.

This is an HPX configuration identity, not a board serial, silicon qualification, or equivalence to a HELX simulation target. It excludes arena capacity, workload, runtime/kernel/toolchain, firmware image, lifecycle, measurements and host paths. Relocation does not change it. Equal identities describe only these seven facts; comparison eligibility remains the consumer’s decision.

Latency bases retain timing-source clocks and add CPU-domain configured and firmware-reported rates, with distinct sources. STIMER frequency is a timer fact, not CPU frequency. The cpu_clocks producer extension (hpx.cpu-clocks, version 1) preserves both rates and explicit reasons when unavailable; disagreements remain visible. Firmware reports an assigned SystemCoreClock, not an independently measured or calibrated frequency. If the timing source is CPU, its existing CPU-domain clock entries are kept without duplicates. Failed captures do not gain successful latency observations from these configuration facts.

The native saved-result and compiled-build formats are unchanged. This mapping version applies to saved run results; the separate profile mapping is unchanged.