# Memory placement

Convert one model twice, once with the planner's own choices and once with
placement rules, and diff the two residency reports. The second conversion
stages the weights and hands arena ownership to the application. It also
illustrates a persistent-state placement rule; this model has no persistent
tensors, so that rule moves no state.

| Field | Value |
| --- | --- |
| Model | kws_ref, MLPerf Tiny keyword spotting, int8 |
| Target | `apollo510_evb` |
| Shows | Per-role placement rules, staged constants, caller-supplied arenas, and residency-report comparisons |
| CI | Conversion and host compilation are declared in the examples CI job, with both residency reports held to the committed copies |

The model is `audio/mlperf-tiny/kws_ref/model.tflite` from helia-model-zoo:
int8, 53,936 bytes, SHA-256 `aeea4368…bd0ae`, fetched by `run.sh` and checked
against the fixed SHA-256 recorded in the checked-in run script. Model licensing is not established by these hashes; see [Model provenance](https://ambiqai.github.io/helia-aot/examples/).

## Prerequisites

Use a repository checkout with Bash and the environment from [Running examples](https://ambiqai.github.io/helia-aot/examples/): `uv sync --frozen --group ci`, then activate `.venv`. Fetched models require network access, `curl` and `sha256sum` or `shasum`. Run the commands below from this example directory. Conversion runs on the host; it does not execute the emitted firmware.

## Run it

```sh
./run.sh
```

Two conversions of the same model: [config-baseline.yaml](https://github.com/AmbiqAI/helia-aot/blob/cf2246a7ad439fa38d57462118e7840e5497a685/examples/memory-placement/config-baseline.yaml)
and [config-tuned.yaml](https://github.com/AmbiqAI/helia-aot/blob/cf2246a7ad439fa38d57462118e7840e5497a685/examples/memory-placement/config-tuned.yaml). The model and target stay fixed.
The memory block changes placement and ownership; separate `module.name` values
keep both outputs for comparison.

## The rules

```yaml
memory:
  dump_residency_json: true
  allocate_arenas: false
  tensors:
    - attributes: { memory: DTCM }
    - type: persistent
      attributes: { memory: SRAM }
    - type: constant
      attributes:
        memory: MRAM
        constant_destination_memory: DTCM
```

Rules are matched by tensor role and merged by specificity, so the first line
is the default for everything and the two below it override it for their role.
The role names are `constant`, `persistent` and `scratch`, spelled as the
report spells them.

`memory` on a constant is where the bytes are stored;
`constant_destination_memory` is where the kernels read them. Naming both, and
naming two different memories, is what makes a constant staged rather than
cold: the image keeps one read-only blob in MRAM and the module hydrates a
writable copy into DTCM before the first run.

`allocate_arenas: false` makes the module declare its arenas and allocate
none. The application binds each one, including the cold constant arena, and
the header carries a size macro, an alignment macro and a region enumerator
per arena.

This model has no persistent tensors, so the `persistent` rule matches nothing
here. It demonstrates the rule syntax; whether a real model
benefits from moving state needs measurement. It establishes no state-access
latency result for this stateless model.

## What to look at

The committed reports in [expected/](https://github.com/AmbiqAI/helia-aot/tree/cf2246a7ad439fa38d57462118e7840e5497a685/examples/memory-placement/expected) are conversion baselines for
this source revision. Compare your generated reports with those files. The
constant arena changes as follows:

```json
"constant": [
  {"kind": "cold", "memory": "dtcm", "source_memory": "dtcm", "tensor_count": 39, "used": 28992}
]
```

```json
"constant": [
  {"kind": "staged", "memory": "dtcm", "source_memory": "mram", "tensor_count": 39, "used": 28992}
]
```

`kind` went from cold to staged, and `source_memory` from `dtcm` to `mram`.
The tuned module also emits `aot_arena_const_dtcm__source.bin`, a sidecar of
the packed source bytes. The generated default hydration helper copies its
embedded source blob into the bound writable DTCM destination during
`aot_model_init()`. Do not bind the read-only source as that destination.

Tensor IDs, sizes and offsets in these two captured reports stay the same, but
constant `source_memory` and the plan identity change. Compare fields instead
of assuming the complete tensor list is identical.

The reports are committed, and CI regenerates and diffs them. A planner or
codegen change that moves a tensor shows up as a failing diff here rather than
as a surprise in someone's build. Regenerate them with:

```sh
./run.sh
cp out/kws_ref_baseline/aot_residency.json expected/kws_ref_baseline-residency.json
cp out/kws_ref_tuned/aot_residency.json expected/kws_ref_tuned-residency.json
```

## Next

- [Memory](https://ambiqai.github.io/helia-aot/guide/memory/) for the planner,
  the hydration contract and the residency schema.
- [Static schedule](https://ambiqai.github.io/helia-aot/examples/static-schedule/) for the other lever this model has.
