Un asistente que me escucha en las reuniones, y lo difícil que fue que oyera bien
Quería un asistente que siguiera mis llamadas en directo: que transcribiera, tomara notas, me resumiera lo hablado y me avisara en el momento de los errores y las incongruencias, tanto de la transcripción como de lo que se dice. La parte difícil resultó ser oír bien. Así investigué qué motor de Whisper usar en un Core Ultra, con CPU, iGPU y NPU, midiendo veinte configuraciones contra la letra de dos canciones y probándolo con mi micro.
Introducción
En muchas reuniones me pasa algo parecido: alguien menciona un producto que no conozco, otro da una cifra que no cuadra con lo que se dijo hace un rato, me hacen una pregunta justo cuando estoy tomando notas y, al final, cuesta reconstruir quién se quedó con qué tarea.
Así que me propuse montar un asistente que escuchara mis llamadas conmigo. No quería solo una transcripción para leer después. Quería algo que, mientras la reunión sigue, me avisara de cosas útiles:
- Nombres de productos, empresas o siglas mal dichos o mal transcritos.
- Datos que no cuadran con lo que se ha dicho antes.
- Preguntas que me hacen a mí, con una respuesta sugerida.
- Tareas y compromisos que me tocan.
Y que al terminar me dejara un resumen con las decisiones y las tareas.
La parte del asistente resultó la más sencilla. Lo que me costó fue lo de abajo: que la transcripción fuera lo bastante buena y lo bastante rápida para que el asistente tuviera algo útil que leer. Este post cuenta esa investigación: cómo está montado todo, qué opciones probé para transcribir en un portátil con CPU, gráfica integrada y NPU, cómo medí cuál oía mejor y qué problemas aparecieron al probarlo con voces de verdad.
Cómo está montado
El montaje tiene cuatro piezas, y solo la última es el asistente:
- Captura. Un script graba por separado dos fuentes con
parec: lo que suena por la salida de audio, que etiqueta comoELLOS, y mi micro, que etiqueta comoYO. Las dos siguen al dispositivo por defecto, así que valen igual con cascos que con altavoces. - Troceo. El audio se corta en frases por energía: a los 700 ms de pausa o, pasados 15 segundos, en el primer hueco entre palabras.
- Transcripción. Cada frase pasa por Whisper en el propio portátil y acaba como una línea en un fichero de texto, con la hora y quién habla.
- El asistente. Lee ese fichero en bloques de 20 segundos y comenta en una a tres líneas, solo si aporta algo. Si alguien me nombra o me pide algo que tengo que responder ya, me manda una notificación por si no estoy mirando la pantalla. Al terminar, saca del fichero el resumen con decisiones y tareas.
El audio no sale del portátil. Se transcribe allí mismo y no se guarda; lo único que lee el asistente es el texto.
Cuando funciona, se nota. En una charla de IBM, la transcripción escribió «Sifer Payments» y el asistente me aclaró al momento que era IBM Safer Payments. En la misma charla, «Datastack» resultó ser DataStax. Son detalles, pero son los que te permiten seguir la conversación sin quedarte buscando en el navegador.
El cuello de botella: lo que no se oye bien no se puede corregir
El asistente solo puede corregir lo que le llega. Si la transcripción confunde un nombre de producto con otra palabra, a veces lo deduce, pero si se come la frase entera, no hay nada que deducir. Y ahí estaba mi problema.
Para llegar a tiempo real en la CPU usaba small con faster-whisper, y small destroza los nombres propios en cuanto el audio no es perfecto. El modelo bueno, large-v3-turbo, tarda unos 12 segundos por llamada en la CPU: en una conversación se queda atrás enseguida.
Hay un detalle de Whisper que lo complica: el encoder procesa siempre una ventana de 30 segundos, dure lo que dure la frase. Una frase de 3 segundos cuesta casi lo mismo que una de 30. En un transcriptor en vivo, que manda frases cortas una detrás de otra, ese coste fijo por llamada decide si llegas o no.
Pero mi portátil no tiene solo una CPU:
| Chip | Qué es | Cómo se usa desde Linux |
|---|---|---|
| CPU | Intel Core Ultra 7 155H, 16 núcleos (22 hilos) | faster-whisper, whisper.cpp, OpenVINO |
| iGPU Arc | Gráfica integrada Xe-LPG, sin unidades de matrices | Vulkan (whisper.cpp) u OpenCL (OpenVINO) |
| NPU | Intel AI Boost, arquitectura 3720 | OpenVINO con el driver Level Zero de la NPU |
La pregunta de la investigación era sencilla: ¿puedo usar large-v3-turbo en la gráfica o en la NPU, al ritmo de una conversación?
Las opciones que probé y cómo montarlas
Probé tres caminos. Todos funcionan en Fedora 44 sin instalar nada en el sistema base: lo hice dentro de un toolbox, un contenedor que comparte tu usuario, tu home y los dispositivos del host, incluidas la GPU y la NPU. Instalar paquetes con dnf en el host también invalida cualquier actualización offline que tengas preparada, así que me venía bien.
1. faster-whisper en la CPU
Es lo que ya tenía: small cuantizado a int8, con 12 hilos. Se instala con pip y no necesita nada más. Me sirvió de referencia para todo lo demás.
2. whisper.cpp con Vulkan en la iGPU
Fedora empaqueta whisper-cpp, pero solo trae la librería, sin whisper-cli ni whisper-server, y arrastra 215 dependencias. Así que lo compilé:
1
2
3
4
5
6
7
8
9
10
toolbox create -y whisper
toolbox run -c whisper sudo dnf install -y cmake gcc-c++ git vulkan-headers \
vulkan-loader-devel glslc mesa-vulkan-drivers vulkan-tools spirv-headers-devel
toolbox run -c whisper vulkaninfo --summary | grep deviceName
git clone --depth 1 --branch v1.9.5 https://github.com/ggml-org/whisper.cpp
cd whisper.cpp
toolbox run -c whisper cmake -B build -DGGML_VULKAN=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release
toolbox run -c whisper cmake --build build -j 16
sh models/download-ggml-model.sh large-v3-turbo-q5_0
vulkaninfo tiene que mostrar Intel(R) Arc(tm) Graphics (MTL). Al compilarlo estático, el binario solo enlaza libvulkan, libgomp y libstdc++, así que corre en el host sin el contenedor. Para medir uso whisper-server, que carga el modelo una vez y atiende peticiones HTTP, como haría el transcriptor.
El toolbox monta tu home, pero no
/srv. Si clonas allí, dentro del contenedor la ruta es/run/host/srv/.... Y sinspirv-headers-devel, cmake falla enfind_package(SPIRV-Headers).
3. OpenVINO en la iGPU y en la NPU
OpenVINO es el runtime de inferencia de Intel, y su librería GenAI trae un pipeline de Whisper que funciona en la CPU, en la GPU y en la NPU. El kernel de Fedora ya traía lo necesario para la NPU (el dispositivo /dev/accel/accel0, el módulo intel_vpu y el firmware). Faltaba la parte de usuario:
1
toolbox run -c whisper sudo dnf install -y intel-npu-driver oneapi-level-zero intel-opencl tbb
intel-opencl es el runtime de cómputo de la iGPU e intel-npu-driver, el driver Level Zero de la NPU. Con openvino-genai instalado con pip en un entorno de Python 3.12, aparecen los tres dispositivos:
1
2
import openvino as ov
print(ov.Core().available_devices) # ['CPU', 'GPU', 'NPU']
La NPU tenía truco. No ejecuta el modelo tal cual: un compilador lo traduce antes a su formato, y sin él la carga falla con ZE_RESULT_ERROR_UNSUPPORTED_FEATURE. El OpenVINO de pip no lo trae, y el paquete de Fedora, intel-npu-compiler, depende del OpenVINO de Fedora y arrastra 657 paquetes y 4 GiB, texlive incluido. Así que saqué solo la librería del paquete de Ubuntu que Intel publica en la release del driver, de la misma versión que el de Fedora:
1
2
3
4
curl -sLO https://github.com/intel/linux-npu-driver/releases/download/v1.32.0/linux-npu-driver-v1.32.0.20260402-23905121947-ubuntu2404.tar.gz
tar xzf linux-npu-driver-*.tar.gz
ar x intel-driver-compiler-npu_*.deb && tar xf data.tar.gz
export LD_LIBRARY_PATH=$PWD/usr/lib/x86_64-linux-gnu
libnpu_driver_compiler.so solo depende de libtbb.so.12, que es el tbb del dnf install de antes.
Los modelos no hace falta convertirlos: la organización OpenVINO los publica en Hugging Face ya exportados, por ejemplo OpenVINO/whisper-large-v3-turbo-fp16-ov. Transcribir es esto:
1
2
3
4
import openvino_genai as ov_genai
pipe = ov_genai.WhisperPipeline("whisper-large-v3-turbo-fp16-ov", "GPU",
CACHE_DIR="/home/tu-usuario/.cache/openvino")
texto = pipe.generate(audio, language="<|es|>", task="transcribe", return_timestamps=True)
La primera carga de turbo en la NPU tarda casi 5 minutos, porque compila el modelo (43 segundos en el caso de
small). ConCACHE_DIR, las siguientes bajan a unos segundos. No pongas esa caché en/tmpsi es tmpfs, como en Fedora: vive en RAM, y la mía llegó a ocupar 7 GB.
Cómo medir sin una transcripción de referencia
Con voz sintética de espeak-ng todos los motores aciertan, así que no sirve para comparar. Me hacía falta audio real con un texto exacto de referencia, y lo encontré en algo que tenía a mano: canciones con la letra sincronizada.
LRCLIB es una base de datos abierta de letras con marcas de tiempo, que se consulta por artista, título, álbum y duración. Spotify, en Linux, expone el reproductor por MPRIS en D-Bus, así que un script puede leer qué canción hay puesta, bajar su letra, empezar a grabar lo que suena y darle a Play:
1
2
3
4
5
6
curl -s -G 'https://lrclib.net/api/get' \
--data-urlencode 'artist_name=Dolly Parton' --data-urlencode 'track_name=Jolene' \
--data-urlencode 'album_name=Jolene' --data-urlencode 'duration=163' -o letra.json
parec -d @DEFAULT_MONITOR@ --format=s16le --rate=16000 --channels=1 > cancion.raw &
busctl --user call org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 \
org.mpris.MediaPlayer2.Player Play
@DEFAULT_MONITOR@ es lo que suena por la salida de audio, lo mismo que captura el transcriptor en una llamada. Con las marcas de la letra recorto el audio desde el primer verso completo, y después lo troceo con la misma lógica de energía que el transcriptor. Así todas las configuraciones se comparan con el mismo audio, y repetí la grabación otro día para ver cuánto cambian las cifras.
Elegí dos canciones que son casi opuestas:
- Jolene, de Dolly Parton: inglés, guitarra acústica y la voz muy por delante. 154 segundos y 203 palabras.
- Manifesting, de shego: castellano, indie con guitarras distorsionadas. 149 segundos y 225 palabras. Esta letra la contrasté a mano con otra copia antes de fiarme de LRCLIB.
La métrica es el WER (word error rate): palabras cambiadas, omitidas o inventadas, divididas entre las palabras de la letra. Como un mismo WER puede esconder cosas muy distintas, también conté por separado las omitidas y las inventadas. Para el asistente no es lo mismo una frase que falta que una frase que no existe.
Mi primera comparación fue injusta y daba un ganador falso: whisper.cpp tenía la detección de idioma activada, que añade una pasada del encoder, y reintentaba con más temperatura al dudar, mientras que faster-whisper tenía un VAD que se saltaba los tramos de música. Antes de comparar motores, iguala idioma, reintentos y VAD. Aquí todos van con el idioma fijo, decodificación voraz y sin reintentos, salvo donde la tabla dice otra cosa.
Los resultados
Veinte configuraciones y dos canciones. Muchas configuraciones las medí más de una vez, con grabaciones distintas, y cuando las medidas no coinciden la tabla da el rango. En las columnas de omitidas e inventadas, la primera cifra es de Jolene y la segunda de Manifesting:
| Motor | Chip | Opciones | WER Jolene | WER Manifesting | Omitidas | Inventadas |
|---|---|---|---|---|---|---|
faster-whisper small int8 | CPU | silero (lo que usaba) | 43,8 a 45,8 % | 84,9 a 92,0 % | 80 / 195 | 1 / 0 |
faster-whisper small int8 | CPU | sin VAD | 5,4 a 31,0 % | 73,3 a 77,3 % | 2 / 122 | 0 / 0 |
whisper.cpp small | iGPU (Vulkan) | 6,9 a 8,9 % | 61,3 a 69,8 % | 2 / 72 | 1 / 4 | |
| whisper.cpp turbo q5_0 | iGPU (Vulkan) | 3,0 a 3,9 % | 64,4 % | 0 / 95 | 1 / 0 | |
| whisper.cpp turbo q8_0 | iGPU (Vulkan) | 3,0 a 3,4 % | 61,3 a 62,2 % | 0 / 86 | 1 / 2 | |
| whisper.cpp turbo q8_0 | iGPU (Vulkan) | beam 5 | 3,9 a 5,4 % | 60,4 % | 1 / 90 | 2 / 1 |
| whisper.cpp turbo q5_0 | iGPU (Vulkan) | silero 0,5 | 49,8 % | 96,9 % | 88 / 215 | 0 / 0 |
| whisper.cpp turbo q5_0 | iGPU (Vulkan) | silero 0,35 | 40,9 a 43,8 % | 94,7 % | 71 / 206 | 0 / 0 |
| whisper.cpp turbo q5_0 | iGPU (Vulkan) | silero 0,2 | 32,0 a 33,0 % | 88,9 % | 55 / 187 | 0 / 0 |
| OpenVINO turbo fp16 | iGPU | sin timestamps | 5,4 a 6,4 % | 62,2 % | 5 / 90 | 1 / 2 |
| OpenVINO turbo fp16 | iGPU | timestamps | 3,0 a 3,4 % | 62,2 % | 0 / 89 | 1 / 2 |
| OpenVINO turbo int8 | iGPU | timestamps | 3,0 a 3,4 % | 61,3 a 62,7 % | 0 / 91 | 1 / 2 |
OpenVINO small fp16 | iGPU | sin timestamps | 6,4 a 12,8 % | 71,1 % | 3 / 95 | 1 / 0 |
OpenVINO small fp16 | iGPU | timestamps | 58,1 a 60,1 % | 71,6 % | 2 / 92 | 94 / 1 |
| OpenVINO turbo int8 | NPU | sin timestamps | 5,4 a 6,4 % | 62,2 % | 5 / 89 | 1 / 2 |
| OpenVINO turbo int8 | NPU | timestamps | 3,0 a 3,9 % | 60,9 % | 0 / 91 | 1 / 1 |
| OpenVINO turbo fp16 | NPU | timestamps | 3,0 % | 61,3 % | 0 / 87 | 1 / 1 |
OpenVINO small fp16 | NPU | sin timestamps | 6,4 a 56,7 % | 71,1 % | 3 / 101 | 1 / 0 |
| OpenVINO turbo fp16 | iGPU | timestamps y glosario | 100,0 % | 293,3 % | 1 / 7 | 198 / 471 |
| OpenVINO turbo fp16 | iGPU | timestamps, glosario, silero y freno | 43,8 % | 91,1 % | 81 / 194 | 0 / 0 |
Y la velocidad. Como el portátil estaba haciendo otras cosas a la vez, para que los tiempos se puedan comparar pasé las cuatro configuraciones clave dos veces seguidas e intercaladas, sobre Jolene:
| Configuración | Chip | Total, pasada 1 | Total, pasada 2 |
|---|---|---|---|
| OpenVINO turbo fp16 con timestamps | iGPU | 30,4 s | 29,1 s |
| OpenVINO turbo int8 con timestamps | NPU | 37,3 s | 34,6 s |
| whisper.cpp turbo q8_0 | iGPU (Vulkan) | 47,2 s | 50,0 s |
faster-whisper small con silero | CPU | 18,2 s | 19,8 s |
Son 154 segundos de canción en 10 trozos, así que cualquiera de los cuatro va holgado. Lo que importa en directo es el tiempo de cada llamada, y con turbo en la iGPU no pasó de unos 5 segundos en ningún trozo.
Cuánto fiarse de las cifras
Antes de sacar conclusiones conviene saber cuánto ruido tienen. Con unas 200 palabras por canción, una sola palabra mueve medio punto de WER, así que un 3,0 % y un 3,4 % son lo mismo. Además, dos grabaciones de la misma canción no se trocean exactamente igual, y algunos motores no dan siempre el mismo resultado con el mismo audio. Por eso no saco nada de diferencias de uno o dos puntos, y me fijo en lo que se repite en todas las medidas.
Lo que me dicen las tablas
Turbo empata en calidad en cualquier chip. Con Jolene, todas las variantes de turbo bien configuradas se quedan entre un 3 y un 4 %: whisper.cpp, OpenVINO en la iGPU y OpenVINO en la NPU. La diferencia está en la velocidad, y ahí gana OpenVINO en la iGPU: tarda un 40 % menos que whisper.cpp en la misma gráfica.
En OpenVINO hay que pedir los timestamps. Sin ellos se come palabras y turbo pasa de un 3 % a entre un 5,4 y un 6,4 % en cualquier chip. No he encontrado documentado por qué, pero se repite siempre. Con small pasa lo contrario: con timestamps se pone a inventar texto (94 palabras de más en Jolene).
La beam search no compensa. Con turbo en whisper.cpp, beam 5 empeora en Jolene (entre un 3,9 y un 5,4 %) y mejora poco en Manifesting (60,4 % frente a 62,2 %), y tarda más en las dos.
El VAD se come la voz cantada. Silero, el VAD que trae faster-whisper, está entrenado para separar habla de todo lo demás, y la voz cantada con instrumentos le cuenta como música. Con cualquier umbral tira media canción, o la canción casi entera en el caso de Manifesting. Esto explica por qué lo que usaba sale tan mal aquí, y no debería pasar con voz hablada. Lo dejé apuntado para la prueba con el micro.
El glosario, sin protección, es peligroso. Al transcriptor le paso un initial_prompt con los nombres y siglas que más se oyen en mis reuniones, que es lo que hace que salgan bien escritos: sin él, small escribe «cubernetes» donde se dijo Kubernetes. Pero sobre música, turbo con glosario entra en bucle y repite las siglas una y otra vez: 198 palabras inventadas en Jolene y 471 en Manifesting.
La NPU funciona, pero no aporta. Iguala a la iGPU en calidad con turbo y, según lo ocupado que estuviera el portátil, fue entre un 20 % y el doble de lenta. Con small no es fiable: la misma configuración dio desde un 6,4 % hasta un 56,7 %. Además, turbo fp16 en la NPU falló al primer intento con ZE_RESULT_ERROR_UNKNOWN en las dos canciones, y funcionó al repetirlo. Su ventaja sería el consumo y dejar libre la gráfica, que en una videollamada no es poco, pero para esto no la necesito.
faster-whisper sin VAD es inestable. En limpio da un 5,4 % con Jolene, pero en otra pasada con la misma grabación dio un 30,5 %: en un tramo entra en bucle y se inventa decenas de palabras. Que una configuración dé resultados distintos con el mismo audio es justo lo que no quiero debajo de un asistente.
Por qué Manifesting sale tan mal
Con Manifesting, ningún motor baja del 60 %. Lo primero que pensé es que era por el castellano, y lo segundo, que las guitarras de los estribillos tapaban la voz. Para salir de dudas transcribí cada verso por separado, cortado con las marcas de tiempo de la letra, y lo comparé con lo que acertaba la pasada normal de turbo en la iGPU:
| Tramo | Nivel de audio (RMS) | Pasada normal | Cada verso aislado |
|---|---|---|---|
| Estrofas | 1.626 | 29 % | 72 % |
| Estribillo, primeras veces | 1.921 | 45 % | 75 % |
| Estribillo, a mitad de canción | 1.963 | 55 % | 55 % |
| Estribillo, al final | 2.097 | 42 % | 44 % |
Ni una cosa ni la otra explican lo principal. Lo que más se pierde son las estrofas, que son el tramo más tranquilo, y aislando cada verso sale el 72 %: el audio se entiende y el castellano no es el problema. Lo que falla es cómo le llega a Whisper. El canto continuo no tiene pausas, así que el troceo manda trozos de hasta 15 segundos, y con esos trozos turbo transcribe una parte y se salta el resto.
Las guitarras sí pesan al final. En los estribillos más altos ni los versos aislados pasan del 44 %, y hay un verso concreto que no sale ni solo.
En los tramos instrumentales apareció además un clásico de Whisper en castellano: «¡Suscríbete!» y «Gracias por ver el video», que se suele atribuir a los subtítulos de vídeos que hay entre sus datos de entrenamiento.
Para una reunión, la conclusión es tranquilizadora: al hablar hay pausas, así que los trozos son cortos, y no hay guitarras distorsionadas encima de la voz. Lo que sí me llevo es cómo reacciona cada motor cuando no entiende: turbo prefiere callarse antes que inventar, y para un asistente que tiene que fiarse de lo que lee, eso es lo que quiero.
De las canciones al micro
Las canciones sirven para ordenar los motores, pero lo que decide es la voz hablada. Puse el transcriptor con OpenVINO en la iGPU a escuchar solo el micro del portátil mientras hablaba con una compañera, y el asistente siguiéndolo.
Con voz cercana el resultado fue muy bueno: frases enteras, bien puntuadas y con los nombres propios correctos, salvo algún fallo suelto. El asistente señaló uno al vuelo: «Contracción», que por el contexto tenía que ser Contratación. Pero aparecieron tres problemas.
La voz lejana no llega. El script solo envía lo que supera un umbral de energía, calibrado con el ruido de los primeros segundos (595 en mi micro). La voz de mi compañera, a un par de metros, llegaba con picos de 320 a 510, al nivel del ruido de fondo. Bajar el umbral la recogería, pero también el ruido, y preferí dejarlo como estaba: en una llamada cada uno habla en su micro.
Los fragmentos flojos se convierten en frases inventadas. Un sonido corto y flojo pasó el umbral y salió como «Just take a look a lot», en inglés, aunque el idioma estaba fijado en castellano.
El glosario entra en bucle también con voz. Sobre otro fragmento flojo, turbo devolvió una de las siglas del glosario repetida decenas de veces seguidas. Es lo mismo que había visto con las canciones.
Las dos últimas son justo lo que silero evitaba en la versión con faster-whisper, y OpenVINO GenAI no trae VAD. Así que lo puse delante del modelo y añadí un freno contra los bucles:
1
2
3
4
5
6
7
8
9
10
11
from faster_whisper.vad import collect_chunks, get_speech_timestamps
voz = get_speech_timestamps(audio) # silero: tramos con voz
if voz:
audio = np.concatenate(collect_chunks(audio, voz)[0])
texto = str(pipe.generate(audio.tolist(), language="<|es|>", task="transcribe",
return_timestamps=True, initial_prompt=glosario,
max_new_tokens=int(len(audio) / 16000 * 6) + 10)).strip()
# Un texto que comprime más de 2,4 veces se está repitiendo: fuera.
if len(texto.encode()) / len(zlib.compress(texto.encode())) > 2.4:
texto = ""
El tope de tokens corta un bucle por la longitud del fragmento, y la razón de compresión es el mismo criterio que usa Whisper para detectar repeticiones. Es la última fila de la tabla: con las canciones sale mal porque silero tira la voz cantada, pero no inventa ni una palabra. Con voz hablada, en la siguiente tanda de conversación no salió ninguna frase inventada ni se perdió ninguna real.
Antes de culpar al modelo de perder frases, comprueba que la captura no se detiene mientras transcribe. Si la llamada al modelo no suelta el GIL de Python, el hilo que lee el micro se queda parado y el audio se pierde. OpenVINO lo suelta; lo comprobé con un hilo contador durante
generate.
Cómo quedó
El transcriptor usa ahora turbo fp16 en la iGPU con OpenVINO, con timestamps, silero delante y el freno de bucles. Como el runtime OpenCL de la gráfica solo está en el toolbox, el script se relanza dentro él mismo:
1
2
if args.motor == "arc" and not os.path.exists("/run/.toolboxenv"):
os.execvp("toolbox", ["toolbox", "run", "-c", "whisper", sys.executable, __file__, *sys.argv[1:]])
/run/.toolboxenv solo existe dentro de un toolbox, así que no entra en bucle. faster-whisper en la CPU se queda como segundo motor, por si la gráfica no está disponible. Una frase de 7 segundos aparece en el fichero unos 4,5 segundos después de terminar, y el asistente la lee en el siguiente bloque.
Al parar el transcriptor desde fuera, matar el
toolbox runno basta: el Python sigue vivo dentro del contenedor. Y cuidado conpkill -f: si el patrón aparece en el propio comando, mata también la shell que lo lanza. Anclar el patrón al principio de la línea de comando (^/ruta/al/python) lo evita.
Lo que me llevo
La conclusión técnica es clara: en un portátil con gráfica Intel, turbo con OpenVINO en la iGPU da la calidad del modelo grande al ritmo de una conversación y deja la CPU casi libre para la videollamada. whisper.cpp con Vulkan empata en calidad, pero tarda un 60 % más, y la NPU funciona, aunque para esto no compensa.
Pero lo que más me ha enseñado la investigación es que el motor no basta. Un asistente que comenta en directo necesita una transcripción que no invente: una frase que falta solo deja un hueco, pero una frase inventada le hace comentar algo que nadie ha dicho. Por eso silero, que con las canciones parecía el villano, se queda, y por eso el glosario solo va con el freno puesto.
Me quedan cosas por resolver. El transcriptor no distingue entre los demás participantes: todo lo que suena por los altavoces es ELLOS. Y una voz lejana del micro se pierde. Pero el método me sirve para lo que venga: grabar una canción con su letra sincronizada da un texto de referencia exacto en minutos, y separar aciertos, omisiones e inventos explica mucho más que el WER solo.