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.

License: AGPL-3.0 Python: 3.10 – 3.12 OS: Windows · macOS · Linux Runs: 100% offline, CPU-only Interface: CLI

#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)

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

Package name on PyPI Install with 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

  1. Install Python from python.org (tick "Add python.exe to PATH") or from the Microsoft Store.
  2. Open PowerShell and run:
    py -m pip install shrinkray-cli

    If py isn't recognized, use python -m pip install shrinkray-cli instead.

  3. Verify:
    shrinkray --help
    shrinkray chips
Windows notes ShrinkRay forces UTF-8 output, so emoji verdicts work even in legacy consoles. If shrinkray isn't found after install, close and reopen the terminal (the Scripts folder is added to PATH at install time).

macOS

  1. Install Python 3.10–3.12 (via Homebrew: brew install python@3.12, or from python.org).
  2. In Terminal:
    python3 -m pip install shrinkray-cli
  3. Verify:
    shrinkray --help
    shrinkray chips

Linux (Debian/Ubuntu/Fedora/…)

  1. Make sure Python 3.10–3.12 and pip are installed (sudo apt install python3 python3-pip on Debian/Ubuntu).
  2. Install:
    python3 -m pip install shrinkray-cli

    On distros with PEP 668 "externally managed environment", use pipx (below) or a virtual environment.

  3. Verify:
    shrinkray --help
    shrinkray chips

Alternative install methods

MethodCommandNotes
pipx (isolated CLI)pipx install shrinkray-cliRecommended if you just want the tool, isolated from your other Python packages.
ESP-DL pipelinepip install "shrinkray-cli[espdl]"Adds ESP-PPQ for the ONNX → ESP32 pipeline. Heavier dependencies; everything else works without it.
Standalone binaryDownload from GitHub ReleasesOne-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 / HomebrewPlanned, not published yet.
About the TensorFlow download Pipeline A (Keras → quantized TFLite) uses TensorFlow, so the first pip install downloads a few hundred MB. That's normal. Inspecting .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.

FormatExtensionsNeeds TensorFlow?
Keras.keras, .h5Yes (loaded lazily, only when needed)
TensorFlow Lite.tfliteNo — parsed directly, TF-free
ONNX.onnxNo (needs the onnx package)
scikit-learn.pklNo
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

FlagMeaning
--chips extra.jsonMerge 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.

FlagValuesMeaning
--targetchip nameTarget chip, e.g. esp32s3 (see shrinkray chips). Case-insensitive. Required unless you use --all-chips or set it in shrinkray.toml.
--methodint8 (default) · int16 · float · inlineCompression method. inline is for scikit-learn .pkl only. int16 requires --data.
--datacal.npyCalibration samples (float32 .npy). Enables full-integer quantization and the accuracy check.
--outdirectoryOutput directory. Default: outputs/.
--all-chipsFit-check against the whole database, ranked by headroom. Always exits 0 — great for exploration.
--chipsmychips.jsonMerge 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.

MethodWhat you getNeeds --data?
int8Full-integer quantization (with data) or dynamic-range (without). The default — best size/speed trade-off on almost every MCU.Recommended
int1616-bit activations — higher accuracy headroom, larger model. For chips with more flash.Required
floatNo quantization — just repackages the model as a C array. Useful for baseline comparisons and for already-.tflite inputs.No
Notes Quantizing an already-.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
Note Peak-RAM estimation isn't possible for generated C code, so the verdict is based on flash and reported RAM shows 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.

ChipFlash (KiB)SRAM (KiB)Clock (MHz)FeaturesRuntime
esp324,096520240litert-m
esp32s38,192512240simdesp-dl
esp32p416,384768400ai-instructionsesp-dl
stm32f4071,024192168dspcmsis-nn
stm32h7432,0481,024480dspcmsis-nn
stm32u5752,048784160dspcmsis-nn
nrf528401,02425664dspcmsis-nn
nrf53401,024512128dspcmsis-nn
rp20402,048264133emlearn
rp23504,096520150emlearn
nano33ble1,02425664dsplitert-m
atmega328p32216emlearn

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:

VerdictConditionMeaning
✅ FITSflash ≤ 90% and RAM ≤ 70%Comfortable headroom — room for the runtime stack, your firmware, and future model growth.
⚠️ TIGHTflash ≤ 100% and RAM ≤ 90%It fits on paper, but you're close to the wall. Verify on real hardware before committing.
❌ TOO BIGanything worseWon't fit. Try a smaller method (int8), a smaller model, or a bigger chip. Exit code 3.
Honesty box The verdict covers the model artifact — it doesn't subtract the inference runtime's own flash/RAM footprint (LiteRT-M arena, ESP-DL, etc.) in v0.1. Runtime-overhead accounting is on the v1 roadmap; until then, treat ⚠️ TIGHT as "measure on the device".

#Reports

Every conversion writes two files into your output directory:

FileWhat it contains
report.mdMarkdown summary — perfect for pasting into GitHub issues, PRs, or docs.
report.htmlSelf-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

#Exit codes & CI usage

CodeMeaningTypical cause
0SuccessConverted and fits (or --all-chips, which always exits 0).
1Model errorUnreadable/unsupported model file, bad config, unknown chip, missing calibration file.
2Missing optional dependencyONNX input without the espdl extra — the error message tells you exactly what to pip-install.
3Doesn't fitConversion 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

SymptomCause & 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 2ESP-PPQ isn't installed. Run pip install "shrinkray-cli[espdl]" (quote the brackets on PowerShell/zsh).
--method int16 failsint16 quantization requires calibration data: add --data cal.npy.
Can't quantize a .tflite fileBy 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 modelsExpected: 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 modelTensorFlow 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 consoleHandled — ShrinkRay forces UTF-8 with safe fallback. Update to the latest release if you ever see an encoding error.
First pip install is slow / largeThat's TensorFlow (needed for Pipeline A). One-time cost; CPU build is enough.
Unknown chip nameChip names are case-insensitive; check shrinkray chips. Add your own via --chips mychips.json.

#Honest limitations (v0.1)

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