voice_audio_multi/docs/models/voxtral_realtime.md
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

234 lines
12 KiB
Markdown

# Voxtral Realtime
Voxtral Realtime is a Mistral realtime ASR model with offline and streaming
sessions. The model manager installs the Q8_0 standalone GGUF package by
default; native Hugging Face directories and other standalone GGUF variants can
also be used when provided directly. A Q4_K GGUF package is available for lower
memory use and faster CUDA runs; in a quick path check its transcripts matched
Q8_0 except for one capitalization-only difference.
## Model
| Field | Value |
|---|---|
| Family | `voxtral_realtime` |
| Model path | `models/Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf` when installed through the model manager |
| GGUF variants | `bf16`, `q8_0`, `q4_k` |
| Task | `asr` |
| Modes | `offline`, `streaming` |
| Output | Transcription text |
| Streaming input | Audio chunks |
| Timestamps | Not exposed |
## CLI
Offline:
```bash
audiocpp_cli --task asr --family voxtral_realtime --model models/Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf --backend cuda --threads 8 --audio assets/resources/sample.wav --text-out transcript.txt
```
Sampling and token-cap options can be passed through request options:
```bash
audiocpp_cli --task asr --family voxtral_realtime --model models/Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf --backend cuda --threads 8 --audio assets/resources/sample.wav --text-out transcript.txt --request-option max_new_tokens=256 --do-sample false --temperature 1.0 --top-p 1.0 --top-k 50 --seed 1234
```
Streaming:
```bash
audiocpp_cli --task asr --family voxtral_realtime --model models/Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf --backend cuda --threads 8 --mode streaming --audio assets/resources/sample.wav --text-out transcript.txt
```
## Live Streaming Input
`--audio -` reads raw, headerless interleaved PCM from stdin and feeds it to the
model chunk by chunk as it arrives, so the audio is never buffered up front and
does not have to exist as a file. Any capture tool that can write PCM to a pipe
works as the source:
```bash
# Microphone (macOS; use -f alsa on Linux or -f dshow on Windows)
ffmpeg -f avfoundation -i ":0" -ar 16000 -ac 1 -f s16le - \
| audiocpp_cli --task asr --family voxtral_realtime --model models/Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf --backend cuda --threads 8 --mode streaming --audio -
```
```bash
# Any file or network stream, decoded to PCM on the fly
ffmpeg -i input.mp3 -ar 16000 -ac 1 -f s16le - \
| audiocpp_cli --task asr --family voxtral_realtime --model models/Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf --backend cuda --threads 8 --mode streaming --audio -
```
Stdin input requires `--mode streaming`, and the PCM format must be described
up front because a live stream carries no header. The defaults (`s16le`, 16 kHz,
mono) match what the model expects. The chosen interpretation is echoed as an
`audio_input=stdin` line.
Each update carries only the text decoded since the last one, matching the
other streaming ASR models, so the updates concatenate into the transcript. On
a terminal they are appended unlabelled and the transcript scrolls like
ordinary output. When stdout is redirected, each update is written as its own
`partial_text=` line and flushed as it is produced, so pipes and logs stay
parseable. The complete transcript is also printed once at the end as
`text_output=`.
An update covers one decoded chunk, so `stream_batch_tokens=<n>` reports every
`n`th token's worth of text in a single update rather than making the updates
`n` times shorter. Whatever the batch size, concatenating the updates
reproduces `text_output=` exactly.
Emitting deltas rather than restating the transcript matters for long runs,
where the restated form is quadratic in the transcript length: a one-hour
session writes roughly 364 MB restated against about 54 KB as deltas.
To capture the transcript itself rather than the update stream, use
`--text-out`, which writes the complete transcript and nothing else:
```bash
ffmpeg -f avfoundation -i ":0" -ar 16000 -ac 1 -f s16le - \
| audiocpp_cli --task asr --family voxtral_realtime --model models/Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf --backend cuda --mode streaming --audio - --text-out transcript.txt
```
`--text-out` and the `text_output=` line are both written when the stream ends,
so an interrupted session leaves neither. The `partial_text=` lines are flushed
as they are produced, so a log of them survives an interrupted run and
concatenates back into the transcript:
```bash
grep '^partial_text=' session.log | sed 's/^partial_text=//' | tr -d '\n' > transcript.txt
```
## Live PCM Over HTTP
The same live source is available to an HTTP client through
`POST /v1/audio/transcriptions/live`: raw PCM goes up in a chunked request body
while transcript deltas come back as SSE on the same connection. This is the
server equivalent of `--audio -`, and the only way to get capture-time partials
without the CLI. See [the server README](../../app/server/README.md) for
parameters and examples.
```bash
ffmpeg -f alsa -i default -ar 16000 -ac 1 -f s16le - \
| curl -N -X POST -H 'Expect:' -T - \
'http://127.0.0.1:8080/v1/audio/transcriptions/live?model=voxtral-realtime'
```
Use `-T -`, not `--data-binary @-`: the latter reads stdin to EOF before it
connects, so a live capture would be uploaded as a finished file and no partial
could arrive early.
Whether text appears while the speaker is still talking depends on the model's
streaming policy rather than on the transport. `voxtral_realtime` decodes as
audio arrives and emits throughout the utterance; `nemotron_asr` consumes the
full utterance in its encoder first, so its deltas arrive only once the audio
ends. Both are supported here; the difference is what the transcript looks
like mid-sentence.
## Streaming Throughput
A streaming step always advances 80 ms of audio, so a step has to cost under
80 ms to keep up with a realtime source. Measured on an Apple M3 Air (Metal,
Q8_0):
| Config | Short clip, cool | Sustained 7 min |
|---|---:|---:|
| default | 78 ms/step (0.98x) | 88 ms/step (1.10x) |
| `stream_batch_tokens=4` | 60 ms/step (0.76x) | 74 ms/step (0.92x) |
The default splits roughly 48 ms for the text decoder and 30 ms for the audio
encoder; batching takes the encoder to about 13 ms. The second column is what a
long session actually gets on a fanless machine: a short clip run immediately
after the seven-minute one still measured 88 ms/step, so the gap is the machine
staying warm rather than anything that resets between sessions. Budget for the
sustained column, and prefer `stream_batch_tokens=4` if the source is realtime.
The decoder runs one step per 80 ms whether the audio holds speech or silence,
so a session that falls behind stays behind. The lag is monotonic and does not
recover during pauses. Measure your own hardware before relying on a live
source.
## Server
Streaming server config:
```json
{
"host": "127.0.0.1",
"port": 8080,
"backend": "cuda",
"device": 0,
"threads": 8,
"lazy_load": true,
"models": [
{
"id": "voxtral-stream",
"family": "voxtral_realtime",
"path": "/path/to/voxtral-mini-4b-realtime-2602-q8_0.gguf",
"task": "asr",
"mode": "streaming"
}
]
}
```
Streaming server request:
```bash
curl -N http://127.0.0.1:8080/v1/audio/transcriptions \
-F model=voxtral-stream \
-F stream=true \
-F file=@assets/resources/sample.wav
```
## Common Options (use directly)
| Option | Values | Default | Meaning |
|---|---|---:|---|
| `--audio` | WAV path or `-` | required | Speech input. `-` streams raw PCM from stdin and requires `--mode streaming`. |
| `--input-format` | `s16le`, `f32le` | `s16le` | Sample format of raw PCM read from stdin. Ignored for file input. |
| `--input-rate` | integer Hz | `16000` | Sample rate of raw PCM read from stdin. Ignored for file input. |
| `--input-channels` | integer | `1` | Channel count of raw PCM read from stdin. Ignored for file input. |
| `--mode` | `offline`, `streaming` | `offline` | Full-context or streaming session. |
| `--do-sample` | bool | `false` | Enable sampling instead of greedy decode. |
| `--temperature` | float > 0 | `1.0` | Sampling temperature. |
| `--top-p` | `(0, 1]` | `1.0` | Nucleus sampling limit. |
| `--top-k` | integer | `50` | Top-k sampling limit; `0` disables top-k. |
| `--seed` | integer | `1234` | Sampling seed. |
| `--text-out` | TXT path | not set | Transcript output. The transcript is also printed to stdout. |
## Request Options (use with `--request-option`)
| Option | Values | Default | Meaning |
|---|---|---:|---|
| `max_tokens` | integer | not set | Maximum generated transcript tokens; omitted uses the audio-derived limit. Offline decoding only; ignored by live streaming. |
| `do_sample` | bool | `false` | Sample rather than greedily decode. |
| `temperature` | float > 0 | `1` | Positive sampling temperature. |
| `top_p` | `(0, 1]` | `1` | Nucleus sampling probability in (0, 1]. |
| `top_k` | integer >= 0 | `50` | Top-k sampling limit; zero disables filtering. |
| `seed` | integer >= 0 | `1234` | Sampling seed. |
The legacy request name `max_new_tokens` remains accepted. Use only one name per request.
## Session Options (use with `--session-option`)
| Option | Values | Default | Meaning |
|---|---|---:|---|
| `voxtral_realtime.weight_type` | `native`, `f32`, `f16`, `bf16`, `q4_0`, `q4_1`, `q5_0`, `q5_1`, `q2_k`, `q3_k`, `q4_k`, `q5_k`, `q6_k`, `q8_0`, `nvfp4` | `native` | Shared matmul weight storage type accepted by the tensor loader; backend and shape support still apply. |
| `voxtral_realtime.audio_encoder_weight_type` | same as above | shared setting | Audio encoder matmul weight storage type. Leave at `native` for streaming: the encoder is not bandwidth-bound there, so quantizing it makes it slower. |
| `voxtral_realtime.text_decoder_weight_type` | same as above | shared setting | Text decoder matmul weight storage type. `q4_k` roughly halves the streaming decoder step cost. |
| `voxtral_realtime.audio_encoder_graph_arena_mb` | integer MiB | `512` | Audio encoder graph arena size. |
| `voxtral_realtime.audio_encoder_weight_context_mb` | integer MiB | `128` | Audio encoder weight context arena size. |
| `voxtral_realtime.text_decoder_prefill_graph_arena_mb` | integer MiB | `512` | Decoder prefill graph arena in MiB. Decoder uses the maximum of prefill and decode arena settings. |
| `voxtral_realtime.text_decoder_decode_graph_arena_mb` | integer MiB | `512` | Decoder step graph arena in MiB. Decoder uses the maximum of prefill and decode arena settings. |
| `voxtral_realtime.text_decoder_weight_context_mb` | integer MiB | `128` | Text decoder weight context arena size. |
| `voxtral_realtime.stream_decode_cache_steps` | integer >= 1 | `1024` | Streaming decoder cache capacity in 80 ms steps. Must be positive and no greater than checkpoint sliding_window. |
| `voxtral_realtime.stream_batch_tokens` | integer >= 1 | `1` | Audio tokens per encoder forward. The decoder still runs one step per 80 ms; batching amortizes the encoder's fixed per-forward cost at the price of delaying each partial by up to `n * 80 ms`. |
Weight storage types are applied when the model loads, so requesting a type the
GGUF does not already hold requantizes on the CPU before the first token. This
takes around three minutes for `q4_k` from the shipped Q8_0 package. Prefer the
published `q4_k` GGUF variant, which needs no load-time conversion. See
[GGUF](../gguf.md).
For backend weight-type controls, use
`audiocpp_cli --inspect --model <model-dir> --family <family>`.