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 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`.
@@ -0,0 +1,7 @@
1
+ """espdlx: esp-dl friendly NN blocks for ESP32-S3 (PyTorch backend)."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .graph import Model
6
+
7
+ __all__ = ["Model", "__version__"]