Repository navigation
Add ESP-NN delegate backend for Espressif ESP32-S3 / ESP32 - #23956
CodeKitty-max wants to merge 9 commits into
Conversation
EspNNBackend lowers PT2E int8 subgraphs to ESP-NN kernels; unsupported ops fall back to ExecuTorch portable kernels. Adds the ESP-IDF runtime component, an on-device test runner, a camera demo and ESP32-S3 / ESP32 benchmarks, alongside the existing examples/espressif executor runner.
|
Hi @CodeKitty-max! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
|
@claude review this PR. |
| return any(not _is_passthrough(node) for node in nodes) | ||
|
|
||
|
|
||
| class EspNNPartitioner(Partitioner): |
|
Claude finished @digantdesai's task in 45s —— View job This PR adds a solid ESP-NN delegate, but it needs changes before merge. A grouped-conv bug produces wrong results and reads out of bounds, the device-side XTS3 parser has integer-overflow holes on 32-bit targets, and none of the 41 host tests run in CI. PR Review: #23956 (click to expand)SummaryThe PR adds an
I found these by reading the code; I did not build or run anything. Security
Thread Safety
API Design
Infrastructure
Testing
Code Quality
Performance
RecommendationRequest Changes To unblock merge, fix:
On the reimplemented passes, report side channel, duplicate build script and extra example tooling, please discuss with maintainers before the next round. Together they account for a good share of the 17k LOC. |
|
Fantastic. Thanks for the contribution. This is a HUGE PR. It may take us some time to review and provide you meaningful feedback. |
Thank you, I hope to work with y'all till I get this up and running and roll out more tests for other boards. |
Harden the XTS3 parser against COPY/UPSAMPLE size overflows and misaligned int32 constants; enforce allow_cpu_fallback=False; drop the component PAL in favour of executor_runner/esp_pal.cpp; serialize invoke(); check delegate I/O shape and dim order; enforce runtime limits at serialization; honour per-module quantizer configs; add backends/espressif/test to pytest.ini and run the operator suite in esp-emu in the Espressif CI job.
|
Suggested edits by @claude have been made and pushed |
|
There are a few compile passes added, can you please ensure you use existing where applicable? there should already be batch norm fusion iirc, maybe dropout removal as well |
Oh, I figure the issue is that I am using custom functions for some functions that have already been implemented, correct? |
Drop _passes/fold_batch_norm and _passes/remove_dropout: PT2E already folds conv+BN, and dropout is a no-op in exported eval models. Replace DuplicateQuantizationFanoutPass with the existing DuplicateDequantNodePass.
|
3 custom passes deleted in favor of the ones already in use by Executorch |
|
@CodeKitty-max - Thanks for putting this together! It is high on my priority list to do a detailed review on this. One early design discussion to work through: ExecuTorch backends come in two different styles: delegate backends and operator library backends. Most of our backends are delegate backends like the one you have created here (they have a partitioner and a runtime BackendInterface implementation). Operator libraries don't have those, but instead have passes that lower the aten ops to custom ops backed by an operator library like Cortex-M's CMSIS-NN or Cadence's nnlib. Given their similarity, did you look at creating an operator library backend like those? I know our documentation is really geared towards the delegate style. Again, huge thanks for putting this together. I'm really excited to dig into it. I just picked up some ESP32-S3 boards to try it out. |
|
I started by looking at delegate backends and even when I did the first primitive prototypes and tests, that's what I had in mind before proceeding. Would the other method be preferred here to this? |
There are different trade-offs between the two methods. The nice benefit to operator library style is that you don't need to write your own serialization format or interpreter, and instead can just leverage executorch's. The downside is that there's no partitioner, or any opportunity for storing state in the delegate handle (though there are other tricks to adding state). |
Then I guess it might not be a dramatic difference in performance. As long as this can already work for the two physical boards that I have already tested, I can focus on improving performance progressively. Have you performed tests on the esp32s3 yet? |
Sorry for the delay. OpLib vs. Delegate BackendIn terms of pure latency, there's probably not a significant difference for fully delegated models, but there likely would be for models with graph breaks. If you need portable kernels to supplement, then you wind up copying tensor data in & out of the delegate boundaries. The real wins with operator libraries are in binary size and RAM usage (which arguably matter more than latency). The executorch runtime already includes a serialization format that supports custom kernels and an execution loop to iterate through those (delegates must duplicate this). Additionally, you get the full benefit of executorch's memory planning, which handles tensor lifetimes & copy elision. I think the long term plan here probably needs to move to an operator library design, but I'm flexible on how we get there. CI StatusThe latest ESP32-S3 job (https://github.com/pytorch/executorch/actions/runs/39214352330/job/121758794836) fails after the existing portable smoke test passes. TestingIt looks like the ESP-NN testing all goes through the XTS3 layer standalone without using the executorch runtime. This doesn't test the integration fully. Since we have the emulator already set up, we should be able to run full e2e tests with it. Eventually, we can also do the backend test suite as well, but probably better for a follow-up. ExamplesWe should consolidate the runner with the existing runner, and we can enhance its functionality if there is anything missing. The camera_stream example also should be using the executorch runtime. I'd probably suggest punting that one to a follow-up PR. Next StepsAs I mentioned above, I'd like to figure out a path forward here. I'm inclined to start developing an operator library backend, but would like to make sure it also works for your use cases as well. I can either start from scratch there, or we can potentially merge this and then convert it. I can't merge as-is though because of CI failures. WDYT? |
cb6036e to
450b751
Compare
Export the backend's test models as BundleIO programs and run each one through examples/espressif/project, built with -DET_ESPNN_BACKEND=ON, in esp-emu. This replaces the standalone XTS3 operator suite in CI. The espnn_runtime component links the ExecuTorch targets that executor_runner imports, executor_runner links quantized_ops_lib, and the standalone runner, board scripts, benchmarks and compiler are removed.
…tion Share qparams through dropout/dropout_/alias and collapse the q-dq-q-dq chain that eval-mode dropout leaves, which fixes MobileNetV2/V3 classifiers reading an unwritten tensor. serialize_graph now rejects reads of unwritten activations. The delegate quantizes float inputs without nearbyintf, which is a software routine on Xtensa (about 93 ms down to 33 ms on MobileNetV3-Small).
Load the model as a .pte with Program/Method, planned memory in PSRAM and the ESP-NN delegate. export_model.py calibrates, runs QAT and reports top-1 of the lowered program. Default camera XCLK is 10 MHz because the OV3660 corrupts frames at 20 MHz.
I don't think I should take the camera example to another PR because we might just be complicating something that does not need complication, I have made a few edits on the parts that you have spoken off, if we can make sure that it runs well and passes the tests then we can start the porting post-commit as an ongoing project. I think what is best now is to ensure that we can at least have a functional delegate that uses the Executorch runtime. |
Description
Why
ExecuTorch can already run on Espressif chips:
examples/espressif/executor_runnerruns
.pteprograms with the portable CPU kernels. But no backend targetsEspressif's own neural-network kernels, so every operator runs as generic C
on the CPU. ESP-NN is Espressif's int8
kernel library, with SIMD kernels for the ESP32-S3. TFLite Micro uses it, but
ExecuTorch users can't reach it today.
This PR adds
EspNNBackend. It is a delegate that lowers PT2E-quantized int8subgraphs to ESP-NN, while everything else stays on ExecuTorch's portable
kernels. The goal is that the usual ExecuTorch flow (export, quantize, lower,
.pte) produces programs that run on an ESP32-S3 at ESP-NN speed. Models thatdon't fully map to ESP-NN still run, because unsupported parts fall back to
the CPU.
What's included
backends/espressif/EspNNQuantizer: PT2E annotations for the int8 scheme ESP-NN executes.Activations are per-tensor and weights are per-channel symmetric.
EspNNPartitioner: delegates int8 regions and leaves the rest of the graphfor the portable kernels. It returns a report of delegated and CPU
operators, and
allow_cpu_fallback=Falseturns leftovers into an error.EspNNBackend.preprocess(): serializes each partition to a compact int8graph format (XTS3) that the device runtime consumes directly.
to_espnn_edge(): a one-call wrapper aroundto_edge_transform_and_lower.espnn_runtime/: an ESP-IDF component. It provides theBackendInterfaceimplementation, the XTS3 interpreter on top of ESP-NN, and an optional
ExecuTorch PAL (
esp_timerclock and PSRAM-aware allocation).reference/: a bit-exact host model of the device arithmetic, used by thetests and by the on-device checks.
test/: 41 pytest tests.Delegated operators:
silu, tanh, gelu and others)
Partitions can have multiple inputs and outputs.
examples/espressif/, alongside the existingexecutor_runner/, which isunchanged:
quantize_and_lower.py: a minimal export → quantize → lower →.pteexample.
aot_espnn_compiler.py: a compiler CLI. It calibrates on real data, can runoptional QAT, and reports accuracy. It also writes test bundles for the
board.
espnn_runner/: an ESP-IDF app that runs bundles on a board and checksevery output against the host reference before timing it.
camera_stream/: a live camera classification demo (ESP32-S3).scripts/:benchmarks/: measured results for the ESP32-S3 and ESP32.Usage
MobileNetV2 lowers to 1 partition with 101 delegated operators and none left
on the CPU.
Test plan
Host, no hardware needed:
The tests lower small models for every operator group. They check the
serialized graphs bit-exactly against the reference interpreter and against
the quantized PyTorch model, and they run CPU-fallback programs through the
ExecuTorch Python runtime.
On hardware, with an ESP-IDF v5.1+ shell and the board on
/dev/ttyACM0:python examples/espressif/scripts/make_test_bundles.py python examples/espressif/scripts/run_bundle.py --port /dev/ttyACM0 \ examples/espressif/espnn_runner/bundles/op_suite.xtb examples/espressif/scripts/build_executorch_esp.sh esp32s3 python examples/espressif/scripts/run_bundle.py --port /dev/ttyACM0 \ --executorch-build-dir cmake-out-esp-esp32s3 \ examples/espressif/espnn_runner/bundles/pte_suite.xtpResults on real boards:
7/7 ExecuTorch programs pass, including the CPU-fallback cases (LSTM,
cumsum, a CPU island between two delegates, and a transformer).
Benchmarks (ESP32-S3, 240 MHz, octal PSRAM)
Each model is verified against the host reference on the board before it is
timed. Full tables, including the ESP32, are in
examples/espressif/benchmarks/..pte, fully delegated).pte, 11 ops delegated, 30 on CPU)Design notes for reviewers
delegate allocates its activation arena, scratch buffers and optional PSRAM
weight copy at
init()with ESP-IDF'sheap_caps, so it can choose betweeninternal SRAM and PSRAM. Nothing is allocated during
execute(). I'm happyto route this through the runtime allocator instead if that's preferred.
rather than a FlatBuffer. That keeps the device side free of extra
dependencies, and every region and record is bounds-checked on load.
component registry. It is not vendored.
Open questions
backends/espressif/testis not yet inpytest.initestpaths.Should I add it in this PR? There's also an existing ESP32-S3 emulator job
(
test-espressif.yml). A follow-up could runespnn_runner's operatorsuite there.
executor_runner/. I can merge the shared parts, such as the ExecuTorchcross-build and the PAL, with the existing runner if maintainers prefer a
single Espressif example tree.
Release notes: backends
cc @cccclai @nil-is-all @psiddh @AdrianLundell @digantdesai @rascani