Incremental model updates for TinyML and embedded AI devices.
Instead of shipping a full TensorFlow Lite Micro model (20–200+ KB) over the air, TinyMLDelta ships a tiny binary patch that mutates the existing model in flash into a new one — safe, atomic, and guardrail-checked.
| Metric | Value |
|---|---|
| Base model size | 67,440 bytes |
| Target model size | 67,440 bytes |
| Patch size | 475 bytes |
| Diff payload | 382 bytes (1 chunk) |
| Bandwidth reduction | 99.3% |
| Integrity | CRC32 per chunk |
| Slot strategy | A/B atomic swap |
| Journal | Crash-safe (power-loss recovery) |
A weight-update to a real TFLite sensor model produces a 475-byte patch instead of a 67 KB re-flash. The entire update, verification, and slot flip runs in under a second on a simulated flash image.
[run_demo] Patch size : 475 bytes
[run_demo] Base model size : 67440 bytes
[run_demo] Target model size: 67440 bytes
...
TinyMLDelta: chunk[0]: off=62728 len=382 enc=0 has_crc=1
TinyMLDelta: patch applied OK, new active slot=0
[verify_flash] SUCCESS: target model found at offset 131072 in flash image.
| Metric | Value |
|---|---|
| Board | Arduino UNO Q (STM32U585 + Qualcomm aarch64 Linux) |
| Sensor | Arduino Modulino Thermo (HS3003, Qwiic) |
| Patch transport | ADB push to Linux co-processor |
| Inference | TFLite C API or Edge Impulse SDK (two variants) |
| Anomaly method | TFLite autoencoder (z-score normalized) |
| Update | Live, no re-flash, CRC32 verified, < 1 ms apply |
[TRAIN] 200/200 25.15 C
[TRAIN] Baseline: mean=25.11 std=3.00 C
[UPDATE] Patch applied in 0.69 ms
TEMP 25.15 C score=0.0166
TEMP 25.14 C score=0.0166
TEMP 32.50 C score=0.0600 *** ANOMALY ***
Reproducible patch-size benchmarks across compression backends
(RAW / RLE / LZ4 / bsdiff) and model formats (TFLite / ONNX / flat) — committed
results and tables in bench/README.md (snapshot CSV:
bench/sample_results.csv). Highlights: RLE/LZ4 reach
0.2–0.4% on repetitive (quant-churn) updates, the best backend depends on the
update type, and bsdiff is smallest but not MCU-deployable.
| Problem | TinyMLDelta's answer |
|---|---|
| OTA bandwidth cost | Ship diffs, not full models |
| Flash wear | One write per changed byte, not the whole model |
| Update latency | Seconds to transfer a patch vs minutes for a full image |
| Fleet fragmentation | Guardrails enforce ABI/opset/arena compatibility before applying |
| Bootloader complexity | No custom bootloader needed — just the C runtime in your firmware |
- TensorFlow Lite Micro models
- POSIX / macOS simulated flash environment
- CRC32 per-chunk integrity
- A/B slot atomic updates
- Crash-safe journaling (power-loss recovery)
- Arduino UNO Q (STM32U585 + Zephyr)
- Edge Impulse SDK integration (external model loading)
- RAW, RLE, and LZ4 chunk encoding (LZ4 via
--lz4; device needsTMD_FEAT_LZ4TINY) - COPY/ADD structure-aware patches (
--copy-add; device needsTMD_FEAT_COPYADD) — robust to serialization offset shifts and model growth (architecture updates) - Base-slot digest verification (
TMD_FEAT_VERIFY_BASE, default on) - Opt-in patch authenticity (
TMD_FEAT_VERIFY_SIG) via a pluggable, crypto-agnosticverify_patch()port — see below
TinyMLDelta separates integrity (default: CRC32 + base-slot digest verify) from authenticity (opt-in). CRC32 is integrity, not security — production deployments over an untrusted channel should enable signing.
// firmware build config (tinymldelta_config.h or -D flags)
#define TMD_FEAT_VERIFY_SIG 1 // require authenticity (fail-closed); 0 = offWhen enabled, the core verifies the patch before apply via one platform hook
(verify_patch()) and rejects it if missing or invalid. The core stays
crypto-agnostic, so the same hook plugs in SHA-256 + Ed25519/ECDSA, a secure
element, or a SUIT (RFC 9019) + COSE_Sign1 (RFC 9052) verifier — forward-
compatible with the IETF standards track. See docs/security.md
for the threat model and the recommended industry-standard stack.
- Capability-envelope provisioning for architecture updates (superset ops, arena, slot, schema-flexible I/O) — see docs/capability-envelope.md
- Reference Ed25519 / COSE_Sign1 verifier + SUIT manifest wrapping (see docs/security.md)
- SHA-256 digests; AES-CMAC / anti-rollback version
- Model versioning TLVs
- Zephyr RTOS port
- Arduino UNO R4 WiFi port
- ESP32 reference port added (
examples/esp32/) — needs on-hardware validation; Tachyon port next
TinyMLDelta safely updates models when the firmware remains compatible. Compatibility is enforced by metadata TLVs generated by PatchGen and validated by the MCU runtime.
- Weight and bias updates
- Quantization parameter changes
- Re-training the same architecture on new data
- Minor graph edits with no operator changes
- Same opset, ABI, arena size, and I/O schema
| Change | Why |
|---|---|
| New operators | Firmware must link the new kernels |
| Opset version change | Operator implementations differ |
| TFLM ABI change | Interpreter ABI mismatch |
| Larger arena requirement | Arena is fixed at compile time |
| Different I/O shapes or dtypes | Application code depends on these |
TinyMLDelta automatically rejects incompatible patches.
PC / CI MCU (device)
──────────────────── ──────────────────────────────
base.tflite ──┐
target.tflite ─┤
▼
PatchGen (Python)
• byte-level diff
• RLE compression flash slot A [active model]
• CRC32 per chunk flash slot B [inactive]
• metadata TLVs
│
│ OTA (serial / BLE / MQTT / …)
▼
TinyMLDelta Core (C)
• parse header + TLVs
• enforce guardrails
• copy A → B
• apply diff chunks → B
• verify CRC32
• atomic slot flip: B → active
│
▼
flash slot B [new active model]
PatchGen is stateless and runs off-target (laptop, CI server). TinyMLDelta Core is platform-agnostic C that lives in your firmware.
cd examples/posix
./setup.sh --runRuns the full flow — model generation → patch generation → simulated flash apply → verification — entirely on your Mac or Linux machine.
cd examples/UnoQ_TinyMLDeltaDemo
./setup.sh # install deps, deploy, compile, flash
python3 run_demo.py # train → update → infer| Example | Platform | What it shows |
|---|---|---|
examples/posix/ |
macOS / Linux | Full update flow, no hardware. Model gen → patch → simulated flash → verify. |
examples/UnoQ_TinyMLDeltaDemo/ |
Arduino UNO Q | Live temperature anomaly demo. Two variants: TFLite C API and Edge Impulse SDK. Train → patch → infer. |
examples/modelgen/ |
PC | Standalone TFLite model generator used by the POSIX demo. |
typedef struct __attribute__((packed)) {
uint8_t v; // format version (always 1)
uint8_t algo; // 0=NONE, 1=CRC32, 2=SHA256, 3=CMAC
uint16_t chunks_n; // number of diff chunks
uint32_t base_len; // expected base model size
uint32_t target_len; // expected target model size
uint8_t base_chk[32]; // integrity digest of base
uint8_t target_chk[32]; // integrity digest of target
uint16_t meta_len; // bytes of metadata TLVs that follow
uint16_t flags;
} tmd_hdr_t;| Tag | Name | Type | Purpose |
|---|---|---|---|
0x01 |
REQ_ARENA_BYTES |
u32 | Reject if firmware arena < this value (active when present) |
0x02 |
TFLM_ABI |
u16 | Reject if target ABI > firmware ABI (active when present) |
0x03 |
OPSET_HASH |
u32 | Reject on op-set hash mismatch — opt-in, off by default |
0x04 |
IO_HASH |
u32 | Reject on I/O signature mismatch — opt-in, off by default |
≥0x80 |
vendor | any | Ignored by core; application-defined |
OPSET_HASH/IO_HASHare opt-in. They enforce only if the firmware setsTMD_FIRMWARE_OPSET_HASH/TMD_ENFORCE_IO_HASH(both default off). The current core check is strict equality (reject on any mismatch). The roadmap reframes this toward accept within a declared capability envelope (op-set ⊆ linked set, I/O compatible — reject only on overflow); see docs/capability-envelope.md. That envelope-accept logic ships today in the reference example; moving it into the core guardrail is a roadmap item.
typedef struct __attribute__((packed)) {
uint32_t off; // byte offset into the model
uint16_t len; // payload length in bytes
uint8_t enc; // 0 = RAW, 1 = RLE
uint8_t has_crc; // 1 = CRC32 appended after payload
} tmd_chunk_hdr_t;cd cli/
./install.sh # creates .tinyenv + installs tensorflow
source .tinyenv/bin/activate
python3 tinymldelta_patchgen.py base.tflite target.tflite patch.tmdTinyMLDelta/
├── cli/
│ ├── install.sh Create .tinyenv + install CLI deps
│ ├── requirements.txt Python dependencies
│ ├── tinymldelta_patchgen.py PatchGen: diff engine, TLV writer, .tmd output
│ └── tinymldelta_meta_compute.py Optional: extract TFLite metadata for TLVs
│
├── examples/
│ ├── posix/ No-hardware simulation (macOS/Linux)
│ │ ├── setup.sh Install deps + build
│ │ ├── run_demo.sh End-to-end: generate → patch → apply → verify
│ │ ├── README.md
│ │ ├── demo_apply.c POSIX patch applier
│ │ ├── tinymldelta_ports_posix.c POSIX flash/journal/log port
│ │ ├── flash_layout.h Simulated A/B flash geometry
│ │ ├── make_flash.py Build flash.bin with A/B slots
│ │ └── verify_flash.py Confirm target model in flash after update
│ │
│ ├── modelgen/
│ │ ├── make_models.py Generate base.tflite + target.tflite for demos
│ │ └── README.md
│ │
│ └── UnoQ_TinyMLDeltaDemo/ Arduino UNO Q + Modulino Thermo (HS3003)
│ ├── arduino/
│ │ └── UnoQ_TinyMLDeltaDemo/
│ │ └── UnoQ_TinyMLDeltaDemo.ino STM32 sensor proxy sketch
│ ├── common/ Shared headers (both variants)
│ │ ├── msgpack.h MsgPack codec (header-only)
│ │ ├── router_client.h Arduino-router RPC client (header-only)
│ │ └── tmd_port_memory.h In-memory TinyMLDelta flash port
│ ├── tflite/ TFLite C API variant
│ │ ├── demo_app.cpp Demo app (TFLite C API inference)
│ │ ├── Makefile
│ │ └── deploy_service.sh Push + build + manage via ADB
│ ├── edgeimpulse/ Edge Impulse SDK variant
│ │ ├── demo_app_ei.cpp Demo app (EI run_classifier inference)
│ │ ├── Makefile
│ │ ├── deploy_service.sh Push + build + manage via ADB
│ │ ├── model-parameters/ EI-exported model metadata
│ │ └── tflite-model/ EI-compiled TFLite model
│ ├── make_model.py Train autoencoder, generate patch.tmd
│ ├── run_demo.py Automated end-to-end demo runner
│ ├── setup.sh One-time setup + deploy + flash
│ └── README.md
│
├── runtime/
│ ├── include/
│ │ ├── tinymldelta.h Public C API (tmd_apply_patch_from_memory)
│ │ ├── tinymldelta_config.h Build-time flags + firmware guardrail config
│ │ ├── tinymldelta_internal.h Wire format: tmd_hdr_t, tmd_chunk_hdr_t, TLVs
│ │ └── tinymldelta_ports.h Platform abstraction: flash, digest, slots, journal
│ └── src/
│ └── tinymldelta_core.c Platform-agnostic patch engine
│
├── CONTRIBUTING.md
├── SECURITY.md
├── LICENSE Apache-2.0
└── README.md This file
Contributions welcome — see CONTRIBUTING.md for guidelines.
Areas of highest interest:
- New MCU ports (Zephyr, ESP32, STM32 bare-metal, Tachyon)
- Edge Impulse frontend
- SHA-256 / AES-CMAC signing pipeline
- LZ4 or bsdiff compression backend
- CI test harness for the POSIX demo
Apache-2.0 © 2024–2026 Felix Galindo