Skip to content

feat: add pocket backend (Kyutai pocket-tts, pure Rust CPU) and make it the default#68

Merged
pszymkowiak merged 6 commits into
mainfrom
feat/pocket-tts-backend
Jul 7, 2026
Merged

feat: add pocket backend (Kyutai pocket-tts, pure Rust CPU) and make it the default#68
pszymkowiak merged 6 commits into
mainfrom
feat/pocket-tts-backend

Conversation

@pszymkowiak

@pszymkowiak pszymkowiak commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What

Adds a new pocket backend wrapping the pocket-tts crate — the Rust/candle port of Kyutai's Pocket TTS (100M-parameter FlowLM + Mimi codec, 24 kHz, CPU-first) — and makes it the default backend on all platforms (was say on macOS, piper elsewhere).

Why

  • Pure Rust, no Python, no GPU required — fills the gap between piper (fast but robotic) and qwen-native (excellent but heavy)
  • Zero-setup: public weights (~226 MB) auto-download from HuggingFace on first use
  • 8 predefined voices (alba, marius, javert, jean, fantine, cosette, eponine, azelma)
  • generate_stream API available in the crate → natural candidate for the streaming TTS phase of the conversational roadmap

Language support (important caveat)

The b6369a24 checkpoint bundled with the crate is English-only, and all 8 predefined voices are English speakers. French text synthesizes intelligibly but with a strong English accent. Kyutai publishes per-language checkpoints (french_24l, german_24l, …) with dedicated voices (e.g. estelle for French), but they use config fields the Rust crate does not implement yet (insert_bos_before_voice, remove_semicolons) — proper French needs an upstream contribution to the crate first.

Implementation notes

  • Vendored model config (src/backend/pocket_config_b6369a24.yaml): the crate resolves its config through a compile-time CARGO_MANIFEST_DIR registry path that does not exist on end-user machines. We stage the YAML under the vox config dir and pass its absolute path as the variant (Path::join with an absolute path replaces the base — this is what makes the override work).
  • Weight selection at runtime: HF_TOKEN set → gated kyutai/pocket-tts weights (WAV voice cloning enabled). No token → public kyutai/pocket-tts-without-voice-cloning weights (predefined voices only), with a clear error if a WAV clone is requested.
  • Warm model in a global Mutex (same pattern as qwen-native), registered in the daemon heavy-backend list and the clone-capable auto-switch list.

Verified end-to-end (Linux, i7-1065G7, CPU only, release build)

Check Result
English generation + playback ✅ 8.6s cold end-to-end (~58-char sentence, incl. full playback) — quality confirmed by ear
French generation + playback ✅ runs (13.9s), but English accent — see language caveat above
Alternate voice (-v fantine) ✅ 6.9s
--list-voices ✅ 8 voices
Default dispatch (no -b flag) ✅ 5.4s
Full test suite ✅ 143 passed, 0 failed (5 new tests in tests/pocket_test.rs)

Generation runs faster than real-time on this laptop CPU; most of the wall time is audio playback plus ~2s model load (daemon keeps it warm).

Follow-ups (not in this PR)

  • French support: upstream insert_bos_before_voice + per-language configs to the pocket-tts crate, then wire -l frfrench_24l + estelle
  • Streaming playback via generate_stream (speak while generating)
  • metal feature forwarding + Metal device selection for Apple Silicon (~2x)
  • M2 Pro / RTX benchmark numbers for the README table
  • Translated READMEs (fr/zh/ja/ko/es) still list six backends

🤖 Generated with Claude Code

pszymkowiak and others added 6 commits July 7, 2026 13:27
…it the default

Integrate the pocket-tts crate (Rust/candle port of Kyutai's 100M-parameter
FlowLM + Mimi TTS) as a new cross-platform backend:

- Warm model cache in a global Mutex (daemon-compatible, listed as heavy)
- 8 predefined voices via public HuggingFace embeddings, zero setup
- Voice cloning from a reference WAV when HF_TOKEN is set (gated
  kyutai/pocket-tts weights); clear error message otherwise
- Vendored model config: the crate resolves configs through a compile-time
  cargo registry path that does not exist on end-user machines, so the YAML
  is staged under the vox config dir and loaded via an absolute-path variant
- pocket becomes DEFAULT_BACKEND on all platforms (was say/piper)

Verified end-to-end on Linux (i7-1065G7, CPU only): English and French
generation, alternate voices, list-voices, default dispatch. Generation is
faster than real-time on CPU; 5-14s end-to-end including model load and
full audio playback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The b6369a24 checkpoint bundled with the pocket-tts crate is the English
model; all 8 predefined voices are English speakers. Kyutai's per-language
checkpoints (french_24l etc.) require config fields the crate does not
implement yet (insert_bos_before_voice).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…wise)

The pocket checkpoint is English-only, so route non-English languages to
piper (per-language voices) when no backend is explicitly selected via CLI
flag or stored preference. Applies to both the CLI and the MCP server.

Also fix the vox init templates: they hardcoded 'vox -b say', which only
exists on macOS — use 'vox -l fr' so the generated CLAUDE.md and Stop hook
work on all platforms.

Verified live: 'vox -l fr' routes to piper (proper French voice),
'vox' with no lang routes to pocket.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Build aarch64-unknown-linux-gnu natively on ubuntu-24.04-arm runners
- Package .deb (cargo-deb) and .rpm (cargo-generate-rpm) for both Linux
  architectures; verified locally with dpkg-deb
- Homebrew formula gains a Linux ARM block
- install.sh accepts aarch64 Linux
- Fix the Linux checksum grep to not match the -cuda archive

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pszymkowiak pszymkowiak merged commit 61acdd6 into main Jul 7, 2026
6 of 8 checks passed
@pszymkowiak pszymkowiak deleted the feat/pocket-tts-backend branch July 7, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant