Fish Audio Drama 3#
Fish Audio Drama 3 is Fish Audio’s new-generation text-to-speech model, built for multi-character scenes and long-form content. It supports multi-speaker generation, voice cloning and natural-language control.
35B parameters, 3B active per token: MoE text backbone with hybrid (full + linear) attention
Codebook-MTP audio decoder: 10 codebooks decoded causally over the whole sequence
FP8 MoE experts with the remaining weights in bf16
Speaker tags
<|speaker:0|>…<|speaker:15|>— one text can carry up to 16 charactersLong text is chunked automatically and can be streamed per chunk
Installation and Startup#
Detailed instructions are in Installation. From the repository root:
uv pip install --prerelease=allow -v -e ".[fishaudio]"
Download the checkpoint, then start the server. This command places preprocessing, the engine, and the vocoder on GPU 0. A stage moves to another GPU only when its --<stage>.gpu flag says so; see Deployment.
hf download fishaudio/drama-3
sgl-omni serve \
--model-path fishaudio/drama-3 \
--port 8000
Synthesize Speech#
The server supports plain text-to-speech, optionally cloning a voice from reference audio, returned as one response or as a stream.
Plain TTS:
curl -X POST http://localhost:8000/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"input": "Hello, how are you?"
}' \
--output output.wav
Voice cloning, with a reference clip and its transcript:
curl -X POST http://localhost:8000/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"input": "Get the trust fund to the bank early.",
"references": [{
"audio_path": "https://huggingface.co/datasets/zhaochenyang20/seed-tts-eval-mini/resolve/main/en/prompt-wavs/common_voice_en_10119832.wav",
"text": "We asked over twenty different people, and they all said it was his."
}]
}' \
--output output_voice_cloning.wav
Streaming:
curl -N -X POST http://localhost:8000/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"input": "Get the trust fund to the bank early.",
"stream": true,
"response_format": "pcm"
}' \
--output output_streaming.pcm
Example Usage#
The reference clip the samples below clone, unmodified:
The reference, en0 — a 3.9 s clip, the same common_voice_en_10119832.wav the cloning example above uses, with the transcript We asked over twenty different people, and they all said it was his.:
Editing as continuation#
Just send the previous VQ tokens and sent it as normal language models do.
{
"text": "The quarterly report is due on Thursday, so please finish it by noon.",
"references": [{"audio_path": "https://huggingface.co/datasets/zhaochenyang20/seed-tts-eval-mini/resolve/main/en/prompt-wavs/common_voice_en_10119832.wav", "text": "We asked over twenty different people, and they all said it was his."}],
"previous_takes": ["The quarterly report is due on Wednesday, so please finish it by noon."]
}
A second edit chained onto the result of the first:
K5 chains a second edit: by noon becomes by five, with both earlier takes in the context.
{
"text": "The quarterly report is due on Thursday, so please finish it by five.",
"references": [{"audio_path": "https://huggingface.co/datasets/zhaochenyang20/seed-tts-eval-mini/resolve/main/en/prompt-wavs/common_voice_en_10119832.wav", "text": "We asked over twenty different people, and they all said it was his."}],
"previous_takes": [
"The quarterly report is due on Wednesday, so please finish it by noon.",
"The quarterly report is due on Thursday, so please finish it by noon."
]
}
Serving Limits#
Reference Audio Length#
Each reference given as audio may be at most 60 seconds long. The limit applies to every reference in the request separately, and a longer one fails the request with HTTP 400 before it is encoded:
Drama 3 request: references.1: reference audio lasts 75.3 s, longer than the 60 s limit; shorten it or raise max_reference_audio_seconds
references.N names the reference by its position in the request’s references list, counted from 0.
Shorten that reference, or raise the limit when you start the server, for example to 90 seconds:
sgl-omni serve \
--model-path <checkpoint> \
--preprocessing.factory.max_reference_audio_seconds 90 \
--preprocessing.total_reserve_bytes 9GiB
or in a config file:
stages:
preprocessing:
factory:
max_reference_audio_seconds: 90
total_reserve_bytes: 9GiB
The GPU memory the preprocessing stage needs grows with the length of the reference it encodes, and the stage is capped at total_reserve_bytes (6 GiB by default, sized for the 60-second limit). When you raise the limit, raise the reserve with it, measured on your GPU; a reference that does not fit the reserve fails with an out-of-memory error in
the preprocessing stage. The budgets of all stages on the GPU must still fit the card, which the server checks at startup.
Prompt Length#
A prompt holds the tokens of the prompt template, the input text and the reference transcripts, plus one row for every codec frame of its reference audio, about 21.5 rows per second of audio (the codec turns 44.1 kHz audio into frames of 2048 samples). A prompt may have at most 8192 rows; a longer one fails the request with HTTP 400:
Drama 3 request: a prompt of 8193 rows exceeds the maximum allowed length of 8192 rows
Change the cap with --tts_engine.factory.max_prompt_rows. A prompt and its max_new_tokens must also fit the engine’s context length.
Attention Backend#
Drama 3 runs its attention on the triton backend only. The server refuses to start with any other attention backend, for example --tts_engine.engine.attention_backend fa3.
Deployment Performance#
On one GPU the default placement works as is: every stage runs on GPU 0, with the vocoder in its own process. With two GPUs, put the vocoder on the second one, so its decoding no longer shares a GPU with the engine:
sgl-omni serve \
--model-path <checkpoint> \
--tts_engine.gpu 0 \
--vocoder.gpu 1
When the engine and the vocoder share one GPU, --mps on lets their processes share it through CUDA MPS (see Same-GPU Data Parallelism with CUDA MPS). It can raise throughput under high concurrency but may make a single stream slower; measure it with your own load before you keep it.
High Concurrency#
The defaults run at most 16 requests at a time on the engine and give it 0.70 of the GPU’s memory. For more streams at once, raise the request cap; the measurements below also raise the engine’s share:
sgl-omni serve \
--model-path <checkpoint> \
--tts_engine.gpu_memory_fraction 0.85 \
--tts_engine.factory.max_running_requests 256
These values are sized for an H200: the engine’s and the vocoder’s shares and the preprocessing reserve must fit one GPU together, which the server checks at startup.
Production Serving#
All numbers below were measured on NVIDIA H200 GPUs with streaming requests on Seed-TTS English prompts. Each request clones a voice from one pre-encoded reference (vq_codes with its transcript) and uses temperature 1.0, top_p 0.9, top_k 30 and max_new_tokens 500. Throughput counts generated acoustic frames per second. TTFA is the time to first audio. RTF is the time from sending a request to its last audio chunk, divided by the duration of the returned audio.
Concurrency |
Throughput (frames/s) |
TTFA p50 |
TTFA p95 |
RTF (mean) |
|---|---|---|---|---|
1 |
166.7 |
88.9 ms |
90.8 ms |
0.131 |
2 |
287.0 |
96.2 ms |
130.1 ms |
0.152 |
4 |
467.6 |
108.2 ms |
156.4 ms |
0.186 |
8 |
678.1 |
153.2 ms |
210.9 ms |
0.256 |
16 |
927.6 |
210.1 ms |
287.2 ms |
0.373 |
32 |
1,244.7 |
304.6 ms |
396.1 ms |
0.551 |
64 |
1,687.3 |
450.9 ms |
1,020.1 ms |
0.801 |
Table 1. Drama 3 streaming performance on one H200. Each cell is the mean of four runs.
A single stream gets its first audio in under 100 ms. At concurrency 16, audio is still generated about 2.7× faster than it plays back, and at concurrency 64 mean RTF stays at 0.80. In SGLang-Omni, how the pipeline stages are grouped into processes and placed on GPUs is deployment-time configuration rather than per-model code. Table 2 uses this to compare two vocoder placements at higher concurrency, with the vocoder’s default GPU share, 1,088 requests per concurrency level, and the average of six server starts. Its throughput also counts the end-of-speech frame that ends each stream.
Vocoder placement |
Concurrency |
Throughput (frames/s) |
TTFA p50 |
TTFA p95 |
|---|---|---|---|---|
Engine’s GPU |
128 |
2,177 |
685 ms |
1,412 ms |
Engine’s GPU |
256 |
2,422 |
3,403 ms |
4,255 ms |
Second GPU |
128 |
3,692 |
401 ms |
995 ms |
Second GPU |
256 |
4,160 |
2,146 ms |
2,469 ms |
Table 2. Drama 3 streaming performance by vocoder placement. Each cell is the mean of six server starts.
With the vocoder on the engine’s GPU, at 256 streams the vocoder is fully loaded and each new stream’s first audio waits in its queue. Moving it to a second GPU, as described under Deployment, gave about 72% more throughput and a 37% lower median TTFA at 256 streams. Use this layout when you need many concurrent streams and a short time to first audio together.
Non-Streaming Requests#
The vocoder decodes a non-streaming request’s audio in one piece after the engine finishes it, batching requests that
finish together up to a frame limit it sets at startup from its memory share (--vocoder.gpu_memory_fraction, 0.10
by default) and logs:
Drama 3 vocoder: a non-streaming decode takes up to <frames> padded frames of <bytes> bytes each; share <share> GiB, held <held> GiB
The limit does not count the memory the vocoder process holds outside PyTorch’s allocator, so it cannot guarantee that the process stays within its share. With the engine’s share raised to 0.85 on the same GPU, concurrent non-streaming requests can run out of GPU memory. To avoid that, stream those requests ("stream": true), keep the engine’s default share of 0.70 on the GPU that runs the vocoder, or put the vocoder on a second GPU, as under Deployment.
Defaults Parameters#
Default |
What it does |
Change it with |
|---|---|---|
Decode CUDA graph |
Replays decode steps as CUDA graphs. |
|
Breakable prefill CUDA graph, up to 2048 tokens |
Replays prefill batches of up to 2048 tokens, backbone and codebook tower together, as one breakable CUDA graph; larger batches run eagerly. Shortens the time to the first audio at the cost of startup time and GPU memory; the KV cache keeps its size. |
|
First-frame CUDA graphs, up to 16 requests |
Replays the step that samples each request’s first frame as a CUDA graph for prefills of up to 16 requests (at most the decode CUDA graph’s largest batch); larger prefills sample eagerly. Needs the decode CUDA graph; shortens each prefill at the cost of startup time and GPU memory. |
|
|
After a prefill, new prefills wait for the next 4 scheduling rounds, so running streams keep decoding. |
|
Triton sampling |
Samples each frame’s main token and codes with Triton kernels instead of the PyTorch sampler, under the same top-k, top-p and repetition-aware rules. Using the same seed does not guarantee the same tokens as the PyTorch sampler. Rounding can keep or drop a candidate whose cumulative probability lies at top_p. |
|
Fused codebook tower layers, up to 16 rows |
Runs the codebook tower layers of decode and first-frame steps of up to 16 rows as fused Triton kernels, eagerly or inside the CUDA graphs; larger steps and the tower’s prefill keep the module path. The kernels add in a different order, so the tower’s outputs can differ in the last bits and the sampled frames can differ. |
|
Vocoder window CUDA graphs |
Replays the decode of each streaming window length at batch 1 as a CUDA graph; batches and longer windows decode eagerly. |
|
Vocoder fp16 decoding |
Decodes frames into audio in fp16. |
|
The same settings in a config file:
stages:
tts_engine:
engine:
cuda_graph_backend_prefill: disabled
prefill_decode_interval: 0
vocoder:
factory:
window_cuda_graphs: false
decode_dtype: float32
Triton sampling and the fused codebook tower layers have been validated on Hopper GPUs only, so they run only on compute capability 9.0; on any other GPU no setting turns them on. Both also fall back without Triton or a CUDA device. Triton sampling falls back to the PyTorch sampler when its kernels do not cover the codebook size or the main-token candidate count, and the fused layers fall back to the module path with deterministic inference or when their kernels do not cover the tower weights or the KV cache. The startup log names the path each one takes and, for a fallback, the reason, for example:
Drama 3 codebook sampling: PyTorch, untested GPU sm_120
Drama 3 tower fused layers: off, untested GPU sm_120