Convert BirdNET V3.0 PyTorch audio classification models into TensorFlow/LiteRT, TorchScript, and ONNX formats.
Handles dynamic input lengths and batch sizes, including post-conversion fixes for Conv2dSame padding and TFLite static ops.
Status: Experimental — under active development. Things will change and break.
This repository contains scripts to convert BirdNET V3.0 PyTorch models (EfficientNetV2-S backbone, multi-head architecture, ~11K species) into deployment-ready formats:
| Format | Output (FP32) | FP16 | INT8 |
|---|---|---|---|
| TF SavedModel (preprocessing) | export/tf/preprocessing/ |
— | — |
| TF SavedModel (main) | export/tf/main/ |
— | — |
| TFLite — preprocessing | export/tf/preprocessing/model.tflite |
— | — |
| TFLite — main (spectrogram in) | export/tf/main/model.tflite |
model_fp16.tflite |
model_int8.tflite ¹ |
| TFLite — combined (audio in) | export/tf/combined/model.tflite |
model_fp16.tflite |
model_int8.tflite ¹ |
| TorchScript | export/torchscript/model.pt |
model_fp16.pt ¹ |
— |
| ONNX | export/onnx/model.onnx |
model_fp16.onnx |
model_int8.onnx ¹ |
¹ Requires --experimental-mode (see Experimental Mode).
Additional utilities:
fix_conv2dsame.py— Replaces baked-in static Conv2dSame padding in ONNX exports with dynamic padding subgraphs.fix_tflite_dynamic_input.py— Patches TFLite flatbuffers to fix static batch/temporal dimensions afterlitert_torchconversion.test_models.py— Compares converted model outputs (ONNX, LiteRT, TorchScript) against the PyTorch reference across different batch sizes and segment durations.
Download or clone the repository:
git clone https://github.com/birdnet-team/model-converter.git
cd model-converterGit LFS required: Model checkpoints are stored with Git LFS. If
git lfsis not installed, checkpoint files will be small pointer files instead of the actual weights. Install Git LFS and pull:sudo apt install git-lfs # or: brew install git-lfs git lfs install git lfs pull
Create a virtual environment (choose one):
Option A — conda:
conda create --name model-converter python=3.12
conda activate model-converter
pip install -r requirements.txtOption B — venv:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtNote: Python 3.10 and 3.11 are also supported, but 3.12 is recommended.
Convert the PyTorch model to all supported formats and precisions:
python convert_model.pyConverted models are written to export/ by default. Use -o to change the output directory:
python convert_model.py -o output/my_exportConvert to specific formats only:
python convert_model.py -f onnx torchscript
python convert_model.py -f tf tfliteExport FP32 only (skip FP16 and INT8):
python convert_model.py -p fp32Export only ONNX in FP16:
python convert_model.py -f onnx -p fp16Use only Head 1 for predictions (reduces model size by pruning unused heads):
python convert_model.py --head-weights 1.0 0.0 0.0Enable full INT8 quantization for ONNX and TFLite (experimental):
python convert_model.py -p int8 --experimental-modeUse a different model directory:
python convert_model.py -m models/my_custom_modelList available models:
python convert_model.py --list-modelsusage: convert_model.py [-h] [-m MODEL_DIR] [-o OUTPUT_DIR] [-f FMT [FMT ...]]
[-p PREC [PREC ...]]
[--batch-size BATCH_SIZE]
[--segment-duration SEGMENT_DURATION]
[--head-weights W W W]
[--experimental-mode]
[--list-models]
-m, --model-dir Path to model directory (default: models/01_BirdNET+_...)
-o, --output-dir Directory for converted model output (default: export/)
-f, --formats FMT Output format(s): tf, tflite, torchscript, onnx (default: all)
-p, --precisions PREC Precision variant(s): fp32, fp16, int8 (default: all)
--batch-size Batch size baked into TFLite models (default: 1)
--segment-duration Min audio segment duration in seconds for tracing (default: 3.0)
--head-weights W W W Weights for the three classifier heads (default: from model definition)
--experimental-mode Enable experimental features like full INT8
--list-models List available models and exit
The BirdNET V3.0 model uses a multi-head architecture with three classifier heads whose outputs are combined as a weighted sum of logits before applying sigmoid:
predictions = sigmoid(w0 * head1_logits + w1 * head2_logits + w2 * head3_logits)
The default weights are [0.4, 0.35, 0.25] as defined in the model definition. You can override them at export time with --head-weights:
# Use default weights [0.4, 0.35, 0.25]
python convert_model.py
# Use only Head 1
python convert_model.py --head-weights 1.0 0.0 0.0
# Use Heads 1 and 2 equally
python convert_model.py --head-weights 0.5 0.5 0.0
# Custom blend
python convert_model.py --head-weights 0.6 0.3 0.1Automatic pruning: When a head weight is set to 0.0, the corresponding head's layers (fully connected layers, attention blocks) are replaced with tiny 1×1 dummy layers after loading the checkpoint. This significantly reduces the exported model size:
| Configuration | Approximate model size |
|---|---|
All heads [0.4, 0.35, 0.25] |
~520 MB |
Head 1 only [1.0, 0.0, 0.0] |
~155 MB |
This is useful for deploying smaller models on edge devices when only a single head's predictions are needed.
Some features are gated behind --experimental-mode because they are unstable or produce models with significant accuracy degradation:
python convert_model.py --experimental-modeFeatures requiring experimental mode:
| Feature | Why experimental |
|---|---|
| ONNX INT8 (dynamic quantization) | Severe numerical differences; changes top predicted species entirely |
| TFLite INT8 (dynamic range quantization) | Same quantization instability; not recommended for production |
| TorchScript FP16 (mixed-precision) | Requires careful BN folding and stage-5 fp32 fallback; may produce NaN on untested models |
Without --experimental-mode, these variants are skipped with an informational message:
⚠️ Skipping ONNX INT8 — this is only supported in experimental mode. Use --experimental-mode to enable.
Default behavior (without --experimental-mode):
| Precision | TF/TFLite | TorchScript | ONNX |
|---|---|---|---|
| FP32 | ✅ Exported | ✅ Exported | ✅ Exported |
| FP16 | ✅ Exported | ✅ Exported | |
| INT8 | — |
By default all non-experimental precision variants are exported. Use -p to select specific ones:
| Precision | Method | Notes |
|---|---|---|
fp32 |
Standard export | Full precision, used as reference |
fp16 |
Weight conversion to float16 | ~2× smaller, minimal accuracy loss |
int8 |
Dynamic quantization (ONNX) / dynamic range quantization (TFLite) | Experimental — ~4× smaller but significant accuracy loss; requires --experimental-mode |
The full pipeline (-f tf tflite torchscript onnx) runs these steps:
- TF Preprocessing — Exports the mel-spectrogram preprocessing as a standalone TF SavedModel and TFLite model.
- TF Main Model — Converts the classifier via
litert_torch, re-wraps with dynamic input signatures, and exports to TFLite. - TF Combined — Chains preprocessing + main model into a single end-to-end TFLite model (audio → predictions).
- TorchScript — Exports the full model (with preprocessing) via
torch.jit.script. - ONNX — Exports via
torch.onnx.exportwithdynamo=True, then patches Resize and Conv2dSame ops for dynamic inputs.
The TFLite preprocessing and main models are exported separately in addition to the combined model. This lets users plug in their own spectrogram pipeline or run the classifier independently — for example, pre-computing spectrograms on a server and sending only the images to an edge device running the main model.
Compare outputs of all converted formats against the TorchScript reference:
python test_models.pyUse a custom audio file or export directory:
python test_models.py -a path/to/audio.wav
python test_models.py -e output/my_exportTest with different segment durations or batch sizes:
python test_models.py --durations 3.0 5.0
python test_models.py --batch-size 2usage: test_models.py [-h] [-e EXPORT_DIR] [-a AUDIO] [--batch-size N]
[--durations S [S ...]] [--gpu]
-e, --export-dir Path to export directory (default: export/)
-a, --audio Audio file for test input (default: example/soundscape.wav)
--batch-size Batch size for test inputs (default: 1)
--durations Segment durations in seconds (default: 3.0)
--gpu Use GPU for TorchScript and ONNX inference
Interpreting results: The test compares each format against TorchScript using a tight tolerance (
atol=0.025). ONNX and LiteRT (combined) should pass at the default 3.0s duration. FP16 variants typically show slightly larger differences but should still pass. INT8 variants are unstable — dynamic quantization introduces severe numerical differences that change the top predicted species entirely. INT8 is included as an experimental option and is not recommended for production use.
The source code is licensed under the MIT License — see the LICENSE file for details.
Trained model weights are licensed under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0). See TERMS_OF_USE.md for full terms.
Our work in the Cornell K. Lisa Yang Center for Conservation Bioacoustics is made possible by the generosity of K. Lisa Yang to advance innovative conservation technologies to inspire and inform the conservation of wildlife and habitats.
The development of BirdNET is supported by the German Federal Ministry of Research, Technology and Space (FKZ 01|S22072), the German Federal Ministry for the Environment, Climate Action, Nature Conservation and Nuclear Safety (FKZ 67KI31040E), the German Federal Ministry of Economic Affairs and Energy (FKZ 16KN095550), the Deutsche Bundesstiftung Umwelt (project 39263/01) and the European Social Fund.
BirdNET is a joint effort of partners from academia and industry. Without these partnerships, this project would not have been possible. Thank you!

