Skip to content

Register the ESP-IDF component's kernels from a project kernel bundle - #24706

Merged
rascani merged 1 commit into
esp-idf-gen-kernelsfrom
esp-idf-kernel-bundles
Nov 9, 2026
Merged

rascani merged 1 commit into
esp-idf-gen-kernelsfrom
esp-idf-kernel-bundles

Conversation

@rascani

@rascani rascani commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

The generated component no longer bakes in op profiles. It vendors every kernel
source of the portable, quantized and Cadence generic kernel libraries into a
plain static library, ships gen_kernels.py with the libraries' yamls and
ExecuTorch's codegen templates under tools/, and registers the kernels of the
project's kernel bundle. A project declares its models and bundle with
executorch_add_model and executorch_add_kernels from project_include.cmake. At
the end of the project's configure, a deferred step checks that the bundle was
made for this component, by version and commit, and for exactly the declared
models (by SHA-256), rejects delegates the component does not provide yet, and
compiles the bundle's registration and prim op selection into the component. A
mismatch fails at configure time and prints the gen_kernels.py command that
fixes it. A bundle used without declared models is used as is, with a warning
that names the models it was made from.

Registration no longer needs WHOLE_ARCHIVE. Each registration file is compiled
through a small wrapper that defines an extern "C" anchor, and the deferred
step adds -u for each one, which reaches the link from a deferred call. Only
the bundle's files are anchored, so a project that declares no models and no
bundle links nothing from the component: CI checks that such an app is the same
size with and without it (169,728 B on ESP32-S3). The kernel library is not
even compiled without a bundle. et_model_create references a symbol that only
the bundle's registration defines, so an app that runs a model without
declaring one fails to link on et_requires_executorch_add_kernels instead of
failing every load with missing operators. CONFIG_ET_ENABLE goes away with the
profile choice. The deferred step reads the bundle's kernel count from its
selected_max_kernel_num.h, which it requires, and sizes the registry with it
unless CONFIG_ET_MAX_KERNEL_NUM, now relative to the bundle, sets a larger size
for an application that registers its own kernels; configuring one smaller
than the bundle is an error.

The smoke image on ESP32-S3 is 253,668 B, down from 266,660 B with the smoke
profile, because the bundle selects only the prim ops the model uses (none
here); with all 28 prims selected it is 266,684 B. CI runs that image on the
emulator with the registry sized from the bundle, and also builds the smoke
consumer for ESP32-C6 (setup.sh gains --targets) with a bundle that adds the
ops of the Cadence-quantized wake word model, so the Cadence generic kernels
stay compiled and linked. It checks that a re-exported model, a model the bundle
was not made from, a bundle made for another component version or commit or
missing its kernel count, and models without a bundle each fail at configure
time, that a bundle used as is names its models, and that an app running an
undeclared model fails to link.

Projects that use an op profile, such as CONFIG_ET_OP_PROFILE_WAKEWORD_INT8,
drop CONFIG_ET_ENABLE and the profile and move to a kernel bundle made from
their model.

Authored with Claude Code.

@pytorch-bot

pytorch-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24706

Note: Links to docs will display an error until the docs builds have been completed.

⏳ No Failures, 1 Pending

As of commit 9b35ce7 with merge base 4411b8d (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@rascani
rascani added this pull request to stack #24713 November 5, 2026 23:48
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

@rascani
rascani force-pushed the esp-idf-kernel-bundles branch from 3f4257a to 10f0224 Compare November 6, 2026 19:19
@rascani
rascani force-pushed the esp-idf-kernel-bundles branch from 10f0224 to dbc9a83 Compare November 6, 2026 20:40
@rascani
rascani requested a review from JakeStevens November 6, 2026 20:41
@rascani
rascani marked this pull request as ready for review November 6, 2026 20:41
@rascani
rascani requested a review from larryliu0820 as a code owner November 6, 2026 20:41
@rascani
rascani force-pushed the esp-idf-kernel-bundles branch from dbc9a83 to a6f5115 Compare November 6, 2026 21:15

@JakeStevens JakeStevens left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. A few non-blocking nits, fine to address here or in a follow-up:

  • project_include.cmake:358-362: CMAKE_CONFIGURE_DEPENDS lists kernels.json and the models but not ${_dir}/VERSION. If the component is regenerated in place at the same version from a different commit, the generated CMake files come out byte-identical, so an incremental idf.py build doesn't reconfigure and the commit check never runs. That is exactly the case the check was written for. Adding "${_dir}/VERSION" there fixes it. The commit-mismatch CI case only covers a fresh reconfigure, so it can't catch this.
  • test_espressif_idf_component.sh:213-226: the only emulator run uses large_registry.defaults (CONFIG_ET_MAX_KERNEL_NUM=64). The default path, where the registry is sized exactly from selected_max_kernel_num.h, is built (C6) but never executed. A short count would fail registration at boot while CI stays green. Consider running the emulator on the default-sized build.
  • project_include.cmake:13,50-52: executorch_add_backend() is documented as "force-link a delegate", but every call reaches a FATAL_ERROR (lines 77-80 and 259-264). Drop it until a delegate exists, or mark it as reserved.
  • Once a bundle exists, all ~250 portable, quantized and Cadence kernel sources compile, even for a model with a handful of ops. --gc-sections takes care of flash, but clean builds get slower for users and in CI. It may be worth a line in the README.
  • In the size loop, the second consumer ... size call overwrites firmware-${name}.log (the redirect in consumer uses >), so the empty/empty-baseline build logs are lost from the artifacts.
  • The version, commit and stale "Regenerate it" messages print a bare python .../gen_kernels.py. In an IDF shell that's IDF's venv, which has no executorch, so a copy-paste gives a ModuleNotFoundError. The no-bundle message already says which environment to use; the others could say the same.

(Review assisted by Claude Code.)

@rascani

rascani commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

LGTM. A few non-blocking nits, fine to address here or in a follow-up:

  • project_include.cmake:358-362: CMAKE_CONFIGURE_DEPENDS lists kernels.json and the models but not ${_dir}/VERSION. If the component is regenerated in place at the same version from a different commit, the generated CMake files come out byte-identical, so an incremental idf.py build doesn't reconfigure and the commit check never runs. That is exactly the case the check was written for. Adding "${_dir}/VERSION" there fixes it. The commit-mismatch CI case only covers a fresh reconfigure, so it can't catch this.
  • test_espressif_idf_component.sh:213-226: the only emulator run uses large_registry.defaults (CONFIG_ET_MAX_KERNEL_NUM=64). The default path, where the registry is sized exactly from selected_max_kernel_num.h, is built (C6) but never executed. A short count would fail registration at boot while CI stays green. Consider running the emulator on the default-sized build.
  • project_include.cmake:13,50-52: executorch_add_backend() is documented as "force-link a delegate", but every call reaches a FATAL_ERROR (lines 77-80 and 259-264). Drop it until a delegate exists, or mark it as reserved.
  • Once a bundle exists, all ~250 portable, quantized and Cadence kernel sources compile, even for a model with a handful of ops. --gc-sections takes care of flash, but clean builds get slower for users and in CI. It may be worth a line in the README.
  • In the size loop, the second consumer ... size call overwrites firmware-${name}.log (the redirect in consumer uses >), so the empty/empty-baseline build logs are lost from the artifacts.
  • The version, commit and stale "Regenerate it" messages print a bare python .../gen_kernels.py. In an IDF shell that's IDF's venv, which has no executorch, so a copy-paste gives a ModuleNotFoundError. The no-bundle message already says which environment to use; the others could say the same.

(Review assisted by Claude Code.)

Thanks, Jake. All six are addressed in the latest push:

  1. VERSION in CMAKE_CONFIGURE_DEPENDS: added. One nuance: regenerating the component in place already reconfigured. build_idf_component.sh deletes the tree and re-copies every file, and Ninja reruns CMake when a file is newer, not when its content changes. The real gap was a change to VERSION alone. I checked it by hand: without the dependency, an incremental idf.py build passes silently; with it, CMake reruns and fails on component_commit.
  2. Default-sized registry on the emulator: the ESP32-S3 smoke image now runs with the registry sized from the bundle (30 slots). The 64-slot config moved to the C6 image, which is only built. From Provide the ESP-NN delegate and esp_nn:: op library in the ESP-IDF component #24708 on, cpu-esp32c6, delegate-esp32s3, backend-esp32s3 and example also run bundle-sized.
  3. executorch_add_backend(): moved to Provide the ESP-NN delegate and esp_nn:: op library in the ESP-IDF component #24708 along with EXECUTORCH_BACKENDS, since that's where the ESP-NN delegate gives it something to link. This PR keeps the error for a delegate listed in a bundle's kernels.json.
  4. Compile cost: the README now notes that a bundle compiles every vendored kernel source but only the registered ones reach the image, so the cost is clean-build time, not flash.
  5. Size logs: the size step writes its own size-.log, so the empty/empty-baseline build logs survive.
  6. Regenerate messages: every "Regenerate it" error now reads "Regenerate it with the Python environment the models were exported with:", matching the no-bundle message.

(Drafted with Claude Code.)

The generated component no longer bakes in op profiles. It vendors every kernel
source of the portable, quantized and Cadence generic kernel libraries into a
plain static library, ships gen_kernels.py with the libraries' yamls and
ExecuTorch's codegen templates under tools/, and registers the kernels of the
project's kernel bundle. A project declares its models and bundle with
executorch_add_model and executorch_add_kernels from project_include.cmake. At
the end of the project's configure, a deferred step checks that the bundle was
made for this component, by version and commit, and for exactly the declared
models (by SHA-256), rejects delegates the component does not provide yet, and
compiles the bundle's registration and prim op selection into the component. A
mismatch fails at configure time and prints the gen_kernels.py command that
fixes it. A bundle used without declared models is used as is, with a warning
that names the models it was made from.

Registration no longer needs WHOLE_ARCHIVE. Each registration file is compiled
through a small wrapper that defines an extern "C" anchor, and the deferred
step adds -u for each one, which reaches the link from a deferred call. Only
the bundle's files are anchored, so a project that declares no models and no
bundle links nothing from the component: CI checks that such an app is the same
size with and without it (169,728 B on ESP32-S3). The kernel library is not
even compiled without a bundle. et_model_create references a symbol that only
the bundle's registration defines, so an app that runs a model without
declaring one fails to link on et_requires_executorch_add_kernels instead of
failing every load with missing operators. CONFIG_ET_ENABLE goes away with the
profile choice. The deferred step reads the bundle's kernel count from its
selected_max_kernel_num.h, which it requires, and sizes the registry with it
unless CONFIG_ET_MAX_KERNEL_NUM, now relative to the bundle, sets a larger size
for an application that registers its own kernels; configuring one smaller
than the bundle is an error.

The smoke image on ESP32-S3 is 253,668 B, down from 266,660 B with the smoke
profile, because the bundle selects only the prim ops the model uses (none
here); with all 28 prims selected it is 266,684 B. CI runs that image on the
emulator with the registry sized from the bundle, and also builds the smoke
consumer for ESP32-C6 (setup.sh gains --targets) with a bundle that adds the
ops of the Cadence-quantized wake word model, so the Cadence generic kernels
stay compiled and linked. It checks that a re-exported model, a model the bundle
was not made from, a bundle made for another component version or commit or
missing its kernel count, and models without a bundle each fail at configure
time, that a bundle used as is names its models, and that an app running an
undeclared model fails to link.

Projects that use an op profile, such as CONFIG_ET_OP_PROFILE_WAKEWORD_INT8,
drop CONFIG_ET_ENABLE and the profile and move to a kernel bundle made from
their model.

Authored with Claude Code.
@rascani
rascani force-pushed the esp-idf-kernel-bundles branch from c3b51ab to 9b35ce7 Compare November 9, 2026 18:37
@rascani
rascani merged commit e721ff8 into main Nov 9, 2026
402 of 403 checks passed
@rascani
rascani deleted the esp-idf-kernel-bundles branch November 9, 2026 21:06

This branch was previously deployed

1 inactive deployment
cadence — 9b35ce77 Deployed Nov 9, 2026 by rascani via hifi-op-test / hifi4 #37406
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants