espdlx 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- espdlx-0.1.0/LICENSE +21 -0
- espdlx-0.1.0/PKG-INFO +223 -0
- espdlx-0.1.0/README.md +197 -0
- espdlx-0.1.0/espdlx/__init__.py +7 -0
- espdlx-0.1.0/espdlx/convert.py +314 -0
- espdlx-0.1.0/espdlx/fomo.py +180 -0
- espdlx-0.1.0/espdlx/graph.py +37 -0
- espdlx-0.1.0/espdlx/layers/__init__.py +27 -0
- espdlx-0.1.0/espdlx/layers/activations.py +70 -0
- espdlx-0.1.0/espdlx/layers/base.py +11 -0
- espdlx-0.1.0/espdlx/layers/conv.py +152 -0
- espdlx-0.1.0/espdlx/layers/linear.py +46 -0
- espdlx-0.1.0/espdlx/layers/math.py +72 -0
- espdlx-0.1.0/espdlx/layers/pool.py +48 -0
- espdlx-0.1.0/espdlx/zoo.py +301 -0
- espdlx-0.1.0/espdlx.egg-info/PKG-INFO +223 -0
- espdlx-0.1.0/espdlx.egg-info/SOURCES.txt +25 -0
- espdlx-0.1.0/espdlx.egg-info/dependency_links.txt +1 -0
- espdlx-0.1.0/espdlx.egg-info/requires.txt +9 -0
- espdlx-0.1.0/espdlx.egg-info/top_level.txt +1 -0
- espdlx-0.1.0/pyproject.toml +49 -0
- espdlx-0.1.0/setup.cfg +4 -0
- espdlx-0.1.0/tests/test_convert.py +126 -0
- espdlx-0.1.0/tests/test_fomo.py +128 -0
- espdlx-0.1.0/tests/test_layers.py +122 -0
- espdlx-0.1.0/tests/test_model.py +37 -0
- espdlx-0.1.0/tests/test_smoke.py +5 -0
espdlx-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Simone Salerno
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
espdlx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: espdlx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Train models in PyTorch and export them to .espdl (native format for ESP32 boards). Deploy via ONNX -> esp-dl.
|
|
5
|
+
Author-email: Simone Salerno <info@salernosimone.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: esp32,esp-dl,espdlx,pytorch,onnx,embedded,tiny-ml
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy>=2.5.1
|
|
18
|
+
Requires-Dist: torch>=2.13.0
|
|
19
|
+
Requires-Dist: torchvision>=0.28.0
|
|
20
|
+
Provides-Extra: convert
|
|
21
|
+
Requires-Dist: esp-ppq; extra == "convert"
|
|
22
|
+
Requires-Dist: onnx>=1.14; extra == "convert"
|
|
23
|
+
Requires-Dist: onnxruntime>=1.19; extra == "convert"
|
|
24
|
+
Requires-Dist: onnxscript; extra == "convert"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# espdlx
|
|
28
|
+
|
|
29
|
+
Train models in PyTorch and export them to `.espdl` — the native model format for ESP32 boards.
|
|
30
|
+
|
|
31
|
+
`espdlx` is the Python training side: build models from `espdlx.layers` inside an
|
|
32
|
+
`espdlx.Model`, train with your own PyTorch loop, then export via the standard path:
|
|
33
|
+
`torch.onnx.export` -> esp-dl quantization (`espdl_quantize_onnx` -> `.espdl`).
|
|
34
|
+
|
|
35
|
+
Deployment is paired with the **[espdlx Arduino library](https://github.com/salernosimone/arduino-espdlx)**
|
|
36
|
+
(esp-dl port for ESP32-S3, installable from the Arduino Library Manager) for maximum
|
|
37
|
+
ease of use: embed the `.espdl` file as a C array, load it with `dl::Model`, call `run()`.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install espdlx
|
|
43
|
+
# with esp-dl quantization support:
|
|
44
|
+
pip install "espdlx[convert]"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Example
|
|
48
|
+
|
|
49
|
+
Simple CNN trained on MNIST:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import torch
|
|
53
|
+
import torch.nn.functional as F
|
|
54
|
+
from torch.utils.data import DataLoader
|
|
55
|
+
from torchvision import datasets, transforms
|
|
56
|
+
|
|
57
|
+
import espdlx
|
|
58
|
+
import espdlx.layers as L
|
|
59
|
+
|
|
60
|
+
device = torch.device("mps")
|
|
61
|
+
|
|
62
|
+
model = espdlx.Model(
|
|
63
|
+
[
|
|
64
|
+
L.Conv2d(1, 16, 3, padding=1), L.ReLU(),
|
|
65
|
+
L.MaxPool2d(2),
|
|
66
|
+
L.Conv2d(16, 32, 3, padding=1), L.ReLU(),
|
|
67
|
+
L.MaxPool2d(2),
|
|
68
|
+
L.Conv2d(32, 10, 1),
|
|
69
|
+
L.Mean(),
|
|
70
|
+
L.Flatten(),
|
|
71
|
+
],
|
|
72
|
+
name="mnist_cnn",
|
|
73
|
+
).to(device)
|
|
74
|
+
|
|
75
|
+
train_ds = datasets.MNIST(root="./data", train=True, download=True, transform=transforms.ToTensor())
|
|
76
|
+
test_ds = datasets.MNIST(root="./data", train=False, download=True, transform=transforms.ToTensor())
|
|
77
|
+
train_ld = DataLoader(train_ds, batch_size=128, shuffle=True)
|
|
78
|
+
test_ld = DataLoader(test_ds, batch_size=512)
|
|
79
|
+
|
|
80
|
+
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
|
|
81
|
+
model.train()
|
|
82
|
+
for epoch in range(3):
|
|
83
|
+
for x, y in train_ld:
|
|
84
|
+
x, y = x.to(device), y.to(device)
|
|
85
|
+
opt.zero_grad()
|
|
86
|
+
loss = F.cross_entropy(model(x), y)
|
|
87
|
+
loss.backward()
|
|
88
|
+
opt.step()
|
|
89
|
+
|
|
90
|
+
model.eval()
|
|
91
|
+
correct = total = 0
|
|
92
|
+
with torch.no_grad():
|
|
93
|
+
for x, y in test_ld:
|
|
94
|
+
correct += (model(x.to(device)).argmax(-1) == y.to(device)).sum().item()
|
|
95
|
+
total += len(y)
|
|
96
|
+
print(f"test acc: {correct / total:.3f}")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Export to `.espdl`
|
|
100
|
+
|
|
101
|
+
Quantize a trained model to `.espdl` (needs the `convert` extra):
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from torch.utils.data import DataLoader, TensorDataset
|
|
105
|
+
from espdlx.convert import convert
|
|
106
|
+
|
|
107
|
+
calib_loader = DataLoader(TensorDataset(calib_images), batch_size=8)
|
|
108
|
+
|
|
109
|
+
report = convert(
|
|
110
|
+
model.cpu(), # any nn.Module; espdlx.Model or plain torch
|
|
111
|
+
example_input, # dummy input for torch.onnx.export
|
|
112
|
+
calib_loader, # calibration batches
|
|
113
|
+
"model.espdl", # also writes model.onnx + espdl_report.json
|
|
114
|
+
calib_steps=16,
|
|
115
|
+
)
|
|
116
|
+
print(report["espdl_bytes"], report["input_scale"], report["input_zero_point"])
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Deploy with the espdlx Arduino library
|
|
120
|
+
|
|
121
|
+
Install `espdlx` from the Arduino Library Manager (ESP32-S3), then embed the exported
|
|
122
|
+
`.espdl` file as a C header next to your sketch:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# model_data.h — generated from model.espdl (xxd-style, one line per 16 bytes)
|
|
126
|
+
xxd -i model.espdl > model_data.h
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Run inference with `dl::Model` (pattern from the espdlx Arduino library examples):
|
|
130
|
+
|
|
131
|
+
```cpp
|
|
132
|
+
#include <Arduino.h>
|
|
133
|
+
#include <espdlx.h>
|
|
134
|
+
#include "model_data.h"; // const unsigned char model_espdl[] = { 0x45, 0x44, ... };
|
|
135
|
+
|
|
136
|
+
dl::Model *model = nullptr;
|
|
137
|
+
|
|
138
|
+
void setup() {
|
|
139
|
+
Serial.begin(115200);
|
|
140
|
+
|
|
141
|
+
// Load the .espdl model straight from flash.
|
|
142
|
+
model = new dl::Model((const char *)model_espdl,
|
|
143
|
+
fbs::MODEL_LOCATION_IN_FLASH_RODATA);
|
|
144
|
+
|
|
145
|
+
auto inputs = model->get_inputs();
|
|
146
|
+
auto outputs = model->get_outputs();
|
|
147
|
+
dl::TensorBase *in = inputs.begin()->second;
|
|
148
|
+
dl::TensorBase *out = outputs.begin()->second;
|
|
149
|
+
Serial.printf("in_elems=%d out_elems=%d\n", in->size, out->size);
|
|
150
|
+
|
|
151
|
+
memcpy(in->data, my_input, in->size); // int8 NHWC, quantized with input_scale
|
|
152
|
+
model->run(); // or model->run(dl::RUNTIME_MODE_MULTI_CORE)
|
|
153
|
+
|
|
154
|
+
float *logits = (float *)out->data;
|
|
155
|
+
int pred = 0;
|
|
156
|
+
for (int i = 1; i < out->size; i++)
|
|
157
|
+
if (logits[i] > logits[pred]) pred = i;
|
|
158
|
+
Serial.printf("pred=%d\n", pred);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
void loop() { delay(1000); }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The library also ships a `SelfTest` example (`examples/SelfTest/SelfTest.ino`) that
|
|
165
|
+
exercises core + vision + audio through the single `espdlx.h` header:
|
|
166
|
+
|
|
167
|
+
```cpp
|
|
168
|
+
#include <Arduino.h>
|
|
169
|
+
#include <espdlx.h>
|
|
170
|
+
|
|
171
|
+
void setup() {
|
|
172
|
+
Serial.begin(115200);
|
|
173
|
+
float data[4] = {1.0f, 2.0f, 3.0f, 4.0f};
|
|
174
|
+
dl::TensorBase t({4}, data, 0, dl::DATA_TYPE_FLOAT, true);
|
|
175
|
+
dl::math::softmax((float *)t.data, 4); // core check via espdlx.h
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
void loop() { delay(5000); }
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Model zoo
|
|
182
|
+
|
|
183
|
+
Prebuilt architectures (`espdlx/zoo.py`, all residual-free variants — no runtime
|
|
184
|
+
tensor `Add` on-device, so skip connections are omitted):
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from espdlx.zoo import MobileNetV2, MobileNetV1, VGG, DSCNN
|
|
188
|
+
|
|
189
|
+
model = MobileNetV2.Slim(num_classes=5) # flowers, 128px RGB
|
|
190
|
+
model = MobileNetV2.Base(num_classes=32) # CIFAR-100 pretrain backbone, 32px
|
|
191
|
+
model = MobileNetV2.Fomo() # EXPERIMENTAL centroid detector, 96px
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
| Model | Input | Params | MMACs | `.espdl` | Sketch flash | PSRAM ctx | S3 latency single / multi |
|
|
195
|
+
|---|---|---:|---:|---:|---:|---:|---|
|
|
196
|
+
| `MobileNetV2.Slim(5)` | 128px RGB | 13,880 | 14.2 | 43.6 KB | 1.20 MB | 324 KB | 87 ms / 73 ms |
|
|
197
|
+
| `MobileNetV2.Base(5)` | 128px RGB | 132,280 | 100.2 | 169 KB | 1.33 MB | 1.09 MB | 243 ms / 256 ms |
|
|
198
|
+
| `MobileNetV2.Base(32)` | 32px RGB | 134,224 | 6.3 | 170 KB | 1.28 MB | 218 KB | 15.0 ms / 14.9 ms |
|
|
199
|
+
| `MobileNetV2.Fomo` (experimental) | 96px gray | 6,498 | 2.3 | 34.5 KB | 1.15 MB | 159 KB | 37.3 ms / 30.9 ms |
|
|
200
|
+
| `MobileNetV1.Slim(10)` | 96px RGB | 352,330 | 52.0 | 367 KB | 1.51 MB | 612 KB | 446 ms / 256 ms |
|
|
201
|
+
| `ResNet.BottleneckTiny(10)` | 96px RGB | 1,305,258 | 104.6 | 1.27 MB | 2.46 MB | 1.53 MB | 1045 ms / 811 ms |
|
|
202
|
+
| `VGG.Small(10)` | 96px RGB | 295,770 | 120.8 | 303 KB | 1.44 MB | 624 KB | 716 ms / 638 ms |
|
|
203
|
+
| `VGG.Stride(10)` | 96px RGB | 1,219,450 | 174.2 | 1.18 MB | 2.37 MB | 1.53 MB | 1570 ms / 1296 ms |
|
|
204
|
+
| `VGG.Wide(10)` | 96px RGB | 1,560,522 | 212.7 | 1.50 MB | 2.70 MB | 1.72 MB | 3102 ms / 2501 ms |
|
|
205
|
+
| `VGG.Base(10)` | 96px RGB | 663,106 | 233.0 | 662 KB | 1.81 MB | 973 KB | 2297 ms / 1780 ms |
|
|
206
|
+
| `VGG.Large(10)` | 96px RGB | 1,176,746 | 475.2 | 1.14 MB | 2.32 MB | 1.80 MB | 4804 ms / 3560 ms |
|
|
207
|
+
| `DSCNN.Tiny(2)` | 40x32 mel | 968 | 0.8 | 6.8 KB | 1.11 MB | 66 KB | 26.3 ms / 17.0 ms |
|
|
208
|
+
| `DSCNN.Small(2)` | 40x98 mel | 3,160 | 1.7 | 16.2 KB | 1.12 MB | 93 KB | 16.0 ms / 14.2 ms |
|
|
209
|
+
|
|
210
|
+
- All device numbers measured 2026-09-12 on ESP32-S3 @ 240 MHz via esp-dl
|
|
211
|
+
(`.espdl` w8a8, OPI PSRAM, `huge_app`, N=20 runs, `micros()` around `model->run()`).
|
|
212
|
+
- `.espdl` from `espdlx.convert` after 1 epoch of synthetic-noise training per model;
|
|
213
|
+
sizes and latency are weights-independent (accuracy on noise weights is meaningless,
|
|
214
|
+
so no accuracy column).
|
|
215
|
+
- PSRAM ctx = model context + working set (`free_psram` drop after load).
|
|
216
|
+
Sketch flash is ~1.1 MB framework floor + model + test vector.
|
|
217
|
+
- `MobileNetV2.Fomo` / `espdlx.fomo` are **experimental**: synthetic unit tests
|
|
218
|
+
only, no on-device accuracy verification yet (latency above just proves it
|
|
219
|
+
runs). Best real-data run so far: P=0.03/R=0.28 — not a working detector yet.
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT — see `LICENSE`.
|
espdlx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# espdlx
|
|
2
|
+
|
|
3
|
+
Train models in PyTorch and export them to `.espdl` — the native model format for ESP32 boards.
|
|
4
|
+
|
|
5
|
+
`espdlx` is the Python training side: build models from `espdlx.layers` inside an
|
|
6
|
+
`espdlx.Model`, train with your own PyTorch loop, then export via the standard path:
|
|
7
|
+
`torch.onnx.export` -> esp-dl quantization (`espdl_quantize_onnx` -> `.espdl`).
|
|
8
|
+
|
|
9
|
+
Deployment is paired with the **[espdlx Arduino library](https://github.com/salernosimone/arduino-espdlx)**
|
|
10
|
+
(esp-dl port for ESP32-S3, installable from the Arduino Library Manager) for maximum
|
|
11
|
+
ease of use: embed the `.espdl` file as a C array, load it with `dl::Model`, call `run()`.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install espdlx
|
|
17
|
+
# with esp-dl quantization support:
|
|
18
|
+
pip install "espdlx[convert]"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
Simple CNN trained on MNIST:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import torch
|
|
27
|
+
import torch.nn.functional as F
|
|
28
|
+
from torch.utils.data import DataLoader
|
|
29
|
+
from torchvision import datasets, transforms
|
|
30
|
+
|
|
31
|
+
import espdlx
|
|
32
|
+
import espdlx.layers as L
|
|
33
|
+
|
|
34
|
+
device = torch.device("mps")
|
|
35
|
+
|
|
36
|
+
model = espdlx.Model(
|
|
37
|
+
[
|
|
38
|
+
L.Conv2d(1, 16, 3, padding=1), L.ReLU(),
|
|
39
|
+
L.MaxPool2d(2),
|
|
40
|
+
L.Conv2d(16, 32, 3, padding=1), L.ReLU(),
|
|
41
|
+
L.MaxPool2d(2),
|
|
42
|
+
L.Conv2d(32, 10, 1),
|
|
43
|
+
L.Mean(),
|
|
44
|
+
L.Flatten(),
|
|
45
|
+
],
|
|
46
|
+
name="mnist_cnn",
|
|
47
|
+
).to(device)
|
|
48
|
+
|
|
49
|
+
train_ds = datasets.MNIST(root="./data", train=True, download=True, transform=transforms.ToTensor())
|
|
50
|
+
test_ds = datasets.MNIST(root="./data", train=False, download=True, transform=transforms.ToTensor())
|
|
51
|
+
train_ld = DataLoader(train_ds, batch_size=128, shuffle=True)
|
|
52
|
+
test_ld = DataLoader(test_ds, batch_size=512)
|
|
53
|
+
|
|
54
|
+
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
|
|
55
|
+
model.train()
|
|
56
|
+
for epoch in range(3):
|
|
57
|
+
for x, y in train_ld:
|
|
58
|
+
x, y = x.to(device), y.to(device)
|
|
59
|
+
opt.zero_grad()
|
|
60
|
+
loss = F.cross_entropy(model(x), y)
|
|
61
|
+
loss.backward()
|
|
62
|
+
opt.step()
|
|
63
|
+
|
|
64
|
+
model.eval()
|
|
65
|
+
correct = total = 0
|
|
66
|
+
with torch.no_grad():
|
|
67
|
+
for x, y in test_ld:
|
|
68
|
+
correct += (model(x.to(device)).argmax(-1) == y.to(device)).sum().item()
|
|
69
|
+
total += len(y)
|
|
70
|
+
print(f"test acc: {correct / total:.3f}")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Export to `.espdl`
|
|
74
|
+
|
|
75
|
+
Quantize a trained model to `.espdl` (needs the `convert` extra):
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from torch.utils.data import DataLoader, TensorDataset
|
|
79
|
+
from espdlx.convert import convert
|
|
80
|
+
|
|
81
|
+
calib_loader = DataLoader(TensorDataset(calib_images), batch_size=8)
|
|
82
|
+
|
|
83
|
+
report = convert(
|
|
84
|
+
model.cpu(), # any nn.Module; espdlx.Model or plain torch
|
|
85
|
+
example_input, # dummy input for torch.onnx.export
|
|
86
|
+
calib_loader, # calibration batches
|
|
87
|
+
"model.espdl", # also writes model.onnx + espdl_report.json
|
|
88
|
+
calib_steps=16,
|
|
89
|
+
)
|
|
90
|
+
print(report["espdl_bytes"], report["input_scale"], report["input_zero_point"])
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Deploy with the espdlx Arduino library
|
|
94
|
+
|
|
95
|
+
Install `espdlx` from the Arduino Library Manager (ESP32-S3), then embed the exported
|
|
96
|
+
`.espdl` file as a C header next to your sketch:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# model_data.h — generated from model.espdl (xxd-style, one line per 16 bytes)
|
|
100
|
+
xxd -i model.espdl > model_data.h
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Run inference with `dl::Model` (pattern from the espdlx Arduino library examples):
|
|
104
|
+
|
|
105
|
+
```cpp
|
|
106
|
+
#include <Arduino.h>
|
|
107
|
+
#include <espdlx.h>
|
|
108
|
+
#include "model_data.h"; // const unsigned char model_espdl[] = { 0x45, 0x44, ... };
|
|
109
|
+
|
|
110
|
+
dl::Model *model = nullptr;
|
|
111
|
+
|
|
112
|
+
void setup() {
|
|
113
|
+
Serial.begin(115200);
|
|
114
|
+
|
|
115
|
+
// Load the .espdl model straight from flash.
|
|
116
|
+
model = new dl::Model((const char *)model_espdl,
|
|
117
|
+
fbs::MODEL_LOCATION_IN_FLASH_RODATA);
|
|
118
|
+
|
|
119
|
+
auto inputs = model->get_inputs();
|
|
120
|
+
auto outputs = model->get_outputs();
|
|
121
|
+
dl::TensorBase *in = inputs.begin()->second;
|
|
122
|
+
dl::TensorBase *out = outputs.begin()->second;
|
|
123
|
+
Serial.printf("in_elems=%d out_elems=%d\n", in->size, out->size);
|
|
124
|
+
|
|
125
|
+
memcpy(in->data, my_input, in->size); // int8 NHWC, quantized with input_scale
|
|
126
|
+
model->run(); // or model->run(dl::RUNTIME_MODE_MULTI_CORE)
|
|
127
|
+
|
|
128
|
+
float *logits = (float *)out->data;
|
|
129
|
+
int pred = 0;
|
|
130
|
+
for (int i = 1; i < out->size; i++)
|
|
131
|
+
if (logits[i] > logits[pred]) pred = i;
|
|
132
|
+
Serial.printf("pred=%d\n", pred);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
void loop() { delay(1000); }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The library also ships a `SelfTest` example (`examples/SelfTest/SelfTest.ino`) that
|
|
139
|
+
exercises core + vision + audio through the single `espdlx.h` header:
|
|
140
|
+
|
|
141
|
+
```cpp
|
|
142
|
+
#include <Arduino.h>
|
|
143
|
+
#include <espdlx.h>
|
|
144
|
+
|
|
145
|
+
void setup() {
|
|
146
|
+
Serial.begin(115200);
|
|
147
|
+
float data[4] = {1.0f, 2.0f, 3.0f, 4.0f};
|
|
148
|
+
dl::TensorBase t({4}, data, 0, dl::DATA_TYPE_FLOAT, true);
|
|
149
|
+
dl::math::softmax((float *)t.data, 4); // core check via espdlx.h
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
void loop() { delay(5000); }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Model zoo
|
|
156
|
+
|
|
157
|
+
Prebuilt architectures (`espdlx/zoo.py`, all residual-free variants — no runtime
|
|
158
|
+
tensor `Add` on-device, so skip connections are omitted):
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from espdlx.zoo import MobileNetV2, MobileNetV1, VGG, DSCNN
|
|
162
|
+
|
|
163
|
+
model = MobileNetV2.Slim(num_classes=5) # flowers, 128px RGB
|
|
164
|
+
model = MobileNetV2.Base(num_classes=32) # CIFAR-100 pretrain backbone, 32px
|
|
165
|
+
model = MobileNetV2.Fomo() # EXPERIMENTAL centroid detector, 96px
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
| Model | Input | Params | MMACs | `.espdl` | Sketch flash | PSRAM ctx | S3 latency single / multi |
|
|
169
|
+
|---|---|---:|---:|---:|---:|---:|---|
|
|
170
|
+
| `MobileNetV2.Slim(5)` | 128px RGB | 13,880 | 14.2 | 43.6 KB | 1.20 MB | 324 KB | 87 ms / 73 ms |
|
|
171
|
+
| `MobileNetV2.Base(5)` | 128px RGB | 132,280 | 100.2 | 169 KB | 1.33 MB | 1.09 MB | 243 ms / 256 ms |
|
|
172
|
+
| `MobileNetV2.Base(32)` | 32px RGB | 134,224 | 6.3 | 170 KB | 1.28 MB | 218 KB | 15.0 ms / 14.9 ms |
|
|
173
|
+
| `MobileNetV2.Fomo` (experimental) | 96px gray | 6,498 | 2.3 | 34.5 KB | 1.15 MB | 159 KB | 37.3 ms / 30.9 ms |
|
|
174
|
+
| `MobileNetV1.Slim(10)` | 96px RGB | 352,330 | 52.0 | 367 KB | 1.51 MB | 612 KB | 446 ms / 256 ms |
|
|
175
|
+
| `ResNet.BottleneckTiny(10)` | 96px RGB | 1,305,258 | 104.6 | 1.27 MB | 2.46 MB | 1.53 MB | 1045 ms / 811 ms |
|
|
176
|
+
| `VGG.Small(10)` | 96px RGB | 295,770 | 120.8 | 303 KB | 1.44 MB | 624 KB | 716 ms / 638 ms |
|
|
177
|
+
| `VGG.Stride(10)` | 96px RGB | 1,219,450 | 174.2 | 1.18 MB | 2.37 MB | 1.53 MB | 1570 ms / 1296 ms |
|
|
178
|
+
| `VGG.Wide(10)` | 96px RGB | 1,560,522 | 212.7 | 1.50 MB | 2.70 MB | 1.72 MB | 3102 ms / 2501 ms |
|
|
179
|
+
| `VGG.Base(10)` | 96px RGB | 663,106 | 233.0 | 662 KB | 1.81 MB | 973 KB | 2297 ms / 1780 ms |
|
|
180
|
+
| `VGG.Large(10)` | 96px RGB | 1,176,746 | 475.2 | 1.14 MB | 2.32 MB | 1.80 MB | 4804 ms / 3560 ms |
|
|
181
|
+
| `DSCNN.Tiny(2)` | 40x32 mel | 968 | 0.8 | 6.8 KB | 1.11 MB | 66 KB | 26.3 ms / 17.0 ms |
|
|
182
|
+
| `DSCNN.Small(2)` | 40x98 mel | 3,160 | 1.7 | 16.2 KB | 1.12 MB | 93 KB | 16.0 ms / 14.2 ms |
|
|
183
|
+
|
|
184
|
+
- All device numbers measured 2026-09-12 on ESP32-S3 @ 240 MHz via esp-dl
|
|
185
|
+
(`.espdl` w8a8, OPI PSRAM, `huge_app`, N=20 runs, `micros()` around `model->run()`).
|
|
186
|
+
- `.espdl` from `espdlx.convert` after 1 epoch of synthetic-noise training per model;
|
|
187
|
+
sizes and latency are weights-independent (accuracy on noise weights is meaningless,
|
|
188
|
+
so no accuracy column).
|
|
189
|
+
- PSRAM ctx = model context + working set (`free_psram` drop after load).
|
|
190
|
+
Sketch flash is ~1.1 MB framework floor + model + test vector.
|
|
191
|
+
- `MobileNetV2.Fomo` / `espdlx.fomo` are **experimental**: synthetic unit tests
|
|
192
|
+
only, no on-device accuracy verification yet (latency above just proves it
|
|
193
|
+
runs). Best real-data run so far: P=0.03/R=0.28 — not a working detector yet.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT — see `LICENSE`.
|