2026-02-10 02:52:44 -05:00
# Qwen3-TTS
2026-02-03 18:11:50 +08:00
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/qwen3_tts>.
2026-02-10 02:52:44 -05:00
## 🛠️ Installation
Please refer to [README.md ](https://github.com/vllm-project/vllm-omni/tree/main/README.md )
2026-02-03 18:11:50 +08:00
## Supported Models
2026-02-27 17:28:35 +08:00
| Model | Task Type | Description |
| -------------------------------------- | ----------- | ----------------------------------------------------- |
2026-02-03 18:11:50 +08:00
| `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice` | CustomVoice | Predefined speaker voices with optional style control |
2026-02-27 17:28:35 +08:00
| `Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign` | VoiceDesign | Natural language voice style description |
| `Qwen/Qwen3-TTS-12Hz-1.7B-Base` | Base | Voice cloning from reference audio |
| `Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice` | CustomVoice | Smaller/faster variant |
| `Qwen/Qwen3-TTS-12Hz-0.6B-Base` | Base | Smaller/faster variant for voice cloning |
2026-02-03 18:11:50 +08:00
2026-03-11 09:36:02 +08:00
## Gradio Demo
2026-03-29 00:33:55 +08:00
!!! note "Gradio is an optional dependency"
The Gradio demo requires the `[demo]` extras. Install them first:
```bash
pip install 'vllm-omni[demo]'
` ``
Or, if installing from source: ` pip install -e '.[demo]'`
2026-03-11 09:36:02 +08:00
Two interactive Gradio demos are available, both supporting all 3 task types:
2026-03-24 14:26:20 +08:00
| Demo | File | Transport | Streaming Quality |
| -------- | ------------------------ | ------------ | -------------------------------------------------- |
| Standard | ` gradio_demo.py` | HTTP chunked | May have small gaps between chunks |
| FastRTC | ` gradio_fastrtc_demo.py` | WebRTC | Gapless streaming (requires ` pip install fastrtc`) |
2026-03-11 09:36:02 +08:00
` ``bash
# Option 1: Launch server + Standard Gradio together
./run_gradio_demo.sh # CustomVoice (default)
./run_gradio_demo.sh --task-type VoiceDesign # VoiceDesign
./run_gradio_demo.sh --task-type Base # Voice cloning
# Option 2: If server is already running
python gradio_demo.py --api-base http://localhost:8000
# Option 3: FastRTC demo (gapless streaming)
pip install fastrtc
python gradio_fastrtc_demo.py --api-base http://localhost:8000
` ``
Then open http://localhost:7860 in your browser.
2026-02-10 02:52:44 -05:00
## Run examples (Qwen3-TTS)
2026-02-03 18:11:50 +08:00
2026-02-10 02:52:44 -05:00
### Launch the Server
2026-02-03 18:11:50 +08:00
` ``bash
2026-02-10 02:52:44 -05:00
# CustomVoice model (predefined speakers)
vllm serve Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice \
2026-04-19 10:36:00 +08:00
--deploy-config vllm_omni/deploy/qwen3_tts.yaml \
2026-02-10 02:52:44 -05:00
--omni \
--port 8091 \
--trust-remote-code \
--enforce-eager
2026-02-03 18:11:50 +08:00
2026-02-10 02:52:44 -05:00
# VoiceDesign model
vllm serve Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign \
2026-04-19 10:36:00 +08:00
--deploy-config vllm_omni/deploy/qwen3_tts.yaml \
2026-02-10 02:52:44 -05:00
--omni \
--port 8091 \
--trust-remote-code \
--enforce-eager
# Base model (voice cloning)
vllm serve Qwen/Qwen3-TTS-12Hz-1.7B-Base \
2026-04-19 10:36:00 +08:00
--deploy-config vllm_omni/deploy/qwen3_tts.yaml \
2026-02-10 02:52:44 -05:00
--omni \
--port 8091 \
--trust-remote-code \
--enforce-eager
2026-02-03 18:11:50 +08:00
` ``
2026-02-10 02:52:44 -05:00
If you have custom stage configs file, launch the server with command below
` ``bash
vllm serve Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice \
--stage-configs-path /path/to/stage_configs_file \
--omni \
--port 8091 \
--trust-remote-code \
--enforce-eager
` ``
Alternatively, use the convenience script:
` ``bash
./run_server.sh # Default: CustomVoice model
./run_server.sh CustomVoice # CustomVoice model
./run_server.sh VoiceDesign # VoiceDesign model
./run_server.sh Base # Base (voice clone) model
` ``
### Send TTS Request
Get into the example folder
` ``bash
cd examples/online_serving/qwen3_tts
` ``
#### Send request via python
2026-02-03 18:11:50 +08:00
` ``bash
# CustomVoice: Use predefined speaker
python openai_speech_client.py \
--text "你好,我是通义千问" \
2026-03-24 13:53:24 +08:00
--speaker vivian \
2026-02-03 18:11:50 +08:00
--language Chinese
# CustomVoice with style instruction
python openai_speech_client.py \
--text "今天天气真好" \
2026-03-24 13:53:24 +08:00
--speaker ryan \
2026-02-03 18:11:50 +08:00
--instructions "用开心的语气说"
# VoiceDesign: Describe the voice style
python openai_speech_client.py \
--model Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign \
--task-type VoiceDesign \
--text "哥哥,你回来啦" \
--instructions "体现撒娇稚嫩的萝莉女声,音调偏高"
# Base: Voice cloning
python openai_speech_client.py \
--model Qwen/Qwen3-TTS-12Hz-1.7B-Base \
--task-type Base \
--text "Hello, this is a cloned voice" \
--ref-audio /path/to/reference.wav \
--ref-text "Original transcript of the reference audio"
` ``
2026-02-10 02:52:44 -05:00
The Python client supports the following command-line arguments:
- ` --api-base`: API base URL (default: ` http://localhost:8091`)
- ` --model` (or ` -m`): Model name/path (default: ` Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice`)
- ` --task-type` (or ` -t`): TTS task type. Options: ` CustomVoice`, ` VoiceDesign`, ` Base`
- ` --text`: Text to synthesize (required)
2026-03-24 13:53:24 +08:00
- ` --speaker`: Speaker name (default: ` vivian`). Options: ` vivian`, ` ryan`, ` aiden`, etc.
2026-02-10 02:52:44 -05:00
- ` --language`: Language. Options: ` Auto`, ` Chinese`, ` English`, ` Japanese`, ` Korean`, ` German`, ` French`, ` Russian`, ` Portuguese`, ` Spanish`, ` Italian`
- ` --instructions`: Voice style/emotion instructions
2026-03-24 14:26:20 +08:00
- ` --ref-audio`: Reference audio file path or URL for voice cloning (Base task). Local paths are automatically base64-encoded by the client before sending to the server.
- ` --ref-text`: Reference audio transcript for voice cloning (Base task).
2026-02-10 02:52:44 -05:00
- ` --response-format`: Audio output format (default: ` wav`). Options: ` wav`, ` mp3`, ` flac`, ` pcm`, ` aac`, ` opus`
- ` --output` (or ` -o`): Output audio file path (default: ` tts_output.wav`)
#### Send request via curl
2026-02-03 18:11:50 +08:00
` ``bash
# Simple TTS request
2026-02-10 02:52:44 -05:00
curl -X POST http://localhost:8091/v1/audio/speech \
2026-02-03 18:11:50 +08:00
-H "Content-Type: application/json" \
-d '{
"input": "Hello, how are you?",
2026-04-03 22:51:58 +02:00
"voice": "vivian",
2026-02-03 18:11:50 +08:00
"language": "English"
}' --output output.wav
# With style instruction
2026-02-10 02:52:44 -05:00
curl -X POST http://localhost:8091/v1/audio/speech \
2026-02-03 18:11:50 +08:00
-H "Content-Type: application/json" \
-d '{
"input": "I am so excited!",
2026-04-03 22:51:58 +02:00
"voice": "vivian",
2026-02-03 18:11:50 +08:00
"instructions": "Speak with great enthusiasm"
}' --output excited.wav
# List available voices in CustomVoice models
2026-02-10 02:52:44 -05:00
curl http://localhost:8091/v1/audio/voices
` ``
### Using OpenAI SDK
` ``python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8091/v1", api_key="none")
response = client.audio.speech.create(
model="Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
2026-04-03 22:51:58 +02:00
voice="vivian",
2026-02-10 02:52:44 -05:00
input="Hello, how are you?",
)
response.stream_to_file("output.wav")
` ``
### Using Python httpx
` ``python
import httpx
response = httpx.post(
"http://localhost:8091/v1/audio/speech",
json={
"input": "Hello, how are you?",
2026-04-03 22:51:58 +02:00
"voice": "vivian",
2026-02-10 02:52:44 -05:00
"language": "English",
},
timeout=300.0,
)
with open("output.wav", "wb") as f:
f.write(response.content)
` ``
2026-02-03 18:11:50 +08:00
## API Reference
2026-03-11 09:36:02 +08:00
### Voices Endpoint
#### GET /v1/audio/voices
List all available voices/speakers from the loaded model, including both built-in model voices and uploaded custom voices.
**Response Example:**
` ``json
{
"voices": ["vivian", "ryan", "custom_voice_1"],
"uploaded_voices": [
{
"name": "custom_voice_1",
"consent": "user_consent_id",
"created_at": 1738660000,
"file_size": 1024000,
2026-04-03 22:51:58 +02:00
"mime_type": "audio/wav",
"ref_text": "The exact transcript of the audio sample.",
"speaker_description": "warm narrator"
2026-03-11 09:36:02 +08:00
}
]
}
` ``
2026-04-03 22:51:58 +02:00
Fields ` ref_text` and ` speaker_description` are omitted per-entry when not provided at upload time.
2026-03-11 09:36:02 +08:00
#### POST /v1/audio/voices
Upload a new voice sample for voice cloning in Base task TTS requests.
**Form Parameters:**
- ` audio_sample` (required): Audio file (max 10MB, supported formats: wav, mp3, flac, ogg, aac, webm, mp4)
- ` consent` (required): Consent recording ID
- ` name` (required): Name for the new voice
2026-03-26 15:26:52 +01:00
- ` ref_text` (optional): Transcript of the audio. Enables in-context voice cloning (higher quality).
2026-04-03 22:51:58 +02:00
- ` speaker_description` (optional): Free-form description of the voice (e.g. "warm narrator", "energetic presenter"). Stored as metadata.
2026-03-11 09:36:02 +08:00
**Response Example:**
` ``json
{
"success": true,
"voice": {
"name": "custom_voice_1",
"consent": "user_consent_id",
"created_at": 1738660000,
"mime_type": "audio/wav",
2026-04-03 22:51:58 +02:00
"file_size": 1024000,
"ref_text": "The exact transcript of the audio sample.",
"speaker_description": "warm narrator"
2026-03-11 09:36:02 +08:00
}
}
` ``
2026-04-03 22:51:58 +02:00
Fields ` ref_text` and ` speaker_description` are omitted when not provided at upload time.
2026-03-11 09:36:02 +08:00
**Usage Example:**
` ``bash
2026-04-03 22:51:58 +02:00
curl -X POST http://localhost:8091/v1/audio/voices \
2026-03-11 09:36:02 +08:00
-F "audio_sample=@/path/to/voice_sample.wav" \
-F "consent=user_consent_id" \
2026-03-26 15:26:52 +01:00
-F "name=custom_voice_1" \
2026-04-03 22:51:58 +02:00
-F "ref_text=The exact transcript of the audio sample." \
-F "speaker_description=warm narrator"
2026-03-11 09:36:02 +08:00
` ``
2026-02-03 18:11:50 +08:00
### Endpoint
` ``
POST /v1/audio/speech
2026-02-10 02:52:44 -05:00
Content-Type: application/json
2026-02-03 18:11:50 +08:00
` ``
This endpoint follows the [OpenAI Audio Speech API](https://platform.openai.com/docs/api-reference/audio/createSpeech) format with additional Qwen3-TTS parameters.
### Request Body
` ``json
{
"input": "Text to synthesize",
2026-04-03 22:51:58 +02:00
"voice": "vivian",
2026-02-03 18:11:50 +08:00
"response_format": "wav",
"task_type": "CustomVoice",
"language": "Auto",
"instructions": "Optional style instructions",
2026-03-24 14:26:20 +08:00
"ref_audio": "HTTP URL, base64 data URL, or file:// URI for voice cloning",
2026-02-03 18:11:50 +08:00
"ref_text": "Reference audio transcript",
"x_vector_only_mode": false,
"max_new_tokens": 2048
}
` ``
> **Note:** The ` model` field is optional when serving a single model, as the server already knows which model is loaded.
### Response
2026-02-10 02:52:44 -05:00
Returns binary audio data with appropriate ` Content-Type` header (e.g., ` audio/wav`).
2026-02-03 18:11:50 +08:00
2026-03-24 13:53:24 +08:00
### Voice and language (summary)
2026-04-03 22:51:58 +02:00
- **Speaker**: Use the ` voice` request field to select the speaker (e.g., ` vivian`, ` ryan`, ` aiden`). List available speakers with ` GET /v1/audio/voices`.
2026-03-24 13:53:24 +08:00
- **Language**: Use the ` language` field for the codec language tag (` Auto`, ` Chinese`, ` English`, etc.). Default is ` Auto` for automatic detection.
- **CustomVoice**: Requires a valid ` voice` from the model’ s speaker set. **VoiceDesign**: Use ` instructions` to describe the voice. **Base**: Use ` ref_audio` and ` ref_text` for voice cloning.
2026-02-03 18:11:50 +08:00
## Parameters
2026-02-10 02:52:44 -05:00
### OpenAI Standard Parameters
2026-02-03 18:11:50 +08:00
2026-02-27 17:28:35 +08:00
| Parameter | Type | Default | Description |
| ----------------- | ------ | -------------- | ----------------------------------------------------------- |
| ` input` | string | **required** | Text to synthesize |
| ` model` | string | server's model | Model to use (optional, should match server if specified) |
2026-04-03 22:51:58 +02:00
| ` voice` | string | "vivian" | Speaker name (e.g., vivian, ryan, aiden) |
2026-02-27 17:28:35 +08:00
| ` response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, aac, opus |
| ` speed` | float | 1.0 | Playback speed (0.25-4.0, not supported with ` stream=true`) |
2026-02-03 18:11:50 +08:00
2026-02-10 02:52:44 -05:00
### vLLM-Omni Extension Parameters
2026-02-03 18:11:50 +08:00
2026-03-24 14:26:20 +08:00
| Parameter | Type | Default | Description |
| ---------------------------- | ------ | ------------- | -------------------------------------------------------------------------------------------------------------------- |
| ` task_type` | string | "CustomVoice" | Task: CustomVoice, VoiceDesign, or Base |
| ` language` | string | "Auto" | Language (see supported languages below) |
| ` instructions` | string | "" | Voice style/emotion instructions |
| ` max_new_tokens` | int | 2048 | Maximum tokens to generate |
| ` initial_codec_chunk_frames` | int | null | Per-request initial chunk size override for TTFA tuning. When null, IC is computed dynamically based on server load. |
| ` stream` | bool | false | Stream raw PCM chunks as they are decoded (requires ` response_format="pcm"`) |
2026-02-03 18:11:50 +08:00
2026-02-10 02:52:44 -05:00
**Supported languages:** Auto, Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian
2026-02-03 18:11:50 +08:00
### Voice Clone Parameters (Base task)
2026-03-24 14:26:20 +08:00
| Parameter | Type | Required | Description |
| -------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------- |
| ` ref_audio` | string | **Yes** | Reference audio (HTTP URL, base64 data URL, or ` file://` URI with ` --allowed-local-media-path`) |
| ` ref_text` | string | No | Transcript of reference audio (for ICL mode) |
| ` x_vector_only_mode` | bool | No | Use speaker embedding only (no ICL) |
2026-02-03 18:11:50 +08:00
2026-02-26 14:02:35 +01:00
## Streaming
Set ` stream=true` with ` response_format="pcm"` to receive raw PCM audio chunks as they are decoded
2026-03-05 16:14:41 +01:00
(one chunk per Code2Wav window, default 25 frames; configurable in the stage config):
2026-02-26 14:02:35 +01:00
` ``bash
curl -X POST http://localhost:8091/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"input": "Hello, how are you?",
2026-04-03 22:51:58 +02:00
"voice": "vivian",
2026-02-26 14:02:35 +01:00
"language": "English",
"stream": true,
"response_format": "pcm"
}' --no-buffer | play -t raw -r 24000 -e signed -b 16 -c 1 -
` ``
**Constraints:**
- ` stream=true` requires ` response_format="pcm"` (raw 16-bit signed PCM, 24 kHz mono).
- ` speed` adjustment is not supported when streaming.
- Requires the server stage config to have ` async_chunk: true` (default in ` qwen3_tts.yaml`).
2026-03-24 11:20:50 +08:00
## Streaming Text Input (WebSocket)
The ` /v1/audio/speech/stream` WebSocket endpoint accepts text incrementally, buffers and splits it at sentence boundaries, and generates audio per sentence.
When ` stream_audio=true`, each sentence is emitted as ` audio.start`, one or more binary PCM frames, and ` audio.done`.
### Quick Start
` ``bash
python streaming_speech_client.py \
--text "Hello world. How are you? I am fine."
python streaming_speech_client.py \
--text "Hello world. How are you? I am fine." \
--simulate-stt --stt-delay 0.1
` ``
### WebSocket Protocol
Client -> Server:
` ``jsonc
{"type": "session.config", "voice": "Vivian", "task_type": "CustomVoice", "language": "Auto", "split_granularity": "sentence", "stream_audio": true, "response_format": "pcm"}
{"type": "input.text", "text": "Hello, how are you? "}
{"type": "input.done"}
` ``
Server -> Client:
` ``jsonc
{"type": "audio.start", "sentence_index": 0, "sentence_text": "Hello, how are you?", "format": "pcm", "sample_rate": 24000}
// binary PCM frame(s)
{"type": "audio.done", "sentence_index": 0, "total_bytes": 96000, "error": false}
{"type": "session.done", "total_sentences": 1}
` ``
2026-02-03 18:11:50 +08:00
## Limitations
- **Single request**: Batch processing is not yet optimized for online serving.
## Troubleshooting
2026-02-10 02:52:44 -05:00
1. **TTS model did not produce audio output**: Ensure you're using the correct model variant for your task type (CustomVoice task → CustomVoice model, etc.)
2. **Connection refused**: Make sure the server is running on the correct port
3. **Out of memory**: Use smaller model variant (` Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice`) or reduce ` --gpu-memory-utilization`
4. **Unsupported speaker**: Use ` /v1/audio/voices` to list available voices for the loaded model
5. **Voice clone fails**: Ensure you're using the Base model variant for voice cloning
2026-02-03 18:11:50 +08:00
## Example materials
2026-03-11 09:36:02 +08:00
??? abstract "gradio_demo.py"
` `````py
--8<-- "examples/online_serving/qwen3_tts/gradio_demo.py"
` `````
??? abstract "gradio_fastrtc_demo.py"
` `````py
--8<-- "examples/online_serving/qwen3_tts/gradio_fastrtc_demo.py"
` `````
2026-02-03 18:11:50 +08:00
??? abstract "openai_speech_client.py"
` `````py
--8<-- "examples/online_serving/qwen3_tts/openai_speech_client.py"
` `````
2026-03-11 09:36:02 +08:00
??? abstract "run_gradio_demo.sh"
` `````sh
--8<-- "examples/online_serving/qwen3_tts/run_gradio_demo.sh"
` `````
2026-02-03 18:11:50 +08:00
??? abstract "run_server.sh"
` `````sh
--8<-- "examples/online_serving/qwen3_tts/run_server.sh"
` `````
2026-03-24 11:20:50 +08:00
??? abstract "streaming_speech_client.py"
` `````py
--8<-- "examples/online_serving/qwen3_tts/streaming_speech_client.py"
` `````
2026-03-11 09:36:02 +08:00
??? abstract "tts_common.py"
` `````py
--8<-- "examples/online_serving/qwen3_tts/tts_common.py"
` `````