ShrinkRay User Guide
ShrinkRay compresses and quantizes small neural networks for microcontrollers — and tells you, before you touch a firmware build, whether the result actually fits your chip's flash and RAM.
You drop in a trained model (.keras, .h5, .tflite,
.onnx, or scikit-learn .pkl), pick a target chip, and get back a
chip-ready artifact (quantized TFLite + C byte array, a C99 header, or an ESP-DL model) plus a
self-contained report proving it fits — with a ✅ FITS / ⚠️ TIGHT / ❌ TOO BIG verdict.
#Who it's for
Makers & hobbyists
You trained a keyword-spotter or gesture model and want it on your ESP32 or Arduino Nano 33 BLE without a weekend of converter-wrangling.
Embedded firmware engineers
You get models from a data-science colleague and need a repeatable, scriptable way to quantize them and prove they fit the STM32/nRF you're shipping. Exit codes make it CI-friendly.
ML engineers going to the edge
You know Keras, not CMSIS-NN. ShrinkRay hides the toolchain sharp edges and gives you the C array + integration notes in one step.
Students & educators
Teaches the real constraints of TinyML: quantization trade-offs, flash/RAM budgets, per-chip limits — with honest numbers instead of magic.
Who it's not for (v0.1)
- Anyone who wants a GUI — v0.1 is a terminal tool (a VS Code extension is on the roadmap).
- Anyone who wants to train models — ShrinkRay starts from an already-trained model.
- Anyone deploying to Linux-class edge boxes, phones, or GPUs — ShrinkRay targets microcontroller-class chips (KiB-to-MiB of flash/RAM).
- Models larger than ~100 MB — this is a TinyML tool, not an LLM tool.
#Feature list
🔍 Inspect any model
Format, parameter count, file size, operator histogram and FLOP estimate for Keras, TFLite, ONNX and scikit-learn models. TFLite inspection works without TensorFlow installed.
🗜️ Quantize & convert
Keras/TFLite → int8 / int16 / float TFLite + C byte array (.cc/.h) for LiteRT-M or CMSIS-NN. scikit-learn → portable C99 header via emlearn. ONNX → .espdl via ESP-PPQ.
🎯 Fit verdicts
Every conversion ends in a verdict per chip: ✅ FITS, ⚠️ TIGHT, or ❌ TOO BIG — based on real artifact bytes and tensor-liveness peak-RAM estimation with a 1.2× safety factor.
📊 Accuracy-delta check
With a small calibration set (--data cal.npy) you get max-abs-error and top-1 agreement between the float and quantized model — no silent accuracy surprises.
🧾 Self-contained reports
Every conversion writes report.md and report.html (inline-SVG charts, zero dependencies) with next-step integration notes per runtime.
💾 12-chip database
ESP32/S3/P4, STM32 F4/H7/U5, nRF52/53, RP2040/RP2350, Nano 33 BLE, ATmega328P — extendable with your own JSON chip definitions.
🖥️ Cross-platform
Windows, macOS, Linux. Tested in CI on all three × Python 3.10/3.11/3.12. Handles legacy Windows consoles, spaces and Unicode in paths.
🔒 100% local & free
No network calls, no telemetry, no account, AGPL-3.0 open source. Your models never leave your machine.
⚙️ Config + CI friendly
shrinkray.toml for per-project defaults; machine-meaningful exit codes (0/1/2/3) so CI can gate on "model must fit".
🧩 Graceful degradation
ESP-PPQ is an optional extra — without it you get a friendly install hint and exit code 2, while everything else keeps working.
#Installation
ShrinkRay needs Python 3.10, 3.11 or 3.12. CPU-only is perfectly fine — no GPU, no cloud. Pick your OS below. Every command ends with the same verification step.
pip install shrinkray-cli — the plain name shrinkray was already
taken on PyPI by an unrelated project. The command you run after installing is still just shrinkray.Windows 10/11
- Install Python from python.org (tick "Add python.exe to PATH") or from the Microsoft Store.
- Open PowerShell and run:
py -m pip install shrinkray-cliIf
pyisn't recognized, usepython -m pip install shrinkray-cliinstead. - Verify:
shrinkray --help shrinkray chips
shrinkray
isn't found after install, close and reopen the terminal (the Scripts folder is added to PATH at install time).macOS
- Install Python 3.10–3.12 (via Homebrew:
brew install python@3.12, or from python.org). - In Terminal:
python3 -m pip install shrinkray-cli - Verify:
shrinkray --help shrinkray chips
Linux (Debian/Ubuntu/Fedora/…)
- Make sure Python 3.10–3.12 and pip are installed (
sudo apt install python3 python3-pipon Debian/Ubuntu). - Install:
python3 -m pip install shrinkray-cliOn distros with PEP 668 "externally managed environment", use pipx (below) or a virtual environment.
- Verify:
shrinkray --help shrinkray chips
Alternative install methods
| Method | Command | Notes |
|---|---|---|
| pipx (isolated CLI) | pipx install shrinkray-cli | Recommended if you just want the tool, isolated from your other Python packages. |
| ESP-DL pipeline | pip install "shrinkray-cli[espdl]" | Adds ESP-PPQ for the ONNX → ESP32 pipeline. Heavier dependencies; everything else works without it. |
| Standalone binary | Download from GitHub Releases | One-file executables for Windows/macOS/Linux, no Python needed. Experimental — bundles TensorFlow, so it's large; pip is recommended. |
| From source (dev) | pip install -e ".[dev]" | For contributors — adds pytest, coverage, onnx, scikit-learn. |
| winget / Homebrew | — | Planned, not published yet. |
.tflite files, the scikit-learn pipeline, the chip database and the
reports all work without TensorFlow — a lighter TF-optional install is on the v1 roadmap.#30-second quickstart
shrinkray chips # browse the 12-chip database
shrinkray inspect model.keras # params, size, ops, FLOPs
shrinkray convert model.keras --target esp32s3 --method int8 --data cal.npy
Output looks like this:
wrote outputs/model_int8.tflite (2.7 KiB)
wrote outputs/model_int8.cc
wrote outputs/model_int8.h
accuracy max-abs-error 0.002717, top-1 agreement 100.0%
esp32s3: ✅ FITS — flash 2.7 KiB / 8192.0 KiB (0.0%), est. RAM 0.1 KiB / 512.0 KiB (0.0%)
wrote outputs/report.md
wrote outputs/report.html
--data cal.npy is a small float32 array of representative samples — create it from your
training data with one line of NumPy:
import numpy as np
np.save("cal.npy", x_train[:100].astype("float32"))
With calibration data you get full-integer quantization and the accuracy-delta check. Without it, int8 falls back to dynamic-range quantization (still fine, slightly less optimal on-chip).
#Command reference
ShrinkRay has three commands. Run any of them with --help for the built-in summary.
1 · shrinkray inspect <model> — know your model
Shows format, file size, parameter count, estimated FLOPs and an operator/layer histogram — before you convert anything. No target chip needed.
| Format | Extensions | Needs TensorFlow? |
|---|---|---|
| Keras | .keras, .h5 | Yes (loaded lazily, only when needed) |
| TensorFlow Lite | .tflite | No — parsed directly, TF-free |
| ONNX | .onnx | No (needs the onnx package) |
| scikit-learn | .pkl | No |
shrinkray inspect gesture.keras
Model: gesture.keras
┌──────────────────┬───────────┐
│ Property │ Value │
├──────────────────┼───────────┤
│ Format │ keras │
│ File size │ 5.2 KiB │
│ Parameters │ 12,403 │
│ Estimated FLOPs │ 24,614 │
└──────────────────┴───────────┘
Operators / layers
┌───────────────┬───────┐
│ Op │ Count │
├───────────────┼───────┤
│ Dense │ 3 │
│ ReLU │ 2 │
└───────────────┴───────┘
2 · shrinkray chips — browse target hardware
| Flag | Meaning |
|---|---|
--chips extra.json | Merge your own chip definitions from a JSON file (your entries override built-ins with the same name). |
shrinkray chips # print all 12 built-in chips
shrinkray chips --chips my.json # built-ins + your custom chips
3 · shrinkray convert <model> — shrink it & prove it fits
The main event: converts the model, measures the artifact, estimates peak RAM, prints the fit verdict, and writes the reports.
| Flag | Values | Meaning |
|---|---|---|
--target | chip name | Target chip, e.g. esp32s3 (see shrinkray chips). Case-insensitive. Required unless you use --all-chips or set it in shrinkray.toml. |
--method | int8 (default) · int16 · float · inline | Compression method. inline is for scikit-learn .pkl only. int16 requires --data. |
--data | cal.npy | Calibration samples (float32 .npy). Enables full-integer quantization and the accuracy check. |
--out | directory | Output directory. Default: outputs/. |
--all-chips | — | Fit-check against the whole database, ranked by headroom. Always exits 0 — great for exploration. |
--chips | mychips.json | Merge custom chip definitions before checking fit. |
Examples
# Keras model → int8 C array for an ESP32-S3, with accuracy check
shrinkray convert model.keras --target esp32s3 --method int8 --data cal.npy
# No calibration data → dynamic-range quantization
shrinkray convert model.keras --target stm32u575 --method int8
# Which chips can take this model at all? (ranked table, always exit 0)
shrinkray convert model.keras --all-chips --data cal.npy
# scikit-learn random forest → C99 header (works even on ATmega328P)
shrinkray convert forest.pkl --target nano33ble --method inline
# ONNX → ESP-DL (needs: pip install "shrinkray-cli[espdl]")
shrinkray convert yolo_nano.onnx --target esp32p4
# Custom board not in the database
shrinkray convert model.keras --target myboard --chips mychips.json --data cal.npy
#The 3 pipelines
ShrinkRay picks the pipeline automatically from the file extension — you never choose it yourself. If no pipeline can handle your file, you get a clear error listing what's supported.
Pipeline A — tflite_c · Keras/TFLite → quantized TFLite + C array
The workhorse. Input: .keras, .h5 or .tflite.
Output: a quantized .tflite plus a .cc/.h pair containing
const unsigned char g_model[] — ready to drop into a LiteRT-M (TFLite Micro) or CMSIS-NN project.
| Method | What you get | Needs --data? |
|---|---|---|
int8 | Full-integer quantization (with data) or dynamic-range (without). The default — best size/speed trade-off on almost every MCU. | Recommended |
int16 | 16-bit activations — higher accuracy headroom, larger model. For chips with more flash. | Required |
float | No quantization — just repackages the model as a C array. Useful for baseline comparisons and for already-.tflite inputs. | No |
.tflite file isn't supported (the original calibration info is gone) —
pass the source .keras/.h5, or use --method float to repackage.
Keras input and quantization need TensorFlow installed (CPU build is fine).Pipeline B — sklearn_c · scikit-learn → C99 header
Input: a pickled scikit-learn model (.pkl) — trees, forests, small MLPs.
Method: inline. Output: a single portable C99 header (via emlearn) with no runtime
dependency — small enough for an ATmega328P with 2 KB of RAM.
shrinkray convert forest.pkl --target atmega328p --method inline
n/a (a static bound is on the v1 roadmap).Pipeline C — espdl · ONNX → ESP-DL (optional)
Input: .onnx. Output: Espressif's .espdl format for ESP32 / ESP32-S3 / ESP32-P4,
quantized by ESP-PPQ. This pipeline is an optional extra:
pip install "shrinkray-cli[espdl]"
Without the extra, converting an .onnx file prints the install hint and exits with code 2 —
every other command keeps working. Nothing breaks, nothing half-installs.
#Chip database
Twelve chips ship built in. The Runtime column is the recommended inference stack for that chip — the reports include matching integration next-steps.
| Chip | Flash (KiB) | SRAM (KiB) | Clock (MHz) | Features | Runtime |
|---|---|---|---|---|---|
esp32 | 4,096 | 520 | 240 | — | litert-m |
esp32s3 | 8,192 | 512 | 240 | simd | esp-dl |
esp32p4 | 16,384 | 768 | 400 | ai-instructions | esp-dl |
stm32f407 | 1,024 | 192 | 168 | dsp | cmsis-nn |
stm32h743 | 2,048 | 1,024 | 480 | dsp | cmsis-nn |
stm32u575 | 2,048 | 784 | 160 | dsp | cmsis-nn |
nrf52840 | 1,024 | 256 | 64 | dsp | cmsis-nn |
nrf5340 | 1,024 | 512 | 128 | dsp | cmsis-nn |
rp2040 | 2,048 | 264 | 133 | — | emlearn |
rp2350 | 4,096 | 520 | 150 | — | emlearn |
nano33ble | 1,024 | 256 | 64 | dsp | litert-m |
atmega328p | 32 | 2 | 16 | — | emlearn |
Add your own chip
Create a JSON file and pass it with --chips. Your entries override built-ins when names collide.
// mychips.json
{"chips": [
{"name": "myboard", "flash_kb": 512, "sram_kb": 128,
"clock_mhz": 100, "features": ["dsp"], "runtime": "cmsis-nn"}
]}
shrinkray convert model.keras --target myboard --chips mychips.json --data cal.npy
#Fit verdicts — how the decision is made
Two numbers drive every verdict:
- Flash = the actual artifact size, measured on disk. No estimation involved.
- Peak RAM = tensor-liveness analysis of the TFLite graph: ShrinkRay walks the operator graph, tracks which activation tensors are alive at each step, takes the largest concurrently-live sum, and applies a ×1.2 safety factor. It's a solid heuristic — but it is an estimate, not an on-device measurement.
| Verdict | Condition | Meaning |
|---|---|---|
| ✅ FITS | flash ≤ 90% and RAM ≤ 70% | Comfortable headroom — room for the runtime stack, your firmware, and future model growth. |
| ⚠️ TIGHT | flash ≤ 100% and RAM ≤ 90% | It fits on paper, but you're close to the wall. Verify on real hardware before committing. |
| ❌ TOO BIG | anything worse | Won't fit. Try a smaller method (int8), a smaller model, or a bigger chip. Exit code 3. |
#Reports
Every conversion writes two files into your output directory:
| File | What it contains |
|---|---|
report.md | Markdown summary — perfect for pasting into GitHub issues, PRs, or docs. |
report.html | Self-contained page (no external assets, works offline) with model info, artifact details, the fit verdict, an inline-SVG usage bar chart, and integration next-steps matched to your chip's runtime (LiteRT-M, CMSIS-NN, ESP-DL, or emlearn). |
The report is the "proof" half of ShrinkRay: attach it to a PR to show the team exactly what the model costs on the target hardware.
#Config file — shrinkray.toml
Tired of repeating flags? Drop a shrinkray.toml in your project directory:
# shrinkray.toml
[defaults]
target = "esp32s3"
data = "cal.npy"
out = "build"
Now a bare shrinkray convert model.keras uses those defaults. Precedence is strict and simple:
CLI flag ▸ shrinkray.toml ▸ built-in default
- Only three keys are supported:
target,data,out(all strings). Unknown keys or wrong types produce a friendly error, not a crash. - The file is read from the current working directory — one config per project folder.
- On Python 3.10 you need the
tomlipackage (installed automatically as a dependency); 3.11+ has TOML built in.
#Exit codes & CI usage
| Code | Meaning | Typical cause |
|---|---|---|
0 | Success | Converted and fits (or --all-chips, which always exits 0). |
1 | Model error | Unreadable/unsupported model file, bad config, unknown chip, missing calibration file. |
2 | Missing optional dependency | ONNX input without the espdl extra — the error message tells you exactly what to pip-install. |
3 | Doesn't fit | Conversion succeeded, but the verdict is ❌ TOO BIG for the target chip. |
Because "doesn't fit" is its own exit code, you can gate CI on model size:
# Fail the build if the model outgrows the chip
shrinkray convert model.keras --target stm32u575 --method int8 --data cal.npy || exit 1
Every error also comes with a Fix: suggestion line telling you the likely remedy — ShrinkRay never just dumps a traceback for expected problems.
#Troubleshooting
| Symptom | Cause & fix |
|---|---|
'shrinkray' is not recognized (Windows) | The Scripts folder isn't on PATH yet — close and reopen the terminal. Still stuck? Use py -m shrinkray --help (works without PATH), or reinstall with py -m pip install --force-reinstall shrinkray-cli. |
| Error: No pipeline can handle '.xyz' | Supported inputs: .keras/.h5/.tflite (Pipeline A), .pkl scikit-learn (B), .onnx (C, needs the espdl extra). |
| ONNX conversion exits with code 2 | ESP-PPQ isn't installed. Run pip install "shrinkray-cli[espdl]" (quote the brackets on PowerShell/zsh). |
--method int16 fails | int16 quantization requires calibration data: add --data cal.npy. |
Can't quantize a .tflite file | By design — quantization needs the pre-quantization graph. Pass the original .keras/.h5, or use --method float to repackage the TFLite as a C array. |
RAM shows n/a for sklearn models | Expected: generated C code can't be liveness-analyzed. Flash verdict still applies; a static RAM bound is on the v1 roadmap. |
| int8 output barely smaller than float on a tiny model | TensorFlow skips quantizing very small layers. On real-sized models int8 shrinks ~4×; the accuracy check confirms the result either way. |
| Weird characters / crash on old Windows console | Handled — ShrinkRay forces UTF-8 with safe fallback. Update to the latest release if you ever see an encoding error. |
| First pip install is slow / large | That's TensorFlow (needed for Pipeline A). One-time cost; CPU build is enough. |
| Unknown chip name | Chip names are case-insensitive; check shrinkray chips. Add your own via --chips mychips.json. |
#Honest limitations (v0.1)
- No GUI, no training — input is an already-trained model ≤ ~100 MB.
- Pipeline A (Keras → TFLite) requires TensorFlow; everything else — TFLite inspect, sklearn pipeline, chips, reports — works without it.
- RAM figures are estimates (liveness ×1.2), not device measurements. Fit verdicts don't yet subtract the inference runtime's own footprint.
- Quantizing an already-
.tflitefile is not supported — pass the original Keras model (or--method float). - Pipeline C requires
pip install "shrinkray-cli[espdl]"; without it you get install instructions and exit code 2 — everything else keeps working. - No ONNX Runtime quantization, no RISC-V paths, no model zoo — planned for later versions.
#FAQ
Is it really free?
Yes — AGPL-3.0 open source. Free for personal and commercial internal use; no account, no telemetry, no network calls, no watermark, no paid tier hiding features. Source-sharing only applies if you distribute ShrinkRay itself or offer it as a service.
Does it need an internet connection?
Only to install. After that, everything runs 100% locally on your machine — your models never leave it.
Do I need a GPU?
No. Quantizing small models is seconds-to-minutes of CPU work. A modest laptop is plenty.
Can I use a PyTorch model?
In v0.1: export to ONNX and use Pipeline C (ESP32-family targets, requires the espdl extra). Broader PyTorch/ExecuTorch support is high on the roadmap.
Which method should I pick?
Start with --method int8 --data cal.npy. If accuracy drops too much, try int16. Use float only as a baseline. For scikit-learn there's just inline — it's already tiny.
How many calibration samples do I need?
50–500 representative samples from real training/validation data are usually enough. More variety matters more than more rows.
The verdict is TIGHT — should I ship it?
Measure on the real device first. TIGHT means the numbers fit on paper within the safety margins; runtime overhead and your firmware's own RAM aren't counted yet.
Where do I report bugs or request chips?
Open an issue on the GitHub repository — chip requests and pipeline requests are welcome and directly drive the roadmap.