kobkrit's picture
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
1dc764a verified
|
Raw History Blame Contribute Delete
4.38 kB
# OpenThai2.0 ASR Streaming service: reference
Install, start and example calls: see the main [README](../README.md).
## Endpoints
| endpoint | what it does |
|---|---|
| `POST /v1/transcribe` | multipart `file` + the fields below → JSON |
| `WS /v1/live` | a live stream of any length: JSON config → PCM16 LE 16 kHz mono frames → text `end`. Replies `partial` (the sentence so far) and one `final` each time the speaker pauses, then `done` |
| `WS /v1/stream` | one utterance: JSON config → frames → `end`; replies `partial`… then one `final` |
| `GET /v1/health` | loaded model and settings |
| field | values | default |
|---|---|---|
| `mode` | `standard` · `conversation` · `meeting` | `standard` |
| `lang` | `th-TH` · `th-mix` | `th-TH` |
| `latency_ms` | 80 · 320 · 560 · 1120 | 560 |
| `diarize` | `true` · `false` | on for `meeting` |
| `split` (files) | `auto` · `on` · `off` | `auto`: cut files longer than 30 s at pauses |
| `end_silence_ms` (`/v1/live`) | 300–3000 | 700 |
**Response fields:**
- `text` (raw, as scored) and `display_text` (for display).
- `duration_s`, `lang`, `latency_ms`, `mode`, `policy`, `delta`, `model`, `processing_s`.
- A file cut at pauses also has `segments` `[{start, end, text}]`, one per piece.
- With speaker labels, also `segments` `[{speaker, start, end, text}]` and `speakers`.
**Errors:** 400 empty file · 415 unreadable audio · 422 bad option.
## What each mode does
The blank bias δ controls how readily the model writes something: the more negative it is, the fewer words get dropped.
| mode | decode |
|---|---|
| `standard` | δ −1.0; clips under 2 s re-decoded at −2.5; empty output re-decoded at −4.0 |
| `conversation` | δ −2.0; empty output re-decoded at −4.0 |
| `meeting` | δ −1.0 + speaker labels from `nvidia/Nemotron-3-Diarization` (downloaded on first use) |
## Environment variables
| var | default | meaning |
|---|---|---|
| `CHINDA_DEVICE` | `cpu` | `cuda` for GPU |
| `CHINDA_MODEL` | `../openthai2.0-asr-streaming_raw.nemo` | must be the raw file; `*_bias-*` files are refused (the bias would apply twice) |
| `CHINDA_PRELOAD` | unset | `1` = load at start-up; otherwise the first request waits about 20 s |
| `CHINDA_REPLICAS` | `1` | model copies, about 2.6 GB each; each copy runs one decode step at a time |
| `CHINDA_NUM_THREADS` | min(16, CPUs); 1 on macOS | CPU threads. On Apple silicon each extra thread makes decoding slower, so macOS uses 1 |
| `CHINDA_DIAR_MODEL` | download | path to a local `Nemotron-3-Diarization.nemo` |
| `CHINDA_CUDA_GRAPHS` | `0` | leave at 0: `1` crashes on the request after speaker labels |
| `CHINDA_SPLIT_FROM_S` | `30` | `split=auto` cuts files longer than this many seconds |
## Long audio
Do not decode a long recording as one stream. After a pause of near-silence this model drops some of the speech that follows: TVSpeech's videos, rebuilt by joining their clips with one second of silence, score 23.5% character errors as one stream and 12.7% cut at pauses (12.4% clip by clip). So:
- `POST /v1/transcribe` cuts any file longer than 30 s where nobody speaks for 0.7 s, and decodes the pieces on their own (`split=auto`). Speaker labels (`mode=meeting`) still decode one stream.
- `WS /v1/live` ends an utterance at each pause and starts the next one fresh, like iApp's hosted OpenThai ASR Realtime.
- `WS /v1/stream` decodes everything you send as one utterance; keep it for short commands and single sentences.
- In Python: `chinda_realtime.transcribe_long(asr, audio)` for recordings, `chinda_realtime.RealtimeSession` for live audio.
The pause detector is an energy rule (`chinda_realtime.Endpointer`): a 20 ms frame is speech when it is louder than both the loudest recent level − 30 dB and the quietest recent level + 8 dB. Stretches with no speech are skipped.
## Good to know
- No authentication or rate limiting.
- With `diarize` on `/v1/stream`, speaker labels arrive with the `final` message only; `/v1/live` has no speaker labels. Speakers are anonymous (`speaker_0`, …).
- Live text equals decoding the finished utterance on its own. Batch decoding (the benchmark) can differ by the last word on clips under 2 s.
- With `ffmpeg` on the PATH, the API also accepts mp3, m4a and ogg.
- The decode loop is adapted from NVIDIA NeMo's `speech_to_text_cache_aware_streaming_infer.py` (Apache-2.0).