kobkrit commited on
Commit
1dc764a
·
verified ·
1 Parent(s): 099f1e7

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
Files changed (4) hide show
  1. README.md +4 -2
  2. serve/README.md +1 -1
  3. serve/app.py +1 -1
  4. 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 with 8 threads: files about 4× 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,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 laptop-class CPU with 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
  ## 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 min(16, os.cpu_count() or 1)
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()