2026-02-10 03:33:39 -05:00
# Speech API
2026-03-12 16:20:52 -04:00
vLLM-Omni provides an OpenAI-compatible API for text-to-speech (TTS) generation. Supported TTS models include:
- **Qwen3-TTS** (`Qwen/Qwen3-TTS-12Hz-*` ) -- Qwen3-based TTS with CustomVoice, VoiceDesign, and Base (voice cloning) task types. Output: 24 kHz.
- **Fish Speech S2 Pro** (`fishaudio/s2-pro` ) -- Dual-AR TTS with DAC codec. Supports text-to-speech and voice cloning via reference audio. Output: 44.1 kHz.
2026-03-21 06:27:43 +01:00
- **Voxtral TTS** (`mistralai/Voxtral-4B-TTS-2603` ) -- AR + FlowMatching TTS with preset voices. Output: 24 kHz.
2026-02-10 03:33:39 -05:00
Each server instance runs a single model (specified at startup via `vllm serve <model> --omni` ).
## Quick Start
### Start the Server
```bash
2026-03-12 16:20:52 -04:00
# Qwen3-TTS: CustomVoice model (predefined speakers)
2026-02-10 03:33:39 -05:00
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-03-12 16:20:52 -04:00
# Fish Speech S2 Pro
vllm-omni serve fishaudio/s2-pro \
--stage-configs-path vllm_omni/model_executor/stage_configs/fish_speech_s2_pro.yaml \
--omni \
--port 8091 \
--trust-remote-code \
--enforce-eager \
--gpu-memory-utilization 0.9
2026-03-19 13:02:11 -07:00
# Voxtral TTS
2026-03-21 06:27:43 +01:00
vllm serve mistralai/Voxtral-4B-TTS-2603 \
2026-03-19 13:02:11 -07:00
--stage-configs-path vllm_omni/model_executor/stage_configs/voxtral_tts.yaml \
--omni \
--port 8091 \
--trust-remote-code \
--enforce-eager
2026-02-10 03:33:39 -05:00
```
### Generate Speech
**Using curl:**
```bash
curl -X POST http://localhost:8091/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"input": "Hello, how are you?",
"voice": "vivian",
"language": "English"
}' --output output.wav
```
**Using Python:**
```python
import httpx
response = httpx . post (
"http://localhost:8091/v1/audio/speech" ,
json = {
"input" : "Hello, how are you?" ,
"voice" : "vivian" ,
"language" : "English" ,
},
timeout = 300.0 ,
)
with open ( "output.wav" , "wb" ) as f :
f . write ( response . content )
```
**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" ,
voice = "vivian" ,
input = "Hello, how are you?" ,
)
response . stream_to_file ( "output.wav" )
```
## API Reference
### Endpoint
```
POST /v1/audio/speech
Content-Type: application/json
```
### Request Parameters
#### OpenAI Standard Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `input` | string | **required** | The text to synthesize into speech |
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `voice` | string | "vivian" | Speaker name (e.g., vivian, ryan, aiden) |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, aac, opus |
| `speed` | float | 1.0 | Playback speed (0.25-4.0) |
#### vLLM-Omni Extension Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `task_type` | string | "CustomVoice" | TTS task type: CustomVoice, VoiceDesign, or Base |
| `language` | string | "Auto" | Language (see supported languages below) |
| `instructions` | string | "" | Voice style/emotion instructions |
| `max_new_tokens` | integer | 2048 | Maximum tokens to generate |
2026-03-10 22:02:33 +01:00
| `initial_codec_chunk_frames` | integer | null | Per-request initial chunk size override for TTFA tuning. When null, IC is computed dynamically based on server load. |
2026-04-03 22:51:58 +02:00
| `stream` | bool | false | Stream raw PCM chunks as they are decoded (requires `response_format="pcm"` ) |
2026-02-10 03:33:39 -05:00
**Supported languages:** Auto, Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian
#### Voice Clone Parameters (Base task)
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
2026-03-08 21:38:26 -04:00
| `ref_audio` | string | null | Reference audio (HTTP URL, base64 data URL, or `file://` URI with `--allowed-local-media-path` ) |
2026-02-10 03:33:39 -05:00
| `ref_text` | string | null | Transcript of reference audio |
| `x_vector_only_mode` | bool | null | Use speaker embedding only (no ICL) |
### Response Format
Returns binary audio data with appropriate `Content-Type` header (e.g., `audio/wav` ).
### Voices Endpoint
```
GET /v1/audio/voices
```
Lists available voices for the loaded model.
```json
{
2026-04-03 22:51:58 +02:00
"voices" : [ "aiden" , "dylan" , "eric" , "ono_anna" , "ryan" , "serena" , "sohee" , "uncle_fu" , "vivian" , "custom_voice_1" ],
"uploaded_voices" : [
{
"name" : "custom_voice_1" ,
"consent" : "user_consent_id" ,
"created_at" : 1738660000 ,
"file_size" : 1024000 ,
"mime_type" : "audio/wav" ,
"ref_text" : "The exact transcript of the audio sample." ,
"speaker_description" : "warm narrator"
}
]
2026-02-10 03:33:39 -05:00
}
```
2026-04-03 22:51:58 +02:00
`uploaded_voices` is always present (empty list when no custom voices have been uploaded). Fields `ref_text` and `speaker_description` are omitted per-entry when not provided at upload time.
2026-03-09 11:02:11 +08:00
```
POST /v1/audio/voices
Content-Type: multipart/form-data
```
Upload a new voice sample for voice cloning in Base task TTS requests.
**Form Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `audio_sample` | file | Yes | Audio file (max 10MB, supported formats: wav, mp3, flac, ogg, aac, webm, mp4) |
| `consent` | string | Yes | Consent recording ID |
| `name` | string | Yes | Name for the new voice |
2026-03-26 15:26:52 +01:00
| `ref_text` | string | No | Transcript of the audio. When provided, enables in-context voice cloning (higher quality). Without it, only the speaker embedding is extracted. |
2026-04-03 22:51:58 +02:00
| `speaker_description` | string | No | Free-form description of the voice (e.g. "warm narrator", "energetic presenter"). Stored as metadata and returned in `GET /v1/audio/voices` . |
2026-03-09 11:02:11 +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-09 11:02:11 +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-09 11:02:11 +08:00
**Usage Example:**
```bash
curl -X POST http://localhost:8091/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" \
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-09 11:02:11 +08:00
```
2026-03-12 11:48:41 +08:00
## Streaming Text Input (WebSocket)
The `/v1/audio/speech/stream` WebSocket endpoint accepts text incrementally and generates audio per sentence as boundaries are detected.
> Note: text input is always streamed incrementally. Audio output remains sentence-scoped:
> use `stream_audio=false` for one binary frame per sentence, or `stream_audio=true` for one or more PCM chunks per sentence.
### WebSocket Protocol
Client -> Server:
| Message | Description |
|---------|-------------|
| `{"type": "session.config", ...}` | Session configuration (sent once, first message) |
| `{"type": "input.text", "text": "..."}` | Text chunk |
| `{"type": "input.done"}` | End of input, flushes remaining buffer |
Server -> Client:
| Message | Description |
|---------|-------------|
| `{"type": "audio.start", "sentence_index": 0, "sentence_text": "...", "format": "pcm", "sample_rate": 24000}` | Audio generation starting for a sentence |
| Binary frame | Raw audio bytes (one or more PCM chunks when `stream_audio=true` ) |
| `{"type": "audio.done", "sentence_index": 0, "total_bytes": 96000, "error": false}` | Audio complete for a sentence |
| `{"type": "session.done", "total_sentences": N}` | Session complete |
| `{"type": "error", "message": "..."}` | Non-fatal error |
### Session Config Parameters
All REST API parameters are supported, plus:
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `stream_audio` | bool | false | Stream one or more PCM chunks per sentence over WebSocket |
| `split_granularity` | string | "sentence" | Text splitting granularity |
2026-03-09 11:02:11 +08:00
```bash
DELETE /v1/audio/voices/{ name}
```
Delete an uploaded voice sample.
**Path Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Name of the voice to delete |
**Response Example:**
```json
{
"success" : true ,
"message" : "Voice 'custom_voice_1' deleted successfully"
}
```
**Error Response (404 Not Found):**
```json
{
"success" : false ,
"error" : "Voice 'unknown_voice' not found"
}
```
**Usage Example:**
```bash
curl -X DELETE http://localhost:8091/v1/audio/voices/custom_voice_1
```
2026-02-10 03:33:39 -05:00
## Examples
### CustomVoice with Style Instruction
```bash
curl -X POST http://localhost:8091/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"input": "I am so excited!",
"voice": "vivian",
"instructions": "Speak with great enthusiasm"
}' --output excited.wav
```
### VoiceDesign (Natural Language Voice Description)
```bash
# Start server with VoiceDesign model first
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
```
```bash
curl -X POST http://localhost:8091/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"input": "Hello world",
"task_type": "VoiceDesign",
"instructions": "A warm, friendly female voice with a gentle tone"
}' --output designed.wav
```
### Base (Voice Cloning)
```bash
# Start server with Base model first
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
```
```bash
curl -X POST http://localhost:8091/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"input": "Hello, this is a cloned voice",
"task_type": "Base",
"ref_audio": "https://example.com/reference.wav",
"ref_text": "Original transcript of the reference audio"
}' --output cloned.wav
```
2026-03-24 14:52:01 +00:00
### Upload Voice
2026-03-26 15:26:52 +01:00
Upload voice (speaker embedding only):
2026-03-09 11:02:11 +08:00
```bash
curl -X POST http://localhost:8091/v1/audio/voices \
-F "audio_sample=@/path/to/voice_sample.wav" \
-F "consent=user_consent_id" \
-F "name=custom_voice_1"
```
2026-03-26 15:26:52 +01:00
Upload voice with transcript (in-context cloning, higher quality):
```bash
curl -X POST http://localhost:8091/v1/audio/voices \
-F "audio_sample=@/path/to/voice_sample.wav" \
-F "consent=user_consent_id" \
-F "name=custom_voice_2" \
-F "ref_text=The exact transcript of the audio sample."
```
2026-03-24 14:52:01 +00:00
### Use Uploaded Voice
2026-03-09 11:02:11 +08:00
```bash
curl -X POST http://localhost:8091/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"input": "Hello, this is a cloned voice",
"voice": "custom_voice_1"
}' --output cloned.wav
```
2026-03-24 14:52:01 +00:00
## Batch Speech Generation
The batch endpoint synthesizes multiple texts in a single request, returning all results as JSON with base64-encoded audio.
### Endpoint
```
POST /v1/audio/speech/batch
Content-Type: application/json
```
### Request Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `items` | array | **required** | List of items to synthesize (1– 32) |
| `model` | string | server's model | Model to use |
| `voice` | string | null | Default voice for all items |
| `response_format` | string | "wav" | Default audio format for all items |
| `speed` | float | 1.0 | Default playback speed (0.25– 4.0) |
| `task_type` | string | null | Default TTS task type |
| `language` | string | null | Default language |
| `instructions` | string | null | Default voice style instructions |
| `ref_audio` | string | null | Default reference audio (Base task) |
| `ref_text` | string | null | Default reference transcript (Base task) |
| `max_new_tokens` | integer | null | Default max tokens |
Each item in the `items` array requires only `input` (the text). All other fields are optional and override the batch-level defaults when set:
| Field | Type | Description |
|-------|------|-------------|
| `input` | string | **required** — text to synthesize |
| `voice` | string | Override voice for this item |
| `response_format` | string | Override format for this item |
| `speed` | float | Override speed for this item |
| `task_type` | string | Override task type |
| `language` | string | Override language |
| `instructions` | string | Override instructions |
| `ref_audio` | string | Override reference audio |
| `ref_text` | string | Override reference transcript |
| `max_new_tokens` | integer | Override max tokens |
### Response Format
```json
{
"id" : "speech-batch-abc123" ,
"results" : [
{
"index" : 0 ,
"status" : "success" ,
"audio_data" : "<base64-encoded audio>" ,
"media_type" : "audio/wav"
},
{
"index" : 1 ,
"status" : "error" ,
"error" : "Input text cannot be empty"
}
],
"total" : 2 ,
"succeeded" : 1 ,
"failed" : 1
}
```
### Examples
**Basic batch with shared defaults:**
```bash
curl -X POST http://localhost:8091/v1/audio/speech/batch \
-H "Content-Type: application/json" \
-d '{
"items": [
{"input": "Hello, how are you?"},
{"input": "Goodbye, see you later!"}
],
"voice": "vivian",
"language": "English"
}'
```
**Per-item overrides (different voices and formats):**
```bash
curl -X POST http://localhost:8091/v1/audio/speech/batch \
-H "Content-Type: application/json" \
-d '{
"items": [
{"input": "Hello!", "voice": "vivian", "response_format": "mp3"},
{"input": "你好!", "voice": "ryan", "language": "Chinese"}
],
"response_format": "wav"
}'
```
**Voice cloning with shared reference audio (Base task):**
```bash
curl -X POST http://localhost:8091/v1/audio/speech/batch \
-H "Content-Type: application/json" \
-d '{
"items": [
{"input": "First sentence in the cloned voice."},
{"input": "Second sentence in the cloned voice."}
],
"task_type": "Base",
"ref_audio": "https://example.com/reference.wav",
"ref_text": "Transcript of the reference audio"
}'
```
Setting `ref_audio` at the batch level applies it to all items, avoiding the need to repeat it per item.
**Decoding the response in Python:**
```python
import base64
import httpx
response = httpx . post (
"http://localhost:8091/v1/audio/speech/batch" ,
json = {
"items" : [
{ "input" : "First sentence." },
{ "input" : "Second sentence." },
],
"voice" : "vivian" ,
},
timeout = 300.0 ,
)
for result in response . json ()[ "results" ]:
if result [ "status" ] == "success" :
audio_bytes = base64 . b64decode ( result [ "audio_data" ])
with open ( f "output_ { result [ 'index' ] } .wav" , "wb" ) as f :
f . write ( audio_bytes )
```
### Configuration
| Parameter | Source | Default | Description |
|-----------|--------|---------|-------------|
| `tts_batch_max_items` | engine kwarg | 32 | Maximum number of items per batch request |
All items are fanned out to `generate()` concurrently. The engine's stage worker automatically batches them up to the configured `max_batch_size` and queues the rest — no client-side throttling needed.
For best throughput, use a batch-optimized stage config with `max_batch_size > 1` :
```bash
vllm serve Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice \
--stage-configs-path vllm_omni/model_executor/stage_configs/qwen3_tts_batch.yaml \
--omni --port 8091 --trust-remote-code --enforce-eager
```
The default `qwen3_tts.yaml` uses `max_batch_size: 1` (single request). The `qwen3_tts_batch.yaml` config sets `max_batch_size: 4` for ~4x throughput.
2026-02-10 03:33:39 -05:00
## Supported Models
2026-03-12 16:20:52 -04:00
### Qwen3-TTS
2026-02-10 03:33:39 -05:00
| Model | Task Type | Description |
|-------|-----------|-------------|
| `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice` | CustomVoice | Predefined speaker voices with optional style control |
| `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-03-12 16:20:52 -04:00
### Fish Speech S2 Pro
| Model | Description |
|-------|-------------|
| `fishaudio/s2-pro` | 4B dual-AR TTS with DAC codec (44.1 kHz). Supports text-to-speech and voice cloning. |
Fish Speech uses `ref_audio` and `ref_text` for voice cloning (no `task_type` needed). The `voice` field should be set to `"default"` . See the [Fish Speech online serving example ](../user_guide/examples/online_serving/fish_speech.md ) for details.
2026-03-19 13:02:11 -07:00
### Voxtral TTS
| Model | Description |
|-------|-------------|
2026-03-21 06:27:43 +01:00
| `mistralai/Voxtral-4B-TTS-2603` | 3B AR + FlowMatching TTS. Supports text-to-speech with preset voices. |
2026-03-19 13:02:11 -07:00
2026-02-10 03:33:39 -05:00
## Error Responses
### 400 Bad Request
Invalid parameters:
```json
{
"error" : {
"message" : "Input text cannot be empty" ,
"type" : "BadRequestError" ,
"param" : null ,
"code" : 400
}
}
```
### 404 Not Found
Model not found:
```json
{
"error" : {
"message" : "The model `xxx` does not exist." ,
"type" : "NotFoundError" ,
"param" : "model" ,
"code" : 404
}
}
```
## Troubleshooting
### "TTS model did not produce audio output"
Ensure you're using the correct model variant for your task type:
- CustomVoice task → CustomVoice model
- VoiceDesign task → VoiceDesign model
- Base task → Base model
### Server Not Running
```bash
# Check if server is responding
curl http://localhost:8091/v1/audio/voices
```
### Out of Memory
If you encounter OOM errors:
1. Use smaller model variant: `Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice`
2. Reduce `--gpu-memory-utilization`
### Unsupported Speaker
Use `/v1/audio/voices` to list available voices for the loaded model.
## Development
Enable debug logging:
```bash
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 \
--uvicorn-log-level debug
```