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", "storage": { "enum_name": "aot_arena_const_dtcm", "runtime": { "data_offset_bytes": 0, "memory": "dtcm", "owner": "generated", "size_bytes": 44176, "symbol": "aot_arena_const_dtcm__blob" }, "sidecar": null, "source": null }, "tensor_count": 14, "total_size": 44176, "used": 44176 } ], "persistent": [ { "alignment": 16, "memory": "dtcm", "region_id": 1, "storage": { "enum_name": "aot_arena_persistent_dtcm", "runtime": { "data_offset_bytes": 0, "memory": "dtcm", "owner": "generated", "size_bytes": 128, "symbol": "aot_arena_persistent_dtcm_buffer" }, "sidecar": null, "source": null }, "total_size": 128, "used": 128 } ], "scratch": [ { "alignment": 16, "fragmentation": 0, "memory": "dtcm", "peak_live": 5696, "region_id": 0, "storage": { "enum_name": "aot_arena_dtcm", "runtime": { "data_offset_bytes": 0, "memory": "dtcm", "owner": "generated", "size_bytes": 5696, "symbol": "aot_arena_dtcm_buffer" }, "sidecar": null, "source": null }, "total_size": 463600, "used": 5696 } ] }, "compiler_version": "0.33.0", "module_prefix": "aot", "plan_hash": "87f6d61da9206a74", "schema_version": 5, "tensor_layout_hash": "b8c2130e3dd06b16", "tensors": [ { "id": "0", "memory": "dtcm", "offset": 4096, "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": 0, "role": "scratch", "size": 4096, "source_memory": "dtcm" }, { "id": "4", "memory": "dtcm", "offset": 4096, "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": 256, "role": "scratch", "size": 32, "source_memory": "dtcm" }, { "id": "18", "memory": "dtcm", "offset": 0, "role": "scratch", "size": 10, "source_memory": "dtcm" }, { "id": "19", "memory": "dtcm", "offset": 16, "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": 5120, "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_packed", "memory": "dtcm", "offset": 43648, "role": "constant", "size": 528, "source_memory": "dtcm" }, { "id": "svdf_5_scratch_a", "memory": "dtcm", "offset": 0, "role": "scratch", "size": 128, "source_memory": "dtcm" }, { "id": "svdf_5_scratch_b", "memory": "dtcm", "offset": 128, "role": "scratch", "size": 128, "source_memory": "dtcm" }, { "id": "svdf_5_weight_sum", "memory": "dtcm", "offset": 43520, "role": "constant", "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 5. 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"] != 5: 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.
Storage identities for consumers
Section titled “Storage identities for consumers”Schema v5 adds compiler_version and a storage object on each arena. Existing
v4 fields and both layout hashes keep their meanings. Enable
memory.dump_residency_json to export this contract; the offline HTML report
embeds the same record. Consumers requiring storage identities should require
v5. A consumer supporting v4 may retain its explicit older-version fallback;
unknown versions or unmapped memory kinds must produce a diagnostic rather
than silently dropping an arena.
storage.enum_nameis the generated C region enumerator forregion_id.storage.runtimedescribes the kernel-visible backing storage.ownerisgeneratedfor internal arenas orapplicationfor externally bound arenas.symbolis the exact emitted C object name, or null when the application supplies the buffer. It is not the enumerator or a guessed application name.- Each storage object has
memory,size_bytes, anddata_offset_bytes.size_bytesincludes any address-zero guard; the usable arena begins atsymbol + data_offset_bytes.usedremains the usable size. External callers provide at leastusedbytes with the arena’salignment; compiler guard bytes are not added to that binding requirement. storage.sourceis a separate generated source object for staged constants, including in external-arena mode. It is null for other arenas. Its memory can differ from runtime memory; count both when budgeting physical storage.storage.sidecargives the exact relativefilenameandsize_bytesof the constant binary emitted in external-arena mode. Sidecars contain payload and inter-tensor padding, but no address-zero guard. A staged source sidecar is an additional representation of the generated source, not another runtime allocation. Internal mode has no sidecars.
These are compiler emission identities, not a promise that an optimizer or linker retains every symbol in a final firmware image. Reconcile against the actual linked ELF. Application-owned buffers require application-provided symbol information. Optional generated test-harness example buffers are not module-owned storage. This record does not account for code, stacks, heaps or other firmware allocations and does not define platform memory capacities.