Zero-shot NER (Named Entity Recognition) via GLiNER2, benchmarked across macOS (Apple Silicon), NVIDIA CUDA, and ARM edge (RK3588S).
| Track | Purpose | Runtime | Use case |
|---|---|---|---|
python/ | Research & iteration | MLX Python / ONNX Runtime | Fast experiments, model conversion, accuracy validation |
native/ | Production FFI | MLX C++ → dylib | Swift/Dart app embedding |
cd python
pip install -r requirements.txt
python gliner_mlx/infer.py
python gliner_onnx/infer.py
cd native && ./build.sh
# → dist/libgliner_wrapper.dylib
Using gliner2-multi-v1 (mDeBERTa-v3-base, 100+ languages).
| Format | Source | Size |
|---|---|---|
| MLX safetensors | OpenMed/gliner-relex-base-v1.0-mlx | ~900 MB |
| ONNX fp16 | SemplificaAI/gliner2-multi-v1-onnx | ~645 MB |
| ONNX fp32 | same | ~1.2 GB |
| Method | NER | Relation Extraction | Notes |
|---|---|---|---|
| FP16 | ✅ | ✅ | Recommended, lossless accuracy |
| INT8 | ✅ Works | ⚠️ Pending | 3-step fp16→fp32→int8 conversion, see convert/fix_fp16.py |
| INT4/Q4 | ❌ Untested | ❌ | DeBERTa encoder may crash |
| GGUF | ❌ Not feasible | ❌ | llama.cpp does not support encoder-only models |
| Variant | Size | Encoder Time | Speedup |
|---|---|---|---|
| fp16 | 531MB | 1878ms | 1.00× |
| int8 | 322MB | 853ms | 2.20× |
⚠️ INT8 quantization pitfall: Running quantize_dynamic directly on fp16 ONNX will fail. You must first strip 153+ hidden fp16 tensors (Constant node attributes, ConstantOfShape, Cast targets). Use python/convert/fix_fp16.py to handle this.
GLiNER-Forge/
├── python/
│ ├── gliner_mlx/ # MLX Python inference (OpenMed)
│ ├── gliner_onnx/ # ONNX Runtime inference
│ │ ├── infer.py # macOS (CoreML)
│ │ └── infer_crossplatform.py # macOS + Linux ARM
│ ├── convert/ # to_onnx, to_mlx, quantize, fix_fp16
│ │ ├── fix_fp16.py # fp16→fp32 cleanup (required before int8)
│ │ ├── quantize.py
│ │ ├── to_mlx.py
│ │ └── to_onnx.py
│ └── requirements.txt
├── native/
│ ├── src/ # gliner_wrapper.h/.cpp (MLX C++)
│ ├── dart/ # Dart FFI binding
│ ├── swift/ # Swift bridge
│ └── build.sh
├── bench/ # speed & accuracy benchmarks
│ ├── bench_rk3588s.py # NanoPi M6 (RK3588S) benchmark
│ └── ...
├── reports/
│ └── rk3588s_benchmark.md # RK3588S detailed report
├── models/ # weights (gitignored)
└── README.md
See bench/results_cuda.md for full details.
| Model | VRAM | Avg ms/article | Notes |
|---|---|---|---|
| small-v2.5 | 675 MB | 94 ms | Best speed/VRAM ratio |
| medium-v2.5 | 836 MB | 112 ms | Marginal improvement |
| large-v2.5 | 1838 MB | 236 ms | Best precision |
| ONNX (CUDA) | — | 162 ms | Slower than PyTorch on CUDA |
Key finding: ONNX wins on Apple Silicon, PyTorch wins on CUDA.
| Platform | Hardware | Backend | Best Model | Latency | VRAM/RAM |
|---|---|---|---|---|---|
| Apple Silicon | M-series (MPS) | ONNX FP32 | small-v2.5 | 13ms | ~900MB |
| NVIDIA CUDA | Quadro T2000 (4GB) | PyTorch FP32 | small-v2.5 | 94ms | 675MB |
| ARM Edge | RK3588S (NanoPi M6) | ONNX INT8 | gliner2-multi-v1 | 853ms | 322MB |
| Platform | INT8 Impact | Notes |
|---|---|---|
| Apple Silicon (MPS) | ❌ Destructive (recall drops to 6.6-51.5%) | ONNX quant on MPS breaks mDeBERTa |
| NVIDIA CUDA | ⚠️ Not tested | PyTorch native faster anyway |
| ARM Edge (RK3588S) | ✅ Zero loss (18/18 cases, 10 langs, 100% recall) | Full pipeline verified: encoder + span_rep + classifier |
RK3588S INT8 accuracy validated with Wikipedia Taiwan multilingual test data (18 cases, 10 languages: zh-TW, en, ja, ko, ru, fr, es, ar, th, vi). All cases achieve 100% recall at threshold 0.3. See
bench/bench_rk3588s_accuracy.py.
| Platform | Model | Backend | Quantization |
|---|---|---|---|
| Apple Silicon | small-v2.5 | ONNX FP32 (th=0.1) | None |
| NVIDIA GPU | small-v2.5 | PyTorch CUDA (th=0.3) | None |
| ARM Edge (RK3588S) | gliner2-multi-v1 | ONNX INT8 | Dynamic int8 via fix_fp16.py |
15 commits
Python
82.3%
C++
15.3%
Shell
1.4%
Zero-shot NER (Named Entity Recognition) via GLiNER2, benchmarked across macOS (Apple Silicon), NVIDIA CUDA, and ARM edge (RK3588S).
| Track | Purpose | Runtime | Use case |
|---|---|---|---|
python/ | Research & iteration | MLX Python / ONNX Runtime | Fast experiments, model conversion, accuracy validation |
native/ | Production FFI | MLX C++ → dylib | Swift/Dart app embedding |
cd python
pip install -r requirements.txt
python gliner_mlx/infer.py
python gliner_onnx/infer.py
cd native && ./build.sh
# → dist/libgliner_wrapper.dylib
Using gliner2-multi-v1 (mDeBERTa-v3-base, 100+ languages).
| Format | Source | Size |
|---|---|---|
| MLX safetensors | OpenMed/gliner-relex-base-v1.0-mlx | ~900 MB |
| ONNX fp16 | SemplificaAI/gliner2-multi-v1-onnx | ~645 MB |
| ONNX fp32 | same | ~1.2 GB |
| Method | NER | Relation Extraction | Notes |
|---|---|---|---|
| FP16 | ✅ | ✅ | Recommended, lossless accuracy |
| INT8 | ✅ Works | ⚠️ Pending | 3-step fp16→fp32→int8 conversion, see convert/fix_fp16.py |
| INT4/Q4 | ❌ Untested | ❌ | DeBERTa encoder may crash |
| GGUF | ❌ Not feasible | ❌ | llama.cpp does not support encoder-only models |
| Variant | Size | Encoder Time | Speedup |
|---|---|---|---|
| fp16 | 531MB | 1878ms | 1.00× |
| int8 | 322MB | 853ms | 2.20× |
⚠️ INT8 quantization pitfall: Running quantize_dynamic directly on fp16 ONNX will fail. You must first strip 153+ hidden fp16 tensors (Constant node attributes, ConstantOfShape, Cast targets). Use python/convert/fix_fp16.py to handle this.
GLiNER-Forge/
├── python/
│ ├── gliner_mlx/ # MLX Python inference (OpenMed)
│ ├── gliner_onnx/ # ONNX Runtime inference
│ │ ├── infer.py # macOS (CoreML)
│ │ └── infer_crossplatform.py # macOS + Linux ARM
│ ├── convert/ # to_onnx, to_mlx, quantize, fix_fp16
│ │ ├── fix_fp16.py # fp16→fp32 cleanup (required before int8)
│ │ ├── quantize.py
│ │ ├── to_mlx.py
│ │ └── to_onnx.py
│ └── requirements.txt
├── native/
│ ├── src/ # gliner_wrapper.h/.cpp (MLX C++)
│ ├── dart/ # Dart FFI binding
│ ├── swift/ # Swift bridge
│ └── build.sh
├── bench/ # speed & accuracy benchmarks
│ ├── bench_rk3588s.py # NanoPi M6 (RK3588S) benchmark
│ └── ...
├── reports/
│ └── rk3588s_benchmark.md # RK3588S detailed report
├── models/ # weights (gitignored)
└── README.md
See bench/results_cuda.md for full details.
| Model | VRAM | Avg ms/article | Notes |
|---|---|---|---|
| small-v2.5 | 675 MB | 94 ms | Best speed/VRAM ratio |
| medium-v2.5 | 836 MB | 112 ms | Marginal improvement |
| large-v2.5 | 1838 MB | 236 ms | Best precision |
| ONNX (CUDA) | — | 162 ms | Slower than PyTorch on CUDA |
Key finding: ONNX wins on Apple Silicon, PyTorch wins on CUDA.
| Platform | Hardware | Backend | Best Model | Latency | VRAM/RAM |
|---|---|---|---|---|---|
| Apple Silicon | M-series (MPS) | ONNX FP32 | small-v2.5 | 13ms | ~900MB |
| NVIDIA CUDA | Quadro T2000 (4GB) | PyTorch FP32 | small-v2.5 | 94ms | 675MB |
| ARM Edge | RK3588S (NanoPi M6) | ONNX INT8 | gliner2-multi-v1 | 853ms | 322MB |
| Platform | INT8 Impact | Notes |
|---|---|---|
| Apple Silicon (MPS) | ❌ Destructive (recall drops to 6.6-51.5%) | ONNX quant on MPS breaks mDeBERTa |
| NVIDIA CUDA | ⚠️ Not tested | PyTorch native faster anyway |
| ARM Edge (RK3588S) | ✅ Zero loss (18/18 cases, 10 langs, 100% recall) | Full pipeline verified: encoder + span_rep + classifier |
RK3588S INT8 accuracy validated with Wikipedia Taiwan multilingual test data (18 cases, 10 languages: zh-TW, en, ja, ko, ru, fr, es, ar, th, vi). All cases achieve 100% recall at threshold 0.3. See
bench/bench_rk3588s_accuracy.py.
| Platform | Model | Backend | Quantization |
|---|---|---|---|
| Apple Silicon | small-v2.5 | ONNX FP32 (th=0.1) | None |
| NVIDIA GPU | small-v2.5 | PyTorch CUDA (th=0.3) | None |
| ARM Edge (RK3588S) | gliner2-multi-v1 | ONNX INT8 | Dynamic int8 via fix_fp16.py |
15 commits
Python
82.3%
C++
15.3%
Shell
1.4%