Magpie-TTS Multilingual
NVIDIA Magpie-TTS Multilingual 357M en Swift sobre Apple Silicon: un modelo TTS autoregresivo multi-codebook sobre el Nano-Codec de 22.05 kHz de NeMo. Nueve idiomas (inglés, español, alemán, francés, italiano, vietnamita, hindi, mandarín, japonés) con cinco identidades de hablante preconfiguradas. Cuantizado a INT4 (~247 MB) o INT8 (~411 MB). Listo para streaming, ~120 ms de latencia del primer paquete.
Magpie es la opción correcta cuando necesitas un único paquete pequeño que hable nueve idiomas con una voz consistente. Los cinco hablantes preconfigurados mantienen su identidad estable entre idiomas, útil para asistentes multilingües, apps educativas o narración de audiolibros con cambio de código. Para clonación de voz zero-shot usa CosyVoice3, Qwen3-TTS Base o VoxCPM2.
Arquitectura
Magpie es una pipeline MLX de 4 paquetes: codificador de texto → decodificador con cross-attention → cabezal de codebook LocalTransformer → códec de audio Causal HiFi-GAN. Los paquetes comparten pesos del decodificador entre los puntos de entrada de prefill y step para mantener compatibilidad con el layout CoreML de FluidInference upstream.
| Etapa | Módulo | Detalles |
|---|---|---|
| 1. Tokenización | MagpieTokenizer | G2P por idioma (diccionario IPA / bytes byT5 / pinyin / katakana), vocabulario compartido de 2360 tokens con offsets por tokenizador, EOS siempre añadido |
| 2. Codificador de texto | MagpieTextEncoder | 6 capas Transformer causales, d=768, FFN conv k=3 |
| 3. Decoder prefill | MagpieDecoder | 12 capas causales con cross-attention. Inicializa la caché KV con el contexto de 110 frames del hablante preconfigurado + BOS. |
| 4. LocalTransformer | MagpieLocalTransformer | Cabezal AR de codebook de 1 capa, d=256. Muestrea los 8 codebooks por frame secuencialmente dado el hidden del decodificador. |
| 5. Decoder step | MagpieDecoder | Un paso AR por frame hasta EOS o el límite de 500 frames (~23 s). |
| 6. NanoCodec | MagpieNanoCodec | Inversa de FSQ → HiFi-GAN causal → forma de onda mono a 22.05 kHz. |
Idiomas y G2P
Los nueve idiomas hacen round-trip a través de Qwen3-ASR en el testMultilingualRoundTrip del SDK. Cada uno tiene una pipeline adaptada:
| Idioma | Código | Pipeline G2P |
|---|---|---|
| Inglés | en | Diccionario CMU IPA (125 k entradas, incluido) |
| Español | es | Diccionario IPA español (incluido) |
| Alemán | de | Diccionario IPA alemán (incluido) |
| Francés | fr | Codificador de bytes UTF-8 byT5 |
| Italiano | it | Codificador de bytes UTF-8 byT5 |
| Vietnamita | vi | Codificador de bytes UTF-8 byT5 |
| Hindi | hi | Búsqueda de codepoint devanagari + sub-vocabulario last-wins |
| Mandarín | zh | Segmentación de palabras NLTokenizer(.simplifiedChinese) + Apple .mandarinToLatin + diccionario pinyin → IPA incluido + marcadores de tono #N |
| Japonés | ja | Lectura kanji con CFStringTokenizer + dakuten preservado por NFC + marcadores de tono heiban + overrides de partículas/saludos |
El vocabulario compartido de 2360 tokens concatena cada sub-tokenizador con un offset por idioma (registrado en MagpieSubVocab). El text-embedding añade dos filas extra después del vocab para BOS/EOS; eos_id = 2361 se añade a cada secuencia de entrada.
Hablantes preconfigurados
El checkpoint embebe cinco contextos de hablante (110 frames × 768 dims cada uno) usados como prefijo de cada decodificación AR. La identidad del hablante es consistente en los nueve idiomas.
| Índice | Nombre CLI | Identidad |
|---|---|---|
| 0 | sofia | Sofia (por defecto) |
| 1 | aria | Aria |
| 2 | jason | Jason |
| 3 | leo | Leo |
| 4 | john | John Van Stan |
Variantes del modelo
| Variante | Disco | RAM (carga + decode) | HuggingFace |
|---|---|---|---|
| INT4 (por defecto) | ~247 MB | ~1.3 GB | aufklarer/Magpie-TTS-Multilingual-357M-MLX-4bit |
| INT8 | ~411 MB | ~1.6 GB | aufklarer/Magpie-TTS-Multilingual-357M-MLX-8bit |
Ambos paquetes usan cuantización flat affine de MLX (mlx_affine_flat, group size 64) y se descuantizan a FP32 al cargar — las activaciones en runtime son de precisión completa. INT4 e INT8 son indistinguibles auditivamente para este modelo; elige INT4 salvo que tengas espacio de sobra.
Uso de CLI
# Inglés, decodificación greedy
speech speak "Hello, world." --engine magpie --magpie-speaker aria \
--magpie-temperature 0 -o out.wav
# Español (cualquiera de los 9 idiomas — elige con --language)
speech speak "Hola, mundo." --engine magpie --language es \
--magpie-speaker aria -o out.wav
# Japonés — requiere decodificación estocástica (greedy se atasca en la primera frase)
speech speak "こんにちは世界、これは音声合成システムです。" \
--engine magpie --language ja --magpie-temperature 0.6 \
--magpie-top-k 80 --seed 42 -o out.wav
# Síntesis en streaming con reproducción
speech speak "Streaming test" --engine magpie --stream --play
# Listar los 5 hablantes preconfigurados
speech speak --engine magpie --list-speakers
# IPA pre-fonemizado omite el G2P por idioma
speech speak "həˈloʊ" --engine magpie --magpie-prephonemized -o out.wav
Opciones
| Opción | Por defecto | Descripción |
|---|---|---|
--magpie-variant | int4 | Variante de cuantización: int4 o int8 |
--magpie-speaker | sofia | Hablante preconfigurado: sofia, aria, jason, leo, john |
--magpie-temperature | 0.6 | Temperatura de muestreo (0 = greedy) |
--magpie-top-k | 80 | Filtro top-k para muestreo |
--magpie-max-frames | 500 | Tope duro de frames de códec (~23 s) |
--magpie-min-frames | 4 | Frames mínimos antes de permitir EOS |
--magpie-prephonemized | off | Trata la entrada como flujo IPA/fonema; omite el G2P por idioma |
--language | english | Selecciona el tokenizador por idioma |
--stream | off | Emite AsyncStream<AudioChunk> en vez de un único WAV |
--seed | — | Muestreo Gumbel reproducible |
Las entradas en japonés de más de una palabra necesitan decodificación estocástica (--magpie-temperature 0.6 --magpie-top-k 80 --seed 42 espeja el test de referencia de NeMo). Greedy se atasca en la primera frase porque la heurística de pitch heiban se desvía de la verdad por palabra.
Clonación de voz — no soportada
Magpie no tiene condicionamiento zero-shot de hablante en el modelo; solo las 5 identidades preconfiguradas están incluidas. La CLI rechaza los flags compartidos --voice-sample, --speaker e --instruct con un error accionable que apunta a --magpie-speaker o a los motores que sí soportan clonación (Qwen3-TTS Base, CosyVoice3, VoxCPM2).
Rendimiento (M4 Pro)
| Configuración | Audio | Tiempo real | RTF |
|---|---|---|---|
| Batch, INT4, greedy, prompt corto | 2.8 s | 0.88 s | 0.32 |
| Batch, INT4, greedy, oración | 5.8 s | 1.35 s | 0.23 |
| Batch, INT4, sampled, salida 23 s | 23 s | 5.6 s | 0.24 |
| Streaming, INT4, sampled | 23 s | 21.6 s | 0.93 |
La latencia del primer paquete en streaming es ≈120 ms tras cargar el modelo. El RTF en streaming es mayor porque el códec se re-invoca sobre el buffer de códigos completo en cada chunk (una versión futura puede cachear el estado del códec).
API de Swift
import MagpieTTS
let model = try await MagpieTTS.fromPretrained(variant: .int4)
// Síntesis batch (en/es/de/fr/it/vi/hi/zh — greedy funciona)
let audio = try model.synthesize(
text: "Hello, world.",
speaker: .aria,
language: .english,
params: MagpieTTSParams(temperature: 0, topK: 1, maxSteps: 500))
// Japonés — usa muestreo estocástico
let audioJA = try model.synthesize(
text: "こんにちは世界、これは音声合成システムです。",
speaker: .aria,
language: .japanese,
params: MagpieTTSParams(temperature: 0.6, topK: 80,
maxSteps: 300, seed: 42))
// Streaming (AsyncStream<AudioChunk>)
let stream = model.synthesizeStream(
text: "Streaming text",
speaker: .aria,
language: .english,
firstChunkFrames: 8,
framesPerChunk: 25)
for try await chunk in stream {
// chunk.samples es Float32 mono a 22.05 kHz
}
Backend CoreML (--engine magpie-coreml)
Junto al bundle MLX, Magpie envía un bundle CoreML (aufklarer/Magpie-TTS-Multilingual-357M-CoreML-8bit, ~342 MB INT8). Cuatro paquetes .mlmodelc — text_encoder, decoder_prefill, decoder_step, nanocodec_decoder — corren en ANE / GPU; un FSQ inverso en Swift convierte los códigos muestreados en los latentes de 32 dimensiones que consume el codec.
# 8 idiomas (sin japonés), 5 hablantes predefinidos
speech speak "Hello world." --engine magpie-coreml --magpie-speaker aria -o hi.wav
speech speak "Hola mundo." --engine magpie-coreml --language es --magpie-speaker leo -o es.wav
# --language ja redirige automáticamente al backend MLX (aviso en stderr)
speech speak "こんにちは" --engine magpie-coreml --language ja -o ja.wav
Salvedades respecto a --engine magpie:
- Pipeline híbrida hoy. El LocalTransformer de 1 capa (la cabeza real de muestreo de codebooks que entrena NeMo) y las 8 tablas de audio embeddings no se envían dentro del bundle CoreML. En la primera síntesis el engine CoreML carga el bundle MLX INT4 para conducir ambas piezas. El round-trip ASR es idéntico bit a bit al backend MLX; la diferencia es que este engine también descarga el bundle MLX. Un camino CoreML puro para despliegue iOS solo en ANE necesita que el bundle envíe
local_transformer/*.npy+audio_embedding_*.npyy un LT en Swift Accelerate (seguimiento pendiente). - Sin streaming.
nanocodec_decoder.mlmodelcestá trazado a una ventana fija de 64 frames. Las secuencias más largas se fragmentan internamente, pero la latencia del primer paquete sería ~3 s si emitiéramos en los límites de fragmento.--streamse rechaza con un error procesable. - Sin tokenizer japonés. El bundle CoreML todavía no envía JSONs del tokenizer JA.
--language jacon este engine cae automáticamente al backend MLX.
El orden de hablantes coincide con el speaker_info.json del bundle CoreML (0=John, 1=Sofia, 2=Aria, 3=Jason, 4=Leo — diferente de MLX); el enum de hablantes mapea internamente para que los nombres CLI funcionen en ambos engines.
Notas de implementación
Tres bugs que vale la pena conocer al portar TTS multilingüe estilo NeMo:
- División entera FSQ — El
/de MLX-swift es división real (mlx_divide); el inverso FSQ de NeMo usa//de Python. UsaMLX.floorDivide(...)o cada slot FSQ decodifica con offsets fraccionales y el códec emborrona el audio. - Offsets de sub-vocabulario — El
AggregatedTTSTokenizerde NeMo concatena los vocabularios por idioma con offsets. Un mapa global ingenuo de first-occurrence siempre cae en la región inglesa y produce audio sin sentido para otros idiomas. - Hindi last-wins dedup —
HindiCharsTokenizeremite entradas devanagari duplicadas en su propio vocabulario (CHARSET solapa con PUNCT_LIST). La dict-comprehension{l: i for i, l in enumerate(tokens)}de Python conserva la última asignación; espéjalo, no uses first-occurrence.
Las tres correcciones están documentadas inline en el módulo Swift.
Fuente
- Pesos upstream: nvidia/magpie_tts_multilingual_357m (NVIDIA Open Model License)
- Códec: nvidia/nemo-nano-codec-22khz-1.89kbps-21.5fps
- Paper: NanoCodec: Towards High-Quality Ultra Fast Speech LLM Inference (2025)
- Puerto CoreML de referencia: FluidInference/mobius
- Módulos Swift: MagpieTTS (MLX) + MagpieTTSCoreML (CoreML)
- Bundle CoreML: aufklarer/Magpie-TTS-Multilingual-357M-CoreML-8bit
Licencia
- Pesos del modelo: NVIDIA Open Model License (uso comercial permitido; ver detalles en la página de HuggingFace)
- Puerto Swift + diccionarios IPA / pinyin incluidos: mismo que NeMo upstream (Apache 2.0 para los diccionarios, NVIDIA OML para el modelo)