Skip to main content
Glama

FireRedTTS3 — TTS multilingüe (24 idiomas, incl. ucraniano)

Servicio local de síntesis de voz basado en FireRedTTS3 con API HTTP y servidor MCP, en el puerto :8020.

El modelo salió el 13.08.2026, licencia Apache 2.0, pesos públicos.

Lo que conviene saber antes de empezar

No hay voces predefinidas. Biblioteca vacía = no hay nada con qué sintetizar. Primero creas una voz a partir de cualquier grabación de habla (add_firered_voice), luego la usas por su nombre. El modelo clona zero-shot directamente durante la síntesis.

Se necesita la transcripción de la referencia. Al modelo no le basta el audio en sí: también necesita el texto de lo que se oye en él. Si no lo pasas, lo reconocemos con Whisper, pero tu propio texto siempre es más preciso.

No hay acentos. Ni diccionario, ni fallback ByT5, ni Му+дрого manual. El modelo pone los acentos por sí mismo según el contexto y se equivocará en los homógrafos (за́мок/замо́к). Es una limitación fundamental del modelo multilingüe.

Related MCP server: STT2TTS MCP

Instalación

cd /Users/admin/Projects/firered-tts
./setup.sh                    # venv + залежності + апстрім + патч + ваги

setup.sh hace cuatro cosas: crea .venv con Python 3.11 y torch 2.8.0 para MPS/CPU, clona el upstream en vendor/FireRedTTS3 en el commit fijado 00570ad, lo parchea para Apple Silicon (ver más abajo) y descarga los pesos base+redae (~11.4 GB) en pretrained_models/.

Pesos por separado (tarda mucho, mejor en detached):

nohup ./download-weights.sh base > data/download.log 2>&1 &
./download-weights.sh instruct   # +7.9 ГБ, для дизайну голосу й редагування

Para qué sirve el parche

El upstream está escrito exclusivamente para NVIDIA y en Mac no arranca en absoluto: flash_attn no se compila en Metal ni por principio, el dispositivo está fijado como cuda, el autocast está clavado a CUDA. El parche sustituye flash_attention_2 por sdpa (funciona en todas partes, incluido NVIDIA), hace el dispositivo dinámico y añade las optimizaciones de la sección de velocidad. La lista completa está en la cabecera de src/patch_upstream.py.

El parche es idempotente y falla si la sustitución no acierta: si el upstream ha cambiado, te enteras al momento, no por un error de CUDA en la primera síntesis.

Ejecución

./run.sh                      # http://localhost:8020

No es necesario ejecutarlo por separado: el servidor MCP levantará el backend por sí mismo. El backend se apaga tras un periodo de inactividad y libera memoria.

API HTTP

Método

Endpoint

Qué hace

GET

/health

estado, dispositivo, qué modelo está en memoria

GET

/voices

nombres de voces (filtros gender, language; full=1 → con metadatos)

GET

/languages

24 idiomas + 21 dialectos

POST

/clone_voice

referencia + transcripción → voz

DELETE

/voices/{name}

eliminar voz (el archivo original de la grabación no se toca)

POST

/tts

texto → bytes de audio en la respuesta

POST

/synthesize

texto → archivo en DATA_DIR, JSON con la ruta

POST

/voice_design

descripción de voz → audio (instruct)

POST

/edit

edición de grabación: semantic | acoustic (instruct)

# 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

Ver mcp_server/README.md. En resumen:

claude mcp add firered-tts -- /Users/admin/Projects/firered-tts/.venv/bin/python \
  /Users/admin/Projects/firered-tts/mcp_server/server.py

Herramientas: firered_backend_status, list_firered_voices, add_firered_voice, delete_firered_voice, synthesize_firered_speech, design_firered_voice, edit_firered_speech.

Memoria

Mac mini M4, 16 GB. Pesos en disco: base (7.9) + redae (3.5) = 11.4 GB. En memoria menos: el backbone LLM vive en half (4.2 en lugar de 8.4 GB), pero en 16 GB sigue siendo justo. Consecuencias incorporadas en el código:

  • en memoria vive exactamente una modelo — base o instruct; el cambio = recarga completa (minutos, se registra en el log de forma ruidosa);

  • la síntesis está serializada con un lock global: dos peticiones paralelas en 16 GB dan OOM, no aceleración;

  • autoapagado por inactividad de 1800 s — largo a propósito: el reinicio cuesta ~40 s de compilación de shaders, así que mantener el proceso vivo es más rentable;

  • Whisper para la transcripción automática — int8 en CPU, se descarga inmediatamente después del reconocimiento.

Velocidad y cuatro optimizaciones

La ejecución ingenua del upstream en M4 daba ×186 del tiempo real: 3 segundos de ucraniano tardaban 9 minutos. Tras cuatro correcciones pasó a ×3.0, es decir, unas ~60 veces más rápido. Qué era exactamente lo que fallaba (todo medido con tools/profile_steps.py):

1. Autocast en el paso del bucle AR — el principal problema. El upstream cuelga @torch.autocast como decorador en _backbone_one_step, es decir, la región se abre y se cierra en CADA paso de la autoregresión. La caché de recasts de pesos en torch vive exactamente dentro de la región, así que 1.7B parámetros fp32 se convertían a half en cada paso. Paso del backbone: 19000 → 2710 ms. Ahora el autocast está desactivado y el cast se hace explícitamente en el límite del backbone.

2. Backbone en fp32. Los pesos están guardados en float32 (3.0B parámetros), lo que en 16 GB significa swap. Pasamos a half solo el backbone LLM (4.2 GB en lugar de 8.4), y dejamos redae y el decodificador flow en fp32: half rompe el MPS-matmul allí. Paso: 2710 → 1387 ms.

3. La referencia se codificaba de nuevo para cada frase. generate() se llama para cada frase del texto, y cada llamada pasaba el codificador redae por la misma grabación de referencia: 5.6 s cada vez, independientemente de la longitud del texto. Ahora hay una caché con clave por contenido de audio. En 6.6 min de audio: 30 aciertos frente a 2 fallos, ~11% del tiempo de ejecución.

4. Compilación de shaders de Metal. La mayor parte de la «lentitud» resultó ser puntual: Metal compila los kernels en la primera ejecución. Mediciones individuales del paso del backbone: 9873, 2104, 120, 93, 96, 97… ms. Por eso el servicio hace un calentamiento al arrancar (FIRERED_WARMUP=1) y tiene un timeout de inactividad largo.

Medido en Mac mini M4 / 16 GB, 3.0 s de ucraniano, modelo calentado:

n_timesteps

Tiempo

×tiempo real

Calidad

10 (por defecto)

12 s

×4.0

referencia

6

10 s

×3.0

diferencia apenas perceptible

4

7 s

×2.3

notablemente más tosco

2

6 s

×1.9

artefactos perceptibles

Perfil de la generación calentada: decodificador flow 57%, backbone 18%, redae 11%: la palanca más eficaz es precisamente n_timesteps. No hay tiempo real, pero ya es una herramienta de trabajo, no un «déjalo corriendo toda la noche».

Normalización de texto

El TN integrado (wetext) solo conoce chino e inglés, así que para el ucraniano es inútil y no lo instalamos. 19:30, 250 грн, 2026 р. el modelo los pronunciará como pueda. Dos salidas: escribir el texto directamente con palabras, o activar el LLM-TN — FIRERED_TN_API_URL / _API_KEY / _MODEL en .env. Vale cualquier endpoint compatible con OpenAI, incluido uno local (llama.cpp / vLLM / Ollama): entonces todo permanece offline.

Licencia

Código y pesos: Apache 2.0. En el README del upstream se indica por separado que el clonado zero-shot es «solely for academic research purposes»: esto contradice Apache 2.0, así que mira con cuidado el uso comercial de voces clonadas. Para uso doméstico no hay problema.

Related MCP Connectors

Related MCP Servers