Skip to content
 
 

Repository files navigation

TinyMLDelta

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.


Results

POSIX simulation (no hardware required)

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.

Arduino UNO Q — live temperature anomaly demo

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 ***

Benchmarks

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.


What it solves

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

Supported today

  • 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 needs TMD_FEAT_LZ4TINY)
  • COPY/ADD structure-aware patches (--copy-add; device needs TMD_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-agnostic verify_patch() port — see below

Security

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 = off

When 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.

On the roadmap

  • 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

When to use a patch vs a full firmware update

TinyMLDelta safely updates models when the firmware remains compatible. Compatibility is enforced by metadata TLVs generated by PatchGen and validated by the MCU runtime.

✔ Patch-friendly (no firmware update needed)

  • 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

✖ Requires a full firmware update

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.


Architecture

   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.


Quickstart

Option A — No hardware (POSIX simulation)

cd examples/posix
./setup.sh --run

Runs the full flow — model generation → patch generation → simulated flash apply → verification — entirely on your Mac or Linux machine.

Option B — Arduino UNO Q + Modulino Thermo (HS3003)

cd examples/UnoQ_TinyMLDeltaDemo
./setup.sh                 # install deps, deploy, compile, flash
python3 run_demo.py        # train → update → infer

Examples

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.

Wire format

Patch header (tmd_hdr_t, packed, little-endian)

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;

Metadata TLVs ([tag][len][value...])

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_HASH are opt-in. They enforce only if the firmware sets TMD_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.

Chunk header (tmd_chunk_hdr_t)

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;

Installation (CLI / PatchGen only)

cd cli/
./install.sh                    # creates .tinyenv + installs tensorflow
source .tinyenv/bin/activate
python3 tinymldelta_patchgen.py base.tflite target.tflite patch.tmd

Directory layout

TinyMLDelta/
├── 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

Contributing

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

License

Apache-2.0 © 2024–2026 Felix Galindo

About

TinyMLDelta is an incremental model-update system for TinyML and embedded AI devices.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages