Tecnología

Cómo Dreamalizing ejecuta un modelo de lenguaje sin la nube

Un sueño es lo más personal que alguien puede escribir. Por eso, el texto del sueño en Dreamalizing nunca se envía a un servicio de IA externo. Este artículo describe cómo funciona esto técnicamente, qué decisiones se tomaron al respecto y qué salió mal en el camino.

Está escrito para desarrolladores y para cualquier persona que quiera comprobar si la promesa de privacidad es algo más que una frase en la página de inicio.

La promesa, en una frase

Tu texto de sueño es procesado por nuestro propio modelo de lenguaje en servidores dentro de la Unión Europea. Tus datos se almacenan de forma cifrada y tu texto de sueño no se envía a servicios de IA externos.

Esa frase aparece en la aplicación, en las tiendas y en este sitio. Todo lo que sigue existe para hacerla realidad, incluso si alguien comete un error tipográfico en un archivo de configuración.

La cadena, desde el teléfono hasta el modelo

La aplicación (React Native, Expo) se comunica con una API (NestJS, PostgreSQL) en un servidor alquilado en la UE. Ese servidor no tiene un modelo de lenguaje. Accede, a través de un túnel WireGuard, a una puerta de enlace (gateway) en hardware propio, y esa puerta de enlace se comunica con el modelo.

El modelo es Gemma 4 con 26 mil millones de parámetros en una configuración de mixture-of-experts, de los cuales aproximadamente 4 mil millones por token están activos, en cuantización de 8 bits. Un solo modelo lo hace todo: las preguntas, el análisis y la extracción estructurada. La ventana de contexto está fijada en 32.768 tokens, ya que el análisis más pesado hasta ahora utilizó 13.671 tokens y la salida puede ser de hasta 8.192 tokens.

Hasta agosto, funcionaban dos modelos: uno más pesado para las preguntas y el análisis, y uno más ligero para la extracción. Eso resultó ser más caro de lo que generaba beneficios. Los dos modelos se desplazaban entre sí en la memoria, lo que provocaba que el modelo se cargara tres veces por sueño, con un coste de 7 a 10 segundos cada vez. El modelo más pesado realiza la extracción 3,7 segundos más lento, pero ahorra 27 segundos de tiempo de carga.

Solo local como salvaguarda, no como intención

La API tiene una capa de enrutamiento que puede enviar a múltiples proveedores. Esto es útil durante el desarrollo pero peligroso en producción. Por ello, existen tres cerrojos.

En primer lugar, una bandera estricta en el runtime. Si está activada, la API rechaza cualquier llamada con contenido de sueños hacia un proveedor que no sea local. Existe una válvula de escape, pero está sujeta a una fecha de finalización de máximo catorce días y caduca automáticamente; una fecha inválida o demasiado lejana será ignorada y la salvaguarda permanecerá cerrada.

En segundo lugar, la gateway. Cada model-id que solicita la API comienza con el prefijo local-. La gateway lo lee como solo-local y, por tanto, rechaza entregar dicha solicitud a un proveedor en la nube, incluso si una cadena de respaldo apunta hacia allí.

En tercer lugar, las pruebas. Una prueba exige que la API nunca elimine el prefijo, otra exige que la configuración del modelo de los entornos de prueba y producción sea idéntica. La primera prueba existe porque la API sí eliminaba el prefijo hasta finales de agosto. Debido a esto, el respaldo de la gateway teóricamente podría haber enviado texto de sueños a la nube, con estado 200 y sin señal. En aquel momento la aplicación aún no se había lanzado y, fuera de nosotros mismos, no tenía usuarios activos. Hemos cerrado el camino y añadido la prueba para que ya no sea posible, ni siquiera de forma inadvertida.

El tiempo de espera es una elección de diseño

Un modelo local en una sola máquina es más lento que una API en la nube. Las preguntas llegan tras 20 a 40 segundos, el análisis tras 30 a 60. No ocultamos esto; la aplicación indica qué está ocurriendo y cuánto tiempo tardará aproximadamente.

El orden está diseñado para ello. Primero, las preguntas van a la aplicación, basadas en el texto bruto del sueño. Después, de forma paralela, una segunda llamada construye los datos estructurados que el análisis necesitará más tarde. Mientras tanto, el soñador responde preguntas y no nota esa segunda llamada.

En la máquina corre un limitador que permite una sola llamada por usuario a la vez y nunca permite que las llamadas opcionales vayan antes que las obligatorias. Esto último lo aprendimos de un incidente en producción: una llamada opcional desplazó a la obligatoria, y el soñador no esperó a nada.

Almacenamiento y offline

El texto del sueño y las respuestas se almacenan de forma cifrada por usuario con AES-256-GCM y una clave propia por usuario, que a su vez reside bajo una clave maestra. En el teléfono, la cola de sueños aún no enviados se encuentra en una base de datos SQLCipher cifrada con una clave en el almacenamiento seguro del dispositivo.

Cada acción de escritura lleva un ID de operación estable. Si la conexión se pierde tras el envío pero antes de la respuesta, una repetición generará el mismo sueño, no uno segundo. El libro de registro de esos IDs nunca contiene texto de sueños, solo el tipo de acción, un hash y el resultado.

El coste

Una sola máquina es un techo. Con usuarios simultáneos, las llamadas se acumulan y el tiempo de espera aumenta. Ese es el precio de no usar la nube, y la razón por la que empezamos en los Países Bajos y no a nivel mundial.

El segundo precio es que el modelo es menos capaz que los modelos de nube más grandes. Por ello, siempre medimos los cambios en los prompts en el modelo local, nunca en un modelo de la nube; lo que funciona en un modelo más grande no funciona automáticamente aquí.

Qué sigue

Las preguntas sobre la arquitectura son bienvenidas. Lo que aquí se presenta es el estado de septiembre de 2026; si algo cambia, este artículo cambiará con ello.