Audio + long-context (Checkpoint C3d)
dominar el STT (speech-to-text) moderno con diarización, y el long-context (qué falla, cómo evaluarlo con rigor, cómo construir RAG que de verdad recupere lo relevante). Dos dominios con ROI inmediato y trampas propias que un AI engineer completo debe conocer.
PARTE 1 — AUDIO / STT
D.1 El panorama STT 2026
| Modelo | Fuerte en | Nota |
|---|---|---|
| NVIDIA Canary-Qwen-2.5B | WER bajísimo (~5.6%), inglés | SOTA precisión |
| NVIDIA Parakeet-TDT-0.6B-v3 | velocidad brutal (RTFx >2000) | streaming/tiempo real |
| Whisper Large v3 / Turbo | multilingüe, robusto | el caballo de batalla |
| Moonshine v2 | edge / baja latencia | dispositivos |
Para diarización (quién habla cuándo): pyannote.audio. La combinación STT + diarización + alineación temporal es el pipeline de cualquier sistema de transcripción de reuniones/llamadas.
Métricas que importan:
- WER (Word Error Rate): (sustituciones+inserciones+borrados)/palabras. Más bajo mejor.
- DER (Diarization Error Rate): error de "quién habló cuándo".
- RTFx: factor de tiempo real (cuántas veces más rápido que el audio); >1 = más rápido que tiempo real.
D.2 Laboratorio D.1 — Pipeline STT + diarización (código completo)
1# lab_n3d_stt.py — transcribe + diariza un audio y mide WER/DER
2import torch
3from transformers import pipeline
4from pyannote.audio import Pipeline as DiarizationPipeline
5
6AUDIO = "reunion.wav" # audio mono 16kHz (convierte con ffmpeg si hace falta)
7
8# 1) STT con Whisper Large v3 Turbo (multilingüe, cabe de sobra en la 5090)
9asr = pipeline("automatic-speech-recognition",
10 model="openai/whisper-large-v3-turbo",
11 torch_dtype=torch.float16, device="cuda",
12 return_timestamps=True, # necesitamos tiempos para alinear con diarización
13 chunk_length_s=30) # trocea audios largos en ventanas de 30s
14asr_out = asr(AUDIO)
15
16# 2) Diarización con pyannote (requiere token HF y aceptar licencia del modelo)
17diar = DiarizationPipeline.from_pretrained(
18 "pyannote/speaker-diarization-3.1", use_auth_token=True).to(torch.device("cuda"))
19diarization = diar(AUDIO)
20
21# 3) Alinear: asignar a cada segmento ASR el hablante cuyo intervalo solapa más
22def speaker_at(t, diarization):
23 best, best_ov = "SPK?", 0.0
24 for turn, _, spk in diarization.itertracks(yield_label=True):
25 ov = max(0, min(t[1], turn.end) - max(t[0], turn.start))
26 if ov > best_ov: best, best_ov = spk, ov
27 return best
28
29for ch in asr_out["chunks"]:
30 spk = speaker_at(ch["timestamp"], diarization)
31 print(f"[{spk}] {ch['text']}")
32
33# 4) WER vs una transcripción de referencia (si la tienes)
34# pip install jiwer ; from jiwer import wer ; print(wer(reference_text, asr_out["text"]))Líneas no triviales:
return_timestamps=True+chunk_length_s=30: Whisper procesa en ventanas; los timestamps son lo que permite alinear transcripción con diarización (sin tiempos no sabes qué texto va con qué hablante).- La función
speaker_at: asigna hablante por máximo solape temporal entre el segmento de texto y los turnos de diarización. Es el pegamento del pipeline. - pyannote requiere aceptar la licencia del modelo en HF y un token; es gratuito pero gated.
PARTE 2 — LONG-CONTEXT
D.3 Qué falla en contexto largo (y por qué)
Tener 128K–1M de ventana de contexto no significa que el modelo use bien toda esa información. Dos fenómenos que tienes que conocer:
- "Lost in the Middle" (Liu et al., TACL 2024): el rendimiento cae cuando la información relevante está en el medio del contexto; los modelos atienden mejor al principio y al final. Implicación: dónde colocas el contexto importa.
- Attention sinks / StreamingLLM (Xiao et al., ICLR 2024): los primeros tokens actúan de "sumidero" de atención; mantenerlos permite streaming infinito sin colapso, y explica parte del comportamiento posicional.
Corolario práctico: ventana grande ≠ recuperación fiable. Por eso (a) hay que evaluar la recuperación, no asumirla, y (b) RAG sigue siendo necesario aunque quepan documentos enteros.
D.4 Evaluar long-context con rigor: RULER
RULER es el benchmark que mide la longitud de contexto efectiva (no la anunciada): genera tareas sintéticas (needle-in-a-haystack, multi-hop, agregación) a distintas longitudes y mide dónde el modelo se rompe. Un modelo "128K" a menudo tiene una longitud efectiva mucho menor.
Laboratorio D.2 — needle-in-a-haystack propio:
1# lab_n3d_niah.py — mide recuperación a distintas longitudes y posiciones (mini-RULER)
2import random
3from openai import OpenAI # apunta a tu servidor vLLM local (N1)
4client = OpenAI(base_url="http://localhost:8000/v1", api_key="x")
5
6NEEDLE = "El código secreto de la bóveda es 4719."
7FILLER = "El clima en la región era templado durante el otoño. " * 50
8
9def make_haystack(n_fillers, needle_pos):
10 chunks = [FILLER] * n_fillers
11 chunks.insert(int(needle_pos * n_fillers), NEEDLE) # inserta el needle en una posición relativa
12 return "\n".join(chunks)
13
14def test(n_fillers, needle_pos):
15 ctx = make_haystack(n_fillers, needle_pos)
16 r = client.chat.completions.create(model="Qwen/Qwen3-8B", temperature=0,
17 messages=[{"role":"user","content": ctx + "\n\n¿Cuál es el código secreto de la bóveda?"}])
18 return "4719" in r.choices[0].message.content
19
20# barrido: longitud (n_fillers) × posición del needle (0=inicio, 0.5=medio, 1=final)
21for n in (10, 50, 100, 200):
22 row = {p: test(n, p) for p in (0.0, 0.25, 0.5, 0.75, 1.0)}
23 print(f"n_fillers={n:4d} {row}") # verás caer la recuperación en el MEDIO y a longitudes altasLíneas no triviales:
- El barrido longitud × posición reproduce el efecto "lost in the middle": esperarás
Trueen inicio/final y fallos crecientes en el medio a longitudes altas. Verlo en tu propio modelo es la lección. temperature=0: recuperación determinista para que el resultado sea limpio.
D.5 Laboratorio D.3 — RAG long-context evaluado (genérico)
Construye un RAG y mide su recall (no asumas que recupera bien):
1# Esquema: chunking -> embeddings -> pgvector -> retrieval -> generación, con eval de recall
2# 1) trocea documentos (chunks de ~512 tokens con solape de 64)
3# 2) embeddings con un modelo fuerte (Qwen3-Embedding-8B o BGE-M3), cabe en la 5090
4# 3) índice en pgvector (o FAISS en memoria para empezar)
5# 4) para cada query de un set de eval con respuesta conocida:
6# recupera top-k -> ¿el chunk con la respuesta está en el top-k? (recall@k)
7# genera con el contexto recuperado -> ¿la respuesta es correcta? (answer accuracy)
8# 5) compara: RAG vs meter todo el documento en contexto (long-context puro).
9# Mide cuál recupera mejor Y cuál es más barato (tokens procesados).La comparación RAG vs long-context puro, medida con recall@k y coste, es exactamente la decisión de ingeniería que demuestra maestría.
D.6 CHECKPOINT C3d — criterio de aprobado
- Pipeline STT + diarización funcionando sobre audio real, con WER y DER medidos (no estimados).
- Demostración empírica del efecto lost-in-the-middle en un modelo (barrido longitud × posición).
- Un RAG con recall@k evaluado sobre un set con respuestas conocidas, comparado contra long-context puro (recall + coste).
- Sabes explicar por qué ventana grande ≠ recuperación fiable y cuándo eliges RAG vs contexto largo.
Rúbrica: Nivel 3 si reproduces lo anterior con números; Nivel 4 si fine-tuneas un STT a un dominio (notebook Unsloth "Whisper Large V3") mejorando el WER, o construyes un eval long-context reutilizable.
D.7 Ejercicios
E1. Mide el WER de Whisper Large v3 Turbo vs Parakeet en el mismo audio. ¿Vale la pena la precisión extra frente a la velocidad para una transcripción en vivo?
E2. En el lab D.2, ¿a qué longitud tu modelo empieza a fallar en el medio? Compara con su ventana anunciada. ¿Cuál es su longitud efectiva?
E3. En el RAG, prueba chunk size 256 vs 1024. ¿Cómo afecta al recall@k y a la calidad de la respuesta?
D.8 Trampas comunes
- Asumir que un modelo "128K" recupera bien a 128K (mídelo con RULER/NIAH).
- STT sin timestamps → no puedes diarizar.
- RAG sin medir recall → no sabes si el fallo es del retriever o del generador.
- Audio no normalizado a 16kHz mono → STT degradado.
D.9 Referencias
- HF Audio Course. Modelos: NVIDIA Canary-Qwen, Parakeet-TDT, Whisper Large v3 Turbo, Moonshine; pyannote.audio. Long-context: "Lost in the Middle" (Liu 2024), StreamingLLM/attention sinks (Xiao 2024), RULER. Embeddings: MTEB leaderboard. Notebook Unsloth "Whisper Large V3".
Cierre del Nivel 3
Con los cuatro tracks a Nivel 3, tienes la breadth de un AI engineer senior completo: ves bajo PyTorch (A), abres el modelo (B), manejas la otra gran familia generativa y el control (C), y dominas audio + las trampas del long-context (D). Combinado con los dos spines (Niveles 1–2), esto es el perfil T completo.