Skip to content
heliaAOT
HELIA HUB

Residency reports

Use a residency report to confirm a placement rule or compare two conversions without scraping console output. Start from a configuration that converts and write the module to a directory:

Terminal window
helia-aot convert --path convert.yaml --module.path ./out --memory.dump-residency-json

Success writes <prefix>_residency.json inside out/<module.name>/. For the default names, that is out/helia_aot_nn/aot_residency.json. An archive output contains the report inside the exported module. Keep it with the model hash, configuration and compiler version that produced it.

Every conversion prints an arena summary; --verbose 2 adds planner diagnostics. For tooling, set memory.dump_residency_json: true and the conversion writes <prefix>_residency.json next to the module. This is the report for the fixture model from the committed documentation export:

DTCM
constant, cold44,104 bytes
persistent256 bytes
scratch463,544 bytes
The residency report for the demo fixture model converted for apollo510_evb.

Example source: residency-example.json, exported by the documentation data generator.

View aot_residency.json
aot_residency.json
{
"arenas": {
"constant": [
{
"alignment": 16,
"kind": "cold",
"memory": "dtcm",
"region_id": 2,
"source_memory": "dtcm",
"tensor_count": 16,
"total_size": 44104,
"used": 44104
}
],
"persistent": [
{
"alignment": 16,
"memory": "dtcm",
"region_id": 1,
"total_size": 256,
"used": 256
}
],
"scratch": [
{
"alignment": 16,
"fragmentation": 192,
"memory": "dtcm",
"peak_live": 5696,
"region_id": 0,
"total_size": 463544,
"used": 5888
}
]
},
"module_prefix": "aot",
"plan_hash": "2d9dd7d2dcf7b35f",
"schema_version": 4,
"tensor_layout_hash": "5b408f45e8840559",
"tensors": [
{
"id": "0",
"memory": "dtcm",
"offset": 0,
"role": "scratch",
"size": 768,
"source_memory": "dtcm"
},
{
"id": "1",
"memory": "dtcm",
"offset": 0,
"role": "constant",
"size": 432,
"source_memory": "dtcm"
},
{
"id": "3",
"memory": "dtcm",
"offset": 768,
"role": "scratch",
"size": 4096,
"source_memory": "dtcm"
},
{
"id": "4",
"memory": "dtcm",
"offset": 4864,
"role": "scratch",
"size": 1024,
"source_memory": "dtcm"
},
{
"id": "5",
"memory": "dtcm",
"offset": 624,
"role": "constant",
"size": 9216,
"source_memory": "dtcm"
},
{
"id": "7",
"memory": "dtcm",
"offset": 0,
"role": "scratch",
"size": 4096,
"source_memory": "dtcm"
},
{
"id": "8",
"memory": "dtcm",
"offset": 4096,
"role": "scratch",
"size": 1024,
"source_memory": "dtcm"
},
{
"id": "9",
"memory": "dtcm",
"offset": 10608,
"role": "constant",
"size": 8,
"source_memory": "dtcm"
},
{
"id": "10",
"memory": "dtcm",
"offset": 4096,
"role": "scratch",
"size": 1024,
"source_memory": "dtcm"
},
{
"id": "11",
"memory": "dtcm",
"offset": 10624,
"role": "constant",
"size": 32768,
"source_memory": "dtcm"
},
{
"id": "12",
"memory": "dtcm",
"offset": 43392,
"role": "constant",
"size": 128,
"source_memory": "dtcm"
},
{
"id": "13",
"memory": "dtcm",
"offset": 43520,
"role": "constant",
"size": 128,
"source_memory": "dtcm"
},
{
"id": "14",
"memory": "dtcm",
"offset": 0,
"role": "persistent",
"size": 128,
"source_memory": "dtcm"
},
{
"id": "15",
"memory": "dtcm",
"offset": 0,
"role": "scratch",
"size": 32,
"source_memory": "dtcm"
},
{
"id": "16",
"memory": "dtcm",
"offset": 43648,
"role": "constant",
"size": 320,
"source_memory": "dtcm"
},
{
"id": "18",
"memory": "dtcm",
"offset": 32,
"role": "scratch",
"size": 10,
"source_memory": "dtcm"
},
{
"id": "19",
"memory": "dtcm",
"offset": 0,
"role": "scratch",
"size": 10,
"source_memory": "dtcm"
},
{
"id": "conv_2d_0_multiplier",
"memory": "dtcm",
"offset": 496,
"role": "constant",
"size": 64,
"source_memory": "dtcm"
},
{
"id": "conv_2d_0_scratch",
"memory": "dtcm",
"offset": 4864,
"role": "scratch",
"size": 128,
"source_memory": "dtcm"
},
{
"id": "conv_2d_0_shift",
"memory": "dtcm",
"offset": 560,
"role": "constant",
"size": 64,
"source_memory": "dtcm"
},
{
"id": "conv_2d_0_weight_sum",
"memory": "dtcm",
"offset": 432,
"role": "constant",
"size": 64,
"source_memory": "dtcm"
},
{
"id": "conv_2d_2_multiplier",
"memory": "dtcm",
"offset": 10096,
"role": "constant",
"size": 256,
"source_memory": "dtcm"
},
{
"id": "conv_2d_2_scratch",
"memory": "dtcm",
"offset": 4096,
"role": "scratch",
"size": 576,
"source_memory": "dtcm"
},
{
"id": "conv_2d_2_shift",
"memory": "dtcm",
"offset": 10352,
"role": "constant",
"size": 256,
"source_memory": "dtcm"
},
{
"id": "conv_2d_2_weight_sum",
"memory": "dtcm",
"offset": 9840,
"role": "constant",
"size": 256,
"source_memory": "dtcm"
},
{
"id": "fully_connected_6_multiplier",
"memory": "dtcm",
"offset": 44016,
"role": "constant",
"size": 40,
"source_memory": "dtcm"
},
{
"id": "fully_connected_6_shift",
"memory": "dtcm",
"offset": 44064,
"role": "constant",
"size": 40,
"source_memory": "dtcm"
},
{
"id": "fully_connected_6_weight_sum",
"memory": "dtcm",
"offset": 43968,
"role": "constant",
"size": 40,
"source_memory": "dtcm"
},
{
"id": "svdf_5_scratch_a",
"memory": "dtcm",
"offset": 32,
"role": "scratch",
"size": 128,
"source_memory": "dtcm"
},
{
"id": "svdf_5_scratch_b",
"memory": "dtcm",
"offset": 160,
"role": "scratch",
"size": 128,
"source_memory": "dtcm"
},
{
"id": "svdf_5_weight_sum",
"memory": "dtcm",
"offset": 128,
"role": "persistent",
"size": 128,
"source_memory": "dtcm"
}
]
}

Read it as three things:

  • used against total_size per arena. The gap is headroom, not waste: used is the emitted arena size, while total_size records the planner’s capacity for that arena. Scratch capacity can include space the plan did not use; constant and persistent envelopes need not have that same headroom. Do not sum total_size across roles as a measure of occupied memory.
  • kind and source_memory on constant arenas, which say whether the weights are cold or staged and where they come from.
  • The two hashes. plan_hash covers the arena envelope only (role, memory, source memory, size, alignment) and mirrors the generated plan-hash macro, so it catches arena ABI drift between separately compiled binaries. tensor_layout_hash covers per-tensor placement, so it catches two tensors swapping offsets inside one arena, which the envelope hash cannot see.

Check schema_version before consuming the report in tooling. The current schema is 4. To check the runtime arenas against a model-specific bank budget, sum used across roles within each memory:

import json
from collections import defaultdict
from pathlib import Path
report = json.loads(Path("out/helia_aot_nn/aot_residency.json").read_text())
if report["schema_version"] != 4:
raise ValueError("Review the residency schema before reading this report")
used = defaultdict(int)
for arenas in report["arenas"].values():
for arena in arenas:
used[arena["memory"]] += arena["used"]
# Example application budget, not a target's total physical capacity.
budgets = {"dtcm": 64 * 1024}
for memory, budget in budgets.items():
if used[memory] > budget:
raise ValueError(f"{memory}: {used[memory]} bytes exceeds {budget}")
print(dict(used))

Passing this check means those runtime arenas fit the stated budgets. It does not account for all firmware storage: include staged source blobs, operator code, stacks, heaps and other application allocations when reviewing the linker map and runtime memory use.

Neither layout hash is a checksum of the weight values. Keep a model-content hash separately; matching layout hashes do not establish matching predictions. Compare reports and linker maps in CI to catch unintended layout changes.

The full field list is in the Generated module reference.