镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

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

Open
rascani wants to merge 1 commit into
esp-idf-gen-kernelsfrom
esp-idf-kernel-bundles
Open

rascani wants to merge 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 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.

❌ 2 New Failures, 1 Unrelated Failure, 1 Unclassified Failure

As of commit af61b8d with merge base 60a8b5f (image):

NEW FAILURES - The following jobs have failed:

UNCLASSIFIED FAILURE - DrCI could not classify the following job because the workflow did not run on the merge base. The failure may be pre-existing on trunk or introduced by this PR:

BROKEN TRUNK - The following job failed but were present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

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

Copy link
Copy Markdown
Contributor

@claude review effort=max

@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @JakeStevens's task in 48m 10s —— View job


Claude finished reviewing PR #24706

This PR replaces the ESP-IDF component's built-in op profiles with per-project kernel bundles. I recommend Request Changes for two main reasons: a project without a bundle now builds cleanly but registers no kernels (this includes any project still set up for the removed CONFIG_ET_OP_PROFILE_* profiles, whose component version stays 1.6.0~3), and the check that the exporter's ExecuTorch commit matches the runtime's was deleted with nothing to replace it.

Detailed review: 2 major and 8 minor findings, plus 13 line-level comments

PR Review: #24706

Summary

The PR replaces the component's built-in op profiles (CONFIG_ET_ENABLE / CONFIG_ET_OP_PROFILE_*) with a project API (executorch_add_model / executorch_add_kernels). That API compiles the kernel registration from a bundle made by tools/gen_kernels.py. The component now vendors all portable, quantized and Cadence generic kernels, and CI gains an ESP32-C6 build. There are two major problems:

  • a project without a bundle registers no kernels, and nothing tells it;
  • the check that the exporter and the runtime come from the same commit was removed.

The rest are minor.

All 15 changed files were reviewed. Nothing was built or run, because this job has no ESP-IDF toolchain, so the CMake and link behavior described here comes from reading the code. Paths are under examples/espressif/. A bare project_include.cmake, CMakeLists.txt, README.md or tools/gen_kernels.py means the file in idf_component_template/.

Backward Compatibility

  • major: project_include.cmake:68-74, CMakeLists.txt:16-31. A project that declares nothing still builds and links, and registers no kernels.

    • With the CONFIG_ET_ENABLE gate gone, et_model.h is always exported and the runtime is always compiled.
    • When nothing is declared, __executorch_finalize prints a STATUS line and returns. Nothing is registered, not even prim ops.
    • The registry falls back to operator_registry.cpp's 2000 slots, which I estimate at about 24 KB of .bss.
    • Every model then fails at method load with OperatorMissing.

    Projects migrating from the profile component hit exactly this. Their CONFIG_ET_ENABLE=y and CONFIG_ET_OP_PROFILE_* settings are now unknown symbols, which I believe kconfgen only warns about. The "Updating" section (README.md:89-94) has no migration note. README.md:53-55 says a project that declares nothing "links nothing from this component", which holds only if the app never calls et_model.

    Before this PR, using the component without enabling it was a compile error. It could become a link error. For example, et_model.cpp could reference a clearly named extern "C" symbol that only finalize's register_prim_ops.cpp wrapper (project_include.cmake:271-277) defines. Apps that never call the runtime would still link unchanged, so the empty-app size check (.ci/scripts/test_espressif_idf_component.sh:139-161) would still pass.

    Fix this →

  • minor: build_idf_component.sh:42. PACKAGING_REV is still 3, though its own comment says "bump when the packaging (not ExecuTorch) changes". As a result:

    • The profile component on main and this bundle component are both 1.6.0~3, so a project that pins or caches that version can't tell which one it has.
    • If 1.6.0~3 was published, the registry would not accept this packaging under the same version.
    • Users upgrading from the profile component get no version change to warn them about the previous finding.

    Fix this →

API Design

  • minor: project_include.cmake:13,51-53,66-79. executorch_add_backend() is documented project API, but every call ends in FATAL_ERROR ("delegate … is not provided by this component", at :75-78 or :228-233). Nothing calls it and the README doesn't mention it. Remove it, along with the EXECUTORCH_BACKENDS property, until the component ships a delegate. Keep the check on the bundle's own backends (:220-233). Fix this →

Testing

  • minor: .ci/scripts/test_espressif_idf_component.sh. Several new configure-time branches are untested, and no emulator run uses the default registry size.

    • No test covers any of these:
      • a declared model missing from the bundle;
      • a bundle model that isn't declared;
      • a bundle with backends;
      • "as is" mode (executorch_add_kernels without executorch_add_model).
    • expect_configure_error no-bundle gen_kernels.py (:129) matches text that the version, commit, stale and incomplete errors also print. Match "no kernel bundle is" instead.
    • The only emulator run (:175-177) uses large_registry.defaults, which sets CONFIG_ET_MAX_KERNEL_NUM=64. The C6 image uses the bundle-sized default, but CI only builds it. If a bundle's count were too small to register its kernels at boot, CI would still pass.

    Fix this →

Performance

  • minor: build_idf_component.sh:213-215, CMakeLists.txt:33-38. Every project with a bundle compiles all ~250 kernel sources (portable, quantized and Cadence generic), even when the bundle selects only a few portable ops. The old flow compiled only each profile's mapped sources. The link still keeps only what the registration references, so the cost is clean-build time, not flash. Either state that cost in the PR description, or compile only what the selected kernels need. That could be the old per-op mapping, or at least one EXCLUDE_FROM_ALL library per kernel library, with finalize linking only those in the bundle's libraries.

Code Quality

  • major: The check that the exporter and the runtime come from the same commit is gone. This PR deletes a warning in build_idf_component.sh that compared the Python package's git_version with the tree's HEAD. Its comment explained why: "The exporter and the runtime must be the same ExecuTorch, or models fail at Method::execute with errors that do not say why." Nothing replaces it:

    • versions_match in tools/gen_kernels.py compares commits only for -dev./-rc. versions, but build_idf_component.sh:85-90 only ever produces X.Y.Z~N.
    • Finalize (project_include.cmake:142-163) compares only component_commit, which gen_kernels.py copies from the component's own VERSION.
    • executorch_git_sha is recorded in the bundle but never compared.

    So an editable install at commit A (1.6.0+A) can make a bundle for a component generated at commit B, and nothing warns. At least restore the warning, in gen_kernels.py or in finalize. Fix this →

  • minor: project_include.cmake:216-218, :82-98, :122-132. "As is" mode trusts bundles made from models without checking them. With no executorch_add_model, finalize skips the model check even when kernels.json lists models. A re-exported, added or removed model then goes unnoticed, which contradicts README.md:47-50.

    The regenerate command is also rebuilt only from the declared models and the bundle's ops. For such a bundle, a version or commit mismatch prints one of two wrong commands:

    • gen_kernels.py --out <bundle>, which gen_kernels.py rejects with "nothing to select";
    • if the bundle also had --ops, a command that silently leaves out the model's ops. The bundle it makes is then accepted as is, and the firmware fails on the device.

    When the bundle records models but none are declared, warn and name them, and keep them in the printed command. Fix this →

  • minor: project_include.cmake:293-309, CMakeLists.txt:74-79. The default registry size depends on __has_include, which fails silently. Finalize reads selected_max_kernel_num.h only when CONFIG_ET_MAX_KERNEL_NUM is set, and the completeness check (:247) looks only at the registration .cpp files. A bundle can lose the header, for example when its include/ directory is dropped as it is checked in. With the default config it then gets the 2000-slot fallback and no message; with the option set, it gets a raw file(STRINGS) error.

    Read the header in every case, fail clearly if it is missing, and pass MAX_KERNEL_NUM explicitly. That also lets the status line show the registry size. Fix this →

  • minor: project_include.cmake:295-308, which uses the count from tools/gen_kernels.py:299-303. The bundle's kernel count includes every prim op, selected or not. gen_max_kernel_num counts each Kernel( in the prim_ops[] array of register_prim_ops.cpp and ignores the selective-build guards, so the count can be up to 28 too high. The CONFIG_ET_MAX_KERNEL_NUM floor check therefore rejects sizes that would fit, and the default registry is larger than it needs to be.

  • minor: build_idf_component.sh:225-226. The widened -not -path '*/test*/*' pattern is matched against absolute $ET_ROOT/… paths. A checkout under any directory whose name starts with test, such as /home/tester/executorch, copies no headers. The script then stops with "generated component is missing include/executorch/runtime/executor/program.h". The old */test/* pattern had this flaw only for a directory named exactly test. The kernel-source find at :213-214 avoids the problem by running from $ET_ROOT. Fix this →

Specific Comments

  • project_include.cmake:17-28: the arguments are parsed by hand, so executorch_add_model(PTE PTE) is accepted and declares nothing. cmake_parse_arguments(_arg "" "" "PTE" ${ARGN}) would fix this and match executorch_add_kernels.
  • project_include.cmake:36-44: calling executorch_add_kernels a second time with the same DIR (for example, once from main/ and once from the project) is a fatal error. Fail only when the directories differ.
  • project_include.cmake:171-205:
    • The model names are stored in dynamic variables (_name_${_sha}). A list of names kept alongside _recorded would be explicit.
    • The name match uses the declared path's basename, but gen_kernels.py:203-205 records the name after resolving symlinks. If a model reached through a symlink is re-exported, it is reported as "not in the bundle" plus "in the bundle, not declared", not as "changed since the bundle was made".
  • project_include.cmake:133: there is no ERROR_VARIABLE, so a bundle without component_version gets a raw string(JSON) error instead of the regenerate hint. Also, _missing at :147 is never read.
  • project_include.cmake:122,167,220,239,299: _count is reused for five different things. _ops is a list at :125-131 and then a count at :267.
  • project_include.cmake:303-307: the message doesn't say what to set (at least N, or 0 to size the registry from the bundle). Low confidence: because this error fires at configure time, idf.py menuconfig may not open to fix it, so the message should mention sdkconfig.
  • build_idf_component.sh:410-414, README.md:77-79: both say that 0 means "the kernel bundle's kernel count". With no bundle, 0 means the 2000-slot fallback.
  • build_idf_component.sh:293 and :351: the prim-ops source path is written in two places. It is hard-coded in kernel_libraries.json, which gen_kernels.py reads, and CMake gets it from prim_src.txt. Write the JSON field from prim_src.txt.
  • README.md:29: "after every export or ExecuTorch upgrade" leaves out packaging-only component updates. These also invalidate the bundle, because component_version includes ~N.
  • examples/espressif/README.md:36-38: this still describes only the S3 emulator run, but CI now also builds an ESP32-C6 image with the Cadence kernels. The new setup.sh --targets option isn't documented at :233 or :252.
  • .ci/scripts/test_espressif_idf_component.sh:68-74: consumer() writes idf.py output only to firmware-<name>.log, so a failing build ends the job with nothing in the console. The log does go into the artifact, but the old script tee'd its builds. The size call at :150 also overwrites that build's log. Print the end of the log on failure.
  • .ci/scripts/test_espressif_idf_component.sh:113-114: the comment says "as two development builds on one day are", but versions carry no date. Any two commits between version.txt bumps share a version.
  • .ci/scripts/test_espressif_idf_component.sh:52-60: the wakeword --ops list doesn't say where it came from (the deleted idf_profiles/wakeword_int8.ops) or how to update it.

Recommendation

Request Changes: both major findings turn a configuration mistake into a failure on the device with no clear cause, and both have small fixes. The minor findings can be fixed here or in follow-ups.


  • Read the PR context and the /pr-review skill
  • Get the diff against origin/esp-idf-gen-kernels (1 commit, 15 files, +724/−633)
  • Run three independent reviewers (between them they read all 15 changed files)
  • Merge and deduplicate their findings (about 35 candidates, mostly duplicates)
  • Re-check every finding against the code (several dropped or reworded)
  • Post the final review

This was a review only. No code was changed, and nothing was built or run, because this job has no ESP-IDF toolchain.

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

@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.)

This branch was previously deployed

1 inactive deployment
cadence — af61b8df Deployed Nov 7, 2026 by rascani via hifi5s-op-test / hifi5s #37201
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