La primera vez que lo cronometré, no me creí el número. Mantuve pulsada la tecla, dije una frase, la solté y arranqué un cronómetro en mi cabeza contra el momento en que el texto aterrizaba en el cursor. Luego hice lo mismo contra una herramienta de dictado en la nube. El viaje de ida y vuelta a la nube no era el doble de lento. En una mañana de Wi-Fi malo de cafetería era un orden de magnitud más lento, y peor aún, era impredecible: a veces 300 ms, a veces dos segundos, ocasionalmente un spinner que se resolvía en nada.

Esa varianza es toda la historia. El dictado es una superficie de UI en tiempo real, y las superficies de UI en tiempo real mueren por la latencia de cola, no por la latencia media. Así que cuando construimos Vext —nuestra app de código cerrado de voz a texto para macOS— la decisión de ejecutar todo en el dispositivo no fue ideológica sobre la privacidad. Empezó como una decisión de latencia. El argumento de la privacidad cayó de ahí gratis.

Este artículo es la matemática y la arquitectura detrás de esa decisión. Está escrito para el ingeniero que está en la misma bifurcación: tienes una funcionalidad de audio o de IA, estás mirando una API de STT en la nube con un SDK amigable, y una voz en tu cabeza te pregunta si no deberías simplemente ejecutar el modelo localmente. Quiero darte una forma de responder esa pregunta con números en lugar de con intuiciones.

Una aclaración por adelantado. Vext en sí es de código cerrado. Las herramientas de desarrollo de Muvon —Octomind, Octocode y el resto del stack de agentes— son de código abierto bajo licencias permisivas, y escribimos sobre sus interioridades libremente. Vext es la app comercial que paga el trabajo abierto, así que lo que sigue trata sobre el enfoque y los compromisos, del tipo que cualquier ingeniero podría razonar, no un recorrido guiado por nuestro árbol de código.


El Dictado Es una Carga de Trabajo Hostil a la Latencia

La mayoría de las funcionalidades de audio son indulgentes. ¿Transcribir un podcast durante la noche? Nadie mira el reloj. ¿Generar el acta de una reunión cuando termina la llamada? Unos segundos de procesamiento son invisibles. Estas son cargas por lotes, y para las cargas por lotes la nube suele ser la respuesta correcta: obtienes las GPUs de otro, el equipo de operaciones de otro, y la latencia simplemente no importa porque ningún humano espera en la ruta crítica.

El dictado es lo contrario. Un humano mira fijamente el cursor, a media idea, esperando que aparezcan palabras. El bucle de interacción es: hablar, soltar, esperar texto ahora. Cada milisegundo entre "soltar" y "aparece el texto" es aire muerto que el usuario siente en las manos. Y a diferencia de una carga de página, no hay spinner que lo haga aceptable: el modelo mental es "estoy escribiendo con mi voz", y al escribir no hay buffer.

La parte brutal es que esto es un coste por enunciado. No lo pagas una vez. Lo pagas en cada dictado, cincuenta o cien veces al día para un usuario intensivo. Una penalización de 700 ms que jamás notarías en una carga de página se convierte, a lo largo de una jornada, en la diferencia entre una herramienta que desaparece en el flujo y una herramienta contra la que peleas.

Así que el presupuesto es ajustado, y el presupuesto es lo primero que hay que razonar.


La Matemática del Viaje de Ida y Vuelta

Construyamos el presupuesto de latencia de la nube con honestidad, a partir de las piezas que no puedes evitar.

Una petición de STT en la nube tiene que hacer, como mínimo, lo siguiente antes de que vuelva una sola palabra:

  1. Capturar y codificar el audio en el dispositivo.
  2. Establecer o reutilizar una conexión: handshake TLS si está en frío, o al menos el coste de un socket mantenido vivo.
  3. Subir los bytes de audio por la red.
  4. Encolar del lado del proveedor hasta que un worker la recoja.
  5. Ejecutar la inferencia en su hardware.
  6. Serializar y descargar el resultado de vuelta hacia ti.

Aquí está la parte que la gente subestima: los pasos 2, 3, 4 y 6 son puro sobrecoste que no tiene nada que ver con lo bueno que sea el modelo. Son impuesto. Y son los pasos con peor varianza.

El tiempo de ida y vuelta de red por sí solo rara vez es tu amigo. Misma región, conexión caliente, buena red, podrías ver un RTT de decenas bajas de milisegundos. Pero "misma región" está haciendo mucho trabajo ahí. Un usuario en Bangkok golpeando un endpoint en us-east paga un par de cientos de milisegundos de RTT antes de que pase nada útil, en un solo viaje de ida y vuelta, y un handshake TLS en frío son múltiples viajes. Añade congestión, añade una red de hotel inestable, añade la profundidad de cola del proveedor en horas pico, y tu p99 se despega de tu p50 por completo.

Aquí está el presupuesto conceptual. Deliberadamente no cito números duros como si se hubieran medido en tu entorno —dependen de tu red y de tu proveedor— pero la forma es lo que importa:

Cloud STT latency = encode
                  + connection_setup        # 0 if warm, 1-3 RTT if cold
                  + upload_time             # audio_bytes / uplink_bandwidth
                  + provider_queue_wait     # the variance killer
                  + inference_time
                  + download_time
                  + deserialize

On-device STT latency = capture_to_buffer
                      + inference_time       # on local Neural Engine / GPU

Mira lo que la ruta en el dispositivo no tiene. Sin establecimiento de conexión. Sin subida. Sin cola. Sin descarga. Toda la columna de red colapsa a cero. Lo que queda es captura más inferencia, y en Apple Silicon la inferencia es lo bastante rápida como para que el término que domina un presupuesto en la nube —la red— simplemente no esté en la ecuación.

Por eso la comparación no es "la nube es un poco más lenta". Para una carga de dictado interactiva es estructuralmente diferente. El presupuesto de la nube está dominado por términos de alta varianza que no controlas. El presupuesto en el dispositivo está dominado por un término que sí controlas, ejecutándose en hardware que puedes medir.

Y el modelo en sí no es ningún flojo. El modelo de voz que usamos por defecto —Parakeet, de NVIDIA, de la familia NeMo— corre mucho más rápido que en tiempo real en chips serie M, en el orden de dos órdenes de magnitud más rápido que el tiempo real cuando se mapea sobre el Neural Engine. Cuando la transcripción es tanto más rápida que el habla que la produjo, el término de inferencia en tu presupuesto se vuelve pequeño en relación con el audio que ya tuviste que esperar a que se pronunciara. El cuello de botella deja de ser el modelo y pasa a ser física: no puedes transcribir una frase antes de que la persona termine de decirla.


La Privacidad Fue un Efecto Secundario, Luego Se Convirtió en el Objetivo

Elegimos el dispositivo por la latencia. Luego nos dimos cuenta de lo que habíamos comprado por accidente.

Si el audio nunca sale de la máquina, no hay audio que filtrar, citar judicialmente, vulnerar, retener o usar para entrenar. Esto no es una política de privacidad —una política es una promesa de que un servidor podría estar haciendo otra cosa—. Es una arquitectura de privacidad: los datos físicamente no tienen ningún otro lugar a donde ir. No hay un cubo de tus grabaciones de voz en la infraestructura de alguien porque la grabación se procesó y descartó a unos centímetros del micrófono.

Para una herramienta de dictado esto importa más de lo que parece al principio, por lo que la gente dicta. Observa tu propio uso durante un día. Dictas contraseñas que estás reseteando. Dictas la arquitectura de sistemas bajo NDA. Dictas el contenido de un mensaje a un abogado, a un médico, a un cofundador en medio de una adquisición. El micrófono en la máquina de un desarrollador apunta a algo del material en bruto más sensible de su vida, y la postura por defecto de la mayoría de las herramientas de voz es transmitirlo todo a un tercero para procesarlo.

Hay también una dimensión de cumplimiento, y no es abstracta. "El audio nunca sale del dispositivo" es una frase que cierra muchos tickets de revisión de seguridad. Ningún acuerdo de procesamiento de datos que negociar, ninguna lista de subprocesadores que auditar, ninguna pregunta de transferencia transfronteriza de datos, ninguna superficie de notificación de brechas para los datos de voz porque no hay datos de voz en ningún servidor que vulnerar. Para un equipo que intenta llevar una herramienta a un entorno regulado, "corre localmente" a menudo es la diferencia entre aprobado e indefinidamente-en-revisión.

La versión honesta: no habríamos hecho de la privacidad el titular si la matemática de la latencia no hubiera apuntado en la misma dirección. Pero lo hizo, y una vez que decides que el audio se queda en casa por razones de rendimiento, negarte a enviar un fallback a la nube que deshaga eso silenciosamente es la decisión fácil y correcta.


Dos Modelos, Un Pipeline, Ambos Locales

El speech-to-text te da palabras. Las palabras no son lo que el usuario quiere.

Lo que dijo fue "eh o sea básicamente necesitamos como, mover la la lógica de reintento al al worker, ¿sabes?, el que eh maneja los webhooks". Lo que quiere que se pegue es "Mueve la lógica de reintento al worker que maneja los webhooks". Quitar la muletilla, arreglar los arranques en falso y dar forma al resultado es un trabajo distinto de la transcripción, y es un trabajo de lenguaje, no de habla. Así que el pipeline tiene dos etapas, y la decisión arquitectónica interesante fue mantener ambas en el dispositivo.

Conceptualmente es un relevo:

   audio ──▶ [ speech model ]  ──▶  raw transcript
                (Parakeet)            "uh so basically we need to..."
                                            │
                                            ▼
                              [ small local LLM ]  ──▶  clean text
                                  (Gemma 3 4B, MLX)    "Move the retry logic..."
                                            │
                              ┌─────────────┼─────────────┐
                              ▼             ▼             ▼
                          cleanup      translation   summary

La etapa uno es un modelo de voz especializado: pequeño, rápido, de propósito único, optimizado para una sola cosa. La etapa dos es un LLM pequeño de propósito general —usamos Gemma 3 4B por defecto (Qwen 3, LLaMA 3.2 y Phi-3.5 también son seleccionables), ejecutándose a través del framework MLX de Apple en la GPU— haciendo la limpieza lingüística, y el mismo modelo, con una instrucción distinta, maneja la traducción en vivo y el resumen de reuniones. Un modelo, tres trabajos, intercambiados por prompt en lugar de por enviar tres binarios distintos.

La razón de que esta división en dos modelos sea la forma correcta: cada etapa puede ser buena en su trabajo. No quieres que tu modelo de voz intente también reescribir prosa, y no quieres que un LLM general intente hacer modelado acústico. Especializado-luego-general es una separación limpia, y mantiene cada modelo lo bastante pequeño como para caber en memoria y correr rápido. Un modelo gigante de extremo a extremo que hiciera ambas cosas sería más lento, más hambriento y más difícil de razonar, y perderías la capacidad de intercambiar el modelo de limpieza independientemente del reconocedor.

Mantener la etapa dos local es la parte que uno se siente tentado a saltarse. El modelo de voz tiene que ser local por la latencia; ¿seguro que la limpieza puede ser una llamada rápida a una API de un gran modelo en la nube? Puede, y entonces has tirado toda la propiedad de privacidad para la etapa que maneja la versión más legible de las palabras del usuario. La transcripción en bruto es desordenada; el texto limpio es el pensamiento claro, citable, completamente formado. Eso es exactamente lo que menos quieres enviar a un servidor. Si la etapa dos va a la nube, que la etapa uno sea local no te compró nada. Así que ambas se quedan en casa.


Dónde el Procesamiento en el Dispositivo Empieza a Doler

Estaría mintiendo si te lo vendiera como gratis. La inferencia en el dispositivo tiene costes reales, y pretender lo contrario es cómo terminas enviando algo elegante en una demo y miserable en el campo. Aquí es donde muerde.

Tamaño del modelo y la descarga. Los modelos locales son archivos, y los buenos modelos locales son archivos grandes. El primer arranque tiene que obtenerlos —una descarga de varios gigabytes para los pesos compilados del modelo de voz, más una descarga de tamaño comparable para el LLM— y esa es una experiencia de primer arranque que tienes que diseñar con cuidado, porque "descargar 3 GB antes de que puedas decir tu primera palabra" es un onboarding brutal si lo manejas mal. No hay equivalente del lado de la nube; su modelo ya está caliente en su hardware.

Presión de memoria. Ambos modelos, cargados y residentes, cuestan RAM. En una máquina de 64 GB, a quién le importa. En un portátil de modelo base que también está corriendo un navegador con noventa pestañas, un IDE y tres apps de Electron, ahora estás compitiendo por memoria con todo lo que el usuario realmente abrió el portátil para hacer. Tienes que ser deliberado sobre cuándo se cargan los modelos, cuándo se liberan y qué tamaño de modelo estás dispuesto a usar por defecto.

Heredas el suelo de hardware. El STT en la nube corre en las mismas GPUs de datacenter para un teléfono insignia y un portátil de cinco años. En el dispositivo, tu rendimiento es el silicio del usuario. Requerimos Apple Silicon precisamente porque el Neural Engine y la memoria unificada son lo que hace que la matemática de la latencia funcione; en hardware más viejo o más débil todo el argumento se ablanda. Estás apostando por un suelo de hardware, y estás cortando a todos los que están por debajo de él.

La cuantización es una perilla con dientes. Para que los modelos quepan y corran rápido los cuantizas —representas los pesos en menos bits, intercambiando un poco de precisión por mucha velocidad y memoria—. Bien hecho es casi gratis. Llevado demasiado lejos es muerte por mil pequeñas incorrecciones: una transcripción ligeramente errónea aquí, una limpieza sutilmente destrozada allá. Elegir el nivel de cuantización es elegir un punto en la curva precisión-vs-recursos, y es una decisión de ingeniería real, no una casilla.

Tú envías el modelo, así que tú eres dueño de las actualizaciones. El proveedor de la nube mejora su modelo y cada usuario lo recibe en silencio. Tu modelo local mejora cuando envías una actualización de la app y el usuario descarga nuevos pesos. Has cambiado "la mejora continua de otro" por "control y predecibilidad", que es el trato correcto para una herramienta que tiene que comportarse idénticamente sin conexión en un avión, pero es un trato.

Ninguno de estos hunde el enfoque en el dispositivo para una herramienta de dictado interactiva. Las ganancias de latencia y privacidad son demasiado grandes. Pero cada uno de ellos es una razón por la que una funcionalidad diferente podría elegir correctamente la nube.


Un Marco de Decisión Que Realmente Puedes Usar

Así que esto es lo que llevarte a tu propia funcionalidad de audio. La pregunta nunca es "¿es mejor el dispositivo?". Es "¿qué columna de costes le importa a mi carga de trabajo?". Mapea tu funcionalidad contra esto:

Señal Inclínate al dispositivo Inclínate a la nube
Modelo de interacción Tiempo real, humano esperando el resultado Por lotes, procesado en segundo plano
Tolerancia a la latencia La latencia de cola se siente; el p99 importa Los segundos están bien; nadie mira
Sensibilidad de los datos El audio es privado, regulado o bajo NDA Contenido público o ya compartido
Requisito offline Debe funcionar en un avión / red mala Siempre-en-línea es un supuesto seguro
Frecuencia de uso Muchas veces al día, por usuario Ocasional
Hardware objetivo Puedes exigir un suelo de hardware moderno Debe correr en cualquier sitio, incluso dispositivos débiles
Frescura del modelo Gana el comportamiento predecible, idéntico, offline Gana la mejora continua silenciosa

Lee la tabla de arriba abajo. Si tus respuestas se agrupan en la columna izquierda —interactivo, sensible a la latencia, privado, frecuente, sobre hardware capaz— tienes un problema con forma de dictado, y el dispositivo es muy probablemente la arquitectura correcta aunque sea más trabajo. Si se agrupan a la derecha —por lotes, tolerante a la latencia, no sensible, ocasional, debe-correr-en-cualquier-sitio— la nube te está haciendo un favor e ir local es dolor autoinfligido.

El error que veo con más frecuencia es tratar esto como una decisión global única para todo un producto. No lo es. Es por funcionalidad. Una herramienta puede perfectamente correr dictado interactivo en el dispositivo por la latencia y aun así entregar una grabación archivada de dos horas a un trabajo por lotes en la nube durante la noche, porque esas dos cargas se sientan en columnas opuestas. Ponemos todo local por defecto en Vext porque nuestras cargas resultan vivir a la izquierda, pero el marco, no la conclusión, es la parte que vale la pena robar.


Lo Que Le Diría a Mí Mismo Hace un Año

Si pudiera enviar una nota de vuelta a la versión de mí mirando el amigable SDK de STT en la nube: el SDK amigable te está vendiendo la inferencia y escondiendo el impuesto. La calidad del modelo rara vez es el problema. La columna de red —establecimiento, subida, cola, descarga y la varianza que vive en las cuatro— es el problema, y es un problema que pagas en cada enunciado, para siempre, en el momento exacto en que un humano espera tu software con las manos.

El dispositivo elimina esa columna por completo y te entrega una arquitectura de privacidad como efecto secundario. Te cuesta una descarga grande, algo de disciplina de memoria, un suelo de hardware y una decisión de cuantización. Para una carga interactiva, sensible y de alta frecuencia, ese es un trato que haría cada vez, y lo hice.

Haz la matemática de tu propia funcionalidad antes de echar mano del SDK. La servilleta es más barata que la reescritura.

— Don


Vext es la app de voz a texto de código cerrado y en el dispositivo de Muvon para Apple Silicon: el reconocimiento de voz y el LLM de limpieza corren localmente, y tu audio nunca sale del Mac. Las herramientas de desarrollo de Muvon, incluyendo Octomind, son de código abierto. La arquitectura y los compromisos descritos aquí son patrones de ingeniería generales, no un recorrido por las interioridades de Vext.