Audio Classification
Transformers
ONNX
Safetensors
English
dualturn_endpointing
feature-extraction
turn-taking
endpointing
end-of-turn
voice-activity-detection
voice-agents
conversation
speech
audio
mimi
dualturn
real-time
custom_code
Instructions to use anyreach-ai/dualturn-endpointing with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use anyreach-ai/dualturn-endpointing with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("audio-classification", model="anyreach-ai/dualturn-endpointing", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("anyreach-ai/dualturn-endpointing", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Fix streaming ONNX: audio_ctx was zero-initialized (Mimi not zero-prefix-invariant) -> streaming diverged 0.17-0.66 from offline. Now grows from empty (dynamic audio_ctx), matches offline to 0.019. Rebuild both stream_tick graphs + helper.
Browse files- README.md +8 -8
- onnx_streaming.py +2 -2
- stream_tick.onnx +2 -2
- stream_tick_240.onnx +2 -2
README.md
CHANGED
|
@@ -65,7 +65,7 @@ Four ready-to-use paths (all **fp32, same weights**). **Offline** processes a wh
|
|
| 65 |
| 1 | Offline | PyTorch β `model(wav, sr)` | ~7.5 ms/frame (batch, ~11Γ real-time) | torch + transformers |
|
| 66 |
| 2 | Streaming | PyTorch β `model.streaming()` | **~63 ms / 80 ms tick** (real-time, flat) | torch + transformers |
|
| 67 |
| 3 | Offline | ONNX β `model.onnx` | ~7.3 ms/frame (~11Γ real-time) | onnxruntime |
|
| 68 |
-
| 4 | Streaming | ONNX β `stream_tick.onnx` | **~
|
| 69 |
|
| 70 |
*Offline latency is amortized batch throughput (all frames returned after the file is processed); streaming
|
| 71 |
latency is the real per-tick cost in a live call β both well under the 80 ms budget. Streaming is causal +
|
|
@@ -88,14 +88,14 @@ runs to keep up with one live stream (`latency Γ calls/sec`):
|
|
| 88 |
|
| 89 |
| graph | infer every | latency / call | calls / audio-sec | grace / call | CPU busy / stream |
|
| 90 |
|---|---|---|---|---|---|
|
| 91 |
-
| `stream_tick.onnx` | **80 ms** |
|
| 92 |
-
| `stream_tick_240.onnx` | **240 ms** |
|
| 93 |
|
| 94 |
-
- **80 ms β snappiest, but heaviest.** Each call eats
|
| 95 |
stream. Fastest reactions, least room for anything else.
|
| 96 |
-
- **240 ms β much cheaper, lots of slack.** Each call takes
|
| 97 |
-
but leaves **
|
| 98 |
-
- **The real win is at scale.** CPU per second of audio drops **~1.8Γ** (
|
| 99 |
machine holds roughly ~1.8Γ as many concurrent calls** β the per-stream % looks modest, but that factor
|
| 100 |
multiplies across every simultaneous call in your fleet. *(`CPU busy %` is a single-stream duty cycle
|
| 101 |
measured with 4 threads β read it as relative cost between graphs, not absolute single-core load.)*
|
|
@@ -187,7 +187,7 @@ graph is fully self-contained β no `torch` or `transformers` needed at inferen
|
|
| 187 |
|
| 188 |
Torch-free real-time streaming via `stream_tick.onnx` + the `onnx_streaming.py` helper (`DualTurnONNXStreamer`).
|
| 189 |
Each 80 ms tick only computes the **new frame** β the graph carries all state (conv buffer, transformer **KV
|
| 190 |
-
window**, downsample history, LSTM state) in/out, so latency is low (**~
|
| 191 |
calls**. Decision-equivalent to the PyTorch model.
|
| 192 |
|
| 193 |
```python
|
|
|
|
| 65 |
| 1 | Offline | PyTorch β `model(wav, sr)` | ~7.5 ms/frame (batch, ~11Γ real-time) | torch + transformers |
|
| 66 |
| 2 | Streaming | PyTorch β `model.streaming()` | **~63 ms / 80 ms tick** (real-time, flat) | torch + transformers |
|
| 67 |
| 3 | Offline | ONNX β `model.onnx` | ~7.3 ms/frame (~11Γ real-time) | onnxruntime |
|
| 68 |
+
| 4 | Streaming | ONNX β `stream_tick.onnx` | **~52 ms / 80 ms tick** (real-time, flat, tightest tail) | onnxruntime |
|
| 69 |
|
| 70 |
*Offline latency is amortized batch throughput (all frames returned after the file is processed); streaming
|
| 71 |
latency is the real per-tick cost in a live call β both well under the 80 ms budget. Streaming is causal +
|
|
|
|
| 88 |
|
| 89 |
| graph | infer every | latency / call | calls / audio-sec | grace / call | CPU busy / stream |
|
| 90 |
|---|---|---|---|---|---|
|
| 91 |
+
| `stream_tick.onnx` | **80 ms** | 52 ms | 12.5 | 28 ms | ~65% |
|
| 92 |
+
| `stream_tick_240.onnx` | **240 ms** | 85 ms | 4.17 | 155 ms | ~36% |
|
| 93 |
|
| 94 |
+
- **80 ms β snappiest, but heaviest.** Each call eats 52 ms β only **28 ms grace**, pool busy **~65%** per
|
| 95 |
stream. Fastest reactions, least room for anything else.
|
| 96 |
+
- **240 ms β much cheaper, lots of slack.** Each call takes 85 ms (vs 3 Γ 52 = 156 ms for three 80 ms ticks)
|
| 97 |
+
but leaves **155 ms grace** and sits at **~36% busy**.
|
| 98 |
+
- **The real win is at scale.** CPU per second of audio drops **~1.8Γ** (650 β 354 ms/s, β45% less), so **one
|
| 99 |
machine holds roughly ~1.8Γ as many concurrent calls** β the per-stream % looks modest, but that factor
|
| 100 |
multiplies across every simultaneous call in your fleet. *(`CPU busy %` is a single-stream duty cycle
|
| 101 |
measured with 4 threads β read it as relative cost between graphs, not absolute single-core load.)*
|
|
|
|
| 187 |
|
| 188 |
Torch-free real-time streaming via `stream_tick.onnx` + the `onnx_streaming.py` helper (`DualTurnONNXStreamer`).
|
| 189 |
Each 80 ms tick only computes the **new frame** β the graph carries all state (conv buffer, transformer **KV
|
| 190 |
+
window**, downsample history, LSTM state) in/out, so latency is low (**~52 ms/tick**) and **flat over long
|
| 191 |
calls**. Decision-equivalent to the PyTorch model.
|
| 192 |
|
| 193 |
```python
|
onnx_streaming.py
CHANGED
|
@@ -48,8 +48,8 @@ class _BaseStreamer:
|
|
| 48 |
|
| 49 |
def reset(self):
|
| 50 |
z = np.zeros
|
| 51 |
-
self.state = {"audio_ctx": z((2,
|
| 52 |
-
"pk": z((self.NL, 2, self.H, 0, self.HD), np.float32), #
|
| 53 |
"pv": z((self.NL, 2, self.H, 0, self.HD), np.float32),
|
| 54 |
"tf_hist": z((2, self.TFK, 512), np.float32),
|
| 55 |
"h": z((2, 1, 256), np.float32), "c": z((2, 1, 256), np.float32),
|
|
|
|
| 48 |
|
| 49 |
def reset(self):
|
| 50 |
z = np.zeros
|
| 51 |
+
self.state = {"audio_ctx": z((2, 0), np.float32), # EMPTY: grow from true start (no
|
| 52 |
+
"pk": z((self.NL, 2, self.H, 0, self.HD), np.float32), # zero-prefix; graph caps at RF). KV likewise grows/caps in-graph
|
| 53 |
"pv": z((self.NL, 2, self.H, 0, self.HD), np.float32),
|
| 54 |
"tf_hist": z((2, self.TFK, 512), np.float32),
|
| 55 |
"h": z((2, 1, 256), np.float32), "c": z((2, 1, 256), np.float32),
|
stream_tick.onnx
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6700ea2f919b4c66355b3dafb4f91f2db9dae9e18a302b812730c04340bf819f
|
| 3 |
+
size 161722822
|
stream_tick_240.onnx
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e841d80d2a004ab4ea9d2e85f168505853064c4a9ba7341d180cf352062ed7c7
|
| 3 |
+
size 162401094
|