# Show HN: ShrinkRay – HandBrake for TinyML

> Source: <https://github.com/TarulAhsan/ShrinkRay>
> Published: 2026-08-25 05:46:09+00:00

**HandBrake for TinyML** — one tool that takes any small model and produces a
chip-ready artifact, with a report proving it fits.

📖 **New here? Read the full user guide** — installation,
every command, and troubleshooting.

ShrinkRay compresses/quantizes small neural networks for microcontrollers and tells you — before you touch a firmware build — whether the result fits your target chip's flash and RAM.

- ✅
**FITS**/⚠️ **TIGHT**/ ❌** TOO BIG**verdict per chip - Keras/TFLite → int8/int16/float TFLite + C byte array (
`.cc`

/`.h`

) for LiteRT-M / CMSIS-NN - scikit-learn → single portable
**C99 header**(via emlearn; works even on ATmega328P) - ONNX →
`.espdl`

via ESP-PPQ (optional extra) - Self-contained
`report.md`

+`report.html`

after every conversion - 100% local:
**no network calls, no telemetry, no account**

```
pip install shrinkray-cli

shrinkray chips                                # the 12-chip database
shrinkray inspect model.keras                  # params, size, ops, FLOPs
shrinkray convert model.keras --target esp32s3 --method int8 --data cal.npy
```

Output:

```
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
(`numpy.save("cal.npy", x[:100].astype("float32"))`

). With it you get
full-integer quantization **and** an accuracy-delta check; without it, int8
falls back to dynamic-range quantization.

| Command | What it does |
|---|---|
`shrinkray inspect <model>` |
Format, parameter count, file size, operator histogram, FLOP estimate for `.keras` / `.h5` / `.tflite` / `.onnx` / `.pkl` |
`shrinkray chips [--chips extra.json]` |
Print the chip database; merge your own chips from JSON |
`shrinkray convert <model> --target <chip>` |
Convert + fit verdict + reports |

Useful `convert`

flags:

| Flag | Meaning |
|---|---|
`--method int8|int16|float|inline` |
int16 needs `--data` ; `inline` is for scikit-learn `.pkl` |
`--data cal.npy` |
Calibration samples (full-integer quant + accuracy check) |
`--out DIR` |
Output directory (default: `outputs/` ) |
`--all-chips` |
Fit-check against the whole database, ranked by headroom (always exits 0) |
`--chips mychips.json` |
Merge custom chip definitions |

Exit codes: `0`

ok · `1`

model error · `2`

missing optional dependency ·
`3`

converted but doesn't fit the target.

Drop a `shrinkray.toml`

in your project directory to stop repeating flags
(CLI flags always win over the file):

```
[defaults]
target = "esp32s3"
data = "cal.npy"
out = "build"
```

Ships with 12 chips: esp32, esp32s3, esp32p4, stm32f407, stm32h743, stm32u575, nrf52840, nrf5340, rp2040, rp2350, nano33ble, atmega328p. Each entry: flash KiB, SRAM KiB, clock MHz, feature flags (dsp / simd / ai-instructions), preferred runtime (litert-m / cmsis-nn / esp-dl / emlearn).

Extend it with your own JSON (`--chips mychips.json`

; your entries override
built-ins on name collision):

```
{"chips": [{"name": "myboard", "flash_kb": 512, "sram_kb": 128,
            "clock_mhz": 100, "features": ["dsp"], "runtime": "cmsis-nn"}]}
```

**Flash**= actual artifact bytes measured on disk.** Peak RAM**= tensor-liveness analysis of the TFLite graph: the largest sum of concurrently live activation tensors × 1.2 safety factor.**Verdicts**: FITS = flash ≤ 90% and RAM ≤ 70%; TIGHT = flash ≤ 100% and RAM ≤ 90%; otherwise TOO BIG.

On PyPI the package is

(the name`shrinkray-cli`

`shrinkray`

was already taken by an unrelated project). The command it installs is still`shrinkray`

.

| Method | Command | Notes |
|---|---|---|
| pip (Win/Linux/macOS) | `pip install shrinkray-cli` |
Python 3.10–3.12, CPU-only is fine |
| pip + ESP-DL pipeline | `pip install shrinkray-cli[espdl]` |
adds ESP-PPQ (heavier deps) |
| pipx | `pipx install shrinkray-cli` |
isolated CLI install |
| Standalone binary | GitHub Releases | experimental; bundles TF, so it's big — pip is recommended |
| winget / brew | planned | not published yet |

**No GUI, no training**— input is an already-trained model ≤ ~100 MB.- Pipeline A (Keras→TFLite) requires TensorFlow (CPU build is fine). Everything else — TFLite inspect, sklearn pipeline, chips, reports — works without it.
- RAM estimates are heuristics, not measurements: TFLite liveness ×1.2 for Pipeline A; not estimable for emlearn C code (reported as n/a).
- Quantizing an already-
`.tflite`

file is not supported — pass the original`.keras`

/`.h5`

(or use`--method float`

to repackage as a C array). - Pipeline C requires
`pip install shrinkray-cli[espdl]`

; without it the command prints install instructions and exits 2 — everything else keeps working. - No ONNX Runtime quantization, no RISC-V paths, no model zoo (v0.2+).

```
pip install -e ".[dev]"
pytest --cov=shrinkray.core --cov=shrinkray.pipelines   # coverage gate: 80%
```

CI runs pytest on Windows / Ubuntu / macOS × Python 3.10 / 3.11 / 3.12
(`.github/workflows/test.yml`

); tag pushes build standalone binaries
(`.github/workflows/release.yml`

).

Test fixtures (tiny Keras net, sklearn forest, ONNX graph) are generated at runtime — no binary models in the repo.

GNU Affero General Public License v3.0 (AGPL-3.0-only) — see [LICENSE](/tarulahsan/ShrinkRay/blob/main/LICENSE).

Free for everyone to use, study, and modify — including commercial internal use. If you distribute ShrinkRay or a modified version, or offer it as a network service, you must share your source under the same license. Commercial closed-source licensing is available on request; contact the maintainer.
