← Todo el material
Curso Intermedio 24 de agosto de 2026

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

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.

ModeloParámetrosVRAM aprox. (fp16)Velocidad relativa¿Cuándo?
tiny39 M~1 GB~10×Prototipo, palabras clave, edge
base74 M~1 GB~7×Audio limpio en inglés, tiempo real ajustado
small244 M~2 GB~4×Buen punto medio multiidioma
medium769 M~5 GB~2×Calidad seria sin GPU tope
large-v31.550 M~10 GB1×Máxima calidad, idiomas difíciles
large-v3-turbo809 M~6 GB~5–8× vs largeCasi 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, del beam_size y 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:

  1. transcribe() es perezoso. Devuelve un generador; el cómputo pesado no ocurre hasta que recorres segments. Si mides el tiempo alrededor de la llamada y no de la iteración, vas a creer que Whisper es instantáneo.
  2. word_timestamps=True cuesta. 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.
  3. info trae el idioma detectado y su probabilidad, disponibles antes de iterar los segmentos. Ese language_probability es 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 que task="translate" con language="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 modelo pyannote/speaker-diarization-3.1 está 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_speakers es 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ónLocal (faster-whisper / WhisperX)API (OpenAI)
CostoGPU fija: caro con poco volumen, barato a escala~US$0,006/min: barato al inicio, sube lineal
PrivacidadEl audio nunca sale de tu perímetroEl audio viaja a un tercero
LatenciaLa controlas tú (turbo + GPU = muy baja)Red + cola del proveedor, menos predecible
DiarizaciónSí, vía pyannote / WhisperXNo en la API de transcripción base
MantenimientoTú parchas drivers, CUDA, modelosCero: el proveedor opera todo
Word timestampsSí, y realineables con WhisperXLimitados 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.wav es 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-whisper en 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-v3 por reflejo. Casi siempre large-v3-turbo te da una calidad indistinguible a una fracción del costo, y small alcanza 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=True es la primera línea; filtrar por no_speech_prob es 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_probability antes 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 iterar segments. 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

VozWhisperfaster-whisperDiarizaciónASR

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.