El modelo corre en tu navegador
Una nota desde mi tesis de máster. El clasificador ganador pesaba 1,3 MB, y ese único número decidió la arquitectura — sin servidor, sin datos saliendo del dispositivo, y una factura de nube de cero. Para este problema, dónde corre un modelo terminó importando más que cuál modelo era.
Servir un DistilBERT con fine-tuning en una GPU dedicada cuesta unos $3.000 al año — una instancia, precios de lista, al volumen que modela esta tesis. En ese benchmark, el dinero compró un modelo que empató en datos limpios y perdió por 5,7 puntos de F1 en datos ruidosos. Así que la pregunta interesante dejó de ser cuál modelo y pasó a ser dónde corre.
El modelo que ganó — un clasificador lineal sobre TF-IDF — se exporta entero a ONNX, el formato abierto que mantiene la Linux Foundation. El pipeline completo entra en un solo grafo: tokenización, n-gramas, ponderación TF-IDF, el clasificador. Entra una descripción cruda, sale una categoría. El archivo pesa 1,3 MB.
Suficientemente pequeño, y suficientemente estándar, como para no necesitar servidor. Corre en el navegador con ONNX Runtime Web, sobre WebAssembly. Cargas la página, el modelo baja con ella, y la clasificación pasa en la pestaña. De ahí se siguen tres cosas, y ninguna es sobre precisión.
1 · La factura de nube es cero
La inferencia corre en el dispositivo del usuario, así que el costo marginal de clasificar una transacción es nada. Pagas por alojar unos megabytes estáticos — que todo proveedor sirve gratis — y ahí termina la línea. No se dobla cuando el tráfico crece.
Léelos como un modelo de costos de la tesis, no una lista de precios: una instancia de GPU, precios publicados, un volumen fijo. Lo que importa es la forma de cada línea, no el tamaño del número. Escala la carga y todas las columnas crecen — la línea de GPU se multiplica con instancias, la del navegador se queda plana en cero — y la GPU sigue sirviendo el modelo que quedó segundo en datos ruidosos. En $3.000 la cifra absoluta es chica; el punto es que la pagas por precisión negativa.
2 · Los datos nunca salen
Un extracto bancario es de lo más personal que hay en texto — delata dónde compras, cuánto ganas, tu salud a través de la farmacia. Como la inferencia es local, nada de eso se transmite ni se almacena. La minimización de datos del RGPD deja de ser una política de retención que prometes cumplir y pasa a ser una propiedad de la arquitectura. No hay log de servidor que filtrar, porque no hay servidor.
3 · El despliegue es reversible
El mismo .onnx corre en el navegador, en Python, en Node, en la JVM. Si el volumen alguna vez forzara una API en el servidor — integración con un tercero, digamos — eso es un cambio de entorno de ejecución, no de modelo. Sin reentrenar, sin reimplementar. Mueves el binario exacto que ya evaluaste.
Dónde corre el modelo tuvo más consecuencias que cuál modelo corrías.
Verifica, no asumas
Exportar un modelo arriesga otra falla, una sin síntoma: que el artefacto desplegado deje de coincidir con el que evaluaste, y nada te avise. La app sigue devolviendo respuestas plausibles mientras tus métricas describen un modelo que ya no corre.
Así que la equivalencia se comprueba, no se confía. La primera exportación reprodujo el 99,95% de las predicciones originales — y ese 0,05% no era redondeo. Eran descripciones donde la limpieza deja tokens de un solo carácter — H-E-B, H&M — que scikit-learn descarta y el grafo conservaba. Una divergencia silenciosa, atrapada solo porque miré. Ahora la app corre 60 casos de referencia en cada carga y muestra el resultado: 60 de 60, idénticos.
La equivalencia de un modelo exportado se verifica, no se supone. Un modelo equivocado que devuelve respuestas plausibles no tiene síntoma.
La misma decisión, con un marco limpio alrededor
Esto es una tesis, así que léela como tal: una comparación acotada sobre un problema definido, no una plantilla para pegar en cualquier sistema. Nada de esto dice "siempre corre en el navegador". Funciona aquí por una razón — el modelo pesa 1,3 MB, y pesa eso porque la etapa anterior mostró que un modelo lineal ya resuelve esta tarea puntual, texto de transacciones corto y separable, tan bien como cualquiera más grande. Pon un modelo que de verdad necesite una GPU detrás de una carga más pesada y todas las líneas de arriba se invierten. La arquitectura siguió al hallazgo; no lo lideró.
Esta es la decisión que tomo en sistemas de producción, solo que entregada con un moño académico encima. Gana el lugar más barato para correr inferencia que pase el umbral — y "pasar el umbral" incluye latencia, privacidad, y la cuenta que llega en cada request, para siempre. Un modelo de 1,3 MB me dejó poner todo eso en el dispositivo. Un transformer 356× más grande nunca habría podido.
¿Qué hay en tu stack sentado en un servidor solo porque nadie preguntó si de verdad hacía falta?