Fish Audio S2-Pro#
Fish Audio S2-Pro is a text-to-speech model served through /v1/audio/speech. It supports plain TTS, voice cloning with a reference clip, and streaming audio chunks.
Prerequisites#
Install sglang-omni by following Installation.
Fish Audio uses the Descript DAC codec, which is not included in the base
sglang-omni package. From the SGLang-Omni repository root, install its
model-specific dependencies:
uv pip install \
"descript-audiotools==0.7.2" \
"descript-audio-codec==1.0.0"
Then download the model:
hf download fishaudio/s2-pro
Server Configuration#
sgl-omni serve \
--model-path fishaudio/s2-pro \
--config examples/configs/s2pro_tts.yaml \
--port 8000
Reference audio encoding runs on GPU 0 in the preprocessing process by default, using the checkpoint’s FP32 codec. Tokenization and audio loading remain on CPU. The preprocessing process has a 6 GiB PyTorch allocator limit; this is a memory budget, not a reference-duration limit or a reservation of all GPU allocations. Long references can exceed it and fail encoding.
Set --preprocessing.total_reserve_bytes 8GiB to increase the budget, while
leaving enough GPU memory for the autoregressive engine and vocoder. To encode
references on CPU instead, clear both the GPU placement and its budget:
sgl-omni serve \
--model-path fishaudio/s2-pro \
--config examples/configs/s2pro_tts.yaml \
--preprocessing.gpu null \
--preprocessing.total_reserve_bytes null \
--port 8000
Async Decode#
On CUDA, enable one-step async decode to overlap CPU result collection with the next GPU decode step:
sgl-omni serve \
--model-path fishaudio/s2-pro \
--config examples/configs/s2pro_tts.yaml \
--tts_engine.factory.enable_async_decode true \
--port 8000
This is opt-in. A decode batch enters the lookahead path only after its request rows stabilize; prefill and batch changes use synchronous decoding. Before selecting an expected prefill batch, the scheduler resolves any pending decode so selection uses the updated request state. The GPU updates the previous codebooks, semantic repetition history, and sampling step before the next forward starts. The existing overlap scheduler remains disabled.
One decode step may already be in flight when the host observes a request completion. The effective CUDA Graph batch size can therefore shrink one step later than in synchronous decoding. Different batch sizes can produce different floating-point results and generated codes, even with the same seed.
Synthesize Speech#
Plain TTS:
curl -X POST http://localhost:8000/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"model": "fishaudio/s2-pro",
"voice": "default",
"input": "Hello, how are you?"
}' \
--output output.wav
Voice cloning:
curl -X POST http://localhost:8000/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"model": "fishaudio/s2-pro",
"voice": "default",
"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.wav
Streaming: add "stream": true to a speech request. Shared raw WAV, PCM, and SSE clients are in TTS Model Usage.
Request Parameters#
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string |
served model |
Served model identifier |
|
string |
required |
Text to synthesize |
|
string |
|
Voice identifier for non-reference requests |
|
string |
|
Output audio format |
|
float |
|
Playback speed multiplier |
|
bool |
|
Stream raw PCM audio chunks |
|
list |
|
Reference clip for voice cloning. Each item has |
|
string |
|
Shorthand for |
|
int |
|
Maximum generated semantic tokens, capped at 2048 |
|
float |
|
Sampling temperature |
|
float |
|
Top-p sampling |
|
int |
|
Top-k sampling. It must be |
|
float |
|
Repetition penalty |
Apple Silicon (Native MLX)#
fish_s2_pro_server from the native MLX runtime (sglang_omni_mlx/native) serves the same /v1/audio/speech API on an Apple Silicon Mac, from the 8-bit mlx-community/fish-audio-s2-pro-8bit checkpoint, as one C++ process without CUDA, PyTorch or Python. It needs about 10 GB of memory, so a Mac with 16 GB or more.
sglang_omni_mlx/native/scripts/build_runtime.sh ~/omni-runtime
hf download mlx-community/fish-audio-s2-pro-8bit \
--revision c8d4481b3f7cbfe64d855c8b7cda7739502fc3ff --local-dir fish-audio-s2-pro-8bit
~/omni-runtime/bin/fish_s2_pro_server --model-path fish-audio-s2-pro-8bit \
--model-name fishaudio/s2-pro --port 8000
Plain TTS works unchanged. The prompt, the semantic sampler (repetition penalty, the hotter resampling when the last four semantic codes repeat one, top_k of at most 30) and the defaults (temperature 0.8, top_p 0.8, top_k 30, repetition_penalty 1.1, max_new_tokens 1024, capped at 2048) follow the CUDA server. A seed draws as the CUDA server’s seeded sampler does, and the same seed repeats the same speech. The native server differs from the CUDA server in that:
The voice to clone comes in the request body:
ref_audiois a WAV file as base64 or adata:audio/wav;base64,...URL, withref_textits transcript;referencesitems take the WAV asdataoraudio, or the codes asvq_codes. Paths and URLs, andaudio_path, return 400. A WAV may be 8, 16, 24 or 32-bit PCM or float, at a common sample rate (8 to 192 kHz), with up to 254 channels. A request may hold 4 references, with 60 s of audio and 2048 transcript characters in all.voicemust bedefault(or empty); there are no uploaded voices.response_formatiswavorpcm;speedmust be1.0;stream_formatmust beaudio. A streamed request decodes in the CUDA server’s windows (40 frames, then every 45, with 20 frames of overlap and a 512-sample crossfade).Started with
--model-name, it returns 404 for any othermodel; without that flag,modelis ignored, as on CUDA.Errors are
{"detail": "..."}, and every invalid field, includingtop_koutside-1or1..30, returns 400.Requests run one at a time and
--max-queued-requests(default 2) more wait; the rest get 503, and/healthshows the queue.A request not finished
--request-timeout-s(default 600) seconds after it arrives, time in the queue included, is cancelled: it returns 504, or its stream ends early. A client that disconnects cancels its request.A request body over 16 MiB returns 413; that fits 60 s of 48 kHz stereo 16-bit WAV as base64.
Measured on an Apple M5 (24 GB), MLX 0.32.3, 15 utterances: 1.0 s load, real-time factor 1.99 (it generates about 11 of the 21.5 frames in a second of audio, so streamed speech arrives slower than it plays), and 9.8 GB peak memory footprint: 10.1 GB for an 80 s utterance, 8.4 GB with a 60 s reference, which takes 4.4 s to encode. Its positions rotate through MLX’s fast::rope rather than the reference’s bfloat16 phase table, so seeded speech differs from mlx-audio’s Fish Speech port on the same checkpoint. Transcribed back with Qwen3-ASR-1.7B, 10 English sentences give 0.0% WER and 5 Chinese sentences give 0.0% CER. sglang_omni_mlx/native/ci/check_model_golden.py checks five seeded requests’ audio codes and PCM (English, Chinese, Japanese, a cloned voice, and a max_new_tokens cap) against sglang_omni_mlx/native/ci/model_golden/fish-s2-pro-8bit.json. Mac CI runs no model, so this check is run by hand.
Known Limitations#
top_kis constrained to-1or1..30; keep requests inside this range because invalid values currently fail the S2-Pro pipeline instead of returning a clean parameter error.Reference quality strongly affects cloned voice quality.
Use streaming for interactive playback; CLI inspection of raw audio responses is awkward.