Files
vh 76314624bb
ci / test (push) Has been cancelled
chatterbox-fast v0.1.0
Sub-second streaming TTS on Chatterbox-Turbo via adaptive buffer-ratchet
chunking. First audio in ~0.5s (vs ~5s one-shot) with no quality compromise —
chunk joins land on natural sentence pauses and the stream converges to one
large near-full-context chunk within 2-3 joins. Works because the engine runs
faster than realtime; the no-starvation guarantee is proven in a GPU-free
simulation (tests/test_scheduler.py).

- chatterbox_fast/scheduler.py: the adaptive-chunk scheduler (pure logic, no GPU)
- chatterbox_fast/app.py: FastAPI server (POST /tts streaming, /voices, /health)
- bench.py: streaming client (ground-truth TTFB + starvation check)
- Self-contained Dockerfile (slim base + chatterbox-tts from PyPI)
- Three public-domain LibriVox starter voices baked in (see voices/ATTRIBUTION.md)

MIT licensed.
2026-06-02 10:43:21 -07:00

183 lines
7.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# chatterbox-fast
**Sub-second streaming text-to-speech on [Chatterbox-Turbo](https://github.com/resemble-ai/chatterbox), with full quality.**
`chatterbox-fast` is a small, self-contained streaming server that delivers the
**first audio in ~0.5 seconds** instead of waiting ~5 seconds for a whole
paragraph to synthesize — without the quality loss of naive sentence-by-sentence
splitting. It does this with a scheduling trick (**adaptive buffer-ratchet
chunking**) rather than any model surgery, so it rides whatever quality and voice
cloning Chatterbox-Turbo gives you.
```
time-to-first-audio
one-shot ████████████████████████ ~5.2 s
fast ██▌ ~0.5 s ← chatterbox-fast
```
- 🚀 **~0.5 s time-to-first-audio** (vs ~5 s one-shot), measured on an RTX A6000
- 🎚️ **No quality compromise** — chunk joins land on natural sentence pauses, and
the stream converges to one large, full-context chunk within 2-3 joins
- 🔌 **Drop-in HTTP**`POST /tts` streams raw PCM or WAV; trivial to consume
- 🗣️ **Voice cloning** — any 5-30 s reference clip; ships with starter voices
- 🧪 **The scheduler is GPU-free and unit-tested** — the no-starvation guarantee
is proven in a pure simulation
- 📦 **Self-contained** — one image, weights auto-download on first run
- ⚖️ **MIT licensed**
---
## Why it works: adaptive buffer-ratchet chunking
The whole idea rests on one fact: **Chatterbox-Turbo generates faster than
realtime** (~3.44× on a modern GPU). That headroom is the fuel.
1. **Chunk 1 = the first sentence**, generated alone and emitted immediately. This
is the latency-critical part — keep it short, get audio out fast.
2. **While chunk N plays, generate chunk N+1** by greedily packing whole sentences
until the next one would take longer to generate than the audio you have
buffered (times a safety margin). You never split mid-sentence, so every chunk
stays prosodically coherent and joins fall on natural pauses.
3. Because generation outruns playback, **each chunk's playback buys wall-clock for
a ~3× bigger next chunk.** After 2-3 joins the rest of the text is one big
near-full-context chunk — so context loss is confined to a couple of sentence
boundaries, not every sentence.
4. The scheduler **measures the realtime factor live** and self-corrects, so it
adapts to your GPU instead of trusting a constant.
> **This only works because the engine is faster than realtime.** A sub-realtime
> model would starve no matter how you chunk it — which is exactly why this is a
> Chatterbox-specific design. The no-starvation property is asserted in
> `tests/test_scheduler.py`, which simulates the whole stream without a GPU.
## Quickstart
```bash
docker build -t chatterbox-fast .
docker run --rm --gpus all -p 8197:8197 \
-e HF_TOKEN=hf_your_token_here \
-v "$HOME/.cache/huggingface:/app/hf_cache" \
chatterbox-fast
```
On first run it downloads the Chatterbox-Turbo weights (~6 GB) from Hugging Face
into the mounted cache.
> **You need a (free) Hugging Face token.** The Chatterbox-Turbo model is
> [MIT-licensed and public](https://huggingface.co/ResembleAI/chatterbox-turbo),
> but the underlying `chatterbox-tts` package requires a token to be present when
> it downloads the weights. Any valid token works — `read` scope is enough; grab
> one at <https://huggingface.co/settings/tokens>. Once the weights are cached,
> later runs reuse them.
Then:
```bash
# stream raw PCM and play it as it arrives
curl -N -X POST http://localhost:8197/tts \
-H 'Content-Type: application/json' \
-d '{"text":"The cake is a lie. But the streaming is real.","format":"wav"}' \
> out.wav
```
A reference streaming client that measures real time-to-first-byte and checks for
starvation lives in [`bench.py`](bench.py):
```bash
python bench.py --host http://localhost:8197 --out out.wav
```
## HTTP API
### `POST /tts` → streamed audio
```json
{
"text": "Your text, with optional [laugh] [whispers] [sigh] tags.",
"voice": "<name or absolute path to a reference wav>",
"format": "pcm", // "pcm" (raw s16le @24kHz, lowest latency) or "wav"
"stream": true, // false = whole-text one-shot
"temperature": 0.8,
"top_p": 0.95,
"top_k": 1000,
"repetition_penalty": 1.2,
"seed": 0 // 0 = random; a fixed seed repeats a one-shot take
}
```
The response is a chunked HTTP stream — read it incrementally to get the
low-latency benefit. `format: "pcm"` is raw signed 16-bit little-endian mono at
24 kHz; `format: "wav"` adds a header (a complete header for one-shot, an
open-ended one for streaming).
### `GET /voices` → `{ "voices": [...], "default": "..." }`
Lists the predefined voice names (the `*.wav` stems in the voices directory).
### `GET /health` → readiness
`{ "status": "ok", ... }` once the model is loaded.
## Voices
A **voice** is just a 5-30 s reference WAV. The server clones it on the fly. Point
`CBF_VOICES_DIR` at a directory of `*.wav` files — the file stem becomes the voice
name in `/voices`, and the first one (or `CBF_DEFAULT_VOICE`) is the default.
```bash
docker run --rm --gpus all -p 8197:8197 \
-v "$HOME/.cache/huggingface:/app/hf_cache" \
-v "$PWD/my-voices:/app/voices" \
-e CBF_DEFAULT_VOICE=my_narrator \
chatterbox-fast
```
The image ships with three **public-domain** starter voices — `catharine`,
`peter`, and `kara` (LibriVox readings; see
[voices/ATTRIBUTION.md](voices/ATTRIBUTION.md)) — so a fresh container works
immediately. Drop in your own clips to add voices — no restart needed for
`/voices` discovery. A clone reference can also be passed per-request as an
absolute path in the `voice` field.
## Configuration
| env var | default | meaning |
|---|---|---|
| `CBF_MODEL_DEVICE` | `cuda` | `cuda` / `cuda:0` / `cpu` |
| `CBF_VOICES_DIR` | `/app/voices` | directory of predefined voice wavs |
| `CBF_DEFAULT_VOICE` | first wav in dir | default voice (name or path) |
| `CBF_BIND` / `CBF_PORT` | `0.0.0.0` / `8197` | server bind |
| `CBF_TF32` / `CBF_SDPA_FLASH` | `1` / `1` | low-risk Ampere+ speed levers |
## Requirements
- An NVIDIA GPU that runs Chatterbox-Turbo **faster than realtime** (any recent
card does; the design depends on it). ~6 GB VRAM for the fp32 model.
- The Docker NVIDIA runtime (`--gpus`).
- Workload assumption: **single-stream interactive** (one request at a time;
generation is serialized under a lock).
## Development
```bash
pip install -e ".[dev]"
pytest # the scheduler simulation — no GPU required
```
The engine is split so the interesting part is testable without hardware:
| file | role |
|---|---|
| `chatterbox_fast/scheduler.py` | the adaptive-chunk scheduler — pure logic, no GPU |
| `chatterbox_fast/app.py` | FastAPI server + model holder |
| `tests/test_scheduler.py` | GPU-free simulation: asserts no-starvation + the ratchet |
| `bench.py` | streaming client: ground-truth TTFB + starvation check |
## Acknowledgements
Built on Resemble AI's [Chatterbox](https://github.com/resemble-ai/chatterbox)
(the `chatterbox-tts` package). Outputs carry Resemble's Perth watermark, applied
by the model.
## License
MIT © 2026 Vuong Hoang. See [LICENSE](LICENSE).