Skip to content

Commit 9ea3272

Browse files
committed
Add Arduino library support for ExecuTorch
Add tooling to package the ExecuTorch runtime as an Arduino library, enabling PyTorch model inference on Arduino microcontrollers. The library vendors ET runtime sources, CMSIS-NN kernels, and portable ops into a self-contained package that compiles under the Arduino build system. Key components: - `build_arduino_library.sh` assembles the distributable library from repository sources (no vendored copies checked in) - `ExecuTorchArduino.h` configures the build environment for Arduino (fixes for std::variant, cmake_macros.h stub, build defines) - `platform_stubs.c` provides C library stubs for the LLEXT environment - Example sketches using the native ExecuTorch C++ API (no wrapper layer) - Zephyr board config for Arduino Uno Q (STM32U585, Cortex-M33) Validated on Arduino Uno Q with DS-CNN keyword spotting model (int8, CMSIS-NN): 390+ source files compile, 106 KB flash (13%), 91 KB RAM. Authored with assistance from Claude.
1 parent 1bf982a commit 9ea3272

9 files changed

Lines changed: 644 additions & 0 deletions

File tree

examples/arduino/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Generated by build_arduino_library.sh — do not check in
2+
arduino_lib/
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
// ExecuTorchArduino — ExecuTorch runtime for Arduino boards
4+
//
5+
// This header configures ExecuTorch for the Arduino build environment
6+
// and exposes the native ET C++ API. There is no wrapper — use
7+
// Program::load(), Method::execute(), etc. directly.
8+
9+
// Arduino's custom <new> header omits <exception>, which breaks
10+
// std::bad_variant_access in <variant>. Include it first.
11+
#include <exception>
12+
13+
// ExecuTorch build configuration for Arduino
14+
#define C10_USING_CUSTOM_GENERATED_MACROS
15+
#define ET_ENABLE_DEPRECATED_CONSTANT_BUFFER 0
16+
#define FLATBUFFERS_MAX_ALIGNMENT 1024
17+
18+
// Core runtime headers
19+
#include <executorch/runtime/platform/runtime.h>
20+
#include <executorch/runtime/executor/program.h>
21+
#include <executorch/runtime/executor/method.h>
22+
#include <executorch/runtime/core/memory_allocator.h>
23+
#include <executorch/runtime/executor/method_meta.h>
24+
#include <executorch/extension/data_loader/buffer_data_loader.h>

examples/arduino/README.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# ExecuTorch Arduino Library
2+
3+
Run PyTorch models on Arduino microcontrollers using ExecuTorch.
4+
5+
This directory contains everything needed to package ExecuTorch as an
6+
Arduino library. A build script vendors the runtime sources from this
7+
repository into a self-contained library that Arduino users install
8+
through the Library Manager or by copying into their libraries folder.
9+
10+
## How It Works
11+
12+
```
13+
PyTorch Model ──► torch.export ──► .pte file ──► model.h (C array)
14+
15+
Arduino Sketch (.ino)
16+
#include <ExecuTorchArduino.h>
17+
#include "model.h"
18+
19+
arduino-cli compile ──► Upload ──► Runs on board
20+
```
21+
22+
### The three pieces
23+
24+
1. **The library** (`arduino_lib/ExecuTorchArduino/`) — the ExecuTorch
25+
runtime, CMSIS-NN kernels, and portable ops packaged for the Arduino
26+
build system. Generated by `build_arduino_library.sh`; not checked in.
27+
28+
2. **The model** (`model.h`) — a `.pte` file converted to a C byte array.
29+
Each user brings their own model, exported from PyTorch with the
30+
Cortex-M backend.
31+
32+
3. **The sketch** (`.ino`) — a standard Arduino program that loads the
33+
model, feeds it input, and reads the output. Uses the native
34+
ExecuTorch C++ API (`Program::load`, `Method::execute`, etc.).
35+
36+
## Supported Boards
37+
38+
| Board | MCU | Status |
39+
|-------|-----|--------|
40+
| Arduino Uno Q | STM32U585 (Cortex-M33) | Tested |
41+
| Arduino Nano 33 BLE | nRF52840 (Cortex-M4F) | Untested |
42+
| Arduino Giga R1 WiFi | STM32H747 (Cortex-M7) | Untested |
43+
| Arduino Portenta H7 | STM32H747 (Cortex-M7) | Untested |
44+
45+
CMSIS-NN accelerated ops work on any board with an ARM Cortex-M processor
46+
with DSP extensions. Portable ops work on any architecture.
47+
48+
## Quick Start
49+
50+
### 1. Build the Arduino library
51+
52+
```bash
53+
cd examples/arduino
54+
./build_arduino_library.sh
55+
```
56+
57+
This copies the required ExecuTorch sources from the repository into
58+
`arduino_lib/ExecuTorchArduino/`, ready for Arduino.
59+
60+
### 2. Install the library
61+
62+
Copy the generated library into your Arduino libraries folder:
63+
64+
```bash
65+
cp -r arduino_lib/ExecuTorchArduino ~/Arduino/libraries/
66+
```
67+
68+
Or with `arduino-cli`:
69+
70+
```bash
71+
arduino-cli lib install --zip-path arduino_lib/ExecuTorchArduino
72+
```
73+
74+
### 3. Export a model
75+
76+
Export a PyTorch model to `.pte` format with Cortex-M quantization, then
77+
convert to a C header:
78+
79+
```bash
80+
python export_model.py --model my_model.pt --target cortex-m33 --output model.h
81+
```
82+
83+
Or use one of the pre-exported models in the `examples/` directory.
84+
85+
### 4. Write a sketch
86+
87+
```cpp
88+
#include <ExecuTorchArduino.h>
89+
#include "model.h"
90+
91+
using executorch::extension::BufferDataLoader;
92+
using executorch::runtime::Error;
93+
using executorch::runtime::HierarchicalAllocator;
94+
using executorch::runtime::MemoryAllocator;
95+
using executorch::runtime::MemoryManager;
96+
using executorch::runtime::Method;
97+
using executorch::runtime::MethodMeta;
98+
using executorch::runtime::Program;
99+
using executorch::runtime::Result;
100+
using executorch::runtime::Span;
101+
102+
alignas(16) uint8_t method_pool[64 * 1024];
103+
alignas(16) uint8_t temp_pool[8 * 1024];
104+
105+
void setup() {
106+
Serial.begin(115200);
107+
delay(2000);
108+
109+
executorch::runtime::runtime_init();
110+
111+
auto loader = BufferDataLoader(model_pte, model_pte_size);
112+
Result<Program> program = Program::load(&loader);
113+
if (!program.ok()) {
114+
Serial.println("Failed to load program");
115+
return;
116+
}
117+
118+
// ... load method, set inputs, execute, read outputs
119+
// See examples/ for complete working sketches.
120+
}
121+
122+
void loop() {
123+
// Run inference periodically
124+
delay(2000);
125+
}
126+
```
127+
128+
The sketch uses the **native ExecuTorch C++ API** — the same API used on
129+
Linux, Android, and bare-metal targets. No wrapper layer, no
130+
Arduino-specific abstractions.
131+
132+
### 5. Compile and upload
133+
134+
```bash
135+
arduino-cli compile --fqbn arduino:zephyr:unoq MySketch
136+
arduino-cli upload --fqbn arduino:zephyr:unoq -p /dev/ttyUSB0 MySketch
137+
```
138+
139+
## What is inside the library
140+
141+
The `build_arduino_library.sh` script assembles these components from
142+
the ExecuTorch repository:
143+
144+
| Component | Source in repo | Purpose |
145+
|-----------|---------------|---------|
146+
| ET Runtime | `runtime/executor/`, `runtime/core/`, `runtime/kernel/`, `runtime/platform/` | Model loading, memory management, op dispatch |
147+
| Portable Ops | `kernels/portable/` | Software op implementations (any CPU) |
148+
| Cortex-M Ops | `backends/cortex_m/ops/` | CMSIS-NN accelerated int8 ops |
149+
| CMSIS-NN | fetched by cmake / Zephyr module | ARM's optimized DSP kernels |
150+
| flatcc | `third-party/flatcc/` | .pte file parsing |
151+
| flatbuffers | `third-party/flatbuffers/` | Schema headers |
152+
| c10 | `runtime/core/portable_type/c10/` | Core type definitions |
153+
154+
The library uses no external dependencies beyond what the Arduino board
155+
core provides.
156+
157+
## Arduino-specific patches
158+
159+
The build script applies these patches to make ExecuTorch compile under
160+
Arduino's build system:
161+
162+
1. **`#include <exception>` before `<variant>`** — Arduino's custom
163+
`<new>` header omits `<exception>`, breaking `std::bad_variant_access`.
164+
165+
2. **`cmake_macros.h` stub** — c10/torch headers expect a cmake-generated
166+
file. The stub defines `C10_USING_CUSTOM_GENERATED_MACROS`.
167+
168+
3. **`platform_stubs.c`** — provides `_Exit()` and `fprintf()` for the
169+
LLEXT environment on boards that lack them.
170+
171+
## Development
172+
173+
### Updating the library
174+
175+
After modifying ExecuTorch sources, regenerate the library:
176+
177+
```bash
178+
./build_arduino_library.sh # rebuild
179+
./build_arduino_library.sh --clean # remove generated output
180+
```
181+
182+
### Testing
183+
184+
```bash
185+
arduino-cli compile --fqbn arduino:zephyr:unoq examples/HelloExecuTorch
186+
arduino-cli upload --fqbn arduino:zephyr:unoq -p /dev/ttyUSB0 examples/HelloExecuTorch
187+
arduino-cli monitor -p /dev/ttyUSB0 --config baudrate=115200
188+
```
189+
190+
### Publishing to Arduino Library Manager
191+
192+
The library is published by adding its repository URL to the
193+
[Arduino Library Registry](https://github.com/arduino/library-registry).
194+
After the initial registration, new git tags are picked up automatically.
195+
196+
## Build Validation
197+
198+
Tested on Arduino Uno Q (STM32U585, Cortex-M33 @ 160 MHz):
199+
200+
- **390+ source files** compile with zero errors
201+
- **Flash**: 106 KB used (13% of 786 KB)
202+
- **RAM**: 91 KB used (69% of 131 KB)
203+
- **Model**: DS-CNN keyword spotting, int8 quantized via CMSIS-NN, 52.6 KB

0 commit comments

Comments
 (0)