Hvordan Dreamalizing kører en sprogmodel uden cloud
En drøm er det mest personlige, et menneske kan skrive ned. Derfor bliver drømmeteksten i Dreamalizing aldrig sendt til en ekstern AI-tjeneste. Denne artikel beskriver den tekniske baggrund, de valg der fulgte, og hvad der gik galt undervejs.
Den er skrevet til udviklere og til alle, der ønsker at kontrollere, om privatløftet er mere end blot en sætning på forsiden.
Løftet i én sætning
Din drømmetekst behandles af vores egen sprogmodel på servere inden for Den Europæiske Union. Dine data gemmes krypteret, og din drømmetekst sendes ikke til eksterne AI-tjenester.
Den sætning står i appen, i butikkerne og på denne side. Alt herunder eksisterer for at gøre den til virkelighed, selv hvis nogen laver en tastefejl i en konfigurationsfil.
Kæden, fra telefon til model
Appen (React Native, Expo) taler med et API (NestJS, PostgreSQL) på en lejet server i EU. Denne server har ingen sprogmodel. Den når via en WireGuard-tunnel en gateway på egen hardware, og denne gateway taler med modellen.
Modellen er Gemma 4 med 26 milliarder parametre i et mixture-of-experts-setup, hvoraf cirka 4 milliarder er aktive per token, i 8-bit kvantisering. Én model gør det hele: spørgsmålene, analysen og den strukturerede ekstraktion. Kontekstvinduet er fastlåst til 32.768 tokens, da den tungeste analyse indtil videre brugte 13.671 tokens, og output må være op til 8.192 tokens.
Indtil august kørte der to modeller: en tungere til spørgsmål og analyse, og en lettere til ekstraktion. Det viste sig at være dyrere, end det gav afkast. De to modeller fortrængte hinanden i hukommelsen, hvilket betød, at modellen blev indlæst tre gange per drøm à 7 til 10 sekunder. Den tungere model udfører ekstraktionen 3,7 sekunder langsommere, men sparer 27 sekunders indlæsningstid.
Lokal-kun som overvågning, ikke som hensigt
API'et har et routinglag, der kan sende til flere udbydere. Det er praktisk under udvikling, men farligt i produktion. Derfor er der tre låse.
For det første et hårdt flag i runtime. Hvis det er slået til, afviser API'et ethvert kald med drømmeindhold til en ikke-lokal udbyder. En nødventil eksisterer, men er bundet til en slutdato på maksimalt fjorten dage og udløber automatisk; en ugyldig eller for fjern dato ignoreres, og overvågningen forbliver lukket.
For det andet gatewayen. Hvert model-id, som API'et anmoder om, starter med præfikset local-. Gatewayen læser dette som lokal-kun og afviser derfor at levere en sådan anmodning til en cloud-udbyder, selv hvis en fallback-kæde peger dertil.
For det tredje tests. En test tvinger API'et til aldrig at fjerne præfikset, en anden sikrer, at modelkonfigurationen i test- og produktionsmiljøet er identisk. Den første test eksisterer, fordi API'et faktisk fjernede præfikset indtil slutningen af august. Det betød, at gatewayens fallback i teorien kunne have sendt drømmetekst til skyen med status 200 og uden signal. Appen var på det tidspunkt endnu ikke lanceret og havde uden for os selv ingen aktive brugere. Vi har lukket stien og tilføjet testen, så det ikke længere er muligt, heller ikke uden at det opdages.
Ventetid er et designvalg
En lokal model på én maskine er langsommere end et cloud-API. Spørgsmålene er klar efter 20 til 40 sekunder, analysen efter 30 til 60. Vi skjuler det ikke; appen fortæller, hvad der sker, og hvor længe det cirka tager.
Rækkefølgen er indrettet derefter. Først sendes spørgsmålene til appen baseret på den rå drømmetekst. Derefter, parallelt, opbygger et andet kald de strukturerede data, som analysen senere har brug for. Imens besvarer drømmeren spørgsmål og bemærker intet til det andet kald.
På maskinen kører en begrænser, der tillader ét kald per bruger ad gangen og aldrig lader valgfrie kald gå før de obligatoriske. Sidstnævnte lærte vi fra en produktionshændelse: et valgfrit kald fortrængte det obligatoriske, og drømmeren ventede på ingenting.
Lagring og offline
Drømmetekst og svar gemmes krypteret per bruger med AES-256-GCM og en unik nøgle per bruger, som i sin tur ligger under en hovednøgle. På telefonen ligger køen af endnu ikke sendte drømme i en krypteret SQLCipher-database med en nøgle i enhedens sikre opbevaring.
Hver skrivehandling bærer et stabilt operations-id. Hvis forbindelsen afbrydes efter afsendelse, men før svaret, vil en gentagelse resultere i den samme drøm, ikke en anden. Loggen over disse id'er indeholder aldrig drømmetekst, kun typen af handling, et hash og resultatet.
Hvad det koster
Én maskine er ét loft. Ved samtidige brugere ophobes kaldene, og ventetiden vokser. Det er prisen for ikke at bruge cloud, og årsagen til, at vi startede i Holland og ikke globalt.
Den anden pris er, at modellen er mindre god end de største cloud-modeller. Vi måler derfor altid ændringer i prompts på den lokale model, aldrig på en cloud-model; det, der virker på en større model, virker ikke nødvendigvis her.
Hvad der følger
Spørgsmål om arkitekturen er velkomne. Det, der står her, er tilstanden i september 2026; hvis noget ændrer sig, ændrer denne artikel sig med.