Nanoflare is a header-only C++17 extensible library designed to be a fast and lightweight alternative to Libtorch for real-time inference of Pytorch models. It was originally developed for using calibrated Pytorch models in audio plugins.
Nanoflare consists of a Python library located in the pynanoflare folder which acts as a wrapper for various Pytorch modules, and a corresponding header-only C++ library located in the include folder.
Calibrating and exporting a Nanoflare compatible Pytorch model to C++ is easy and involves the following steps:
- Calibrating the model implemented in Python using the
pynanoflaremodule - Serialising it to JSON with the
generate_docmethod - Loading its C++ instance using the
Nanoflare::ModelBuilderclass
If you would like to use your own neural network architecture, you would just:
- Define its Python class using the
pynanoflaremodule - Add a
generate_docfunction that handles its JSON serialisation - Write a C++ equivalent version that derives from the
Nanoflare::BaseModelvirtual abstract class.
New models can be trained and exported as any other built-in network architectures. Examining the Python and C++ code for the models provided and Nanoflare::ModelBuilder are great ressources for understanding how the code is structured.
Models are registered to Nanoflare::ModelBuilder at runtime, so new models can be defined in their own Python and C++ modules while still being managed by this class.
The basic layer types currently available are:
- BatchNorm1d
- Biquad
- Conv1d
- GRU
- GRUCell
- Linear
- LSTM
- LSTMCell
- PReLU
They were used to define the following custom block types:
- CausalDilatedConv1d
- FiLM
- MicroTCNBlock
- PlainSequential
- ResidualBlock
- TCNBlock
Which were in turn used to define the following models:
- HammersteinWiener
- MicroTCN
- ResRNN e.g. ResGRU or ResLSTM
- TCN
- WaveNet
The library uses Eigen3 for fast matrix computation, and nlohmann::json for saving and loading models to file. Both are defined as Git submodules and built with the library.
A simple way of using the library is to register it as a Git submodule to your project, add it as a sub-directory, and define the include folders with the NANOFLARE_INCLUDE_DIRS variable:
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/nanoflare)
include_directories(${NANOFLARE_INCLUDE_DIRS})The tests are handled by the Catch2 testing framework also defined as a Git submodule and use Libtorch as reference.
When configuring the tests, pass the path to the Libtorch directory to CMake as a CMAKE_PREFIX_PATH variable and define the NANOFLARE_TESTING variable:
mkdir build && cd build
cmake .. -DNANOFLARE_TESTING=ON -DCMAKE_PREFIX_PATH=<path/to/libtorch>Launch the build and run accuracy tests:
cmake --build .
make testThe benchmarks comparing the processing speed of the library with Libtorch is run through:
./tests/models_benchmarkingLibtorch can be quite slow for the first few runs post-load so to make it fairer, a few preliminary warm-up runs are performed before measuring its performance. The generate_test_data.py scripts generates a new set of data used in the accuracy testing.
On the testing machine, Nanoflare is substantially faster than Libtorch/TorchscriptJIT across all neural network architectures available.
| Benchmark Name | Mean Time | Std Dev |
|---|---|---|
| HammersteinWiener | 3.11 ms | 0.06 ms |
| HammersteinWiener TorchScript | 4.24 ms | 0.30 ms |
| MicroTCN | 0.81 ms | 0.03 ms |
| MicroTCN TorchScript | 2.05 ms | 0.06 ms |
| ResGRU | 2.98 ms | 0.06 ms |
| ResGRU TorchScript | 73.29 ms | 2.24 ms |
| ResLSTM | 2.84 ms | 0.24 ms |
| ResLSTM TorchScript | 3.82 ms | 0.24 ms |
| TCN | 1.10 ms | 0.01 ms |
| TCN TorchScript | 2.51 ms | 0.08 ms |
| WaveNet | 1.37 ms | 0.02 ms |
| WaveNet TorchScript | 2.41 ms | 0.05 ms |
Nanoflare uses static initialization to register models automatically. This allows you to add custom models in separate repositories without modifying nanoflare's code. This is useful for proprietary models or research projects.
1. Repository Structure:
your-private-models/
├── CMakeLists.txt
├── models/
│ ├── YourModel.h # C++ implementation
│ └── PrivateModels.h # Registration header
├── python/
│ ├── your_model.py # Python implementation
│ └── __init__.py
└── nanoflare/ # Git submodule pointing to this repo
2. C++ Model Registration:
Create a header file that registers your models using static initialization:
// models/PrivateModels.h
#pragma once
#include "nanoflare/ModelBuilder.h"
#include "YourModel.h"
#include "AnotherModel.h"
namespace YourNamespace
{
namespace {
inline bool registerPrivateModels()
{
// Register your custom models
Nanoflare::registerModel<YourModel>("YourModel");
Nanoflare::registerModel<AnotherModel>("AnotherModel");
return true;
}
// Auto-register during static initialization (before main())
static const bool _privateModelsRegistered = registerPrivateModels();
}
}3. CMake Integration:
In your private repository's CMakeLists.txt:
cmake_minimum_required(VERSION 3.24)
project(YourPrivateModels)
# Add nanoflare as subdirectory (git submodule)
add_subdirectory(nanoflare)
# Create your models library
add_library(your_models INTERFACE)
target_link_libraries(your_models INTERFACE nanoflare)
target_include_directories(your_models INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/models
)4. Using Custom Models in C++:
Simply include your registration header before using ModelBuilder:
#include "nanoflare/ModelBuilder.h"
#include "models/PrivateModels.h" // Auto-registers via static initialization
// Now your models are registered and can be loaded from JSON
std::ifstream model_file("your_model.json");
std::shared_ptr<Nanoflare::BaseModel> model;
nlohmann::json j = nlohmann::json::parse(model_file);
Nanoflare::ModelBuilder::getInstance().buildModel(j, model);