# F5-TTS

## Name

**F5-TTS** — A flow-matching zero-shot voice cloning TTS by SWivid.

---

## Overview

F5-TTS is a flow-matching text-to-speech model that performs zero-shot voice cloning from a single reference clip plus its transcript. It is smaller and faster than autoregressive alternatives in this stack (~336M params vs Qwen3-TTS's 1.7B) while producing cleaner clones on identical reference audio. Released under MIT license, model weights are openly downloadable from HuggingFace (`SWivid/F5-TTS`).

---

## Integration Status

- **Model ID:** `tts-1-f5`
- **Status:** Enabled by default (additive, alongside `tts-1-qwen`)
- **Engine:** `f5-tts` (PyPI package `f5-tts==1.1.20`)
- **License:** MIT
- **Added:** 2026-05-24

### Supported Languages

- **English (primary)** — what our voice registry currently exercises
- Community fine-tunes exist for Chinese and other languages (not wired up in our voice map yet)

---

## Technical Specifications

| Field | Value |
|-------|-------|
| Parameters | ~336M |
| Sample rate | 24 kHz (matches `tts-1-qwen`) |
| Vocoder | Vocos (bundled, downloaded on first use) |
| Model download | ~1.5 GB (F5-TTS_v1 + Vocos) |
| Reference format | WAV (3+ seconds preferred) + transcript |
| Inference style | Flow-matching ODE, non-autoregressive |
| Tuning knobs | `nfe_step` (ODE steps), `cfg_strength` (CFG), `speed` |

### Model Loading

The model is pre-downloaded by `startup.sh` on container boot. First boot fetches ~1.5 GB into the HuggingFace cache mounted at `/app/voices/hub/`. Subsequent boots hit cache.

Loaded lazily on first `tts-1-f5` request via `f5_load_semaphore` (single concurrent load). After the first request, the model lives in GPU memory until the process exits.

---

## Integration Details

### Voice Configuration

F5-TTS uses the same reference-audio + reference-transcript shape as `tts-1-qwen`, so our voice map literally duplicates the Qwen block under a `tts-1-f5:` heading. All 40 LibriSpeech voices work for both engines from the same WAV files in `cloned-voices/`.

```yaml
tts-1-f5:
  aria:
    ref_audio: cloned-voices/aria.wav
    ref_text: "BUT THE WINDOWS ARE PATCHED WITH WOODEN PANES AND THE DOOR I THINK IS LIKE THE GATE IT IS NEVER OPENED"
    language: English
```

To add a new voice, drop a 3+ second clean WAV into `cloned-voices/`, transcribe it accurately, and append a stanza to both the `tts-1-qwen:` and `tts-1-f5:` blocks (or just one, depending which engine you want it on).

### Makefile Targets

```bash
make test-f5      # Quick smoke test against tts-1-f5
make voices-f5    # No-op (alias) — reuses voices-qwen LibriSpeech samples
make voices-all   # Includes voices-f5
```

### API Usage

```bash
curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"model":"tts-1-f5","voice":"aria","input":"Raccoon mission TTS test with F5 flow matching"}' \
  -o /tmp/f5_test.mp3
```

`tts-1-f5` accepts all standard OpenAI-compatible request fields (`input`, `voice`, `response_format`, `speed`). Engine-specific knobs (`nfe_step`, `cfg_strength`) are NOT exposed at the HTTP API layer today — they default to 32 and 2.0 respectively inside the wrapper. Add request fields only if a benchmark shows they need to be tunable per-call.

---

## Wrapper Implementation

`speech.py:f5_wrapper` mirrors the `qwen3_wrapper` pattern:

```python
class f5_wrapper():
    def __init__(self, device='cuda'):
        from f5_tts.api import F5TTS
        self.model = F5TTS(device=device)
        self.sample_rate = 24000

    def tts(self, text, ref_audio, ref_text, speed=1.0, nfe_step=32, cfg_strength=2.0):
        wav, sr, _spec = self.model.infer(
            ref_file=ref_audio,
            ref_text=ref_text,
            gen_text=text,
            nfe_step=nfe_step,
            cfg_strength=cfg_strength,
            speed=speed,
            show_info=lambda *a, **k: None,
            progress=None,
        )
        if hasattr(wav, 'detach'):
            wav = wav.detach().to('cpu', dtype=torch.float32).numpy()
        return np.asarray(wav, dtype=np.float32).flatten().tobytes()
```

The dispatch in `generate_speech()` splits text by sentence (shared `simple_sentence_split`), runs the model on each sentence in a generator thread, pipes float32 PCM into ffmpeg, and streams back the encoded result. Same scaffold as Qwen and Kokoro.

---

## Performance Characteristics

Per Richard's empirical benchmark on identical reference clips (2026-05-23):
- **Faster** than Qwen3-TTS on the same hardware
- **Better clone fidelity** than Qwen3-TTS on the same reference audio

Quantitative numbers (latency, RTF, VRAM) pending a fresh `make hydrate` run after first deploy on `3090-ai.foxhop.net`.

### Reference (from VoiceClone, MonumentalSystems)
- NVIDIA CUDA: well under realtime on a modern GPU
- Apple Silicon MPS: ~1.5–2× realtime
- CPU: many× realtime (last-resort fallback)

---

## Voice Quality

Subjective verdict (Richard, fox): clones sound closer to the source speaker than Qwen3-TTS on the same 9-second LibriSpeech clips. Less prosody drift, fewer artifacts on long-form output.

---

## Known Issues

- **No `temperature` / `top_p` / `top_k` support.** Flow-matching is deterministic given the reference + seed; sampling knobs from autoregressive models do not apply. Requests containing them are accepted at the FastAPI layer (Pydantic ignores extra fields) but silently dropped in the F5 path.
- **First-request latency:** ~1.5 GB model download on first launch if cache is cold. `startup.sh` pre-downloads on boot to avoid hitting end users with this.
- **Non-English fine-tunes not wired up.** Only English voices in our current map. To add Chinese, German, etc., point a voice stanza at a non-English reference clip + transcript and run `make test-f5` against it.

---

## Raccoon Mission Notes

F5-TTS is a textbook dumpster-dive find: MIT-licensed, smaller than the incumbent, empirically better, and the upstream community already wrote a high-quality single-file web app ([MonumentalSystems/VoiceClone](https://github.com/MonumentalSystems/VoiceClone)) we can reference and learn from. Our wrapper deliberately mirrors their `F5TTS.infer()` call pattern so future upstream changes are easy to follow.

The VoiceClone wrapper itself ships features we have NOT yet pulled in (chunk inspector, regen queue, session export, server-side audio pipeline). Those are engine-agnostic UX wins; consider porting them separately once F5-TTS proves itself in production here.

---

## Future Enhancements

- Expose `nfe_step` and `cfg_strength` as request fields after a benchmark shows them load-bearing
- Add non-English voices to the registry (Chinese fine-tunes exist)
- Port VoiceClone's chunk-inspector frontend as a layer atop our existing endpoint
- Consider making `tts-1-f5` the default (replacing `tts-1-qwen`) once production VRAM/latency data confirms Richard's bench

---

## Resources

- Upstream model + code: https://github.com/SWivid/F5-TTS
- Paper: "F5-TTS: A Fairytaler that Fakes Fluent and Faithful Speech with Flow Matching"
- HuggingFace weights: https://huggingface.co/SWivid/F5-TTS
- PyPI: https://pypi.org/project/f5-tts/
- Reference web app: https://github.com/MonumentalSystems/VoiceClone

---

## License

MIT (model + code, SWivid/F5-TTS).
