Voz e IA — Módulo 3: Whisper en la práctica
Tercer módulo del Curso de Voz e IA en tiempo real: llevar Whisper a la práctica. Elegir tamaño de modelo, faster-whisper para acelerar, timestamps a nivel de palabra, manejo de idiomas, diarización básica (quién habla), y las trampas reales: alucinaciones en silencio, formatos de audio y costo local vs API.
☰ Índice del contenido
- La tesis del módulo
- Decisión 1 — Qué tamaño de modelo
- Decisión 2 — La implementación: faster-whisper, no el paquete de referencia
- Decisión 3 — Idiomas: detectar, forzar o traducir
- Decisión 4 — Diarización: quién habla y cuándo
- Decisión 5 — Local vs API
- Las trampas reales
- Alucinaciones en silencio
- Repeticiones y bucles
- Formato y sample rate
- Normalización del audio
- El pipeline práctico
- Errores comunes
- La frase para llevarse
- Qué viene
- Material relacionado
En el módulo 2 abrimos el capó del ASR (Automatic Speech Recognition, reconocimiento automático de voz) y vimos cómo Whisper convierte un espectrograma en texto: encoder que escucha, decoder que escribe, todo entrenado sobre 680 mil horas de audio. Sabemos cómo funciona. Este módulo es lo contrario de la teoría: es el manual de campo. Porque entre entender el modelo y ponerlo en producción hay un abismo lleno de decisiones que la documentación no te toma por ti — qué tamaño elegir, qué implementación usar, cómo sacarle timestamps por palabra, y sobre todo cómo blindarte contra las dos trampas por las que Whisper se hace famoso en cuanto lo pones frente a audio real: inventa texto cuando hay silencio y no tiene ni idea de quién está hablando.
La tesis del módulo
Poner Whisper en producción no es llamar
transcribe(): es elegir el tamaño correcto para tu latencia y tu presupuesto, usar una implementación acelerada, y blindarte contra sus dos trampas famosas — las alucinaciones en silencio y la ausencia de diarización. El modelo de referencia de OpenAI es un excelente punto de partida y una pésima elección para producción tal cual viene. La diferencia entre un prototipo y un servicio está en las cinco decisiones de este módulo, ninguna de las cuales aparece en el “hello world” del README.
Decisión 1 — Qué tamaño de modelo
Whisper no es un modelo: es una familia. El mismo diseño entrenado a distintas escalas, desde tiny (39 millones de parámetros) hasta large-v3 (1.550 millones). Más parámetros significan mejor WER (Word Error Rate, tasa de error por palabra: qué fracción de palabras salió mal), pero también más VRAM y más lentitud. La decisión no es “el mejor modelo”: es “el modelo más chico que aún cumple tu requisito de calidad”, porque cada escalón que subes multiplica el costo por hora de audio.
| Modelo | Parámetros | VRAM aprox. (fp16) | Velocidad relativa | ¿Cuándo? |
|---|---|---|---|---|
tiny | 39 M | ~1 GB | ~10× | Prototipo, palabras clave, edge |
base | 74 M | ~1 GB | ~7× | Audio limpio en inglés, tiempo real ajustado |
small | 244 M | ~2 GB | ~4× | Buen punto medio multiidioma |
medium | 769 M | ~5 GB | ~2× | Calidad seria sin GPU tope |
large-v3 | 1.550 M | ~10 GB | 1× | Máxima calidad, idiomas difíciles |
large-v3-turbo | 809 M | ~6 GB | ~5–8× vs large | Casi la calidad de large, mucho más rápido |
La fila que cambió el juego en 2024 es la última. large-v3-turbo es large-v3 con el decoder podado de 32 capas a 4 — el encoder, que hace el trabajo pesado de escuchar, queda idéntico. Resultado: 809 millones de parámetros, unos 6 GB de VRAM y una velocidad cercana a medium, con una calidad muy cerca de large-v3. Para la enorme mayoría de casos de producción en español, turbo es hoy el punto de partida sensato; small si no tienes GPU.
Ojo: la “velocidad relativa” de la tabla es orientativa y depende del hardware, del
batch, delbeam_sizey de si activaste VAD. No la tomes como promesa de latencia. La única cifra que vale es la que mides en tu máquina con tu audio: cronometra el RTF (Real-Time Factor, factor de tiempo real: segundos de cómputo por segundo de audio; RTF < 1 significa que transcribes más rápido de lo que dura el audio).
Decisión 2 — La implementación: faster-whisper, no el paquete de referencia
El paquete openai-whisper es la implementación de referencia, escrita en PyTorch para ser legible y correcta, no para ser rápida. En producción casi nadie lo usa directamente. El estándar de facto es faster-whisper, una reimplementación sobre CTranslate2 (motor de inferencia optimizado para modelos Transformer, con cuantización y kernels afinados). Los mismos pesos de Whisper, ejecutados mucho más rápido y con menos memoria: en la práctica alrededor de 4× más veloz y con la mitad de VRAM, sin pérdida de calidad porque los pesos son idénticos.
La pieza que habilita ese ahorro es compute_type, la cuantización (*representar los pesos con menos bits: float16 en vez de float32, o int8 en vez de float16*). Menos bits significan menos memoria y más velocidad, a cambio de un error de redondeo casi siempre imperceptible en ASR.
from faster_whisper import WhisperModel
# device="cuda" en GPU; en CPU usa device="cpu", compute_type="int8".
# compute_type="int8_float16" mezcla: pesos en int8, cómputo en float16.
model = WhisperModel(
"large-v3-turbo",
device="cuda",
compute_type="float16", # "int8" si vas corto de VRAM
)
# transcribe() NO transcribe de inmediato: devuelve un generador perezoso.
# El trabajo real ocurre cuando iteras `segments`.
segments, info = model.transcribe(
"reunion.wav",
beam_size=5, # búsqueda por haz; 5 es el default sensato
word_timestamps=True, # timestamps por PALABRA, no solo por segmento
vad_filter=True, # recorta los silencios ANTES de transcribir
vad_parameters={"min_silence_duration_ms": 500},
)
print(f"Idioma: {info.language} (p={info.language_probability:.2f})")
for seg in segments:
print(f"[{seg.start:6.2f}s -> {seg.end:6.2f}s] {seg.text}")
for w in seg.words: # disponible por word_timestamps=True
print(f" {w.word!r:20} {w.start:6.2f}s p={w.probability:.2f}")
Tres detalles que muerden si no los sabes de antemano:
transcribe()es perezoso. Devuelve un generador; el cómputo pesado no ocurre hasta que recorressegments. Si mides el tiempo alrededor de la llamada y no de la iteración, vas a creer que Whisper es instantáneo.word_timestamps=Truecuesta. Añade alrededor de un 10% de tiempo porque alinea el audio con cada palabra usando los pesos de atención. Si solo necesitas el texto corrido, no lo actives.infotrae el idioma detectado y su probabilidad, disponibles antes de iterar los segmentos. Eselanguage_probabilityes tu semáforo de confianza para la Decisión 3.
Decisión 3 — Idiomas: detectar, forzar o traducir
Por defecto Whisper detecta el idioma solo, mirando los primeros segundos de audio. Funciona bien con audio limpio, pero tiene dos modos de fallar caros: en audio con ruido o silencio al inicio puede detectar el idioma equivocado, y en audio multiidioma (alguien que salta del español al inglés a media frase) se casa con el primer idioma y arrastra el error el resto del archivo.
- Forzar el idioma (
language="es") cuando sabes que todo el audio está en un idioma. Es más rápido (se salta el paso de detección) y más robusto. Si tu producto atiende un mercado de un solo idioma, fuérzalo siempre. - Dejar detectar solo cuando de verdad no sabes qué llega. Y aun así, revisa
info.language_probability: si viene bajo (por debajo de ~0,5), no confíes en la transcripción sin una segunda pasada. - Traducir a inglés con
task="translate". Whisper puede transcribir y traducir al inglés en un solo paso — pero solo al inglés, nunca a otro idioma. Para español→inglés funciona; para inglés→español necesitas un paso de traducción aparte.
# Forzar español y no gastar en detección:
segments, info = model.transcribe("audio.wav", language="es")
# Traducir un audio en español directo a inglés (solo inglés como destino):
segments, info = model.transcribe(
"audio.wav", language="es", task="translate")
Cuidado:
task="translate"traduce únicamente al inglés, sin importar el idioma de origen. Es una limitación del modelo, no de la librería. Si alguien en tu equipo asume quetask="translate"conlanguage="en"va a darle inglés→español, va a recibir inglés→inglés (una copia) y no entender por qué. Para cualquier par que no termine en inglés, transcribe primero y traduce en un servicio aparte.
Decisión 4 — Diarización: quién habla y cuándo
Aquí está la trampa que sorprende a casi todos: Whisper no diariza. Te da el texto y los tiempos, pero no sabe cuántas personas hablan ni cuál dijo qué. Para una nota de voz de una persona da igual; para una reunión, una entrevista o una llamada de soporte es la mitad del valor. La diarización (“who spoke when”: segmentar el audio por hablante y etiquetar cada tramo con un identificador anónimo — hablante A, hablante B) es un problema distinto que se resuelve con otro modelo y se combina con Whisper.
Las dos rutas prácticas:
- pyannote.audio: la librería de referencia para diarización. Corre un pipeline que devuelve tramos
(inicio, fin, hablante). Tú te encargas de cruzar esos tramos con las palabras de Whisper por solapamiento temporal. Requiere un token de Hugging Face porque el modelopyannote/speaker-diarization-3.1está tras aceptación de licencia. - WhisperX: envuelve todo el flujo — transcribe con faster-whisper, realinea los timestamps a nivel de palabra con un modelo de alineación fonética (más precisos que los de Whisper), y llama a pyannote por debajo. Su función
assign_word_speakerses el puente que le pega a cada palabra su etiqueta de hablante. Si quieres “quién dijo qué” con la menor cantidad de pegamento propio, WhisperX es el atajo.
import whisperx
device = "cuda"
audio = whisperx.load_audio("entrevista.wav")
# 1. Transcribir (usa faster-whisper por debajo)
model = whisperx.load_model("large-v3-turbo", device, compute_type="float16")
result = model.transcribe(audio, language="es")
# 2. Realinear a nivel de palabra con un modelo fonético del idioma
model_a, meta = whisperx.load_align_model(language_code="es", device=device)
result = whisperx.align(result["segments"], model_a, meta, audio, device)
# 3. Diarizar (pyannote por debajo; hf_token accede al modelo con licencia)
diarize = whisperx.diarize.DiarizationPipeline(
use_auth_token="hf_...", device=device)
diarize_segments = diarize(audio)
# 4. Pegar cada palabra a su hablante
result = whisperx.assign_word_speakers(diarize_segments, result)
# ahora cada segmento trae un campo "speaker": SPEAKER_00, SPEAKER_01, ...
Ojo: la diarización etiqueta hablantes anónimos (
SPEAKER_00,SPEAKER_01), no nombres. Saber que “el hablante 1 dijo tal cosa” es diarización; saber que “el hablante 1 es Felipe” es identificación de hablante, otro problema (requiere una muestra de voz de referencia por persona). No los confundas en la especificación de tu producto: prometer nombres cuando solo tienes etiquetas anónimas es una promesa que el modelo no puede cumplir.
Decisión 5 — Local vs API
La última decisión es de arquitectura, no de código: ¿corres Whisper tú, o pagas la API de OpenAI (whisper-1, o los más nuevos gpt-4o-transcribe / gpt-4o-mini-transcribe)? No hay respuesta universal; hay trade-offs que dependen de tu volumen, tu sensibilidad a la privacidad y tu tolerancia a mantener infraestructura.
| Dimensión | Local (faster-whisper / WhisperX) | API (OpenAI) |
|---|---|---|
| Costo | GPU fija: caro con poco volumen, barato a escala | ~US$0,006/min: barato al inicio, sube lineal |
| Privacidad | El audio nunca sale de tu perímetro | El audio viaja a un tercero |
| Latencia | La controlas tú (turbo + GPU = muy baja) | Red + cola del proveedor, menos predecible |
| Diarización | Sí, vía pyannote / WhisperX | No en la API de transcripción base |
| Mantenimiento | Tú parchas drivers, CUDA, modelos | Cero: el proveedor opera todo |
| Word timestamps | Sí, y realineables con WhisperX | Limitados según endpoint |
La regla gruesa: con volumen bajo y sin restricción de datos, la API gana — no mantienes nada y pagas centavos. Con volumen alto, audio sensible, o necesidad de diarización y control de latencia, local gana. El eje de privacidad suele ser el que decide en serio: si transcribes llamadas con datos personales de clientes, mandar ese audio a un tercero es una decisión de cumplimiento, no de ingeniería — conéctalo con lo que vimos en datos, privacidad y PII. Un audio de una llamada de soporte es tan PII (Personally Identifiable Information, información que identifica a una persona) como el texto que contiene.
Las trampas reales
Estas son las cosas que no salen en el tutorial y que rompen tu servicio en cuanto ve audio del mundo real.
Alucinaciones en silencio
La más famosa. Cuando Whisper procesa un tramo sin habla — silencio, ruido de fondo, música — sus embeddings de audio quedan cerca de cero y el decoder, entrenado para siempre producir texto, “rellena” con lo que cree que debería ir ahí. El resultado clásico: repite la última frase en bucle, o inventa un “Gracias por ver el video” o un “Subtítulos por la comunidad” que jamás se dijo (residuos de su entrenamiento con subtítulos de YouTube).
Cuidado: esta no es una rareza que pasa una vez cada mil audios. Cualquier grabación real tiene pausas, y cualquier pausa larga es una invitación a alucinar. Un asistente de voz que transcribe “Gracias por ver el video” cuando el usuario simplemente dejó de hablar es un bug reproducible, no mala suerte. La mitigación no es opcional en producción.
La defensa de primera línea es VAD (Voice Activity Detection, detección de actividad de voz: un modelo liviano que marca qué tramos tienen habla y cuáles no) antes de Whisper: solo le pasas al modelo los tramos con voz. faster-whisper trae VAD integrado con vad_filter=True (usa Silero por debajo), que ya recorta buena parte del problema. La defensa de segunda línea es filtrar por las señales que el propio modelo entrega: descarta segmentos con no_speech_prob alto o avg_logprob muy bajo. El VAD como componente de arquitectura lo desarrollamos completo en VAD y turn-taking; aquí basta con activarlo.
Repeticiones y bucles
Primo hermano de la alucinación: el decoder se atasca repitiendo una palabra o frase, a veces por decenas de líneas. Ocurre en audio degradado o muy largo sin cortes. Mitigaciones: condition_on_previous_text=False (evita que arrastre su propio texto anterior como contexto envenenado), trocear el audio con VAD, y poner un tope de repetición si tu pipeline lo permite.
Formato y sample rate
Whisper trabaja internamente a 16 kHz mono (16.000 muestras por segundo, un solo canal). Si le entregas audio a 44,1 kHz estéreo, la librería lo resamplea por ti — pero si tu pipeline lo hace mal (o le pasas un formato raro), la calidad se cae sin un error visible.
Ojo: el fallo por sample rate es silencioso. No revienta con excepción: simplemente transcribe peor, y tú culpas al modelo cuando el culpable es tu preprocesamiento. Resamplea explícitamente a 16 kHz mono antes de transcribir y quítate la duda.
ffmpeg -i entrada.m4a -ar 16000 -ac 1 salida.waves el conjuro estándar.
Normalización del audio
Audio muy bajo o saturado degrada el WER. Una normalización de volumen (llevar el pico o el nivel RMS a un rango estándar) antes de transcribir es barata y ayuda, sobre todo con grabaciones caseras dispares.
El pipeline práctico
Todas las decisiones anteriores se ordenan en un solo flujo. De izquierda a derecha, este es el camino que recorre un audio del mundo real hasta salir como transcripción con hablantes:
audio crudo (m4a, 44.1kHz, estéreo)
│
▼
┌──────────────────┐
│ resample 16 kHz │ ffmpeg -ar 16000 -ac 1
│ mono + normaliz. │
└────────┬─────────┘
▼
┌──────────────────┐ sin voz
│ VAD (Silero) │ ──────────► DESCARTAR tramo
│ ¿hay habla? │ (evita alucinación)
└────────┬─────────┘
│ con voz
▼
┌──────────────────┐
│ faster-whisper │ large-v3-turbo, fp16
│ language="es" │
└────────┬─────────┘
▼
┌──────────────────┐
│ word timestamps │ word_timestamps=True
└────────┬─────────┘
▼
┌──────────────────┐ opcional
│ diarización │ pyannote / WhisperX
│ (quién habla) │
└────────┬─────────┘
▼
┌──────────────────┐
│ transcripción │ [A] hola [B] qué tal
│ con hablantes │
└──────────────────┘
Fíjate en la rama de la izquierda del VAD: el tramo sin voz no llega nunca a Whisper. Esa flecha es la que te ahorra el “Gracias por ver el video”. Y la caja de diarización está marcada como opcional a propósito: es cómputo extra que solo pagas si tu producto necesita distinguir hablantes.
Errores comunes
- Usar
openai-whisperen producción “porque es el oficial”. Es la implementación de referencia, no la de producción. faster-whisper te da los mismos pesos varias veces más rápido y con la mitad de VRAM. No hay razón para dejar rendimiento en la mesa. - Elegir
large-v3por reflejo. Casi siemprelarge-v3-turbote da una calidad indistinguible a una fracción del costo, ysmallalcanza de sobra para audio limpio. El modelo correcto es el más chico que cumple, no el más grande que cabe. - No activar VAD y culpar al modelo por las alucinaciones. El “Gracias por ver el video” en un silencio no es un misterio: es Whisper rellenando.
vad_filter=Truees la primera línea; filtrar porno_speech_probes la segunda. - Confiar en la detección automática de idioma con audio ruidoso. Si sabes el idioma, fuérzalo: más rápido y más robusto. Si no lo sabes, al menos mira
language_probabilityantes de creerle a la transcripción. - Esperar que Whisper diarice. No lo hace. “Quién habla” es otro modelo (pyannote) y otro paso. Presupuestar diarización como si viniera gratis con la transcripción es planificar un incidente de alcance.
- Prometer nombres de hablante cuando solo tienes etiquetas anónimas. Diarización te da
SPEAKER_00; ponerle “Felipe” es identificación de hablante, un problema aparte que necesita muestras de voz de referencia. - Ignorar el sample rate. El fallo es silencioso: transcribe peor sin lanzar error. Resamplea a 16 kHz mono explícitamente y descarta esa variable.
- Medir el tiempo alrededor de
transcribe()en faster-whisper. Es perezoso: el trabajo ocurre al iterarsegments. Si no lo sabes, vas a reportar una latencia falsa de milisegundos.
La frase para llevarse
“Whisper no falla por el modelo, falla por lo que le pones alrededor. El texto correcto sale cuando resampleas a 16 kHz, cortas el silencio con VAD antes de transcribir, y aceptas que ‘quién habla’ es un segundo modelo, no un parámetro.”
Qué viene
Módulo 4: síntesis de voz (TTS). Llevamos tres módulos convirtiendo voz en texto; ahora invertimos la flecha. Vamos a ver cómo un sistema de TTS (Text-To-Speech, texto a voz) genera audio que suene humano: de los modelos que producen un espectrograma y un vocoder que lo vuelve onda, hasta la clonación de voz y el streaming de audio a medida que se genera, para que el asistente empiece a hablar antes de terminar la frase. Con Whisper como oído y TTS como voz, tendrás las dos mitades del bucle conversacional que uniremos más adelante.
Material relacionado
Preguntas sobre este curso
Regístrate gratis para hacer preguntas y guardar tu progreso.
✉ Entérate de lo nuevo
Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.