Come Dreamalizing esegue un modello linguistico senza cloud
Un sogno è la cosa più personale che qualcuno possa scrivere. Per questo motivo, il testo del sogno in Dreamalizing non viene mai inviato a un servizio IA esterno. Questo articolo descrive come funziona tecnicamente, quali scelte ne sono derivate e cosa è andato storto lungo il percorso.
È scritto per gli sviluppatori e per chiunque voglia verificare se la promessa sulla privacy sia qualcosa di più di una semplice frase sulla homepage.
La promessa, in una frase
Il tuo testo del sogno viene elaborato dal nostro modello linguistico proprietario su server all'interno dell'Unione Europea. I tuoi dati vengono archiviati in modo criptato e il tuo testo del sogno non viene inviato a servizi IA esterni.
Questa frase si trova nell'app, negli store e su questo sito. Tutto ciò che segue esiste per renderla realtà, anche nel caso in cui qualcuno commetta un errore di battitura in un file di configurazione.
La catena, dal telefono al modello
L'app (React Native, Expo) comunica con un'API (NestJS, PostgreSQL) su un server affittato nell'UE. Quel server non possiede un modello linguistico. Raggiunge tramite un tunnel WireGuard un gateway su hardware proprietario, e quel gateway comunica con il modello.
Il modello è Gemma 4 con 26 miliardi di parametri in un'architettura mixture-of-experts, di cui circa 4 miliardi sono attivi per token, in quantizzazione a 8-bit. Un unico modello fa tutto: le domande, l'analisi e l'estrazione strutturata. La finestra di contesto è fissata a 32.768 token, poiché l'analisi più pesante finora ha utilizzato 13.671 token e l'output può arrivare fino a 8.192 token.
Fino ad agosto venivano utilizzati due modelli: uno più pesante per le domande e l'analisi, uno più leggero per l'estrazione. Si è rivelato più costoso di quanto rendesse. I due modelli si escludevano a vicenda nella memoria, costringendo il caricamento di un modello tre volte per ogni sogno, con tempi di 7-10 secondi ciascuno. Il modello più pesante esegue l'estrazione con 3,7 secondi di ritardo, ma risparmia 27 secondi di tempo di caricamento.
Local-only come monitoraggio, non come intenzione
L'API ha uno strato di routing che può inviare richieste a più fornitori. Questo è utile durante lo sviluppo ma pericoloso in produzione. Per questo motivo, ci sono tre lucchetti.
In primo luogo, un flag rigido nel runtime. Se è attivo, l'API rifiuta ogni chiamata contenente contenuti di sogni verso un fornitore non locale. Esiste una valvola di emergenza, ma è vincolata a una data di scadenza massima di quattordici giorni e scade automaticamente; una data non valida o troppo lontana viene ignorata e il monitoraggio rimane chiuso.
In secondo luogo, il gateway. Ogni model-id richiesto dall'API inizia con il prefisso local-. Il gateway lo legge come local-only e rifiuta di consegnare tale richiesta a un fornitore cloud, anche se una catena di fallback punta verso di esso.
In terzo luogo, i test. Un test impone che l'API non rimuova mai il prefisso, un altro che la configurazione del modello degli ambienti di test e di produzione sia identica. Il primo test esiste perché fino alla fine di agosto l'API rimuoveva effettivamente il prefisso. Ciò permetteva al fallback del gateway di inviare teoricamente il testo del sogno al cloud, con stato 200 e senza segnali di errore. Abbiamo chiuso quel percorso e aggiunto il test; non possiamo dire con certezza dai log se sia mai successo.
Il tempo di attesa è una scelta di design
Un modello locale su una singola macchina è più lento di un'API cloud. Le risposte arrivano dopo 20-40 secondi, l'analisi dopo 30-60. Non lo nascondiamo; l'app comunica cosa sta succedendo e quanto tempo ci vorrà approssimativamente.
L'ordine è progettato per questo. Prima le domande vanno all'app, basandosi sul testo grezzo del sogno. Successivamente, in parallelo, una seconda chiamata costruisce i dati strutturati di cui l'analisi avrà bisogno in seguito. Nel frattempo, il sognatore risponde alle domande e non si accorge della seconda chiamata.
Sulla macchina gira un limitatore che permette una sola chiamata per utente alla volta e non permette mai che le chiamate opzionali precedano quelle obbligatorie. Quest'ultima cosa l'abbiamo imparata da un incidente in produzione: una chiamata opzionale ha scalzato quella obbligatoria, e il sognatore non ha ricevuto nulla.
Archiviazione e offline
Il testo del sogno e le risposte vengono archiviati in modo criptato per ogni utente con AES-256-GCM e una chiave specifica per utente, che a sua volta è protetta da una chiave principale. Sul telefono, la coda dei sogni non ancora inviati si trova in un database SQLCipher criptato con una chiave nella memoria sicura del dispositivo.
Ogni operazione di scrittura porta un ID operazione stabile. Se la connessione cade dopo l'invio ma prima della risposta, un tentativo di ripetizione produrrà lo stesso sogno, non un secondo. Il registro di questi ID non contiene mai il testo del sogno, ma solo il tipo di azione, un hash e il risultato.
Cosa costa
Una singola macchina rappresenta un limite. Con utenti simultanei, le chiamate si accumulano e il tempo di attesa aumenta. Questo è il prezzo del non utilizzare il cloud, ed è il motivo per cui siamo iniziati nei Paesi Bassi e non a livello globale.
Il secondo prezzo è che il modello è meno performante dei più grandi modelli cloud. Per questo motivo, misuriamo sempre le modifiche ai prompt sul modello locale, mai su un modello cloud; ciò che funziona su un modello più grande non funziona automaticamente qui.
Cosa segue
Domande sull'architettura sono benvenute. Ciò che è riportato qui rappresenta lo stato di settembre 2026; se qualcosa cambierà, anche questo articolo cambierà di conseguenza.