Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion docs/serving/speech_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,22 @@ See the [Gepard section of the online TTS hub](../user_guide/examples/online_ser

| Model | Description |
| ------- | ------------- |
| `k2-fsa/OmniVoice` | Pure-diffusion TTS. Supports voice cloning via `ref_audio` (with optional `ref_text`); no built-in voice presets. |
| `k2-fsa/OmniVoice` | OmniVoice generates mono speech at 24 kHz. It supports text-to-speech without a reference clip, voice cloning with `ref_audio` and optional `ref_text`, and voice design. It has no built-in voice presets. |

For voice cloning, send `ref_audio`. If `ref_text` is missing or blank, the default deploy config leaves Whisper unloaded until the first such request, then loads `openai/whisper-large-v3-turbo` on the worker device and transcribes the clip. Set `additional_config.omnivoice_asr.load_asr_on_startup` to `true` to load it during worker startup. Set `additional_config.omnivoice_asr.asr_device` to `cuda:0` or `cpu` to override the worker device. Sending `ref_text` avoids ASR and uses the supplied transcript.

OmniVoice prepares the reference clip before it builds the voice cloning input. It performs these steps:

- It converts the clip to mono, resamples it to 24 kHz, and measures its original root mean square (RMS) level.
- When the original RMS is greater than zero and less than `0.1`, it raises the waveform level to `0.1` and keeps the original RMS for output volume control.
- When `ref_text` is missing, it trims a clip longer than 20 seconds at a detected silence. Supplied text skips this special long-audio trim.
- It removes middle silence and trims silence at both edges whether the transcript is automatic or supplied by the caller.
- It aligns the sample count down to the audio tokenizer hop size.
- When `ref_text` is missing, it sends the prepared waveform to Whisper.
- It adds a final period for English text or a Chinese full stop for Chinese text when the reference text has no ending punctuation.
- It sends the same prepared waveform to the audio tokenizer.

OmniVoice processes voice-cloning audio after decoding. It removes middle gaps of at least 500 milliseconds. It trims edge silence while keeping 100 milliseconds at each edge. It scales cloned output using the original reference RMS when that value is below `0.1`, and leaves the level unchanged when the value is at least `0.1`. It fades the first and last 100 milliseconds, then adds 100 milliseconds of silence at each edge. Text-only output returns the decoder tensor without this voice-cloning postprocessing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] "removes middle gaps of at least 500 milliseconds" reads as if a 600 ms gap is removed. In audio.py the threshold is 500 ms and 500 ms is kept on each side, so only gaps longer than 1 s shrink, and they shrink to 1 s. The same sentence appears in the offline and online text-to-speech example docs. It would also help to say there that the ASR model is downloaded on first use, so offline (HF_HUB_OFFLINE=1) setups need to pre-download it like k2-fsa/OmniVoice.


OmniVoice uses packed variable-length attention for batched generator execution. The attention operator accepts FP16 and BF16 inputs, so its query,
key, and value tensors are evaluated in BF16 even when the stage is configured with `dtype: float32`; the attention output is converted back to the model's
Expand Down
37 changes: 34 additions & 3 deletions docs/user_guide/examples/offline_inference/text_to_speech.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ python examples/offline_inference/text_to_speech/<model>/end2end.py \
--ref-text "Transcript of the reference audio."
```

`--ref-audio` and `--ref-text` are optional (text-only synthesis works without them) and must be provided together for voice cloning. The exotic scripts — Qwen3-TTS, Voxtral TTS, CosyVoice3 — accept additional model-specific flags documented in their per-model section below.
For most models, `--ref-audio` and `--ref-text` are optional for text-only synthesis but must be provided together for voice cloning. OmniVoice also accepts `--ref-audio` without `--ref-text` and transcribes the reference lazily with Whisper. Qwen3-TTS, Voxtral TTS, and CosyVoice3 accept additional model-specific flags documented in their sections below.

---

Expand Down Expand Up @@ -252,15 +252,15 @@ Text → [Stage 0: AR] → Speech Tokens → [Stage 1: DiT + HiFT] → Audio (24

## OmniVoice

Zero-shot multilingual TTS supporting 600+ languages, with three modes (auto / clone / design).
OmniVoice creates speech in more than 600 languages. It supports text-to-speech without a reference clip, voice cloning, and voice design.

### Prerequisites

```bash
huggingface-cli download k2-fsa/OmniVoice
```

Voice cloning requires `transformers>=5.3.0`. Auto and design modes work with `transformers>=4.57.0`.
Voice cloning requires `transformers>=5.3.0`. Automatic voice and voice design use `transformers>=4.57.0`.

### Quick start (auto voice)

Expand All @@ -272,6 +272,8 @@ python examples/offline_inference/text_to_speech/omnivoice/end2end.py \

### Voice cloning

With an explicit transcript, voice cloning does not load Whisper:

```bash
python examples/offline_inference/text_to_speech/omnivoice/end2end.py \
--model k2-fsa/OmniVoice \
Expand All @@ -280,6 +282,30 @@ python examples/offline_inference/text_to_speech/omnivoice/end2end.py \
--ref-text "This is the reference transcription."
```

If `--ref-text` is omitted, OmniVoice loads `openai/whisper-large-v3-turbo` on the worker device and transcribes the reference audio after it prepares the waveform. The first request downloads and loads Whisper, so it takes longer and uses more device memory. Set `additional_config.omnivoice_asr.load_asr_on_startup` to `true` to load Whisper during worker startup, or set `additional_config.omnivoice_asr.asr_device` to use another GPU or the CPU.

```bash
python examples/offline_inference/text_to_speech/omnivoice/end2end.py \
--model k2-fsa/OmniVoice \
--text "hello" \
--ref-audio trump_ref.wav
```

### Reference audio processing

OmniVoice prepares the reference clip before it transcribes the clip or builds the voice cloning input. It performs these steps:

- It converts the clip to mono and resamples it to 24 kHz.
- It measures the original root mean square (RMS) level. When the RMS is greater than zero and below `0.1`, it raises the waveform level to `0.1` and keeps the original RMS for output volume control.
- When `--ref-text` is omitted, it trims a clip longer than 20 seconds at a detected silence.
- It removes middle silence and trims silence at both edges whether the transcript is automatic or supplied by the caller.
- It aligns the sample count down to the audio tokenizer hop size.
- When `--ref-text` is omitted, it sends the prepared waveform to Whisper.
- It adds a final period for English text or a Chinese full stop for Chinese text when the reference text has no ending punctuation.
- It sends the same prepared waveform to the audio tokenizer and uses the resulting reference tokens to estimate the target duration.

OmniVoice processes voice cloning audio after decoding. It removes middle gaps of at least 500 milliseconds and trims edge silence while keeping 100 milliseconds at each edge. It uses the original reference RMS when the RMS is below `0.1`, and it leaves the level unchanged when the RMS is at least `0.1`. It fades the first and last 100 milliseconds, then adds 100 milliseconds of silence at both edges. Text only output returns the decoder tensor without this processing.

### Voice design

```bash
Expand All @@ -302,6 +328,11 @@ python examples/offline_inference/text_to_speech/omnivoice/end2end.py \

- Stage 0 (Generator): Qwen3-0.6B with 32-step iterative unmasking.
- Stage 1 (Decoder): HiggsAudioV2 RVQ + DAC at 24 kHz.
- By default, the first request with reference audio and no `--ref-text` downloads
and loads `openai/whisper-large-v3-turbo`, so it has extra latency and GPU
memory use. Set `additional_config.omnivoice_asr.load_asr_on_startup` to `true`
to move that load to worker startup.
- Generated audio remains mono at 24 kHz.

---

Expand Down
30 changes: 26 additions & 4 deletions docs/user_guide/examples/online_serving/text_to_speech.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,15 +449,15 @@ bash examples/online_serving/text_to_speech/glm_tts/run_gradio_demo.sh

## OmniVoice

Zero-shot multilingual TTS (600+ languages). Online serving currently exposes **auto voice** only; voice cloning and voice design are available offline.
OmniVoice creates speech in more than 600 languages. It supports text-to-speech without a reference clip, voice cloning, and voice design.

### Prerequisites

```bash
huggingface-cli download k2-fsa/OmniVoice

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] The new clone-without-ref-text path Hub-loads openai/whisper-large-v3-turbo (`_…

Evidence and suggested fix

The new clone-without-ref-text path Hub-loads openai/whisper-large-v3-turbo (_ASR_MODEL_NAME / yaml asr_model_name) with no local_files_only. Both OmniVoice Prerequisites still only run huggingface-cli download k2-fsa/OmniVoice (online:401, offline:215), then immediately give a --ref-audio command with no --ref-text. After that prefetch, an HF_HUB_OFFLINE/airgapped first ASR request fails because Whisper was never fetched. Add huggingface-cli download openai/whisper-large-v3-turbo beside the OmniVoice fetch in both Prerequisites blocks (same gap at examples/online_serving/text_to_speech/README.md:685, examples/offline_inference/text_to_speech/README.md:369, recipes/k2-fsa/OmniVoice.md:25).

Evidence: Trigger: follow the rewritten Prerequisites, then the new copy-paste clone-without-ref-text example, with Whisper absent from the HF cache (airgapped / HF_HUB_OFFLINE). Adverse effect: _load_asr_pipeline Hub-loads the default ASR id and the first request fails because Prerequisites never fetched it. docs/user_guide/examples/online_serving/text_to_speech.md:401 huggingface-cli download k2-fsa/OmniVoice. Same file:420 python speech_client.py --text "hello" --ref-audio /path/to/ref_audio.wav. docs/user_guide/examples/offline_inference/text_to_speech.md:215 huggingface-cli download k2-fsa/OmniVoice. Same file:240 The first request downloads and loads Whisper, so it takes longer and uses more device memory. vllm_omni/diffusion/models/omnivoice/pipeline_omnivoice.py:66 _ASR_MODEL_NAME = "openai/whisper-large-v3-turbo". Same file:276-280 asr_pipeline = hf_pipeline(\n "automatic-speech-recognition",\n model=self._asr_model_name,\n dtype=asr_dtype,\n device=asr_device,\n ) — no local_files_only. vllm_omni/deploy/omnivoice.yaml:19 asr_model_name: "openai/whisper-large-v3-turbo".

Suggestion: huggingface-cli download k2-fsa/OmniVoice
huggingface-cli download openai/whisper-large-v3-turbo

```

Voice cloning (offline) needs `transformers>=5.3.0`; auto voice works with `transformers>=4.57.0`.
Voice cloning needs `transformers>=5.3.0`. When `ref_text` is omitted, OmniVoice loads `openai/whisper-large-v3-turbo` on the worker device to transcribe the reference audio. Set `additional_config.omnivoice_asr.load_asr_on_startup` to `true` to load Whisper during worker startup, or set `additional_config.omnivoice_asr.asr_device` to use another GPU or the CPU.

### Launch

Expand All @@ -473,13 +473,35 @@ vllm serve k2-fsa/OmniVoice --omni --port 8091 --trust-remote-code
cd examples/online_serving/text_to_speech/omnivoice
python speech_client.py --text "Hello, how are you?"
python speech_client.py --text "Bonjour, comment allez-vous?" --language French
python speech_client.py --text "hello" --ref-audio /path/to/ref_audio.wav
```

The client supports `--api-base`, `--model`, `--text`, `--response-format`, `--language`, `--output`.
The client supports `--api-base`, `--model`, `--text`, `--response-format`,
`--language`, `--voice`, `--ref-audio`, `--ref-text`, `--instructions`,
`--seed`, and `--output`.

### Notes

- Voice cloning and voice design require offline inference; see the [offline OmniVoice section](https://github.com/vllm-project/vllm-omni/tree/main/examples/offline_inference/text_to_speech/README.md#omnivoice).
- The explicit `--ref-text` path is faster because it does not load Whisper.
- By default, the first request with reference audio and no reference text loads
`openai/whisper-large-v3-turbo` on the worker device, which adds latency and
device memory use. Set `additional_config.omnivoice_asr.load_asr_on_startup` to
`true` to move that load to worker startup. Generated audio is mono at 24 kHz.

### Reference audio and output processing

When a request includes `ref_audio`, OmniVoice prepares the reference clip before it transcribes the clip or builds the voice cloning input. It performs these steps:

- It converts the clip to mono and resamples it to 24 kHz.
- It measures the original root mean square (RMS) level. When the RMS is greater than zero and below `0.1`, it raises the waveform level to `0.1` and keeps the original RMS for output volume control.
- When `ref_text` is omitted, it trims a clip longer than 20 seconds at a detected silence.
- It removes middle silence and trims silence at both edges whether the transcript is automatic or supplied by the caller.
- It aligns the sample count down to the audio tokenizer hop size.
- When `ref_text` is omitted, it sends the prepared waveform to Whisper.
- It adds a final period for English text or a Chinese full stop for Chinese text when the reference text has no ending punctuation.
- It sends the same prepared waveform to the audio tokenizer.

After decoding, OmniVoice processes voice cloning output by removing middle gaps of at least 500 milliseconds and trimming edge silence while keeping 100 milliseconds at each edge. It uses the original reference RMS when the RMS is below `0.1`, and it leaves the level unchanged when the RMS is at least `0.1`. It fades the first and last 100 milliseconds, then adds 100 milliseconds of silence at both edges. Text only output returns the decoder tensor without this processing.

---

Expand Down
37 changes: 34 additions & 3 deletions examples/offline_inference/text_to_speech/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ python examples/offline_inference/text_to_speech/<model>/end2end.py \
--ref-text "Transcript of the reference audio."
```

`--ref-audio` and `--ref-text` are optional (text-only synthesis works without them) and must be provided together for voice cloning. The exotic scripts — Qwen3-TTS, Voxtral TTS, CosyVoice3 — accept additional model-specific flags documented in their per-model section below. Qwen3-TTS in particular uses its own argparse surface (`--query-type`, `--audio-path`, etc.) and does not follow the common shape; see its section.
For most models, `--ref-audio` and `--ref-text` are optional for text-only synthesis but must be provided together for voice cloning. OmniVoice also accepts `--ref-audio` without `--ref-text` and transcribes the reference lazily with Whisper. Qwen3-TTS, Voxtral TTS, and CosyVoice3 accept additional model-specific flags documented in their sections below. Qwen3-TTS uses its own argparse surface with options such as `--query-type` and `--audio-path`, so it does not follow the common command shape.

---

Expand Down Expand Up @@ -407,15 +407,15 @@ python examples/offline_inference/text_to_speech/moss_tts_nano/end2end.py \

## OmniVoice

Zero-shot multilingual TTS supporting 600+ languages, with three modes (auto / clone / design).
OmniVoice creates speech in more than 600 languages. It supports text-to-speech without a reference clip, voice cloning, and voice design.

### Prerequisites

```bash
huggingface-cli download k2-fsa/OmniVoice
```

Voice cloning requires `transformers>=5.3.0`. Auto and design modes work with `transformers>=4.57.0`.
Voice cloning requires `transformers>=5.3.0`. Automatic voice and voice design use `transformers>=4.57.0`.

### Quick start (auto voice)

Expand All @@ -427,6 +427,8 @@ python examples/offline_inference/text_to_speech/omnivoice/end2end.py \

### Voice cloning

With an explicit transcript, voice cloning does not load Whisper:

```bash
python examples/offline_inference/text_to_speech/omnivoice/end2end.py \
--model k2-fsa/OmniVoice \
Expand All @@ -435,6 +437,30 @@ python examples/offline_inference/text_to_speech/omnivoice/end2end.py \
--ref-text "This is the reference transcription."
```

Omit `--ref-text` to use automatic transcription. The default OmniVoice deploy config loads Whisper on the worker device when the first request needs it. Set `load_asr_on_startup: true` in `additional_config.omnivoice_asr` to load it during worker startup. Set `asr_device: "cuda:0"` or `asr_device: "cpu"` in the same block to override the worker device:

```bash
python examples/offline_inference/text_to_speech/omnivoice/end2end.py \
--model k2-fsa/OmniVoice \
--text "hello" \
--ref-audio trump_ref.wav
```

### Reference audio processing

OmniVoice prepares the reference clip before it transcribes the clip or builds the voice cloning input. It performs these steps:

- It converts the clip to mono and resamples it to 24 kHz.
- It measures the original root mean square (RMS) level. When the RMS is greater than zero and below `0.1`, it raises the waveform level to `0.1` and keeps the original RMS for output volume control.
- When `--ref-text` is omitted, it trims a clip longer than 20 seconds at a detected silence.
- It removes middle silence and trims silence at both edges whether the transcript is automatic or supplied by the caller.
- It aligns the sample count down to the audio tokenizer hop size.
- When `--ref-text` is omitted, it sends the prepared waveform to Whisper.
- It adds a final period for English text or a Chinese full stop for Chinese text when the reference text has no ending punctuation.
- It sends the same prepared waveform to the audio tokenizer.

After decoding, OmniVoice applies silence removal, RMS scaling, fades, and padding only to voice-cloning output. Text-only output returns the decoder tensor unchanged. Output audio is mono at 24 kHz.

### Voice design

```bash
Expand Down Expand Up @@ -466,6 +492,11 @@ python examples/offline_inference/text_to_speech/omnivoice/end2end.py \

- Stage 0 (Generator): Qwen3-0.6B with 32-step iterative unmasking.
- Stage 1 (Decoder): HiggsAudioV2 RVQ + DAC at 24 kHz.
- The default deploy config uses `openai/whisper-large-v3-turbo` for lazy ASR on
the worker device. Set `load_asr_on_startup: true` in `additional_config.omnivoice_asr`
to load it during worker startup. Set `asr_device` in the same block to use a
different GPU or the CPU. Supplying `--ref-text` avoids ASR.
- Generated audio remains mono at 24 kHz.

---

Expand Down
11 changes: 9 additions & 2 deletions examples/offline_inference/text_to_speech/omnivoice/end2end.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""End-to-end OmniVoice TTS inference via vLLM-Omni.

Supports:
- Auto voice mode: text only → generated speech
- Voice cloning mode: text + reference audio → cloned voice speech
- Automatic reference transcription: reference audio without reference text

Usage:
# Auto voice
Expand All @@ -13,6 +14,11 @@
# Voice cloning
python end2end.py --model k2-fsa/OmniVoice --text "Hello" \
--ref-audio ref.wav --ref-text "reference transcription"

# Automatic reference transcription (lazy by default; set
# load_asr_on_startup: true to preload Whisper at worker startup)
python end2end.py --model k2-fsa/OmniVoice --text "hello" \
--ref-audio trump_ref.wav
"""

import argparse
Expand Down Expand Up @@ -113,7 +119,8 @@ def run_e2e():

audio_signal, sr = load_audio(args.ref_audio, sr=None)
multi_modal_data["audio"] = (audio_signal.astype(np.float32), sr)
mm_processor_kwargs["ref_text"] = args.ref_text or ""
if args.ref_text is not None:
mm_processor_kwargs["ref_text"] = args.ref_text
mm_processor_kwargs["sample_rate"] = sr

if args.lang:
Expand Down
Loading
Loading