Dragonhead commited on
Commit
245508e
·
0 Parent(s):

Consolidated release (history squashed to remove internal filesystem paths from prior revisions)

Browse files
Files changed (5) hide show
  1. .gitattributes +37 -0
  2. README.md +289 -0
  3. config.yaml +161 -0
  4. load_diarizer.py +97 -0
  5. 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
+ ![License](https://img.shields.io/badge/license-AudarAI%20Community%20v1.0-6f42c1)
71
+ ![Task](https://img.shields.io/badge/task-speaker%20diarization-blue)
72
+ ![Params](https://img.shields.io/badge/params-117.7M-f59e0b)
73
+ ![Speakers](https://img.shields.io/badge/speakers-up%20to%208-f59e0b)
74
+ ![Streaming](https://img.shields.io/badge/streaming-1.04s%20latency%20%C2%B7%20RTF%200.003-informational)
75
+ ![DER](https://img.shields.io/badge/8--corpus%20macro%20DER-22.03%25%20%C2%B7%20SOTA-brightgreen)
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