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

Test the ESP-IDF component through the component manager on every emulated target - #24709

Open
rascani wants to merge 1 commit into
esp-idf-espnnfrom
esp-idf-consumer-ci
Open

rascani wants to merge 1 commit into
esp-idf-espnnfrom
esp-idf-consumer-ci

Conversation

@rascani

@rascani rascani commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

A new CI job builds the component the way a project using the published
component gets it. It generates and packs the component, adds the archive to a
file:// component mirror (.ci/scripts/esp_idf_mirror.py writes the layout the
registry serves, and refuses conditional dependencies, which it cannot
evaluate the way the registry does), and builds idf_component_consumer, which
resolves pytorch/executorch from that mirror at the version just generated and
esp-nn from the registry. The builds use a fresh ESP-IDF Python environment
that the job checks cannot import executorch, with EXECUTORCH_PYTHON unset, so
they take the kernel bundles made beforehand. They build a copy of the project,
which keeps the component manager's state out of the source tree, and the
source, model, kernel bundle and build paths have a space. On ESP-IDF 6.1 the
job builds for ESP32 (run on ESP-IDF's QEMU), ESP32-S3, -C3, -C5, -C6 and -P4
(run on esp-emu); on 6.0.1, a second job, for ESP32-S3 and -C6. It also lints
the component's manifest, and checks that the lock file records the mirror's
archive.

Every build runs the smoke model. The ESP32-S3 and -C6 builds also run the
MLPerf Tiny streaming wake word model, quantized with the Cadence quantizer
and exported by the new export_wakeword_model.py as a bundled program of 40
consecutive frames, all 40 of which must match the quantized PyTorch model,
and the quantized mul on the esp_nn:: op library; the ESP32-S3 build also runs
the ESP-NN delegate program, so the delegate and op library are built from the
packed archive against the esp-nn the component manager resolves. To run a
stateful model, the smoke app now runs all of a bundled program's test cases
in order on one loaded method, and split_bundled_program.py can repeat them so
single-case models still check a second execute. idf_component_consumer reuses
the smoke app's main component, so the two cannot drift apart.

setup.sh gains --idf-version and, unless --skip-emulator, installs QEMU when
esp32 is a target. The workflow now also runs for changes to what the component
vendors (runtime, the portable, quantized and prim op kernels, schema, the
buffer data loader and flat_tensor schema, codegen, the Cadence AOT and generic
kernels, the flatbuffers submodule and build_variables.bzl) and to version.txt.

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/24709

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

❌ 4 New Failures, 1 Unrelated Failure

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

NEW FAILURES - The following jobs have failed:

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-consumer-ci branch from 9ca8878 to 1dc802f Compare November 6, 2026 19:19
@rascani
rascani force-pushed the esp-idf-consumer-ci branch from 1dc802f to c9cb5e4 Compare November 6, 2026 20:40
@rascani
rascani requested a review from JakeStevens November 6, 2026 20:47
@rascani
rascani marked this pull request as ready for review November 6, 2026 20:47
@rascani
rascani requested a review from larryliu0820 as a code owner November 6, 2026 20:47
@rascani
rascani force-pushed the esp-idf-consumer-ci branch from c9cb5e4 to bd1e01c Compare November 6, 2026 21:15
@rascani

rascani commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

@claude review effort=max

@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @rascani's task in 53m 27s —— View job


PR Review: #24709

Request Changes: the new paths: filter starts all five ESP32 jobs on many PRs that touch nothing the component uses, yet it leaves out version.txt and third-party/flatbuffers; separately, setup.sh --targets=esp32 now installs QEMU even with --skip-emulator. The other findings are minor.

Detailed review

Summary

This PR packs the generated ESP-IDF component and serves it from a local file:// component mirror. It then builds idf_component_consumer against that mirror with ESP-IDF's own Python, from paths that contain spaces:

  • ESP-IDF 6.1: every target an emulator runs.
  • ESP-IDF 6.0.1: ESP32-S3 and ESP32-C6.

It also adds a Cadence-quantized streaming wake word model, checked frame by frame on S3 and C6. Two findings block approval: the workflow paths: filter (Infrastructure) and the setup.sh QEMU install (Backward Compatibility). The rest are minor.

Code Quality

  • Minor. examples/espressif/idf_component_consumer/CMakeLists.txt:14-22: configuring the project writes main/idf_component.yml into the source tree, which is why .gitignore needs a new entry.

    • The project also fails without the CI-only -DET_COMPONENT_VERSION, whose name is the same as the component's own ET_COMPONENT_VERSION (set in et_generated.cmake).
    • CI already builds from a copy (.ci/scripts/test_espressif_idf_consumer.sh:93-101). The script could write the pinned manifest into that copy and keep a static main/idf_component.yml in the tree.
    • Low confidence: I believe the component manager expands $VAR/${VAR} environment variables in manifest values. If that covers versions, the comment at :17-18 is wrong.
  • Minor. idf_component_consumer/CMakeLists.txt:23-25 together with test_espressif_idf_consumer.sh:97-100: the consumer works only when it sits next to idf_component_smoke/, and CI copies only that project's main/ and sdkconfig.defaults. The plain set(SDKCONFIG_DEFAULTS ...) also shadows any -DSDKCONFIG_DEFAULTS. Two consequences:

    • ESP-IDF loads a future idf_component_smoke/sdkconfig.defaults.<target> automatically, but CI wouldn't copy it, so only CI would miss it.
    • The extra defaults files the sibling script passes (small_registry.defaults, nano.defaults, large_registry.defaults) can't be passed to the consumer.

    Copy the whole directory and use if(NOT DEFINED SDKCONFIG_DEFAULTS).

  • Minor. .ci/scripts/esp_idf_mirror.py:69-70: every dependency is written with "rules": [] and "matches": []. Suppose idf_component.yml.in later makes a dependency conditional, for example an if: target in [...] rule on espressif/esp-nn. The mirror would then serve it unconditionally, and the test would no longer match the registry that the docstring (:12-13) says it reproduces. Fail if a dependency declares rules or matches. Fix this →

  • Minor. .ci/scripts/test_espressif_idf_consumer.sh:118-149: run_firmware has two diagnostic gaps.

    • If idf.py qemu exits early, the poll loop (:126-131) still waits the full 120 s.
    • esp-emu output goes only to the log file (:140), while the sibling script tees it (test_espressif_idf_component.sh:223-227). A CI failure therefore shows only "see emulator-X.log".

    Fix this →

  • Minor. test_espressif_idf_consumer.sh:150-151: export -f consumer and export idf_python_env mirror project firmware_root version pass state through the environment of every later child process, only so that setsid bash -c (:124) can call consumer. Launching QEMU with setsid env IDF_TARGET=... IDF_PYTHON_ENV_PATH=... IDF_COMPONENT_STORAGE_URL=... <idf.py command> qemu & avoids this side channel.

  • Minor. examples/espressif/export_wakeword_model.py:40-43: reset_history zeroes every buffer whose name contains "history". The model defines StreamingWakeWord.HISTORY_NAMES for exactly this. Zero module.get_buffer(name) for each name instead; it raises if a name goes missing. Fix this →

  • Minor. test_espressif_idf_consumer.sh:70: the version is scraped from the generated YAML with sed. bash examples/espressif/build_idf_component.sh --version prints it directly. Fix this →

Infrastructure

  • Major. .github/workflows/test-espressif.yml:13-18: these pull_request.paths entries gate the whole workflow. Any match runs all five jobs on 8-core runners with 45-minute timeouts: two test-esp32s3, one test-idf-component and two test-idf-consumer.

    • The entries cover far more than the component vendors or the exporters import (line numbers are in examples/espressif/build_idf_component.sh):
      • extension/: only data_loader/buffer_data_loader.h and the flat_tensor schema are used (:161-174, 269-271).
      • kernels/: only portable, quantized and prim_ops (:214-216, 272-274).
      • backends/cadence/: only aot and generic (:216, 275, 299).
    • As written, a PR that touches only extension/llm, kernels/optimized or backends/cadence/hifi runs every ESP32 job.
    • The filter also leaves out two inputs the component does use:
      • version.txt, which sets the component version (:58, 86-88).
      • The third-party/flatbuffers submodule, which supplies the vendored headers and flatc (:93, 141-142, 282).
    • push to main has no filter, so narrowing the PR filter keeps coverage after merge.

    Fix this →

Testing

  • Minor. .ci/scripts/test_espressif_idf_consumer.sh:153-165: no consumer build declares a backend or uses esp_nn:: ops.

    • The ESP-NN delegate and op library from the base PR are EXCLUDE_FROM_ALL (idf_component_template/CMakeLists.txt:49-50) and are compiled only when a model needs them. As a result, they are never built from the packed archive.
    • Nothing that runs calls the esp-nn that the component manager resolves.
    • A pack-time exclusion of those sources, or an esp-nn release the delegate can't build against, would still pass this test.

    Add delegate.pte and mul.pte to at least one consumer build, the same way the sibling script's models list includes them. Fix this →

  • Minor. test_espressif_idf_consumer.sh:88-92: the test's main claim is that the build works with ESP-IDF's Python and no ExecuTorch, but nothing checks that idf_python_env can't import executorch. In addition, project_include.cmake:113-117 checks $ENV{EXECUTORCH_PYTHON} before IDF's Python, so a leaked variable would silently bring conda's Python back. Assert that the import fails, and unset EXECUTORCH_PYTHON in consumer(). Fix this →

  • Minor. test_espressif_idf_consumer.sh:93-102: only the project sources and build directories have a space in their path. The .pte/.io files and kernel bundles stay under build_dir, so three things never see a space: .incbin in the generated models.S, executorch_add_model, and executorch_add_kernels. Users keep those files in their project tree, so put them under a directory with a space as well.

  • Minor. test_espressif_idf_consumer.sh:64: --atol 1e-6 --rtol 0 on the softmax output holds only if every requantization in the Cadence kernels exactly matches the fake-quant reference (get_fake_quant_model, backends/cadence/aot/compiler.py:185-215) for all 40 frames, because the history carries any difference forward. The other tolerances in these scripts come with a rationale; this one has none. If exact agreement is the intent, say so in a comment. Otherwise, have the exporter record the tolerance, as export_espnn_test_programs.py does in manifest.json.

  • Question. examples/espressif/export_wakeword_model.py:77-80: the reference and the exported initial state both start from zero history. A regression in how the runtime loads a mutable buffer's initial state would therefore go unnoticed whenever the planned arena happens to start zeroed. Would a non-zero initial history, set before both the reference and the export, work here? With it, the test would catch that regression.

Backward Compatibility

  • Major. examples/espressif/setup.sh:91-93: whenever --targets includes esp32, setup.sh now installs ESP-IDF's QEMU. According to this PR's own comment (test_espressif_idf_consumer.sh:44-45), that install fails on hosts without libSDL2 and libslirp.

    • Before this PR, --targets=esp32 installed only the toolchain. Anyone who uses it on a headless host or in a container to build for real ESP32 hardware now gets a failing setup.
    • --skip-emulator doesn't avoid the failure, because the QEMU step is gated only by install_idf. README:268 says that flag lets you install only the tools you need.
    • Fix: gate the QEMU install on install_emulator as well, and update the help text (:31-32) and README:269-270.
    • Separately, with low confidence: this call passes no --targets. If idf_tools.py install defaults to all targets and records them in idf-env.json, a non-minimal source setup_path.sh (without IDF_SKIP_TOOLS_CHECK) may then require the RISC-V toolchain. Passing --targets "${targets}", as the other install calls do, rules that out.

    Fix this →

Specific Comments

  • .github/workflows/test-espressif.yml:1: the name "Test ESP32-S3 Emulator" is stale; the workflow now also runs ESP32, C3, C5, C6 and P4.
  • examples/espressif/README.md:40-41: the text says "for every target an emulator runs, on ESP-IDF 6.1 and 6.0.1", but 6.0.1 builds only ESP32-S3 and ESP32-C6.
  • .ci/scripts/test_espressif_idf_consumer.sh:181: "ran on every target" is not true on the 6.0.1 leg.
  • test_espressif_idf_consumer.sh:30: the full target list is keyed to the literal v6.1, so the next bump of the default release silently drops to two targets. Passing the targets in through the workflow matrix would avoid that.
  • test_espressif_idf_consumer.sh:44-52: running sudo apt-get update/install at test time adds a network step to the v6.1 leg; the two libraries could go into the CI image instead.
  • test_espressif_idf_consumer.sh:78: kernels() is the sibling's gen_kernels() helper under a different name.
  • test_espressif_idf_consumer.sh:159: the 40/40 marker relies on the --frames default of export_wakeword_model.py; pass --frames 40 at :60.
  • examples/espressif/idf_component_smoke/main/CMakeLists.txt:7: "Paths are relative to this file" is inaccurate; the paths are absolute, built from CMAKE_CURRENT_LIST_DIR.
  • examples/espressif/idf_component_smoke/main/main.c:116-121: the input count isn't checked against et_model_num_inputs, unlike the output count in check_outputs (:63). A case with too few inputs runs on whatever the previous case left behind.
  • examples/espressif/idf_component_smoke/split_bundled_program.py:47: the --repeat help doesn't say the option is valid only for stateless models (the wake word cases would fail on the second pass). The error at :55 also blames --repeat when the suite itself is empty.
  • examples/espressif/setup.sh:33-34: "v6.0.1 is also tested" is CI detail in user help. "Use one ESPRESSIF_TOOLS_DIR per release" works against the shared default at :45: with that default, installing a second release with --idf-version fails at :71-74. Consider a per-release default like the CI script's (test_espressif_idf_consumer.sh:25).
  • examples/espressif/setup.sh:31-32: low confidence: ESP-IDF's QEMU guide also lists libgcrypt20, libglib2.0-0 and libpixman-1-0 for Linux hosts.
  • .ci/scripts/test_espressif_idf_component.sh:78-88: the consumer test now exports the wake word model and runs it on ESP32-C6. Is the hand-written wakeword_ops list still needed?

Recommendation

Request Changes: fix the setup.sh QEMU gating, which breaks --targets=esp32 on hosts without the QEMU libraries, and the workflow paths: filter. The remaining items are minor. Of those, the no-executorch assertion is the cheapest to add and backs the test's main claim.

…lated target

A new CI job builds the component the way a project using the published
component gets it. It generates and packs the component, adds the archive to a
file:// component mirror (.ci/scripts/esp_idf_mirror.py writes the layout the
registry serves, and refuses conditional dependencies, which it cannot
evaluate the way the registry does), and builds idf_component_consumer, which
resolves pytorch/executorch from that mirror at the version just generated and
esp-nn from the registry. The builds use a fresh ESP-IDF Python environment
that the job checks cannot import executorch, with EXECUTORCH_PYTHON unset, so
they take the kernel bundles made beforehand. They build a copy of the project,
which keeps the component manager's state out of the source tree, and the
source, model, kernel bundle and build paths have a space. On ESP-IDF 6.1 the
job builds for ESP32 (run on ESP-IDF's QEMU), ESP32-S3, -C3, -C5, -C6 and -P4
(run on esp-emu); on 6.0.1, a second job, for ESP32-S3 and -C6. It also lints
the component's manifest, and checks that the lock file records the mirror's
archive.

Every build runs the smoke model. The ESP32-S3 and -C6 builds also run the
MLPerf Tiny streaming wake word model, quantized with the Cadence quantizer
and exported by the new export_wakeword_model.py as a bundled program of 40
consecutive frames, all 40 of which must match the quantized PyTorch model,
and the quantized mul on the esp_nn:: op library; the ESP32-S3 build also runs
the ESP-NN delegate program, so the delegate and op library are built from the
packed archive against the esp-nn the component manager resolves. To run a
stateful model, the smoke app now runs all of a bundled program's test cases
in order on one loaded method, and split_bundled_program.py can repeat them so
single-case models still check a second execute. idf_component_consumer reuses
the smoke app's main component, so the two cannot drift apart.

setup.sh gains --idf-version and, unless --skip-emulator, installs QEMU when
esp32 is a target. The workflow now also runs for changes to what the component
vendors (runtime, the portable, quantized and prim op kernels, schema, the
buffer data loader and flat_tensor schema, codegen, the Cadence AOT and generic
kernels, the flatbuffers submodule and build_variables.bzl) and to version.txt.

Authored with Claude Code.

This branch was previously deployed

1 inactive deployment
cadence — 29ecee24 Deployed Nov 7, 2026 by rascani via hifi-op-test / hifi4 #37202
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.

1 participant