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
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 \
--stage-configs-path vllm_omni/model_executor/stage_configs/qwen3_tts.yaml \
--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 \
--stage-configs-path vllm_omni/model_executor/stage_configs/qwen3_tts.yaml \
--omni \
--port 8091 \
--trust-remote-code \
--enforce-eager
# Base model (voice cloning)
vllm serve Qwen/Qwen3-TTS-12Hz-1.7B-Base \
--stage-configs-path vllm_omni/model_executor/stage_configs/qwen3_tts.yaml \
--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-03-24 13:53:24 +08:00
"speaker": "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-03-24 13:53:24 +08:00
"speaker": "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-03-24 13:53:24 +08:00
speaker = "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-03-24 13:53:24 +08:00
"speaker" : "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 )
```
### FAQ
If you encounter error about backend of librosa, try to install ffmpeg with command below.
```
sudo apt update
sudo apt install ffmpeg
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 ,
"mime_type" : "audio/wav"
}
]
}
```
#### 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-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" ,
"file_size" : 1024000
}
}
```
**Usage Example:**
```bash
curl -X POST http://localhost:8000/v1/audio/voices \
-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" \
-F "ref_text=The exact transcript of the audio sample."
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-03-24 13:53:24 +08:00
"speaker" : "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)
- **Speaker**: Use the `speaker` request field to select the speaker (e.g., `vivian` , `ryan` , `aiden` ). List available speakers with `GET /v1/audio/voices` .
- **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-03-24 13:53:24 +08:00
| `speaker` | 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-03-24 13:53:24 +08:00
"speaker": "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"
` `````