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:
helia-aot convert --path convert.yaml --module.path ./out --memory.dump-residency-jsonSuccess 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.
The residency report
Section titled “The residency report”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:
demo fixture
model converted for apollo510_evb.
Example source: residency-example.json, exported by the documentation data generator.
View 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:
usedagainsttotal_sizeper arena. The gap is headroom, not waste:usedis the emitted arena size, whiletotal_sizerecords 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 sumtotal_sizeacross roles as a measure of occupied memory.kindandsource_memoryon constant arenas, which say whether the weights are cold or staged and where they come from.- The two hashes.
plan_hashcovers 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_hashcovers 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 jsonfrom collections import defaultdictfrom 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.