Automatic Speech Recognition
NeMo
Thai
English
speech
streaming
Thai
FastConformer
Transducer
NeMo
Nemotron
Instructions to use iapp/OpenThai2.0-ASR-Streaming with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- NeMo
How to use iapp/OpenThai2.0-ASR-Streaming with NeMo:
import nemo.collections.asr as nemo_asr asr_model = nemo_asr.models.ASRModel.from_pretrained("iapp/OpenThai2.0-ASR-Streaming") transcriptions = asr_model.transcribe(["file.wav"]) - Notebooks
- Google Colab
- Kaggle
Mac: the server uses 1 CPU thread on macOS (more threads are slower on Apple silicon); card: measured M3 Max speeds, and the 8-thread CPU figure is a server, not a laptop
Browse files- README.md +4 -2
- serve/README.md +1 -1
- serve/app.py +1 -1
- serve/chinda_asr.py +17 -1
README.md
CHANGED
|
@@ -37,7 +37,7 @@ Streaming Thai speech-to-text with 0.64B parameters. It writes Thai speech down
|
|
| 37 |
<tr><td><b>Training data</b></td><td>12,373 hours of Thai speech and 961 hours of English (LibriSpeech)</td></tr>
|
| 38 |
<tr><td><b>Input</b></td><td>16 kHz mono audio (128 log-mel bands); any length when cut at pauses, as the included server does</td></tr>
|
| 39 |
<tr><td><b>Files</b></td><td>Two <code>.nemo</code> files of 2.56 GB each, 32-bit weights (see <a href="#use">Use</a>). An ONNX export, full precision and int8, is at <a href="https://huggingface.co/iapp/OpenThai2.0-ASR-Streaming-ONNX">iapp/OpenThai2.0-ASR-Streaming-ONNX</a></td></tr>
|
| 40 |
-
<tr><td><b>Speed</b></td><td>One NVIDIA H100: files about 257× faster than real time, about 20 live streams at once at 560 ms. A CPU
|
| 41 |
<tr><td><b>Memory</b></td><td>About 2.6 GB for the weights; about 6.5 GB of RAM in all on a CPU</td></tr>
|
| 42 |
<tr><td><b>Licence</b></td><td>OpenMDW-1.1</td></tr>
|
| 43 |
</table>
|
|
@@ -220,7 +220,9 @@ print(transcribe_long(asr, load_audio("talk.wav"), policy=MODES["standard"])["di
|
|
| 220 |
|
| 221 |
All fields, environment variables and error codes: [`serve/README.md`](serve/README.md). The service has no authentication; keep it on localhost or behind your own gateway.
|
| 222 |
|
| 223 |
-
**Speed.** On one NVIDIA H100, files decode about 257 times faster than real time, and one copy of the model keeps up with about 20 live streams at 560 ms. On a
|
|
|
|
|
|
|
| 224 |
|
| 225 |
## What the output looks like
|
| 226 |
|
|
|
|
| 37 |
<tr><td><b>Training data</b></td><td>12,373 hours of Thai speech and 961 hours of English (LibriSpeech)</td></tr>
|
| 38 |
<tr><td><b>Input</b></td><td>16 kHz mono audio (128 log-mel bands); any length when cut at pauses, as the included server does</td></tr>
|
| 39 |
<tr><td><b>Files</b></td><td>Two <code>.nemo</code> files of 2.56 GB each, 32-bit weights (see <a href="#use">Use</a>). An ONNX export, full precision and int8, is at <a href="https://huggingface.co/iapp/OpenThai2.0-ASR-Streaming-ONNX">iapp/OpenThai2.0-ASR-Streaming-ONNX</a></td></tr>
|
| 40 |
+
<tr><td><b>Speed</b></td><td>One NVIDIA H100: files about 257× faster than real time, about 20 live streams at once at 560 ms. A server CPU limited to 8 threads: files about 4× real time, a live stream keeps up at 560 and 320 ms. Apple M3 Max with 1 thread: files about 3× real time, a live stream keeps up at 560 and 320 ms</td></tr>
|
| 41 |
<tr><td><b>Memory</b></td><td>About 2.6 GB for the weights; about 6.5 GB of RAM in all on a CPU</td></tr>
|
| 42 |
<tr><td><b>Licence</b></td><td>OpenMDW-1.1</td></tr>
|
| 43 |
</table>
|
|
|
|
| 220 |
|
| 221 |
All fields, environment variables and error codes: [`serve/README.md`](serve/README.md). The service has no authentication; keep it on localhost or behind your own gateway.
|
| 222 |
|
| 223 |
+
**Speed.** On one NVIDIA H100, files decode about 257 times faster than real time, and one copy of the model keeps up with about 20 live streams at 560 ms. On a server CPU limited to 8 threads, files decode about 4 times faster than real time and live streams keep up at 560 and 320 ms, with about 6.5 GB of memory.
|
| 224 |
+
|
| 225 |
+
**On a Mac (Apple silicon), use one CPU thread.** There, every extra thread makes decoding slower: on an M3 Max a 7.6-second clip takes 2.3 s with 1 thread, 6.4 s with 4 and 20.7 s with 16. The reference server uses 1 thread on macOS unless `CHINDA_NUM_THREADS` says otherwise. For NeMo's streaming script, run it with `OMP_NUM_THREADS=1`; in your own Python code, call `torch.set_num_threads(1)`. With 1 thread on the M3 Max, live streams keep up at 560 and 320 ms; at 80 ms it decodes at only about real time, with no margin, and memory peaks at about 6 GB. The model runs on the CPU only on a Mac: the server does not use the Apple GPU (MPS).
|
| 226 |
|
| 227 |
## What the output looks like
|
| 228 |
|
serve/README.md
CHANGED
|
@@ -46,7 +46,7 @@ The blank bias δ controls how readily the model writes something: the more nega
|
|
| 46 |
| `CHINDA_MODEL` | `../openthai2.0-asr-streaming_raw.nemo` | must be the raw file; `*_bias-*` files are refused (the bias would apply twice) |
|
| 47 |
| `CHINDA_PRELOAD` | unset | `1` = load at start-up; otherwise the first request waits about 20 s |
|
| 48 |
| `CHINDA_REPLICAS` | `1` | model copies, about 2.6 GB each; each copy runs one decode step at a time |
|
| 49 |
-
| `CHINDA_NUM_THREADS` | min(16, CPUs) | CPU threads |
|
| 50 |
| `CHINDA_DIAR_MODEL` | download | path to a local `Nemotron-3-Diarization.nemo` |
|
| 51 |
| `CHINDA_CUDA_GRAPHS` | `0` | leave at 0: `1` crashes on the request after speaker labels |
|
| 52 |
| `CHINDA_SPLIT_FROM_S` | `30` | `split=auto` cuts files longer than this many seconds |
|
|
|
|
| 46 |
| `CHINDA_MODEL` | `../openthai2.0-asr-streaming_raw.nemo` | must be the raw file; `*_bias-*` files are refused (the bias would apply twice) |
|
| 47 |
| `CHINDA_PRELOAD` | unset | `1` = load at start-up; otherwise the first request waits about 20 s |
|
| 48 |
| `CHINDA_REPLICAS` | `1` | model copies, about 2.6 GB each; each copy runs one decode step at a time |
|
| 49 |
+
| `CHINDA_NUM_THREADS` | min(16, CPUs); 1 on macOS | CPU threads. On Apple silicon each extra thread makes decoding slower, so macOS uses 1 |
|
| 50 |
| `CHINDA_DIAR_MODEL` | download | path to a local `Nemotron-3-Diarization.nemo` |
|
| 51 |
| `CHINDA_CUDA_GRAPHS` | `0` | leave at 0: `1` crashes on the request after speaker labels |
|
| 52 |
| `CHINDA_SPLIT_FROM_S` | `30` | `split=auto` cuts files longer than this many seconds |
|
serve/app.py
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
Environment:
|
| 6 |
CHINDA_MODEL raw (unbaked) .nemo (default ../openthai2.0-asr-streaming_raw.nemo)
|
| 7 |
CHINDA_DEVICE cpu (default) | cuda
|
| 8 |
-
CHINDA_NUM_THREADS torch CPU threads (default min(16, cpu_count))
|
| 9 |
CHINDA_REPLICAS model replicas (default 1; each ~2.6 GB)
|
| 10 |
CHINDA_PRELOAD 1 = load the model at startup instead of on the first request
|
| 11 |
CHINDA_DIAR_MODEL Nemotron-3-Diarization .nemo for speaker labels (default: downloaded from Hugging Face)
|
|
|
|
| 5 |
Environment:
|
| 6 |
CHINDA_MODEL raw (unbaked) .nemo (default ../openthai2.0-asr-streaming_raw.nemo)
|
| 7 |
CHINDA_DEVICE cpu (default) | cuda
|
| 8 |
+
CHINDA_NUM_THREADS torch CPU threads (default min(16, cpu_count); 1 on macOS, where more threads are slower)
|
| 9 |
CHINDA_REPLICAS model replicas (default 1; each ~2.6 GB)
|
| 10 |
CHINDA_PRELOAD 1 = load the model at startup instead of on the first request
|
| 11 |
CHINDA_DIAR_MODEL Nemotron-3-Diarization .nemo for speaker labels (default: downloaded from Hugging Face)
|
serve/chinda_asr.py
CHANGED
|
@@ -53,6 +53,22 @@ import numpy as np
|
|
| 53 |
log = logging.getLogger("chinda_asr")
|
| 54 |
|
| 55 |
SAMPLE_RATE = 16000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
FEATURE_TOL = 1e-3 # max |log-mel difference| accepted between streamed and whole-utterance features
|
| 57 |
# (GPU rounding noise); larger => the stream is re-decoded from the buffered audio
|
| 58 |
HOP = 160 # samples per feature frame (preprocessor window_stride 0.01 s)
|
|
@@ -394,7 +410,7 @@ class ChindaASR:
|
|
| 394 |
)
|
| 395 |
self.model_path = model_path
|
| 396 |
self.device = device or os.environ.get("CHINDA_DEVICE", "cpu") # "cuda" / "cuda:0" on a GPU host
|
| 397 |
-
self.num_threads = num_threads or int(os.environ.get("CHINDA_NUM_THREADS", "0")) or
|
| 398 |
self.n_replicas = max(1, int(replicas))
|
| 399 |
self.verify_stream_max_s = verify_stream_max_s
|
| 400 |
self._load_lock = threading.Lock()
|
|
|
|
| 53 |
log = logging.getLogger("chinda_asr")
|
| 54 |
|
| 55 |
SAMPLE_RATE = 16000
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def default_threads() -> int:
|
| 59 |
+
"""CPU threads when CHINDA_NUM_THREADS is not set.
|
| 60 |
+
|
| 61 |
+
macOS: 1. On Apple silicon, PyTorch's CPU path gets slower with every extra thread for this model
|
| 62 |
+
(M3 Max, a 7.6 s clip: 1 thread about 2 s, 16 threads about 21 s; measured 13 Nov 2026).
|
| 63 |
+
Elsewhere: min(16, CPUs).
|
| 64 |
+
"""
|
| 65 |
+
import sys
|
| 66 |
+
|
| 67 |
+
if sys.platform == "darwin":
|
| 68 |
+
return 1
|
| 69 |
+
return min(16, os.cpu_count() or 1)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
FEATURE_TOL = 1e-3 # max |log-mel difference| accepted between streamed and whole-utterance features
|
| 73 |
# (GPU rounding noise); larger => the stream is re-decoded from the buffered audio
|
| 74 |
HOP = 160 # samples per feature frame (preprocessor window_stride 0.01 s)
|
|
|
|
| 410 |
)
|
| 411 |
self.model_path = model_path
|
| 412 |
self.device = device or os.environ.get("CHINDA_DEVICE", "cpu") # "cuda" / "cuda:0" on a GPU host
|
| 413 |
+
self.num_threads = num_threads or int(os.environ.get("CHINDA_NUM_THREADS", "0")) or default_threads()
|
| 414 |
self.n_replicas = max(1, int(replicas))
|
| 415 |
self.verify_stream_max_s = verify_stream_max_s
|
| 416 |
self._load_lock = threading.Lock()
|