firered-tts
by taral14
README.md
# FireRedTTS3 — багатомовний TTS (24 мови, вкл. українську)
Локальний сервіс синтезу мовлення на [FireRedTTS3](https://github.com/FireRedTeam/FireRedTTS3)
з HTTP-API і MCP-сервером, на порту :8020.
Модель вийшла 13.08.2026, ліцензія Apache 2.0, ваги публічні.
## Що варто знати до старту
**Пресетних голосів немає.** Порожня бібліотека = синтезувати нічим. Спершу
заводиш голос із будь-якого запису мовлення (`add_firered_voice`), далі
вживаєш його за іменем. Модель клонує zero-shot прямо під час синтезу.
**Потрібен транскрипт референсу.** Моделі мало самого аудіо — треба ще текст
того, що в ньому звучить. Не передаси — розпізнаємо Whisper'ом, але свій
текст завжди точніший.
**Наголосів немає.** Ні словника, ні ByT5-фолбека, ні ручного `Му+дрого`.
Наголоси модель ставить сама з контексту й на омографах (`за́мок`/`замо́к`)
плутатиме. Це принципове обмеження багатомовної моделі.
## Встановлення
```bash
cd /Users/admin/Projects/firered-tts
./setup.sh # venv + залежності + апстрім + патч + ваги
```
`setup.sh` робить чотири речі: піднімає `.venv` з Python 3.11 і torch 2.8.0
під MPS/CPU, клонує апстрім у `vendor/FireRedTTS3` на запиненому комміті
`00570ad`, патчить його під Apple Silicon (див. нижче) і тягне ваги
base+redae (~11.4 ГБ) у `pretrained_models/`.
Ваги окремо (довго — краще detached):
```bash
nohup ./download-weights.sh base > data/download.log 2>&1 &
./download-weights.sh instruct # +7.9 ГБ, для дизайну голосу й редагування
```
### Навіщо патч
Апстрім написаний виключно під NVIDIA і на Mac не запускається взагалі:
`flash_attn` на Metal не збирається в принципі, пристрій зашитий як `cuda`,
autocast прибитий цвяхами до CUDA. Патч замінює `flash_attention_2` на `sdpa`
(працює скрізь, у т.ч. на NVIDIA), робить пристрій динамічним і додає
оптимізації з розділу про швидкість. Повний список — у шапці
`src/patch_upstream.py`.
Патч ідемпотентний і **падає, якщо заміна не влучила** — якщо апстрім
змінився, ти дізнаєшся про це одразу, а не CUDA-помилкою на першому синтезі.
## Запуск
```bash
./run.sh # http://localhost:8020
```
Окремо запускати не обов'язково — MCP-сервер підніме бекенд сам. Бекенд
вимикається після простою і звільняє пам'ять.
## HTTP API
| Метод | Ендпойнт | Що робить |
|---|---|---|
| `GET` | `/health` | статус, пристрій, яка модель у пам'яті |
| `GET` | `/voices` | імена голосів (фільтри `gender`, `language`; `full=1` → з метаданими) |
| `GET` | `/languages` | 24 мови + 21 діалект |
| `POST` | `/clone_voice` | референс + транскрипт → голос |
| `DELETE` | `/voices/{name}` | видалити голос (оригінал запису не чіпається) |
| `POST` | `/tts` | текст → аудіо-байти у відповіді |
| `POST` | `/synthesize` | текст → файл у `DATA_DIR`, JSON зі шляхом |
| `POST` | `/voice_design` | опис голосу → аудіо (instruct) |
| `POST` | `/edit` | правка запису: `semantic` \| `acoustic` (instruct) |
```bash
# 1) завести голос
curl -X POST localhost:8020/clone_voice -H 'Content-Type: application/json' -d '{
"audio": "prompts/зразок.wav", "name": "Богдан",
"prompt_text": "Це зразок мого голосу для клонування.",
"language": "Ukrainian", "gender": "male"}'
# 2) озвучити
curl -X POST localhost:8020/tts -H 'Content-Type: application/json' -d '{
"text": "Сьогодні чудова погода, ходімо гуляти в парк.",
"voice": "Богдан", "format": "mp3"}' -o out.mp3
# Або one-shot — без реєстрації голосу: передай reference_audio замість voice,
# транскрипт зробить Whisper (перший виклик +~30с, далі кешується)
curl -X POST localhost:8020/tts -H 'Content-Type: application/json' -d '{
"text": "Сьогодні чудова погода.", "reference_audio": "prompts/зразок.mp3",
"format": "mp3"}' -o out.mp3
```
## MCP
Див. [`mcp_server/README.md`](mcp_server/README.md). Коротко:
```bash
claude mcp add firered-tts -- /Users/admin/Projects/firered-tts/.venv/bin/python \
/Users/admin/Projects/firered-tts/mcp_server/server.py
```
Інструменти: `firered_backend_status`, `list_firered_voices`,
`add_firered_voice`, `delete_firered_voice`, `synthesize_firered_speech`,
`design_firered_voice`, `edit_firered_speech`.
## Пам'ять
Mac mini M4, 16 ГБ. Ваги на диску: `base` (7.9) + `redae` (3.5) = 11.4 ГБ.
У пам'яті менше — LLM-бекбон живе у half (4.2 замість 8.4 ГБ), — але на
16 ГБ це все одно впритул. Наслідки, закладені в код:
* у пам'яті живе **рівно одна** модель — `base` **або** `instruct`;
перемикання = повне перезавантаження (хвилини, голосно логується);
* синтез серіалізований глобальним локом — два паралельні запити на 16 ГБ
дають OOM, а не прискорення;
* автозупинка за простоєм 1800с — довга навмисно: рестарт коштує ~40с
компіляції шейдерів, тож тримати процес живим вигідніше;
* Whisper для автотранскрипту — `int8` на CPU, вивантажується одразу після
розпізнавання.
## Швидкість і чотири оптимізації
Наївний запуск апстріму на M4 давав **×186 реального часу** — 3 секунди
української рахувались 9 хвилин. Після чотирьох виправлень стало **×3.0**,
тобто у ~60 разів швидше. Що саме було не так (усе заміряно
`tools/profile_steps.py`):
**1. Autocast на кроці AR-циклу — головна біда.** Апстрім вішає
`@torch.autocast` декоратором на `_backbone_one_step`, тобто регіон
відкривається й закривається на КОЖНОМУ кроці авторегресії. Кеш перекастів ваг
у torch живе рівно всередині регіону, тож 1.7B параметрів fp32 переганялись у
half щокроку. Крок бекбону: **19000 → 2710 мс**. Тепер autocast вимкнений, а
каст робиться явно на межі бекбону.
**2. Бекбон у fp32.** Ваги збережені у float32 (3.0B параметрів), що на 16 ГБ
означає своп. Переводимо в half **лише LLM-бекбон** (4.2 ГБ замість 8.4), а
redae і flow-декодер лишаємо fp32 — half ламає там MPS-matmul. Крок:
**2710 → 1387 мс**.
**3. Референс кодувався заново на кожне речення.** `generate()` викликається
на кожне речення тексту, і кожен виклик проганяв redae-енкодер по тому самому
референсному запису: **5.6с щоразу**, незалежно від довжини тексту. Тепер
кеш із ключем за вмістом аудіо. На 6.6 хв аудіо — 30 влучань проти 2
промахів, ~11% часу прогону.
**4. Компіляція Metal-шейдерів.** Найбільша частина «повільності» виявилась
разовою: Metal компілює ядра при першому виконанні. Поштучні заміри кроку
бекбону: `9873, 2104, 120, 93, 96, 97…` мс. Тому сервіс робить прогрів на
старті (`FIRERED_WARMUP=1`) і має довгий таймаут простою.
Заміряно на Mac mini M4 / 16 ГБ, 3.0с української, прогріта модель:
| n_timesteps | Час | ×реального часу | Якість |
|---|---|---|---|
| 10 (дефолт) | 12 с | ×4.0 | еталон |
| 6 | 10 с | ×3.0 | різниця ледь чутна |
| 4 | 7 с | ×2.3 | помітно грубіше |
| 2 | 6 с | ×1.9 | помітні артефакти |
Профіль прогрітої генерації: flow-декодер 57%, бекбон 18%, redae 11% —
найдієвіший важіль саме `n_timesteps`. Real-time немає, але це вже робочий
інструмент, а не «запусти на ніч».
## Text normalization
Вбудований TN (`wetext`) знає лише китайську й англійську, тож для
української він марний і ми його не ставимо. `19:30`, `250 грн`, `2026 р.`
модель озвучить як вийде. Два виходи: писати текст одразу словами, або
увімкнути LLM-TN — `FIRERED_TN_API_URL` / `_API_KEY` / `_MODEL` у `.env`.
Годиться будь-який OpenAI-сумісний ендпойнт, включно з локальним
(llama.cpp / vLLM / Ollama) — тоді все лишається офлайн.
## Ліцензія
Код і ваги — Apache 2.0. У README апстріму окремо написано, що zero-shot
клонування «solely for academic research purposes» — це суперечить Apache 2.0
і на комерційне використання клонованих голосів дивись обережно. Для
домашнього використання питання не стоїть.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues