# 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:

```bash
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.

## 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:

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

View aot_residency.json

```json title="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:

```python
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](https://ambiqai.github.io/helia-aot/reference/module/#residency-report).
