¿Por qué tokenizar audio?
Los modelos de lenguaje operan con tokens discretos — palabras, subpalabras, pares de bytes. Un modelo como GPT o LLaMA nunca ve texto crudo; ve IDs enteros extraídos de un vocabulario fijo. Entonces, ¿qué sucede cuando queremos que un modelo de lenguaje entienda o genere audio? El audio crudo es una forma de onda continua: miles de muestras de amplitud de punto flotante por segundo. Necesitamos una forma de convertir esa señal continua en una secuencia de tokens discretos que un modelo de lenguaje pueda consumir, predecir y generar. Esa conversión es la tokenización de audio .
Hay dos tipos fundamentalmente diferentes de información en el audio, y requieren diferentes tipos de tokens:
- Tokens semánticos: capturan qué se dice — el contenido lingüístico, las palabras y el significado. Estos se generan agrupando las representaciones internas de modelos de habla autosupervisados como HuBERT o WavLM . La idea es simple: pasar el audio por un modelo pre-entrenado, extraer características de capas ocultas, luego agrupar esas características (por ejemplo, con k-means) para obtener etiquetas discretas. Dos expresiones de «hola» por diferentes hablantes en diferentes salas se mapearán a los mismos tokens semánticos, porque el modelo aprendió a ignorar los detalles del hablante y la acústica.
- Tokens acústicos: capturan cómo suena — identidad del hablante, tono, emoción, calidad de grabación, acústica de la sala, ruido de fondo. Estos se generan por codecs de audio neuronales (como EnCodec o SoundStream) que comprimen audio en códigos discretos mientras preservan suficiente detalle para reconstruir la forma de onda fielmente.
La jerarquía es directa: tokens semánticos para significado, tokens acústicos para fidelidad. AudioLM (Borsos et al., 2022) estableció este paradigma de dos etapas: primero generar tokens semánticos (decidiendo qué decir), luego generar tokens acústicos condicionados en esos tokens semánticos (decidiendo cómo decirlo). Esta separación permite que el modelo planifique el contenido antes de comprometerse con los detalles acústicos — análogo a cómo un escritor podría esbozar un argumento antes de elegir las palabras exactas.
Cuantización vectorial y codebooks
Tanto los tokens semánticos como los acústicos comparten un mecanismo común: la cuantización vectorial (VQ) . El codificador de audio produce una secuencia de vectores de características continuos — uno por trama de audio. Para convertir cada vector continuo en un token discreto, necesitamos un codebook : una tabla de $K$ vectores prototipo (también llamados centroides o codewords), aprendidos durante el entrenamiento. Para cuantizar un vector $z$, encontramos la entrada más cercana del codebook:
donde $e_k$ es la $k$-ésima entrada del codebook. El índice $k$ se convierte en el token discreto. Con $K = 1024$ entradas, cada token lleva $\log_2(1024) = 10$ bits de información. Esto es idéntico en concepto a la Cuantización de Producto usada en búsqueda vectorial (cubierta en el artículo de indexación ), excepto que aquí estamos cuantizando características de audio en lugar de embeddings de documentos.
Pero hay un problema. El audio es rico — un solo codebook de 1024 entradas no puede capturar toda la variación en tono, timbre, identidad de fonema y acústica de fondo simultáneamente. Aumentar $K$ a, digamos, $2^{20} \approx 1{,}000{,}000$ entradas nos daría 20 bits por token, pero el codebook se vuelve enorme y la mayoría de las entradas quedan sin usar durante el entrenamiento (un problema llamado colapso de codebook ). Necesitamos una forma más inteligente de aumentar la capacidad sin hacer explotar el tamaño del codebook.
Cuantización Vectorial Residual (RVQ)
La solución es la Cuantización Vectorial Residual (RVQ) : en lugar de un codebook grande, usar múltiples codebooks más pequeños en serie. Cada codebook cuantiza el error residual dejado por el anterior, refinando progresivamente la aproximación:
- Paso 1: Cuantizar $z$ con el codebook 1 para obtener la aproximación $\hat{z}_1$. Calcular el residual $r_1 = z - \hat{z}_1$.
- Paso 2: Cuantizar $r_1$ con el codebook 2 para obtener $\hat{z}_2$. Calcular el residual $r_2 = r_1 - \hat{z}_2$.
- Paso $q$: Cuantizar $r_{q-1}$ con el codebook $q$ para obtener $\hat{z}_q$. Calcular el residual $r_q = r_{q-1} - \hat{z}_q$.
Después de $Q$ niveles, la aproximación final es la suma de todas las contribuciones del codebook:
Cada nivel captura detalles más finos — como aumentar progresivamente la resolución de una imagen. El nivel 1 captura la estructura gruesa (qué fonema, tono aproximado), el nivel 2 corrige los errores más grandes dejados por el nivel 1, y así sucesivamente.
La tasa de bits total de un codec RVQ es:
donde $Q$ es el número de niveles de codebook, $K$ es el tamaño del codebook (entradas por codebook), y $f_s$ es la tasa de tramas en Hz (cuántos conjuntos de tokens por segundo). Por ejemplo, 8 codebooks $\times$ $\log_2(1024) = 10$ bits $\times$ 75 Hz = 6000 bits/s = 6 kbps.
Recorramos los límites de $Q$. Con $Q = 1$ (un solo codebook), obtenemos una aproximación muy gruesa — suficiente para capturar contenido lingüístico aproximado (qué fonema se está pronunciando) pero no suficiente para reconstruir fielmente la forma de onda. El audio suena robótico y pierde la identidad del hablante. Con $Q = 32$, obtenemos detalles extremadamente finos — calidad de reconstrucción casi transparente, indistinguible del original para la mayoría de los oyentes. Pero 32 codebooks a 75 Hz significan 32 $\times$ 75 = 2,400 tokens por segundo de audio. Para un clip de 30 segundos, eso son 72,000 tokens — una carga pesada para cualquier modelo de lenguaje. Este es el compromiso fundamental: más niveles de RVQ significa mayor fidelidad pero más tokens para que el LLM procese.
El código a continuación demuestra RVQ sobre puntos 2D simples usando un codebook pequeño. Observa cómo el error residual se reduce con cada nivel de cuantización — cada codebook corrige los errores del anterior.
import math, random, json, js
random.seed(42)
# --- Tiny codebook: 4 entries in 2D ---
def make_codebook(n_entries, dim):
return [[random.uniform(-2, 2) for _ in range(dim)] for _ in range(n_entries)]
def nearest(vec, codebook):
best_k, best_dist = 0, float('inf')
for k, entry in enumerate(codebook):
dist = sum((v - e) ** 2 for v, e in zip(vec, entry)) ** 0.5
if dist < best_dist:
best_k, best_dist = k, dist
return best_k, codebook[best_k], best_dist
def subtract(a, b):
return [ai - bi for ai, bi in zip(a, b)]
# Create 4 codebooks (one per RVQ level)
Q = 4
K = 4
dim = 2
codebooks = [make_codebook(K, dim) for _ in range(Q)]
# Original vector to quantize
z = [1.7, -0.3]
rows = []
residual = z[:]
total_approx = [0.0] * dim
for q in range(Q):
k, z_hat, dist = nearest(residual, codebooks[q])
total_approx = [t + zh for t, zh in zip(total_approx, z_hat)]
residual = subtract(residual, z_hat)
res_norm = sum(r ** 2 for r in residual) ** 0.5
total_err = sum((o - a) ** 2 for o, a in zip(z, total_approx)) ** 0.5
rows.append([
str(q + 1),
str(k),
f"({z_hat[0]:+.3f}, {z_hat[1]:+.3f})",
f"({residual[0]:+.3f}, {residual[1]:+.3f})",
f"{total_err:.4f}"
])
js.window.py_table_data = json.dumps({
"headers": ["RVQ Level", "Codebook Index", "Quantized Vector", "Remaining Residual", "Total Error (L2)"],
"rows": rows
})
print(f"Original vector: ({z[0]:.3f}, {z[1]:.3f})")
print(f"Final approximation: ({total_approx[0]:.3f}, {total_approx[1]:.3f})")
print(f"Error after {Q} levels: {rows[-1][-1]}")
print(f"Error after 1 level: {rows[0][-1]}")
print()
print("Each RVQ level reduces the remaining error by quantizing the residual.")
SoundStream y EnCodec: Los codecs neuronales
Con la cuantización vectorial y RVQ como nuestros bloques de construcción, ahora podemos entender los codecs de audio neuronales que impulsan la generación moderna de habla y música. Estos codecs comparten una arquitectura común: un codificador convolucional comprime la forma de onda cruda en una secuencia de vectores latentes, un cuello de botella RVQ discretiza esos vectores en tokens, y un decodificador convolucional reconstruye la forma de onda a partir de la representación cuantizada. Todo el sistema se entrena de extremo a extremo con una combinación de pérdida de reconstrucción, pérdida perceptual y pérdida adversarial (un discriminador que intenta distinguir audio real de audio reconstruido).
SoundStream (Zeghidour et al., 2021) fue el primer codec neuronal de extremo a extremo con RVQ. La innovación clave de Google fue el dropout estructurado en niveles de cuantizador : durante el entrenamiento, eliminan aleatoriamente los últimos $n$ niveles de RVQ, forzando a los niveles anteriores a llevar suficiente información para una reconstrucción razonable por sí solos. Esto da tasa de bits variable en inferencia — usar 3 niveles para baja calidad (3 kbps), 6 para media, o todos los 12-18 para calidad completa (18 kbps) — todo desde un solo modelo entrenado.
EnCodec (Défosséz et al., 2022) de Meta siguió una arquitectura similar pero añadió un discriminador STFT multi-escala para entrenamiento adversarial — múltiples discriminadores operando en diferentes resoluciones tiempo-frecuencia del audio, empujando al codec a preservar tanto detalle temporal fino como estructura espectral amplia. EnCodec soporta mono a 24 kHz y estéreo a 48 kHz, opera a tasas de bits de 1.5 a 24 kbps, y se convirtió en el codec estándar de facto para una generación de modelos de lenguaje de audio: VALL-E (texto a voz), Bark (texto a audio) y MusicGen (texto a música) todos usan tokens de EnCodec como su representación de audio discreta.
DAC (Descript Audio Codec) (Kumar et al., 2023) llevó la calidad más lejos con discriminadores mejorados y activaciones Snake (funciones de activación periódicas que ayudan al decodificador a modelar la naturaleza oscilatoria de las formas de onda de audio). DAC es un codec universal — maneja habla, música y sonidos ambientales en un solo modelo — y logra mayor calidad perceptual que EnCodec a la misma tasa de bits.
Todos estos codecs operan a tasas de trama de 50-75 Hz, lo que significa que producen un conjunto de tokens RVQ cada 13-20 ms de audio. A 75 Hz con 8 codebooks, eso son 600 tokens por segundo — ya mucho menos que las 16,000-48,000 muestras de forma de onda cruda por segundo. Estos codecs comprimen el audio aproximadamente 100\times mientras mantienen calidad cercana a CD, y son el puente entre formas de onda continuas y las secuencias de tokens discretas que los modelos de lenguaje pueden procesar.
Mimi: Unificando tokens semánticos y acústicos
El enfoque de dos etapas de AudioLM — generar tokens semánticos primero, luego tokens acústicos — funciona bien, pero requiere dos sistemas de tokenización completamente separados: un modelo autosupervisado (como HuBERT) para tokens semánticos y un codec neuronal (como EnCodec) para tokens acústicos. ¿Qué pasaría si pudiéramos obtener ambos de un solo codec?
Mimi , introducido como parte del sistema Moshi (Défosséz et al., 2024) , hace exactamente eso. Su arquitectura combina un codificador-decodificador convolucional SeaNet (la misma estructura base que EnCodec) con un cuello de botella transformer de 8 capas insertado entre el codificador y el RVQ. El transformer procesa las características codificadas antes de la cuantización, dando al modelo suficiente capacidad para separar información semántica y acústica dentro de un solo codec.
La innovación clave es Split RVQ : el primer codebook se entrena con destilación de conocimiento de WavLM (un modelo de habla autosupervisado), por lo que aprende a capturar contenido semántico — qué se está diciendo. Los 7 codebooks restantes se entrenan normalmente con pérdida de reconstrucción, por lo que capturan detalle acústico — cómo suena. Un codec, dos tipos de tokens, sin necesidad de un pipeline separado de HuBERT.
Mimi opera a una tasa de trama de solo 12.5 Hz (una trama por 80 ms), con 8 codebooks de 2048 entradas cada uno, produciendo una tasa de bits de solo 1.1 kbps a 24 kHz. El codec completo es totalmente streaming con 80 ms de latencia algorítmica — crítico para la generación de habla en tiempo real y full-duplex de Moshi, donde el modelo escucha y habla simultáneamente.
La unificación importa porque elimina un cuello de botella arquitectónico importante. En AudioLM, el modelo semántico y el modelo acústico se entrenan por separado con diferentes objetivos, y las discrepancias entre ellos pueden causar artefactos. Con Mimi, un solo codec entrenado de extremo a extremo produce tanto tokens semánticos como acústicos en una representación unificada y coherente.
Desde Mimi, la tendencia ha continuado. Los codecs más nuevos de 2025 llevan el enfoque unificado más lejos: Voxtral Codec (Mistral, 2025) opera a 12.5 Hz con 1 codebook semántico y 36 codebooks acústicos FSQ (Finite Scalar Quantization). DualCodec optimiza explícitamente tanto para comprensión como generación de habla. SpectroStream comprime en el dominio espectral en lugar del dominio de la forma de onda. El campo avanza rápido, pero la idea central permanece: producir tanto tokens semánticos como acústicos desde un solo codec con capacidad de streaming.
El problema de la tasa de tramas
Aquí está el desafío práctico que impulsa gran parte del diseño moderno de codecs de audio. Un codec funcionando a 75 Hz con 8 codebooks produce $75 \times 8 = 600$ tokens por segundo de audio. Para un clip de audio de 30 segundos, eso son 18,000 tokens. Para un segmento de podcast de 5 minutos, eso son 180,000 tokens. Eso es mucho para que un modelo de lenguaje procese — consume la ventana de contexto, eleva el costo computacional (la atención escala cuadráticamente con la longitud de secuencia) y ralentiza la generación.
Por eso los codecs modernos están reduciendo las tasas de trama agresivamente. La tabla a continuación compara varios codecs en los dos números que más importan para la integración con LLM: tokens por segundo y tokens para un clip de 30 segundos.
import math, json, js
codecs = [
("EnCodec (2022)", 75.0, 8, 1024),
("DAC (2023)", 86.0, 9, 1024),
("SoundStream (2021)", 50.0, 12, 1024),
("Mimi (2024)", 12.5, 8, 2048),
("Voxtral (2025)", 12.5, 37, 2048),
("Qwen3-TTS (2025)", 12.0, 1, 8192),
]
rows = []
for name, fps, Q, K in codecs:
bits_per_cb = math.log2(K)
tokens_per_sec = fps * Q
tokens_30s = tokens_per_sec * 30
bitrate = fps * Q * bits_per_cb / 1000 # kbps
rows.append([
name,
f"{fps}",
str(Q),
f"{int(bits_per_cb)}",
f"{int(tokens_per_sec)}",
f"{int(tokens_30s):,}",
f"{bitrate:.1f}"
])
js.window.py_table_data = json.dumps({
"headers": [
"Codec", "Frame Rate (Hz)", "Codebooks (Q)",
"Bits/Token", "Tokens/sec", "Tokens (30s)", "Bitrate (kbps)"
],
"rows": rows
})
print("Key takeaway: Mimi produces 100 tokens/sec vs EnCodec's 600.")
print("For a 30-second clip, that's 3,000 vs 18,000 tokens.")
print("Qwen3-TTS goes even further: just 12 tokens/sec with a single large codebook.")
print()
print("Lower frame rate = fewer tokens = more audio fits in the LLM context window.")
El compromiso es claro: una tasa de tramas más baja significa que cada token debe codificar más información temporal (80 ms de audio por trama a 12.5 Hz vs 13 ms a 75 Hz). Para compensar, los codecs de baja tasa de tramas usan codebooks más grandes ($K = 2048$ o $K = 8192$ en lugar de $K = 1024$) y/o arquitecturas de codificador más expresivas (Mimi añade un cuello de botella transformer). La información tiene que ir a algún lugar — o la distribuyes a través de muchos tokens pequeños a lo largo del tiempo, o la empaquetas en menos tokens más ricos.
Algunos sistemas evitan el problema de la tasa de tramas por completo aplanando los códigos RVQ: en lugar de tratar cada nivel de codebook como un flujo separado, intercalan todos los $Q$ tokens de cada trama en una sola secuencia plana y dejan que el LLM los modele autorregresivamente. Otros usan patrones de retardo (MusicGen) o predicción paralela de niveles de codebook (VALL-E, SoundStorm) para reducir la longitud de secuencia efectiva que ve el LLM. Cubriremos estas estrategias de generación en artículos posteriores.
Quiz
Pon a prueba tu comprensión de la tokenización de audio, la cuantización vectorial y los codecs de audio neuronales.
En la Cuantización Vectorial Residual (RVQ), ¿qué cuantiza cada codebook sucesivo?
Un codec usa 8 codebooks, cada uno con $K = 1024$ entradas, a una tasa de tramas de 50 Hz. ¿Cuál es la tasa de bits total?
¿Cuál es la innovación clave del Split RVQ de Mimi comparado con el RVQ estándar?
¿Por qué los codecs de audio modernos están avanzando hacia tasas de tramas más bajas (por ejemplo, 12.5 Hz en lugar de 75 Hz)?