# Changes before 1.0

Each item names the 0.x release that made the change. A configuration or a
program written for an earlier release may need the change the item describes.

- **Next release.** A looping `--log-file` symbolic link is rejected with exit 2 instead of a traceback and exit 1.
- **Next release.** Archive output links whose target directory is missing are rejected before conversion with exit 2.
- **Next release.** int16 CONCATENATION, EXPAND_DIMS and FILL with nonzero zero points are rejected, as in LiteRT.

- **0.28.0.** The legacy `neuralspot` module type was removed; use `cmake`
  (the new default), `zephyr` or `nsx`. With it, `<prefix>_platform.h` no
  longer maps `<PREFIX>_PRINTF` to `ns_lp_printf` or the `<PREFIX>_PUT_IN_*`
  hooks to neuralSPOT's sections, and `<PREFIX>_PUT_CODE_IN_ITCM` is empty, so
  operators with `code_placement: ITCM` stay in the default code section.
  Define the hooks your application needs; without them the arenas and code
  follow the linker's default placement and logging is silent.
- **0.28.0.** `memory.planner` defaults to `hill_climb` instead of `greedy`.
  Its scratch arenas are the same size or smaller than `greedy`'s in every
  memory when `greedy`'s plan fits, but tensor offsets and the memory of an
  individual tensor can change. Set `memory.planner: greedy` to keep the
  previous layout.
- **0.28.0.** An unknown key in a nested block, a list entry or
  `memory.tensors[].attributes` is an error that names the nearest valid key
  (the command line exits 2). Earlier releases dropped such keys, with a
  warning in the later ones; unknown top-level keys were already rejected. See
  [When a key is wrong](https://ambiqai.github.io/helia-aot/guide/configuring/#when-a-key-is-wrong).
- **0.28.0.** A `TRANSPOSE_CONV` with `SAME` padding whose input height or
  width is not divisible by its stride, such as a Keras `Conv2DTranspose`
  decoder, produced output shifted by one pixel in earlier releases. It now
  follows LiteRT, and a declared output shape the operators do not produce is
  a typed error.
- **0.28.0.** Conversion applies LiteRT's dtype, scale and quantization rules,
  so a model that breaks one is a typed error with a hint where earlier
  releases gave a traceback, wrong output or code that did not compile.
  Platform and configuration-value errors that do not depend on the model exit
  2 (see [When a key is wrong](https://ambiqai.github.io/helia-aot/guide/configuring/#when-a-key-is-wrong)),
  and the public I/O tables report scale 1 and zero point 0 for every tensor
  but an int8, uint8 or int16 one with a scale.
- **0.28.0.** Golden data must match each tensor's element count and dtype; a
  mismatch or an archive that cannot be loaded raises `GoldenDataError` and
  exits 2. Attribute values are checked before conversion starts, and a `.zip`
  export appears at the output path only when it is complete.
- **0.31.0.** The plan's `platform.core` and the `core` of each report
  measurement are named `cpu`, as in the configuration's `platform.cpu`,
  because CORE means heliaCORE everywhere else; `measurements_for` takes
  `cpu`. A plan or report that names it `core` still reads.
  `OptimizationPlan` and `OptimizationReport` read a file from a newer release
  with the same `schema_version` instead of rejecting a field, knob, value or
  member they do not know. To check that a file holds only what one release
  writes, validate it against the `json_schema` object in that release's
  `plan-schema.json` or `report-schema.json`. The rename keeps
  `schema_version` 1, an exception made once before 1.0: a script that reads
  the raw JSON reads `cpu` and falls back to `core`, and heliaAOT 0.30 and
  earlier cannot read a plan or report that 0.31.0 or later writes.
  `PlanOptimization.goal` is the string written, which still compares equal to
  its `OptimizationGoal` member.
- **0.31.0.** A built-in operator's `operators[].attributes` keys are checked:
  a key the operators a rule reaches do not take is an error that names the
  nearest valid key, where earlier releases passed it through. A custom
  operator class keeps free-form attributes only when its `ATTRIBUTES` model
  allows extra keys. A custom transform takes no options unless it declares
  `OPTIONS`; a built-in transform that takes none has rejected options since
  0.28.0.
- **0.31.0.** Removed settings, each an error that names its replacement:
  `memory.auto_hydrate_constants`, which had no effect (`<prefix>_model_init`
  always hydrates staged constants); `operators[].attributes.scratch_placement`,
  which was never implemented (use a `memory.tensors` rule); and the `ble`,
  `usb` and `psram` values of `platform.capabilities`, which nothing read
  (declare PSRAM under `platform.memories`). The
  `--memory.auto-hydrate-constants` and `--no-memory.auto-hydrate-constants`
  flags are gone, so the command line rejects them as unknown options.
- **0.31.0.** `model.type` accepts only `tflite` and `litert`, and an unknown
  value is a command-line or configuration error. `model.version` defaults to
  none, so the generated documentation shows the LiteRT schema version the
  model file records (`3`) instead of `v1.0.0`. A custom platform no longer
  needs `platform.speeds`.
- **0.31.0.** `list-targets` and `target-info` are deprecated; use
  `platforms list` and `platforms show NAME`.
- **0.31.0.** `AotConverter.convert()` returns a `ConversionResult` with the
  output path, the prefix, the parsed plan and report, and the kept work
  directory, instead of the internal code generation context. Read those
  fields, or the residency report, instead of the context's.
- **0.31.0.** `ConvertArgs.from_yaml` raises `InvalidConfigError`, a
  `ConfigValueError` with `exit_code` 2, for a file it cannot load or a key or
  value that does not validate, where earlier releases raised
  `FileNotFoundError`, `ValueError` or pydantic's `ValidationError`.
- **0.31.0.** `ModelLoadError` is an `OSError` rather than a
  `FileNotFoundError`, and `UnsupportedModelError` is a `ConfigValueError`.
  Catch `ModelLoadError` or `OSError` for a model that cannot be loaded, and
  put an `except UnsupportedModelError` before an `except ConfigValueError`.
- **0.31.0.** Each typed error carries the exit code the command line ends
  with, as `exit_code` on its class. `ConfigValueError` no longer takes an
  `exit_code` argument: a configuration rejected on its own or against the
  target raises its subclass `InvalidConfigError`, which declares 2.
- **0.31.0.** `helia_aot` exports `AotConverter`, `ConversionResult` and
  `ConvertArgs` and no module objects, and each manifest name is promised at
  one import path: `AotConverter` and `ConvertArgs` from `helia_aot`, the
  registry names from `helia_aot.registry`, the operator names from
  `helia_aot.aot.operators`, the memory names from `helia_aot.memory`, and
  `ValidCVarNameType` and `ValidModuleNameType` from `helia_aot.defines`.
- **0.31.0.** A generated module needs ns-cmsis-nn 7.46.0 or later; a project
  on 7.45.x or earlier updates ns-cmsis-nn before rebuilding a regenerated
  module. See [the heliaCORE floor](https://ambiqai.github.io/helia-aot/reference/compatibility/#the-heliacore-floor).
- **0.32.0.** Public enum members are spelled in UPPER_CASE
  (`ModuleType.CMAKE`, `ScheduleMode.TABLE`, `MemoryPlannerType.HILL_CLIMB`,
  `ModelType.TFLITE`, `ArenaRole.SCRATCH`). The earlier lowercase names remain
  aliases of the same members, and configuration values are unchanged.
  `member.name` now returns the uppercase name (`ModuleType.cmake.name` is
  `"CMAKE"`), so code that compares `.name` with a lowercase string compares
  `.value` instead.
- **0.32.0.** `Registry.unfreeze` and `RegistryContext.unfreeze` are removed.
- **0.32.0.** `MemoryType.readable` and `MemoryType.executable` are removed,
  and `MemoryType.writeable` and the other compiler helpers on `MemoryType`,
  `ConvertArgs`, `ModuleArgs` and `AirTensor` are private. An `AirTensor`
  constructor keyword or assignment that uses a former field name is an error
  naming the new spelling.
- **0.32.0.** `ConversionResult` compares and hashes by identity.
- **0.32.0.** The command line logs at the level `verbose` names: 0 errors
  only, 1 stage milestones, 2 debug details, 3 debug details with each AIR
  operator's tensors and options. Level 0 shows no warnings, configuration
  warnings included; from level 1 each reaches standard error and `--log-file`
  once. `AotConverter.convert()` leaves the `helia_aot` logger as the caller
  configured it.
- **0.32.0.** `platform.speeds` is deprecated; no conversion reads it, and a
  non-empty value is accepted with a warning until 2.0 removes it.
- **0.32.0.** On the experimental Python custom-operator path, a `type: "*"`
  operator rule is checked against the built-in operators' attributes even
  when a custom operator with free-form (`extra="allow"`) attributes is
  registered. A key no operator declares, or a removed key such as
  `scratch_placement` in a rule for a free-form custom operator, is a
  configuration error. Put free-form keys on a rule for the custom operator's
  type.
- **0.32.0.** A former key of a renamed setting is accepted, with one warning
  per conversion, in transform options, planner options, operator attributes
  and tensor attributes as well as the configuration blocks.
- **0.32.0.** Generated modules require ns-cmsis-nn 7.47.0 or newer (was
  7.46.0).
- **0.32.0.** Generated modules no longer contain the empty
  `<prefix>_compiler.h`; include `<prefix>_model.h`.
- **0.32.0.** The file-name promise covers only the names `module-layout.json`
  marks public (see [What a release promises](https://ambiqai.github.io/helia-aot/reference/compatibility/#what-a-release-promises)); operator, helper and CMSIS-Pack file
  names can change.
- **0.32.0.** `<prefix>_context_init` is advanced instead of public:
  `<prefix>_model_init` already calls it, so an application that only runs a
  model does not call it.
- **0.32.0.** The generated C API is version 1.1.0. The operator callback's
  `op` argument is the operator's layer index, its 0-based position in
  execution order, not its AIR node id. The two differ when a transform
  removed or reordered operators or the parser skipped one;
  `ConversionResult.operators[op].source_indices` gives the model-file
  operator index. `<prefix>_status_t` reserves 1000 to 1999 for custom
  operators (`<prefix>_status_custom_first`, `<prefix>_status_custom_last`).
  Hold statuses in `int32_t`.
- **0.32.0.** Exit code 2 means rejected input wherever the check runs. These
  used to exit 1 and now exit 2: a missing, unreadable or corrupt model file;
  an existing output without `--force`; a missing golden archive;
  `test.state_feedback` and `test.tolerance` values the model rejects; an
  out-of-range `model.subgraph`; an unsupported model extension; an invalid
  `SOURCE_DATE_EPOCH`; `memory.tensors` rules that give one constant memory
  two source memories; a float16 model on a target without FP16 support; an
  Ethos-U model on a target without an NPU. A script that treated 1 as bad
  input checks for 2.
- **0.32.0.** A rule that names an `id` outranks a rule that names only a
  `type`, for `operators[]` and `memory.tensors`. Where a type rule used to
  override an id rule, the id rule now wins; a rule that others override on
  everything it matches logs a warning.
- **0.32.0.** `memory.tensors[].attributes.memory` is unset by default. A rule
  that sets only `constant_destination_memory` stages its constants from a
  source the planner chooses, read-only memory first (MRAM on Apollo510),
  instead of taking DTCM as the source. Set `memory: DTCM` in the rule to keep
  the previous placement.
- **0.32.0.** Operator ids must be strings holding a decimal integer from 0 to
  2147483647, unique in the model; `OperatorIdError` names any other. Only a
  custom parser or transform can produce another.
- **0.32.0.** Resource variables start at the constant their `CALL_ONCE`
  initialization assigns, as they do in LiteRT. They used to start at zero or
  at their zero point, which gave wrong output for stateful models whose
  variables have nonzero initial values, such as NNSE and NNVAD. Models whose
  variables start at zero are unchanged. Two handles that name one variable
  now share it.
- **0.32.0.** Conversion rejects, with `UnsupportedModelError`, models that
  earlier releases converted silently. These models are invalid: LiteRT
  refuses them, or for an element-count mismatch gives an undefined result:
  - a plain `CALL` node, which LiteRT has no kernel for;
  - a `CALL_ONCE` node that names no other subgraph of the model;
  - a `CALL_ONCE` initial value whose type differs from the variable's read
    (LiteRT refuses the read), or whose element count differs from it (LiteRT
    copies the read's byte count, past the variable's end or short of it);
  - a fused activation on `CONCATENATION`, which LiteRT's builtin kernel
    rejects;
  - int8 `CONCATENATION`, or int8 and int16 `PACK`, `UNPACK`,
    `SPACE_TO_BATCH_ND`, `BATCH_TO_SPACE_ND`, `REDUCE_MAX`, `REDUCE_MIN`,
    `EXPAND_DIMS` or `FILL`, whose inputs and outputs differ in scale or zero
    point.

  LiteRT runs these, but heliaAOT does not support them:
  - a `CALL_ONCE` node after a `READ_VARIABLE` or `ASSIGN_VARIABLE`;
  - a `CALL_ONCE` initialization subgraph that runs anything but `VAR_HANDLE`
    and the assignment of a constant, such as a nested `CALL_ONCE` or an
    initializer that computes its value.
- **0.32.0.** A module converted for a target without MVE stops a build with
  `ARM_MATH_MVEI` (`#error` in `<prefix>_common.h`) when one of its layers
  would be planned differently with MVE: another kernel entry, other scratch or
  weight sums. Examples are int8 convolution, fully connected, batch matmul
  and average pooling, and int8 3x3 depthwise. Layers planned the same either
  way, such as int8 max pooling or int16 fully connected, still build for
  both. `HELIA_AOT_SKIP_TARGET_CHECK` lifts the check.
- **0.32.0.** The optimization report counts MACs for `TRANSPOSE_CONV` and
  `BATCH_MATMUL` too. A `TRANSPOSE_CONV` rewritten to `CONV_2D` is counted at
  its input positions, no longer on its upscaled input, which counted four
  times too many at stride 2.
