Static schedule
Convert one model under both dispatch modes and diff the generated
aot_model.c. The table mode walks a function-pointer table; the static mode
emits the operator sequence as direct calls.
| Field | Value |
|---|---|
| Model | kws_ref, MLPerf Tiny keyword spotting, int8 |
| Target | apollo510_evb |
| Shows | module.schedule, generated dispatch code and callback availability |
| CI | Conversion and host compilation are declared in the examples CI job |
The model is the committed ../first-model/kws_ref/model.tflite,
MLPerf Tiny keyword spotting: int8, 53,936 bytes, SHA-256 aeea4368…bd0ae,
Apache-2.0. Its README records where it comes from.
Prerequisites
Section titled “Prerequisites”Use a repository checkout with Bash and the environment from Running examples: uv sync --frozen --group ci, then activate .venv. Run the commands below from this example directory. Conversion runs on the host; it does not execute the emitted firmware.
Run it
Section titled “Run it”./run.shdiff out/kws_ref_table/src/aot_model.c out/kws_ref_static/src/aot_model.cA second run stops with Output path ... already exists, because the first
run’s module is still in out/. Run ./run.sh --force to replace it.
config-table.yaml and config-static.yaml change the execution schedule and use different module names to retain both outputs. The behavior-affecting choice is:
module: schedule: static # or table, the defaultWhat to look at
Section titled “What to look at”The table build declares one rodata entry per operator and a loop over it:
typedef int32_t (*aot_op_fn)(aot_model_context_t *ctx);
typedef struct { aot_op_fn init; // Per-operator init hook aot_op_fn run; // Per-operator run hook (resolves its own I/O)} aot_op_entry_t;
static const aot_op_entry_t aot_op_table[13] = { { aot_conv_2d_0_init, aot_conv_2d_0_run }, { aot_depthwise_conv_2d_1_init, aot_depthwise_conv_2d_1_run }, ...};The static build has no table and no loop. Every call target is visible to the linker and the optimizer, which is what makes inlining and tail calls available across the sequence.
The trade is the per-node callback seam. The table loop is where an RTOS can
yield between operators and where per-layer counters are sampled, through
ctx->callback, which receives each operator’s position in the table, its
layer index. The static build omits it. Both modes emit the same public
API, the same status codes, the same lifecycle latch and the same constant
hydration, so the choice is dispatch only and can be made per build.
Source line counts do not measure firmware size or inference time. The size and cycle effect belongs to a real build and a real board: measure it with Profile with hpx rather than inferring it from the source.
- Configuring a conversion for the rest of the module settings.