Westwind

Westwind turns an edge AI pipeline into Zephyr RTOS firmware. A pipeline is one westwind.yaml file: the training data, the model, the board, where inference runs, and the on-device graph that takes camera, audio or sensor data through preprocessing, ExecuTorch inference and postprocessing to a result your code acts on.

Data comes from Hugging Face, Kaggle or Edge Impulse. Models come from MediaPipe, from a PyTorch training recipe, from Edge Impulse or from Hugging Face, and are converted to ExecuTorch programs. The same file drives the westwind command line tool, the VS Code extension and this site.

Open the editorDownload the VS Code extensionRead the docs

Projects are kept in your browser, and editing and validation work without an account. Building firmware from this site needs a GitHub or Google sign-in.

From data to firmware

westwind run goes from fetch to build and westwind flash does the last step. Each stage also runs on its own.

fetch
Downloads the data entries: Hugging Face datasets, Kaggle datasets, Edge Impulse projects, URLs or local paths. Credentials come from environment variables named in the file, never from the file itself.
train
Runs a builtin recipe or your train.py (build_model() and train() in PyTorch). With a federated section, Flower trains across edge hosts and only the weights leave each site.
convert
Turns a MediaPipe TFLite model or a PyTorch model into an ExecuTorch program (.pte), in float or int8, and checks its outputs against the original.
generate
Writes a Zephyr application from the graph: mpipe elements from Zephyr and the westwind-zephyr module, prj.conf, and your C or C++ code.
build
Runs west build for the board, in GitHub Actions from this site, on your machine, in Docker Compose or as a Kubernetes Job.
flash
Writes the image from the browser over Web Serial for ESP32 boards, as a .uf2 file for the RP2040, or with westwind flash for the other methods.

Running on real hardware

These are photos, console captures and logs from boards on my bench, with the numbers they printed. Each run starts with a self-test that compares the board's output on an embedded input with PyTorch's, then times the inference. The code for each board is in its repository, linked below.

Arduino UNO Q

AnomalyNet on the STM32U585 (Cortex-M33), 5544 bytes of model in flash. The self-test matches PyTorch to 3.3e-06 and one inference takes 1621 us; verdicts go to the Linux side as JSON lines. executorch-zephyr-arduino-uno-q

JSON verdicts from the UNO Q scrolling during a simulated heater spike
JSON verdicts during a simulated heater spike: p_anomaly goes to 1.000 and back.
Console of the Arduino UNO Q: ExecuTorch self-test pass and JSON lines with infer_us 1621
LPUART1 read over adb on the Linux side, 2026-10-11.

Arducam Pico4ML-BLE

TinyDigitNet on the RP2040 (Cortex-M0+ at 133 MHz, no FPU), reading hand-written digits with the HM01B0 camera. The self-test predicts 7 at 100 %, matches PyTorch to 3.3e-06 and takes 767987 us. executorch-zephyr-arducam-pico4ML-BLE

Pico4ML-BLE held over a notebook page of hand-written digits, its screen showing the camera crop and the digit
A frame from my video of the board reading digits from a notebook page.
Pointing the camera at digits; the screen shows the 28x28 crop, the digit and the confidence.
Console of the Pico4ML-BLE: self-test predicted 7, SELF-TEST PASS, camera ready, first JSON line
USB console up to the first camera frame.

ESP32-DevKitC

TinyDigitNet on the ESP32 (Xtensa LX6), run on the esp32_devkitc target on an M5Stack ATOM Echo. It matches PyTorch to 2.86e-06 and takes 243475 us on average over 10 runs. executorch-zephyr-esp32-devkitc

Console of the ESP32 run: ten logits, predicted 7, SELF-TEST PASS, 243475 us average
COM3, 2026-10-12.

BBC micro:bit v2

AnomalyNet on the nRF52833 (Cortex-M4F at 64 MHz). It matches PyTorch to 1.91e-06 and takes 4314 us on average over 10 runs; the build uses 178 KB of 512 KB flash and 67 KB of 128 KB RAM. executorch-zephyr-microbit-v2

A BBC micro:bit v2 board
A BBC micro:bit v2. Photo: SimonWaldherr, CC BY 4.0, via Wikimedia Commons.
Console of the micro:bit v2: predicted 1, SELF-TEST PASS, 4314 us average, JSON heartbeat lines
DAPLink serial on COM14, 2026-10-11.

Nordic Thingy:91

AnomalyNet on the nRF9160 (Cortex-M33 at 64 MHz). It matches PyTorch to 1.91e-06 and takes 3969 us on average over 10 runs. executorch-zephyr-thingy91

Console of the Thingy:91: predicted 1, SELF-TEST PASS, 3969 us average, JSON heartbeat lines
Bridge CDC port COM16, 2026-10-11.

M5Stack ATOM Echo

Keyword spotting on the ESP32 with the on-board PDM microphone. The exported model matches PyTorch to 4e-6, one inference takes 172 ms and the 49-frame MFCC 30 ms, and the trained model scores 96.6 % on held-out clips. The decoded microphone level is still being checked. executorch-zephyr-m5stack-atom-echo

Sipeed Tang Nano 20K

A LiteX SoC with a VexRiscv RISC-V core at 48 MHz in the Gowin GW2AR-18C FPGA, with 8 MiB of SDRAM. Zephyr 4.4 boots from the LiteX BIOS over serial and ExecuTorch runs TinyDigitNet in soft float: SELF-TEST PASS, 3.34e-06 from PyTorch, 2956766 us on average over 10 runs.

--============= Liftoff! ===============--

*** Booting Zephyr OS build 684c9e8f32e4 ***

ExecuTorch on tang_nano_20k/litex_vexriscv (Zephyr 4.4.0)
model: 62888 bytes, method pool 131072 B, temp pool 16384 B
I [executorch:main.cpp:118 init()] planned buffer 0: 52928 bytes
I [executorch:main.cpp:148 init()] method 'forward' loaded; pool used 59584 / 131072 bytes
  logit[0] =   -1.2525
  logit[1] =   -2.3244
  logit[2] =    4.0803
  logit[3] =    2.5802
  logit[4] =   -5.6759
  logit[5] =   -5.8199
  logit[6] =   -7.9416
  logit[7] =   10.5990  <-- argmax
  logit[8] =   -2.2308
  logit[9] =   -0.1515
self-test: predicted 7, expected 7, max |device - pytorch| = 3.34e-06, 2956822 us
SELF-TEST PASS
inference: 2956766 us average over 10 runs at 48 MHz
Serial console after the LiteX BIOS hands over to Zephyr, 2026-11-10. Full boot log (ANSI colour codes and the two memory speed progress lines removed).

Tests and training runs

The westwind-zephyr elements have unit tests that run under QEMU. Locally on mps2/an385, 7 of 7 pass:

------ TESTSUITE SUMMARY START ------

SUITE PASS - 100.00% [westwind_blazeface]: pass = 4, fail = 0, skip = 0, total = 4 duration = 0.517 seconds
 - PASS - [westwind_blazeface.test_one_face] duration = 0.117 seconds
 - PASS - [westwind_blazeface.test_overlapping_anchors_merge_by_score] duration = 0.104 seconds
 - PASS - [westwind_blazeface.test_threshold] duration = 0.192 seconds
 - PASS - [westwind_blazeface.test_two_faces_best_first] duration = 0.104 seconds

SUITE PASS - 100.00% [westwind_json]: pass = 3, fail = 0, skip = 0, total = 3 duration = 0.005 seconds
 - PASS - [westwind_json.test_classify_line] duration = 0.002 seconds
 - PASS - [westwind_json.test_detection_line] duration = 0.002 seconds
 - PASS - [westwind_json.test_short_buffer] duration = 0.001 seconds

------ TESTSUITE SUMMARY END ------

===================================================================
PROJECT EXECUTION SUCCESSFUL
ztest summary. Full console log.

In GitHub Actions, Twister ran 14 of 14 test cases on mps2/an385 and native_sim, and the classify sample built for rpi_pico at 327,912 B of flash and 187,176 B of RAM.

Training runs of the AnomalyNet pipeline, as each run wrote them to model/anomaly_net.json (values to six significant digits; the linked files have them in full):

RunResultProgram
westwind submit in Docker Compose, builtin recipeaccuracy 0.98075 on 8000 examples, loss 0.0713489, 4 s5544 bytes, max abs diff 2.38419e-7
Flower profile in Docker Compose, 2 SuperNodeseval_acc by round: 0.79275, 0.8118755544 bytes, max abs diff 0
westwind federate --run, 3 roundseval_acc by round: 0.79275, 0.811875, 0.83555544 bytes, max abs diff 5.96046e-8
Host terminal: AnomalyNet training for 15 epochs, test accuracy 0.9983, export to a 5544 byte program, host runtime check OK
AnomalyNet for the UNO Q trained on the host (test accuracy 0.9983), exported to a 5544 byte program and checked on the ExecuTorch host runtime: 3.10e-06 and 9.54e-07 from PyTorch.

The editor

The project editor on this site has eight tabs over the same westwind.yaml. Edits in one tab show up in the others, and the file is checked against the schema and the catalogs as you type. The screenshots below are of this site with the digits example loaded.

Westwind editor, Flow tab: the digits-pico4ml graph from camera to tee, display and uart_json, with the preprocess element selected
Flow: the digits example, preprocess selected.

Start from one of the 7 example pipelines, or import a westwind.yaml.

Westwind editor, Blocks tab: the digits graph as stacked Blockly blocks with a tee and two branches
Blocks: the same graph as Blockly blocks.
Westwind editor, YAML tab: westwind.yaml in Monaco with no errors or warnings from the schema check
YAML: the file, checked against the schema.
Westwind editor, Target tab: the board table with RAM, PSRAM, flash method, validation date and model fit for each board
Target: the model fit for each board.
Westwind editor, Build tab while signed out: the GitHub Actions dispatch inputs and the sign-in note
Build: dispatch to GitHub Actions (signed out here).
Westwind editor, Flow tab in the dark theme, with the executorch element selected
The same graph in the dark theme.

Where the model runs

device
The model runs in the firmware through the ww_executorch element.
edge
The firmware sends frames or tensors to westwind serve on a nearby host: a PC, a Jetson, or the Linux side of an Arduino UNO Q.
cloud
As edge, but westwind serve forwards the request to a hosted endpoint such as a Hugging Face Space.
split
A small model on the device decides which frames are worth sending to the edge or cloud model.

On the host side, westwind serve answers offload requests over HTTP, or over UART and USB CDC as WW1 lines that share the console with log output.

Where the host-side work runs

The firmware always runs on the board. Fetching, training, conversion, firmware builds, westwind serve and the Flower SuperLink run where the compute section says:

local
The westwind CLI on your machine. The default.
compose
Containers from deploy/compose.yaml on one machine, one profile per job.
hybrid
Boards, the serial bridge and Flower SuperNodes with their data stay on site in Compose; training, firmware builds, westwind serve and the SuperLink run on Kubernetes.
kubernetes
Everything on a cluster, for example Amazon EKS, with outputs in S3.

westwind deploy writes the Compose file or the Kubernetes manifests for a pipeline and westwind submit runs stages there. See Running in containers.

Boards

18 boards in the catalog. 8 have been run on hardware with ExecuTorch on Zephyr; the others are expected to work. Details and flash methods are on the boards page.

BoardMemoryValidated
Arduino UNO Q
arduino_uno_q/stm32u585xx
786 KiB2026-10-11
Arducam Pico4ML-BLE
rpi_pico
264 KiB2026-10-11
Seeed XIAO ESP32S3 Sense
xiao_esp32s3/esp32s3/procpu/sense
512 KiB + 8 MiB PSRAM2026-10-09
M5Stack ATOM Echo
m5stack_atom_lite/esp32/procpu
520 KiB2026-10-12
ESP32-DevKitC
esp32_devkitc/esp32/procpu
520 KiB2026-10-12
BBC micro:bit v2
bbc_microbit_v2
128 KiB2026-10-11
Nordic Thingy:91
thingy91/nrf9160
256 KiB2026-10-11
Arduino Nicla Vision
arduino_nicla_vision/stm32h747xx/m7
1 MiBnot yet
Arduino Opta
arduino_opta/stm32h747xx/m7
1 MiBnot yet
Nordic nRF7002 DK
nrf7002dk/nrf5340/cpuapp
512 KiBnot yet
NXP FRDM-MCXN947
frdm_mcxn947/mcxn947/cpu0
512 KiBnot yet
TI LAUNCHXL-CC1352P1
cc1352p1_launchxl
80 KiBnot yet
Espressif ESP32-S3-EYE
esp32s3_eye/esp32s3/procpu
512 KiB + 8 MiB PSRAMnot yet
Espressif ESP32-C3-DevKitM
esp32c3_devkitm
400 KiBnot yet
M5StickC Plus
m5stickc_plus/esp32/procpu
520 KiBnot yet
Sipeed Tang Nano 20K (LiteX VexRiscv)
tang_nano_20k
8 MiB2026-11-10
Arm Corstone-300 FVP
mps3/corstone300/fvp
4 MiBnot yet
Native simulator
native_sim
64 MiBnot yet

Models

12 models in the catalog: 8 MediaPipe models for faces, pose, hands, segmentation, audio and objects, 3 small builtin models published on Hugging Face (digits, anomaly detection, keyword spotting), and a Hugging Face Space for cloud segmentation. Each entry says whether it fits a board, needs PSRAM, or belongs on an edge or cloud host. See the models page.

VS Code and OpenCode

The VS Code extension runs the CLI stages from the command palette, including writing deployment files, submitting to Compose or Kubernetes and running federated training. It registers two MCP servers for coding agents: the Westwind server (westwind mcp) for validation, generation, builds and dataset search, and the Zephyr documentation server. westwind editor --opencode writes the same setup for OpenCode. The extension, the CLI install line and the container images are on the downloads page.

Documentation

Account

Sign-in is not configured on this deployment (AUTH_SECRET and provider credentials are not set). Projects, editing and validation work without an account; builds need one.

GitHub accounts build in a repository you choose with your own token (scopes read:user, repo, workflow). Google accounts build in the shared build repository.