TinyML inference engine for embedded devices โ Rust no_std core with Python bindings
Project description
๐ง NANO-RUST-AI
TinyML Framework for Embedded Devices โ Rust no_std Core + Python Bindings
Train in PyTorch โ Quantize (i8) โ Run on MCU (ESP32, STM32, Cortex-M)
โจ Features
- ๐ No Heap: Pure
no_stdโ zeromalloc, zero dynamic allocation - โก Int8 Quantization: All compute in i8/i32 for 4ร memory savings over f32
- ๐ง Hybrid Memory: Frozen weights in Flash (0 bytes RAM), trainable head in RAM
- ๐ฏ Scale-Aware Requantization: TFLite-style
(acc ร M) >> shiftfor accurate i8 output - ๐ Python Bindings: PyO3 wrapper for seamless PyTorch โ NANO-RUST pipeline
- ๐ฆ Arena Allocator: User provides
&mut [u8]buffer โ library self-manages within it
๐ Quick Start
1. Prerequisites
| Tool | Version |
|---|---|
| Rust | 1.70+ (rustup install stable) |
| Python | 3.9+ |
| maturin | pip install maturin |
2. Create Virtual Environment
# Create and activate venv
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux/Mac
source .venv/bin/activate
# Install dependencies
pip install maturin numpy torch torchvision jupyter ipykernel
3. Build & Install the Library
# IMPORTANT: Set CARGO_TARGET_DIR outside OneDrive to avoid file locking
# Windows PowerShell:
$env:CARGO_TARGET_DIR = "$env:USERPROFILE\.nanorust_target"
# Build and install into the active venv
maturin develop --release
4. Register Jupyter Kernel (for notebooks)
python -m ipykernel install --user --name nanorust --display-name "NanoRust (venv)"
Then select the "NanoRust (venv)" kernel in Jupyter when running notebooks.
๐๏ธ Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Python (PyTorch + nano_rust_utils) โ โ Train & Quantize
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ PyO3 Binding (nano_rust_py) โ โ Bridge
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Rust Core (nano-rust-core) โ โ Inference Engine
โ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโโ โ
โ โ math.rsโ โlayers/ โ โarena.rs โ โ
โ โ matmul โ โdense โ โbump ptr โ โ
โ โ conv2d โ โconv โ โckpt/rst โ โ
โ โ relu โ โpool โ โโโโโโโโโโโ โ
โ โsigmoid โ โflatten โ โ
โ โ tanh โ โactivateโ โ
โ โโโโโโโโโโ โโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Memory Layout on MCU
FLASH (4MB) RAM (320KB)
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
โ Frozen Backbone โ โ Arena Buffer โ
โ - Conv2D weights โ โ โโโโโโโโโโโโโโโโ โ
โ - Dense weights โ โ โ Intermediate โ โ
โ - Bias arrays โ โ โ activations โ โ
โ (read-only, static) โ โ โโโโโโโโโโโโโโโโค โ
โ โ โ โ Trainable โ โ
โ โ โ โ Head weights โ โ
โ โ โ โโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
๐ Python API Reference
nano_rust_py.PySequentialModel
model = nano_rust_py.PySequentialModel(
input_shape=[C, H, W], # or [N] for 1D
arena_size=32768 # bytes for scratch memory
)
Layer Methods
| Method | Description |
|---|---|
add_dense(weights, bias) |
Dense layer (i8 weights/bias as lists) |
add_dense_with_requant(weights, bias, M, shift) |
Dense with calibrated requant |
add_conv2d(kernel, bias, in_ch, out_ch, kh, kw, stride, padding) |
Conv2D layer |
add_conv2d_with_requant(kernel, bias, in_ch, out_ch, kh, kw, stride, padding, M, shift) |
Conv2D with calibrated requant |
add_trainable_dense(in_features, out_features) |
Trainable Dense (RAM weights) |
add_relu() |
ReLU activation |
add_sigmoid() |
Sigmoid (fixed scale, for general use) |
add_sigmoid_scaled(scale_mult, scale_shift) |
Sigmoid with scale-aware LUT |
add_tanh() |
Tanh (fixed scale, for general use) |
add_tanh_scaled(scale_mult, scale_shift) |
Tanh with scale-aware LUT |
add_softmax() |
Softmax (pseudo-probabilities) |
add_flatten() |
Flatten 3Dโ1D |
add_max_pool2d(kernel, stride, padding) |
MaxPool2D |
Inference
output = model.forward(input_i8_list) # Returns list of i8 values
Python Utilities (scripts/nano_rust_utils.py)
from nano_rust_utils import quantize_to_i8, quantize_weights, calibrate_model
# Quantize input
q_input, input_scale = quantize_to_i8(float_array)
# Quantize model weights
q_weights = quantize_weights(pytorch_model)
# Calibrate requantization parameters
requant = calibrate_model(model, input_tensor, q_weights, input_scale)
# Returns dict: layer_name โ (M, shift, bias_corrected) for parametric layers
# ('sigmoid', mult, shift) for Sigmoid
# ('tanh', mult, shift) for Tanh
๐ Notebooks
Validation Notebooks (notebooks/)
Quick-run notebooks using _setup.py for auto-build:
| # | File | Description |
|---|---|---|
| 01 | 01_pipeline_validation.ipynb |
ConvโReLUโFlattenโDense end-to-end |
| 02 | 02_mlp_classification.ipynb |
MLP (DenseโReLUโDense) |
| 03 | 03_deep_cnn.ipynb |
Deep CNN with MaxPool |
| 04 | 04_activation_functions.ipynb |
ReLU vs Sigmoid vs Tanh comparison |
| 05 | 05_transfer_learning.ipynb |
Frozen backbone + trainable head |
Real-World Test Scripts (notebooks-for-test/)
GPU-accelerated training โ i8 quantization โ NANO-RUST verification:
| # | File | Task | Accuracy |
|---|---|---|---|
| 06 | run_06_mnist.py |
MNIST digit classification (CNN) | ~97% |
| 07 | run_07_fashion.py |
Fashion item classification (CNN) | ~87% |
| 08 | run_08_sensor.py |
Industrial sensor fusion (MLP) | ~98% |
| 09 | run_09_keyword_spotting.py |
Voice keyword spotting (MFCC+MLP) | ~79% |
| 10 | run_10_text_classifier.py |
Text classification (BoW+MLP) | 100% |
Run all tests:
python notebooks-for-test/run_06_mnist.py
# ... etc
๐ ESP32 Deployment Guide
Step 1: Train & Export in Python
import torch.nn as nn
from nano_rust_utils import quantize_weights, calibrate_model, export_to_rust
# 1. Train your PyTorch model
model = nn.Sequential(
nn.Linear(416, 128), nn.ReLU(),
nn.Linear(128, 64), nn.ReLU(),
nn.Linear(64, 10),
)
# ... train on GPU ...
# 2. Quantize & calibrate
q_weights = quantize_weights(model)
requant = calibrate_model(model, sample_input, q_weights, input_scale)
# 3. Export to Rust source code
rust_code = export_to_rust(model, "keyword_model", input_shape=[416])
with open("model.rs", "w") as f:
f.write(rust_code)
Step 2: Use in ESP32 Rust Firmware
#![no_std]
use nano_rust_core::{Arena, model::SequentialModel};
// Generated model from export_to_rust()
include!("model.rs");
#[entry]
fn main() -> ! {
// Arena in RAM โ size from model.estimate_arena_size()
let mut arena_buf = [0u8; 16384];
loop {
// Get sensor/audio data โ quantize to i8
let input: [i8; 416] = read_mfcc_features();
// Run inference (< 1ms on ESP32 @ 240MHz)
let mut arena = Arena::new(&mut arena_buf);
let model = build_keyword_model(); // From generated code
let (output, _) = model.forward(&input, &[416], &mut arena).unwrap();
let predicted_class = output.iter()
.enumerate()
.max_by_key(|(_, v)| **v)
.map(|(i, _)| i)
.unwrap();
}
}
Memory Budget (ESP32)
| Component | Flash | RAM |
|---|---|---|
| Frozen weights | 60KB | 0B |
| Arena buffer | 0B | 16KB |
| Code + stack | ~20KB | ~4KB |
| Total | ~80KB | ~20KB |
| Available | 4MB | 520KB |
๐ง Rust Core API (nano-rust-core)
Layers
use nano_rust_core::layers::*;
// Frozen (Flash) โ 0 bytes RAM for weights
let dense = FrozenDense::new_with_requant(weights, bias, in_f, out_f, M, shift)?;
let conv = FrozenConv2D::new_with_requant(kernel, bias, in_ch, out_ch, kh, kw, s, p, M, shift)?;
// Trainable (RAM) โ weights allocated in Arena
let head = TrainableDense::new(in_features, out_features);
// Activations
let _ = ReLULayer;
let _ = ScaledSigmoidLayer { scale_mult: 42, scale_shift: 8 };
let _ = ScaledTanhLayer { scale_mult: 84, scale_shift: 8 };
let _ = SoftmaxLayer;
// Structural
let _ = FlattenLayer;
let _ = MaxPool2DLayer::new(2, 2, 0)?;
Arena Allocator
use nano_rust_core::Arena;
let mut buf = [0u8; 32768];
let mut arena = Arena::new(&mut buf);
// Checkpoint/restore for scratch memory reuse
let cp = arena.checkpoint();
let scratch = arena.alloc_i8_slice(1024)?;
arena.restore(cp); // reclaim scratch memory
Sequential Model
use nano_rust_core::model::SequentialModel;
let mut model = SequentialModel::new();
model.add(Box::new(dense));
model.add(Box::new(ReLULayer));
let (output, shape) = model.forward(input, &input_shape, &mut arena)?;
๐ Accuracy Targets
| Model Type | Expected Max Diff (vs PyTorch) |
|---|---|
| Dense + ReLU | โค 3 |
| Conv + ReLU + Dense | โค 5 |
| Deep CNN + Pool | โค 10 |
| Sigmoid/Tanh (scaled) | โค 20 |
๐๏ธ Project Structure
nano-rust/
โโโ core/ # Rust no_std core library
โ โโโ src/
โ โโโ lib.rs # Crate root
โ โโโ arena.rs # Bump pointer allocator
โ โโโ math.rs # Matmul, conv2d, activations
โ โโโ error.rs # Error types
โ โโโ model.rs # SequentialModel
โ โโโ layers/
โ โโโ mod.rs # Layer trait + Shape
โ โโโ dense.rs # FrozenDense + TrainableDense
โ โโโ conv.rs # FrozenConv2D
โ โโโ activations.rs # ReLU, Sigmoid, Tanh, Softmax
โ โโโ flatten.rs # Flatten layer
โ โโโ pooling.rs # MaxPool2D
โโโ py_binding/ # PyO3 Python bindings
โ โโโ src/lib.rs
โโโ scripts/
โ โโโ nano_rust_utils.py # Quantization + calibration utilities
โ โโโ export.py # CLI weight exporter
โโโ notebooks/ # Quick validation notebooks (01-05)
โโโ notebooks-for-test/ # Real-world test scripts (06-10)
โโโ pyproject.toml # pip install configuration
โโโ Cargo.toml # Workspace config
โโโ LICENSE # MIT License
โโโ README.md
๐ License
๐ฎ Roadmap
- v0.1.0: Core inference engine with scale-aware requantization
- v0.2.0: Const Generics refactor for compile-time optimization
- v0.3.0: On-device training (backprop for trainable head)
- v0.4.0: ARM SIMD intrinsics (SMLAD) for Cortex-M
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nano_rust_py-0.1.0.tar.gz.
File metadata
- Download URL: nano_rust_py-0.1.0.tar.gz
- Upload date:
- Size: 31.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db5eb9b8ed305b68815b28ad2f1a0ecf88a4819f203fb5b34dc0dd21d86474de
|
|
| MD5 |
c69bbf446c558ef3e63c64a85cacdb4c
|
|
| BLAKE2b-256 |
1c4fe20e6c13400e8a87e435af95584c6c619ade756dd017bb86a4b1f6eb4689
|
File details
Details for the file nano_rust_py-0.1.0-cp39-cp39-win_amd64.whl.
File metadata
- Download URL: nano_rust_py-0.1.0-cp39-cp39-win_amd64.whl
- Upload date:
- Size: 158.5 kB
- Tags: CPython 3.9, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b269e706e866910f99ccdffa807781a3f96505d85f2fccb670f45fd4f9971b01
|
|
| MD5 |
3a91f0518a13b4de3e32fe33e405ec3e
|
|
| BLAKE2b-256 |
9b6e60fd6dd857ebb7e47387d8ed0141f8897134bb22ddba6874ae0a55a6bd25
|