Wie Dreamalizing ein Sprachmodell ohne Cloud betreibt
Ein Traum ist das Persönlichste, was jemand aufschreiben kann. Deshalb geht der Traumtext in Dreamalizing niemals an einen externen KI-Dienst. Dieser Artikel beschreibt, wie das technisch funktioniert, welche Entscheidungen daraus folgten und was unterwegs schiefging.
Er wurde für Entwickler geschrieben und für alle, die überprüfen wollen, ob das Datenschutzversprechen mehr ist als nur ein Satz auf der Homepage.
Das Versprechen in einem Satz
Dein Traumtext wird durch unser eigenes Sprachmodell auf Servern innerhalb der Europäischen Union verarbeitet. Deine Daten werden verschlüsselt gespeichert und dein Traumtext wird nicht an externe KI-Dienste gesendet.
Dieser Satz steht in der App, in den Stores und auf dieser Website. Alles Folgende existiert, um ihn wahr zu machen, selbst wenn jemand einen Tippfehler in einer Konfigurationsdatei macht.
Die Kette, vom Telefon zum Modell
Die App (React Native, Expo) kommuniziert mit einer API (NestJS, PostgreSQL) auf einem gemieteten Server in der EU. Dieser Server besitzt kein Sprachmodell. Er erreicht über einen WireGuard-Tunnel ein Gateway auf eigener Hardware, und dieses Gateway kommuniziert mit dem Modell.
Das Modell ist Gemma 4 mit 26 Milliarden Parametern in einem Mixture-of-Experts-Setup, von denen etwa 4 Milliarden pro Token aktiv sind, in 8-Bit-Quantisierung. Ein Modell erledigt alles: die Fragen, die Analyse und die strukturierte Extraktion. Das Kontextfenster ist fest auf 32.768 Token eingestellt, da die schwerste Analyse bisher 13.671 Token beanspruchte und die Ausgabe bis zu 8.192 Token betragen darf.
Bis August liefen zwei Modelle: ein schwereres für die Fragen und die Analyse, ein leichteres für die Extraktion. Das erwies sich als teurer, als es einbrachte. Die beiden Modelle verdrängten sich gegenseitig im Speicher, wodurch pro Traum ein Modell dreimal geladen werden musste, was jeweils 7 bis 10 Sekunden dauerte. Das schwerere Modell erledigt die Extraktion 3,7 Sekunden langsamer, spart aber 27 Sekunden Ladezeit.
Nur lokal als Überwachung, nicht als Absicht
Die API verfügt über eine Routing-Schicht, die an mehrere Anbieter senden kann. Das ist bei der Entwicklung nützlich, aber in der Produktion gefährlich. Deshalb gibt es drei Sperren.
Erstens eine harte Flagge in der Runtime. Wenn diese aktiviert ist, lehnt die API jeden Aufruf mit Traum-Inhalt an einen nicht-lokalen Anbieter ab. Es gibt ein Notventil, das jedoch an ein Enddatum von maximal vierzehn Tagen gebunden ist und automatisch abläuft; ein ungültiges oder zu weit in der Zukunft liegendes Datum wird ignoriert und die Überwachung bleibt geschlossen.
Zweitens das Gateway. Jede Model-ID, die die API abfragt, beginnt mit dem Präfix local-. Das Gateway liest dies als lokal-only und lehnt eine solche Anfrage bei einem Cloud-Anbieter ab, selbst wenn eine Ausweichkette dorthin führt.
Drittens Tests. Ein Test erzwingt, dass die API das Präfix niemals entfernt, ein anderer, dass die Modellkonfiguration der Test- und Produktionsumgebung identisch ist. Der erste Test existiert, weil die API das Präfix bis Ende August tatsächlich entfernt hat. Dadurch hätte der Ausweichmechanismus des Gateways theoretisch Traumtext in die Cloud senden können, mit Status 200 und ohne Signal. Die App war zu diesem Zeitpunkt noch nicht gestartet und hatte außerhalb unseres Teams keine aktiven Nutzer. Wir haben den Pfad geschlossen und den Test hinzugefügt, damit es nicht mehr möglich ist, auch nicht unbemerkt.
Wartezeit ist eine Designentscheidung
Ein lokales Modell auf einer einzigen Maschine ist langsamer als eine Cloud-API. Die Antworten auf die Fragen kommen nach 20 bis 40 Sekunden, die Analyse nach 30 bis 60. Das verbergen wir nicht; die App sagt, was passiert und wie lange es etwa dauert.
Die Reihenfolge ist darauf ausgelegt. Zuerst gehen die Fragen an die App, basierend auf dem rohen Traumtext. Danach baut parallel ein zweiter Aufruf die strukturierten Daten auf, die die Analyse später benötigt. Der Träumende beantwortet in der Zwischenzeit Fragen und bemerkt von diesem zweiten Aufruf nichts.
Auf der Maschine läuft ein Begrenzer, der pro Benutzer nur einen Aufruf gleichzeitig zulässt und optionale Aufrufe niemals vor die obligatorischen stellt. Letzteres haben wir aus einem Produktionsvorfall gelernt: Ein optionaler Aufruf verdrängte den obligatorischen, und der Träumende wartete auf nichts.
Speicherung und Offline
Traumtext und Antworten werden pro Benutzer verschlüsselt mit AES-256-GCM und einem eigenen Schlüssel pro Benutzer gespeichert, der wiederum unter einem Hauptschlüssel liegt. Auf dem Telefon befindet sich die Warteschlange noch nicht gesendeter Träume in einer verschlüsselten SQLCipher-Datenbank mit einem Schlüssel im sicheren Speicher des Geräts.
Jeder Schreibvorgang trägt eine stabile Operations-ID. Bricht die Verbindung nach dem Senden, aber vor der Antwort ab, liefert eine Wiederholung denselben Traum, nicht einen zweiten. Das Logbuch dieser IDs enthält niemals Traumtext, sondern nur die Art der Aktion, einen Hash und das Ergebnis.
Was es kostet
Eine Maschine ist eine Grenze. Bei gleichzeitigen Benutzern stauen sich die Aufrufe an und die Wartezeit wächst. Das ist der Preis für den Verzicht auf die Cloud und der Grund, warum wir in den Niederlanden begonnen haben und nicht weltweit.
Der zweite Preis ist, dass das Modell weniger leistungsfähig ist als die größten Cloud-Modelle. Wir messen Prompt-Änderungen daher immer am lokalen Modell, niemals an einem Cloud-Modell; was auf einem größeren Modell funktioniert, funktioniert hier nicht automatisch.
Was folgt
Fragen zur Architektur sind willkommen. Was hier steht, ist der Stand von September 2026; wenn sich etwas ändert, passt sich dieser Artikel entsprechend an.