Audio Classification
NeMo
Safetensors
PyTorch
Arabic
English
multilingual
speaker-diarization
diarization
streaming
realtime
sortformer
arabic
audar
Eval Results (legacy)
Instructions to use audarai/Audar-Diarization-V1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- NeMo
How to use audarai/Audar-Diarization-V1 with NeMo:
# tag did not correspond to a valid NeMo domain.
- Notebooks
- Google Colab
- Kaggle
Commit ·
245508e
0
Parent(s):
Consolidated release (history squashed to remove internal filesystem paths from prior revisions)
Browse files- .gitattributes +37 -0
- README.md +289 -0
- config.yaml +161 -0
- load_diarizer.py +97 -0
- model.safetensors +3 -0
.gitattributes
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
ft8_best.nemo filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
Audar-Diarization-V1.nemo filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
license_name: audarai-community-license-v1.0
|
| 4 |
+
license_link: https://www.audarai.com/license/audarai-community-license-v1.0/
|
| 5 |
+
library_name: nemo
|
| 6 |
+
language:
|
| 7 |
+
- ar
|
| 8 |
+
- en
|
| 9 |
+
- multilingual
|
| 10 |
+
pipeline_tag: audio-classification
|
| 11 |
+
inference: false
|
| 12 |
+
tags:
|
| 13 |
+
- speaker-diarization
|
| 14 |
+
- diarization
|
| 15 |
+
- streaming
|
| 16 |
+
- realtime
|
| 17 |
+
- sortformer
|
| 18 |
+
- arabic
|
| 19 |
+
- multilingual
|
| 20 |
+
- nemo
|
| 21 |
+
- pytorch
|
| 22 |
+
- audar
|
| 23 |
+
datasets:
|
| 24 |
+
- ami
|
| 25 |
+
- alimeeting
|
| 26 |
+
- dipco
|
| 27 |
+
- icsi
|
| 28 |
+
- voxconverse
|
| 29 |
+
- chime6
|
| 30 |
+
- msdwild
|
| 31 |
+
metrics:
|
| 32 |
+
- der
|
| 33 |
+
model-index:
|
| 34 |
+
- name: Audar-Diarization-V1
|
| 35 |
+
results:
|
| 36 |
+
- task: {type: speaker-diarization, name: Speaker Diarization}
|
| 37 |
+
dataset: {type: ami, name: AMI (Headset Mix)}
|
| 38 |
+
metrics: [{type: der, value: 15.24, name: DER (collar=0.25s)}]
|
| 39 |
+
- task: {type: speaker-diarization, name: Speaker Diarization}
|
| 40 |
+
dataset: {type: alimeeting, name: AliMeeting (Far)}
|
| 41 |
+
metrics: [{type: der, value: 18.70, name: DER (collar=0.25s)}]
|
| 42 |
+
- task: {type: speaker-diarization, name: Speaker Diarization}
|
| 43 |
+
dataset: {type: dipco, name: DiPCo}
|
| 44 |
+
metrics: [{type: der, value: 23.77, name: DER (collar=0.25s)}]
|
| 45 |
+
- task: {type: speaker-diarization, name: Speaker Diarization}
|
| 46 |
+
dataset: {type: icsi, name: ICSI}
|
| 47 |
+
metrics: [{type: der, value: 14.46, name: DER (collar=0.25s)}]
|
| 48 |
+
- task: {type: speaker-diarization, name: Speaker Diarization}
|
| 49 |
+
dataset: {type: msdwild, name: MSDWild (Few)}
|
| 50 |
+
metrics: [{type: der, value: 21.09, name: DER (collar=0.25s)}]
|
| 51 |
+
- task: {type: speaker-diarization, name: Speaker Diarization}
|
| 52 |
+
dataset: {type: msdwild, name: MSDWild (Many)}
|
| 53 |
+
metrics: [{type: der, value: 29.41, name: DER (collar=0.25s)}]
|
| 54 |
+
- task: {type: speaker-diarization, name: Speaker Diarization}
|
| 55 |
+
dataset: {type: voxconverse, name: VoxConverse}
|
| 56 |
+
metrics: [{type: der, value: 8.55, name: DER (collar=0.25s)}]
|
| 57 |
+
- task: {type: speaker-diarization, name: Speaker Diarization}
|
| 58 |
+
dataset: {type: chime6, name: CHiME-6}
|
| 59 |
+
metrics: [{type: der, value: 45.00, name: DER (collar=0.25s)}]
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
<div align="center">
|
| 63 |
+
|
| 64 |
+
# Audar-Diarization-V1
|
| 65 |
+
|
| 66 |
+
### Real-time streaming speaker diarization — up to 8 speakers, state of the art on 8 corpora.
|
| 67 |
+
|
| 68 |
+
**From Arabic to the world.**
|
| 69 |
+
|
| 70 |
+

|
| 71 |
+

|
| 72 |
+

|
| 73 |
+

|
| 74 |
+

|
| 75 |
+

|
| 76 |
+
|
| 77 |
+
<p><a href="#-what-it-is"><b>🧭 Overview</b></a> · <a href="#-benchmarks"><b>📊 Benchmarks</b></a> · <a href="#-quickstart"><b>⚡ Quickstart</b></a> · <a href="#-real-time-streaming"><b>🎙️ Streaming</b></a> · <a href="#-files"><b>📦 Files</b></a> · <a href="https://www.audarai.com"><b>☁️ Audar API</b></a> · <a href="https://www.audarai.com/license/audarai-community-license-v1.0/"><b>📜 License</b></a></p>
|
| 78 |
+
|
| 79 |
+
</div>
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## 🧭 What it is
|
| 84 |
+
|
| 85 |
+
**Audar-Diarization-V1** answers *"who spoke when"* — in real time, for up to **8 speakers**, across
|
| 86 |
+
hour-long multi-speaker audio. It is the speaker-attribution engine of the Audar realtime stack: paired
|
| 87 |
+
with [**Audar-ASR-V1**](https://huggingface.co/audarai/Audar-ASR-V1-Turbo) it turns a verbatim transcript
|
| 88 |
+
into a speaker-labeled one — the difference between an undifferentiated wall of text and a minutes-ready
|
| 89 |
+
board record.
|
| 90 |
+
|
| 91 |
+
It is built on NVIDIA's **Streaming Sortformer v2.1** and advanced in-house through Audar's diarization
|
| 92 |
+
program:
|
| 93 |
+
|
| 94 |
+
- 🔧 **Surgical 4→8 speaker head expansion** — the released Sortformer supports only 4 speaker slots.
|
| 95 |
+
Audar extends the output layer to **8** by modifying exactly two Linear layers (adding just **2,312**
|
| 96 |
+
parameters), cloning the learned 4-speaker weights so the pretrained decision boundary is preserved
|
| 97 |
+
while capacity opens for speakers 5–8.
|
| 98 |
+
- 🧊 **Freeze-and-fine-tune** — the **109.55M**-parameter FastConformer acoustic encoder is frozen; only
|
| 99 |
+
the lightweight Transformer encoder + Sortformer assignment modules (**8.15M**) are trained. This buys
|
| 100 |
+
a **4.2-point DER** advantage over full-model fine-tuning and keeps training fast on a single node.
|
| 101 |
+
- 🧬 **Correct-by-construction synthetic data** — Audar fixes a systematic bug in the legacy synthetic-
|
| 102 |
+
data generator (97 % of samples had labels running past the audio) and generates **200 h** of clean
|
| 103 |
+
5–8-speaker conversations, on top of **486 h** of real far-field meetings.
|
| 104 |
+
- 🎯 **Arrival-Order Speaker Cache (AOSC)** — speakers are assigned to output slots in the order they
|
| 105 |
+
first speak, resolving the permutation problem without global clustering or Hungarian matching. Identity
|
| 106 |
+
is held across sessions **up to 74 minutes**, and the cache auto-sizes to however many speakers are
|
| 107 |
+
actually present.
|
| 108 |
+
|
| 109 |
+
The result **streams on a single GPU** with **1.04 s** algorithmic latency and a **0.003** real-time
|
| 110 |
+
factor (1 s of audio processed in ~3 ms), while posting the **lowest DER of any evaluated system on all
|
| 111 |
+
eight benchmark corpora**.
|
| 112 |
+
|
| 113 |
+
## Model summary
|
| 114 |
+
|
| 115 |
+
<table>
|
| 116 |
+
<tbody>
|
| 117 |
+
<tr><td width="220"><b>Model</b></td><td>Audar-Diarization-V1 — streaming speaker diarization (up to 8 speakers)</td></tr>
|
| 118 |
+
<tr><td><b>Task</b></td><td>Speaker diarization ("who spoke when") — streaming <i>and</i> offline whole-file</td></tr>
|
| 119 |
+
<tr><td><b>Architecture</b></td><td>Sortformer (encoder-label): frozen FastConformer → trainable Transformer encoder → Sortformer modules + AOSC</td></tr>
|
| 120 |
+
<tr><td><b>Base</b></td><td>NVIDIA Streaming Sortformer v2.1, surgically extended 4 → 8 speaker slots</td></tr>
|
| 121 |
+
<tr><td><b>Total parameters</b></td><td>117,696,272 (117.7M)</td></tr>
|
| 122 |
+
<tr><td><b>Trainable / frozen</b></td><td>8.15M trainable · 109.55M frozen (acoustic encoder)</td></tr>
|
| 123 |
+
<tr><td><b>Max speakers</b></td><td>8 per session (AOSC auto-sizes to the number present)</td></tr>
|
| 124 |
+
<tr><td><b>Prediction frame</b></td><td>80 ms</td></tr>
|
| 125 |
+
<tr><td><b>Algorithmic latency</b></td><td>1.04 s (streaming mode)</td></tr>
|
| 126 |
+
<tr><td><b>Real-time factor</b></td><td>0.003 (single GPU, batch 1)</td></tr>
|
| 127 |
+
<tr><td><b>Sample rate</b></td><td>16 kHz mono</td></tr>
|
| 128 |
+
<tr><td><b>Format</b></td><td>safetensors (fp32, lossless) — PyTorch / CUDA via NeMo</td></tr>
|
| 129 |
+
<tr><td><b>License</b></td><td>AudarAI Community License v1.0</td></tr>
|
| 130 |
+
</tbody>
|
| 131 |
+
</table>
|
| 132 |
+
|
| 133 |
+
## 📊 Benchmarks
|
| 134 |
+
|
| 135 |
+
Evaluated with **`dscore`** at a **0.25 s collar, ignoring overlap** (DIHARD protocol) on the official
|
| 136 |
+
dev/eval splits of **8 corpora** spanning meetings, dinner parties, broadcast, and in-the-wild audio.
|
| 137 |
+
Audar-Diarization-V1 posts the **lowest DER on every corpus** and a **macro DER of 22.03 %** — beating
|
| 138 |
+
pyannote 3.1 by **7.63 pp** and stock Sortformer v2.1 by **12.16 pp**.
|
| 139 |
+
|
| 140 |
+
### DER % per corpus (lower is better)
|
| 141 |
+
|
| 142 |
+
| System | AMI | AliMeeting | DiPCo | ICSI | MSDWild-few | MSDWild-many | VoxConverse | CHiME-6 | **Macro** |
|
| 143 |
+
|---|--:|--:|--:|--:|--:|--:|--:|--:|--:|
|
| 144 |
+
| **Audar-Diarization-V1** | **15.24** | **18.70** | **23.77** | **14.46** | **21.09** | **29.41** | **8.55** | **45.00** | **22.03** |
|
| 145 |
+
| pyannote 3.1 | 28.60 | 27.38 | 30.72 | 22.48 | 27.12 | 34.83 | 12.92 | 53.19 | 29.66 |
|
| 146 |
+
| Sortformer v2.1 | 24.84 | 25.94 | 33.80 | 23.22 | 36.92 | 50.77 | 17.06 | 60.97 | 34.19 |
|
| 147 |
+
|
| 148 |
+
### Where the gain comes from — DER decomposition (macro)
|
| 149 |
+
|
| 150 |
+
| System | Miss | False alarm | Confusion | **DER** |
|
| 151 |
+
|---|--:|--:|--:|--:|
|
| 152 |
+
| **Audar-Diarization-V1** | 10.33 | 6.90 | **4.80** | **22.03** |
|
| 153 |
+
| pyannote 3.1 | 8.72 | 3.38 | 17.56 | 29.66 |
|
| 154 |
+
| Sortformer v2.1 | 11.91 | 5.04 | 17.24 | 34.19 |
|
| 155 |
+
|
| 156 |
+
The advantage is **confusion: 4.80 % vs 17.2–17.6 %** — a **3.6× reduction**, from the AOSC's stable
|
| 157 |
+
identity tracking. The slightly higher false-alarm rate reflects a deliberately assertive streaming VAD
|
| 158 |
+
(a missed utterance costs more than a brief false activation in live transcription) and is tunable via
|
| 159 |
+
the onset threshold.
|
| 160 |
+
|
| 161 |
+
### Out-of-domain (CALLHOME, 8 kHz telephony — not in training)
|
| 162 |
+
|
| 163 |
+
| Audar-Diarization-V1 | Sortformer v2.1 | pyannote 3.1 |
|
| 164 |
+
|--:|--:|--:|
|
| 165 |
+
| **10.29** | 12.22 | 18.51 |
|
| 166 |
+
|
| 167 |
+
Identity also holds on the longest sessions in the benchmark — e.g. a 74-minute, 5-speaker ICSI meeting
|
| 168 |
+
at 22.2 % DER with ~2 % confusion.
|
| 169 |
+
|
| 170 |
+
## ⚡ Quickstart
|
| 171 |
+
|
| 172 |
+
Ships as a single **fp32 safetensors** bundle — `model.safetensors` + `config.yaml` + `load_diarizer.py`.
|
| 173 |
+
The loader instantiates the NeMo Sortformer model and loads the weights directly (no `.nemo` tar):
|
| 174 |
+
|
| 175 |
+
```python
|
| 176 |
+
# needs: nemo_toolkit[asr]>=2.6, safetensors, omegaconf
|
| 177 |
+
from huggingface_hub import snapshot_download
|
| 178 |
+
import sys; sys.path.insert(0, snapshot_download("audarai/Audar-Diarization-V1"))
|
| 179 |
+
from load_diarizer import load_diarizer
|
| 180 |
+
|
| 181 |
+
model = load_diarizer() # fp32, CUDA (device="cpu" also works)
|
| 182 |
+
segs = model.diarize(audio=["meeting.wav"], batch_size=1)
|
| 183 |
+
# → RTTM-style [(start_s, end_s, speaker_slot), ...] per file
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
Training recipes, the gold synthetic-data generator, and the full 8-corpus evaluation harness are open at
|
| 187 |
+
**[github.com/AudarAI/Audar-diarization](https://github.com/AudarAI/Audar-diarization)**.
|
| 188 |
+
|
| 189 |
+
## 🎙️ Real-time streaming
|
| 190 |
+
|
| 191 |
+
The same checkpoint runs **true streaming** via `forward_streaming_step` with persistent `spkcache` / FIFO
|
| 192 |
+
state: ~1 s chunks, **80 ms** prediction frames, up to **8 concurrent speakers**, and session-stable slot
|
| 193 |
+
labels that never rewrite once committed. Algorithmic latency is **1.04 s** at a **0.003** real-time
|
| 194 |
+
factor on a single GPU.
|
| 195 |
+
|
| 196 |
+
**Speaker-attributed transcription.** The Audar serving gateway runs diarization in parallel with
|
| 197 |
+
[**Audar-ASR-V1**](https://huggingface.co/audarai/Audar-ASR-V1-Turbo) and assigns each transcribed word to
|
| 198 |
+
the speaker dominant during its time span — so combined latency is the *max* of the two streams, not the
|
| 199 |
+
sum. One deployment exposes **ASR-only**, **diarization-only**, and **ASR+diarization** endpoints over HTTP
|
| 200 |
+
and an OpenAI-Realtime-compatible WebSocket. For a managed, production-hosted endpoint, see the
|
| 201 |
+
[**Audar API**](https://www.audarai.com).
|
| 202 |
+
|
| 203 |
+
## 📦 Files
|
| 204 |
+
|
| 205 |
+
| File | What it is |
|
| 206 |
+
|---|---|
|
| 207 |
+
| `model.safetensors` | **fp32 weights** — bit-exact, lossless, safetensors (safe, zero-copy `mmap`, no pickle) |
|
| 208 |
+
| `config.yaml` | model config (the `.nemo`'s `model_config.yaml`) |
|
| 209 |
+
| `load_diarizer.py` | self-contained loader (instantiates the NeMo model + loads the weights) |
|
| 210 |
+
|
| 211 |
+
**Lossless fp32, and faster to load.** The `model.safetensors` carries the full-precision weights
|
| 212 |
+
bit-for-bit — verified by a round-trip check (990/990 tensors identical) and by downcasting to the prior
|
| 213 |
+
fp16 release with **zero mismatches** across all 971 float tensors, so it reproduces the exact model. The
|
| 214 |
+
weight-load step is **~28× faster** than the legacy `.nemo` (≈12 ms `mmap` vs ≈344 ms untar + unpickle),
|
| 215 |
+
and `safetensors` is the safe, community-standard format (no arbitrary-code pickle path).
|
| 216 |
+
|
| 217 |
+
<details>
|
| 218 |
+
<summary><b>Re-quantizing to fp16 (optional)</b></summary>
|
| 219 |
+
|
| 220 |
+
`load_diarizer.py` auto-detects weight dtype, so you can quantize `model.safetensors` to fp16 yourself and
|
| 221 |
+
it will load unchanged. When fp16 is detected the loader keeps the preprocessor (STFT/mel) in fp32 and runs
|
| 222 |
+
the streaming path under `torch.set_default_dtype(torch.float16)` (NeMo's streaming state is otherwise
|
| 223 |
+
created dtype-less). ONNX export is not supported out-of-the-box (NeMo 2.6.2's Sortformer export needs
|
| 224 |
+
streaming-state wiring).
|
| 225 |
+
</details>
|
| 226 |
+
|
| 227 |
+
## Intended use & limitations
|
| 228 |
+
|
| 229 |
+
**Intended use.** Speaker-attributed meeting/broadcast/call-center transcription, board and panel
|
| 230 |
+
recordings, and any real-time or offline "who spoke when" task — cloud, on-prem, or edge.
|
| 231 |
+
|
| 232 |
+
**Limitations.**
|
| 233 |
+
- Up to **8 speakers** per session; very large panels beyond 8 are out of scope.
|
| 234 |
+
- **Far-field, high-overlap, extreme-noise** audio (e.g. CHiME-6-style dinner parties) remains the hardest
|
| 235 |
+
case for every system.
|
| 236 |
+
- Very-low-activity speakers (<2 % of talk time) can have their cache entry decay during long silences.
|
| 237 |
+
- Not evaluated for, and must **not** be used for, covert speaker identification.
|
| 238 |
+
|
| 239 |
+
## 📜 License
|
| 240 |
+
|
| 241 |
+
Released under the **AudarAI Community License v1.0** — research and limited commercial use for qualifying
|
| 242 |
+
Community Entities; enterprise, large-scale, or model-as-a-service use requires an AudarAI Enterprise
|
| 243 |
+
License. See
|
| 244 |
+
[audarai.com/license/audarai-community-license-v1.0](https://www.audarai.com/license/audarai-community-license-v1.0/),
|
| 245 |
+
or contact **contact@audarai.com** for enterprise licensing.
|
| 246 |
+
|
| 247 |
+
## Citation
|
| 248 |
+
|
| 249 |
+
```bibtex
|
| 250 |
+
@techreport{audar-diarization-v1-2026,
|
| 251 |
+
title = {Audar-Diarization-V1: Real-Time Streaming Speaker Diarization for Long-Form, Multi-Speaker Audio},
|
| 252 |
+
author = {Audar AI Team},
|
| 253 |
+
institution = {AudarAI},
|
| 254 |
+
year = {2026},
|
| 255 |
+
url = {https://huggingface.co/audarai/Audar-Diarization-V1}
|
| 256 |
+
}
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
---
|
| 260 |
+
|
| 261 |
+
## About AudarAI
|
| 262 |
+
|
| 263 |
+
<div align="center">
|
| 264 |
+
|
| 265 |
+
### Leading Arabic-First Multilingual Audio Intelligence
|
| 266 |
+
|
| 267 |
+
*AudarAI starts with Arabic — and expands to the world.*
|
| 268 |
+
|
| 269 |
+
</div>
|
| 270 |
+
|
| 271 |
+
We are building advanced multilingual audio intelligence that helps individuals, enterprises, and
|
| 272 |
+
governments communicate across languages, cultures, and borders. By combining Arabic-first speech
|
| 273 |
+
technology with global multilingual AI, AudarAI transforms voice into understanding, interaction,
|
| 274 |
+
and connection.
|
| 275 |
+
|
| 276 |
+
Our work spans speech recognition, speech understanding, speaker diarization, voice-enabled digital
|
| 277 |
+
assistants, human-computer interaction, and intelligent audio systems designed for real-world impact.
|
| 278 |
+
From empowering people to access technology in their native language to helping organizations communicate
|
| 279 |
+
globally, AudarAI is shaping a future where every voice can be heard, understood, and connected.
|
| 280 |
+
|
| 281 |
+
**Arabic-first. Multilingual by design. Human-centered at heart.**
|
| 282 |
+
|
| 283 |
+
<div align="center">
|
| 284 |
+
|
| 285 |
+
**[🌐 www.audarai.com](https://www.audarai.com)** · [🤗 Hugging Face](https://huggingface.co/audarai) · [GitHub](https://github.com/AudarAI) · contact@audarai.com
|
| 286 |
+
|
| 287 |
+
© 2026 AUDARAI PTE. LTD. · Licensed under the AudarAI Community License v1.0
|
| 288 |
+
|
| 289 |
+
</div>
|
config.yaml
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
sample_rate: 16000
|
| 2 |
+
pil_weight: 0.5
|
| 3 |
+
ats_weight: 0.5
|
| 4 |
+
max_num_of_spks: 8
|
| 5 |
+
streaming_mode: true
|
| 6 |
+
model_defaults:
|
| 7 |
+
fc_d_model: 512
|
| 8 |
+
tf_d_model: 192
|
| 9 |
+
train_ds:
|
| 10 |
+
manifest_filepath: null
|
| 11 |
+
sample_rate: 16000
|
| 12 |
+
num_spks: 8
|
| 13 |
+
session_len_sec: 90
|
| 14 |
+
soft_label_thres: 0.5
|
| 15 |
+
soft_targets: false
|
| 16 |
+
labels: null
|
| 17 |
+
batch_size: 4
|
| 18 |
+
shuffle: true
|
| 19 |
+
num_workers: 2
|
| 20 |
+
validation_mode: false
|
| 21 |
+
use_lhotse: false
|
| 22 |
+
use_bucketing: false
|
| 23 |
+
pin_memory: true
|
| 24 |
+
window_stride: 0.01
|
| 25 |
+
subsampling_factor: 8
|
| 26 |
+
validation_ds:
|
| 27 |
+
manifest_filepath: null
|
| 28 |
+
is_tarred: false
|
| 29 |
+
tarred_audio_filepaths: null
|
| 30 |
+
sample_rate: 16000
|
| 31 |
+
num_spks: 8
|
| 32 |
+
session_len_sec: 90
|
| 33 |
+
soft_label_thres: 0.5
|
| 34 |
+
soft_targets: false
|
| 35 |
+
labels: null
|
| 36 |
+
batch_size: 4
|
| 37 |
+
shuffle: false
|
| 38 |
+
num_workers: 2
|
| 39 |
+
validation_mode: true
|
| 40 |
+
use_lhotse: false
|
| 41 |
+
use_bucketing: false
|
| 42 |
+
drop_last: false
|
| 43 |
+
pin_memory: true
|
| 44 |
+
window_stride: 0.01
|
| 45 |
+
subsampling_factor: 8
|
| 46 |
+
test_ds:
|
| 47 |
+
manifest_filepath: null
|
| 48 |
+
is_tarred: false
|
| 49 |
+
tarred_audio_filepaths: null
|
| 50 |
+
sample_rate: 16000
|
| 51 |
+
num_spks: 8
|
| 52 |
+
session_len_sec: 90
|
| 53 |
+
soft_label_thres: 0.5
|
| 54 |
+
soft_targets: false
|
| 55 |
+
labels: null
|
| 56 |
+
batch_size: 4
|
| 57 |
+
shuffle: false
|
| 58 |
+
seq_eval_mode: true
|
| 59 |
+
num_workers: 18
|
| 60 |
+
validation_mode: true
|
| 61 |
+
use_lhotse: false
|
| 62 |
+
use_bucketing: false
|
| 63 |
+
drop_last: false
|
| 64 |
+
pin_memory: true
|
| 65 |
+
window_stride: 0.01
|
| 66 |
+
subsampling_factor: 8
|
| 67 |
+
preprocessor:
|
| 68 |
+
_target_: nemo.collections.asr.modules.AudioToMelSpectrogramPreprocessor
|
| 69 |
+
normalize: NA
|
| 70 |
+
window_size: 0.025
|
| 71 |
+
sample_rate: 16000
|
| 72 |
+
window_stride: 0.01
|
| 73 |
+
window: hann
|
| 74 |
+
features: 128
|
| 75 |
+
n_fft: 512
|
| 76 |
+
frame_splicing: 1
|
| 77 |
+
dither: 1.0e-05
|
| 78 |
+
sortformer_modules:
|
| 79 |
+
_target_: nemo.collections.asr.modules.sortformer_modules.SortformerModules
|
| 80 |
+
num_spks: 8
|
| 81 |
+
dropout_rate: 0.5
|
| 82 |
+
fc_d_model: 512
|
| 83 |
+
tf_d_model: 192
|
| 84 |
+
spkcache_len: 188
|
| 85 |
+
fifo_len: 0
|
| 86 |
+
chunk_len: 188
|
| 87 |
+
spkcache_update_period: 188
|
| 88 |
+
chunk_left_context: 1
|
| 89 |
+
chunk_right_context: 1
|
| 90 |
+
spkcache_sil_frames_per_spk: 3
|
| 91 |
+
scores_add_rnd: 0
|
| 92 |
+
pred_score_threshold: 0.25
|
| 93 |
+
max_index: 99999
|
| 94 |
+
scores_boost_latest: 0.05
|
| 95 |
+
sil_threshold: 0.2
|
| 96 |
+
strong_boost_rate: 0.75
|
| 97 |
+
weak_boost_rate: 1.5
|
| 98 |
+
min_pos_scores_rate: 0.5
|
| 99 |
+
causal_attn_rate: 0.5
|
| 100 |
+
causal_attn_rc: 7
|
| 101 |
+
encoder:
|
| 102 |
+
_target_: nemo.collections.asr.modules.ConformerEncoder
|
| 103 |
+
feat_in: 128
|
| 104 |
+
feat_out: -1
|
| 105 |
+
n_layers: 17
|
| 106 |
+
d_model: 512
|
| 107 |
+
subsampling: dw_striding
|
| 108 |
+
subsampling_factor: 8
|
| 109 |
+
subsampling_conv_channels: 256
|
| 110 |
+
causal_downsampling: false
|
| 111 |
+
ff_expansion_factor: 4
|
| 112 |
+
self_attention_model: rel_pos
|
| 113 |
+
n_heads: 8
|
| 114 |
+
att_context_size:
|
| 115 |
+
- -1
|
| 116 |
+
- -1
|
| 117 |
+
att_context_style: regular
|
| 118 |
+
xscaling: true
|
| 119 |
+
untie_biases: true
|
| 120 |
+
pos_emb_max_len: 5000
|
| 121 |
+
conv_kernel_size: 9
|
| 122 |
+
conv_norm_type: batch_norm
|
| 123 |
+
conv_context_size: null
|
| 124 |
+
dropout: 0.1
|
| 125 |
+
dropout_pre_encoder: 0.1
|
| 126 |
+
dropout_emb: 0.0
|
| 127 |
+
dropout_att: 0.1
|
| 128 |
+
stochastic_depth_drop_prob: 0.0
|
| 129 |
+
stochastic_depth_mode: linear
|
| 130 |
+
stochastic_depth_start_layer: 1
|
| 131 |
+
transformer_encoder:
|
| 132 |
+
_target_: nemo.collections.asr.modules.transformer.transformer_encoders.TransformerEncoder
|
| 133 |
+
num_layers: 18
|
| 134 |
+
hidden_size: 192
|
| 135 |
+
inner_size: 768
|
| 136 |
+
num_attention_heads: 8
|
| 137 |
+
attn_score_dropout: 0.5
|
| 138 |
+
attn_layer_dropout: 0.5
|
| 139 |
+
ffn_dropout: 0.5
|
| 140 |
+
hidden_act: relu
|
| 141 |
+
pre_ln: false
|
| 142 |
+
pre_ln_final_layer_norm: true
|
| 143 |
+
loss:
|
| 144 |
+
_target_: nemo.collections.asr.losses.bce_loss.BCELoss
|
| 145 |
+
weight: null
|
| 146 |
+
reduction: mean
|
| 147 |
+
lr: 0.0001
|
| 148 |
+
optim:
|
| 149 |
+
name: adamw
|
| 150 |
+
lr: 2.0e-05
|
| 151 |
+
betas:
|
| 152 |
+
- 0.9
|
| 153 |
+
- 0.98
|
| 154 |
+
weight_decay: 0.001
|
| 155 |
+
sched:
|
| 156 |
+
name: InverseSquareRootAnnealing
|
| 157 |
+
warmup_steps: 500
|
| 158 |
+
warmup_ratio: null
|
| 159 |
+
min_lr: 1.0e-06
|
| 160 |
+
target: nemo.collections.asr.models.sortformer_diar_models.SortformerEncLabelModel
|
| 161 |
+
nemo_version: 2.7.3
|
load_diarizer.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Load the ft8 Sortformer diarizer from config.yaml + model.safetensors — fp32, lossless, no .nemo tar.
|
| 3 |
+
|
| 4 |
+
Native path that works (NeMo 2.6.2, torch 2.10):
|
| 5 |
+
1. cfg = OmegaConf.load("config.yaml") # the .nemo model_config.yaml IS the
|
| 6 |
+
model-level cfg (top-level keys: encoder, sortformer_modules, ... — NOT nested
|
| 7 |
+
under 'model'). Identical to restore_from(..., return_config=True).
|
| 8 |
+
2. Null out train_ds / validation_ds / test_ds: ModelPT.__init__ otherwise tries to
|
| 9 |
+
build dataloaders from cluster manifest paths that don't exist at serve time.
|
| 10 |
+
3. model = SortformerEncLabelModel(cfg=cfg) # direct instantiation works
|
| 11 |
+
4. model.load_state_dict(safetensors_sd, strict=True)
|
| 12 |
+
|
| 13 |
+
fp16 quirks (ONLY relevant if you re-quantize to fp16 yourself; the shipped weights are
|
| 14 |
+
fp32 and the loader skips all of this — kept so the community can quantize freely):
|
| 15 |
+
* preprocessor.* (STFT window + mel fb) is kept fp32 in the safetensors; after
|
| 16 |
+
model.half() we re-float the preprocessor and cast its output features to fp16.
|
| 17 |
+
Halving the STFT itself degrades mel features / breaks torch.stft dtype paths.
|
| 18 |
+
* Sortformer's STREAMING path creates fp32 state tensors internally
|
| 19 |
+
(sortformer_modules.init_streaming_state / streaming_update use torch.zeros
|
| 20 |
+
without dtype). torch.cat promotes the fp16 chunk to fp32 and the fp16 encoder
|
| 21 |
+
then throws "expected scalar type Float but found Half". Fix: run forward with
|
| 22 |
+
torch.set_default_dtype(torch.float16) so those internal states are fp16 too.
|
| 23 |
+
* forward returns fp32 preds (cast at the end) so NeMo's CPU post-processing
|
| 24 |
+
(ts_vad_post_processing) never sees Half tensors.
|
| 25 |
+
|
| 26 |
+
Usage:
|
| 27 |
+
from load_diarizer import load_diarizer
|
| 28 |
+
model = load_diarizer() # dtype auto-detected (fp32 shipped)
|
| 29 |
+
segs = model.diarize(audio=["x.wav"], batch_size=1,
|
| 30 |
+
postprocessing_yaml=pp_yaml, verbose=False)
|
| 31 |
+
"""
|
| 32 |
+
import os
|
| 33 |
+
import torch
|
| 34 |
+
from omegaconf import OmegaConf, open_dict
|
| 35 |
+
|
| 36 |
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def load_diarizer(artifact_dir: str = _HERE, device: str = "cuda"):
|
| 40 |
+
from nemo.collections.asr.models import SortformerEncLabelModel
|
| 41 |
+
from safetensors.torch import load_file
|
| 42 |
+
|
| 43 |
+
cfg = OmegaConf.load(os.path.join(artifact_dir, "config.yaml"))
|
| 44 |
+
if "model" in cfg and "encoder" not in cfg: # nested checkpoint (not ft8; safety)
|
| 45 |
+
cfg = cfg.model
|
| 46 |
+
with open_dict(cfg):
|
| 47 |
+
for k in ("train_ds", "validation_ds", "test_ds"):
|
| 48 |
+
if k in cfg:
|
| 49 |
+
cfg[k] = None
|
| 50 |
+
|
| 51 |
+
model = SortformerEncLabelModel(cfg=cfg)
|
| 52 |
+
|
| 53 |
+
sd = load_file(os.path.join(artifact_dir, "model.safetensors"))
|
| 54 |
+
fp16 = any(v.dtype == torch.float16 for v in sd.values())
|
| 55 |
+
if fp16:
|
| 56 |
+
model = model.half()
|
| 57 |
+
model.preprocessor.float() # STFT/mel stays fp32 (matches fp32 sd keys)
|
| 58 |
+
info = model.load_state_dict(sd, strict=True)
|
| 59 |
+
assert not info.missing_keys and not info.unexpected_keys
|
| 60 |
+
|
| 61 |
+
if fp16:
|
| 62 |
+
# bridge fp32 mel features -> fp16 encoder
|
| 63 |
+
_orig_pre = model.preprocessor.forward
|
| 64 |
+
|
| 65 |
+
def _cast_pre(*a, **kw):
|
| 66 |
+
out = _orig_pre(*a, **kw)
|
| 67 |
+
if isinstance(out, tuple):
|
| 68 |
+
return (out[0].half(),) + tuple(out[1:])
|
| 69 |
+
return out.half()
|
| 70 |
+
|
| 71 |
+
model.preprocessor.forward = _cast_pre
|
| 72 |
+
|
| 73 |
+
# make internally-created streaming state (spkcache/fifo zeros) fp16 as well,
|
| 74 |
+
# and hand fp32 preds back to NeMo's CPU post-processing
|
| 75 |
+
_orig_fwd = model.forward
|
| 76 |
+
|
| 77 |
+
def _half_fwd(*a, **kw):
|
| 78 |
+
prev = torch.get_default_dtype()
|
| 79 |
+
torch.set_default_dtype(torch.float16)
|
| 80 |
+
try:
|
| 81 |
+
out = _orig_fwd(*a, **kw)
|
| 82 |
+
finally:
|
| 83 |
+
torch.set_default_dtype(prev)
|
| 84 |
+
if torch.is_tensor(out) and out.is_floating_point():
|
| 85 |
+
return out.float()
|
| 86 |
+
return out
|
| 87 |
+
|
| 88 |
+
model.forward = _half_fwd
|
| 89 |
+
|
| 90 |
+
model = model.to(device).eval()
|
| 91 |
+
return model
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
if __name__ == "__main__":
|
| 95 |
+
m = load_diarizer()
|
| 96 |
+
n = sum(p.numel() for p in m.parameters())
|
| 97 |
+
print(f"loaded OK: {n/1e6:.1f}M params, encoder dtype={next(m.encoder.parameters()).dtype}")
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:86444dd50d63cad3875ef3aab679ebc842466511c49753f9869b8e4ad5395cba
|
| 3 |
+
size 471103752
|