Como o Dreamalizing executa um modelo de linguagem sem cloud
Um sonho é o que há de mais pessoal que alguém pode escrever. Por isso, o texto do sonho no Dreamalizing nunca é enviado para um serviço de IA externo. Este artigo descreve como isso funciona tecnicamente, que escolhas daí resultaram e o que correu mal pelo caminho.
Foi escrito para programadores e para todos os que querem verificar se a promessa de privacidade é mais do que uma frase na página inicial.
A promessa, em uma frase
O seu texto de sonho é processado pelo nosso próprio modelo de linguagem em servidores dentro da União Europeia. Os seus dados são armazenados de forma encriptada e o seu texto de sonho não é enviado para serviços de IA externos.
Essa frase está na aplicação, nas lojas e neste site. Tudo o que se segue existe para a tornar realidade, mesmo que alguém cometa um erro de digitação num ficheiro de configuração.
A cadeia, do telemóvel ao modelo
A aplicação (React Native, Expo) comunica com uma API (NestJS, PostgreSQL) num servidor alugado na UE. Esse servidor não tem um modelo de linguagem. Ele acede, através de um túnel WireGuard, a um gateway em hardware próprio, e esse gateway comunica com o modelo.
O modelo é o Gemma 4 com 26 mil milhões de parâmetros numa configuração mixture-of-experts, dos quais cerca de 4 mil milhões por token estão ativos, em quantização de 8 bits. Um único modelo faz tudo: as perguntas, a análise e a extração estruturada. A janela de contexto é fixa em 32.768 tokens, porque a análise mais pesada até agora utilizou 13.671 tokens e a saída pode chegar aos 8.192 tokens.
Até agosto, existiam dois modelos: um mais pesado para as perguntas e a análise, e um mais leve para a extração. Isso revelou-se mais caro do que o retorno que trazia. Os dois modelos competiam por memória, o que fazia com que, por cada sonho, um modelo fosse carregado três vezes, demorando entre 7 a 10 segundos cada vez. O modelo mais pesado faz a extração 3,7 segundos mais devagar, mas poupa 27 segundos de tempo de carregamento.
Apenas local como monitorização, não como intenção
A API tem uma camada de encaminhamento que pode enviar para múltiplos fornecedores. Isso é útil durante o desenvolvimento e perigoso em produção. Por isso, existem três trincos.
Em primeiro lugar, uma flag rígida no runtime. Se estiver ativa, a API recusa qualquer chamada com conteúdo de sonho para um fornecedor não local. Existe uma válvula de escape, mas está sujeita a uma data de validade máxima de quatorze dias e expira automaticamente; uma data inválida ou demasiado distante é ignorada e a monitorização permanece fechada.
Em segundo lugar, o gateway. Cada model-id que a API solicita começa com o prefixo local-. O gateway lê isso como apenas-local e recusa entregar tal pedido a um fornecedor de nuvem, mesmo que uma cadeia de fallback aponte para lá.
Em terceiro lugar, testes. Um teste garante que a API nunca remova o prefixo, outro garante que a configuração do modelo dos ambientes de teste e de produção sejam idênticas. O primeiro teste existe porque a API até removia o prefixo até ao final de agosto. Por isso, o fallback do gateway poderia, em teoria, ter enviado texto de sonho para a nuvem, com status 200 e sem qualquer sinal. A app ainda não tinha sido lançada e não tinha utilizadores ativos fora de nós próprios. Fechámos o caminho e adicionámos o teste, para que não seja mais possível, nem mesmo de forma inadvertida.
O tempo de espera é uma escolha de design
Um modelo local numa única máquina é mais lento do que uma API de cloud. As respostas às perguntas surgem após 20 a 40 segundos, a análise após 30 a 60. Não escondemos isso; a aplicação diz o que está a acontecer e quanto tempo irá demorar aproximadamente.
A ordem foi desenhada para isso. Primeiro, as perguntas vão para a aplicação, baseadas no texto bruto do sonho. Depois, em paralelo, uma segunda chamada constrói os dados estruturados de que a análise precisará mais tarde. Entretanto, o sonhador responde às perguntas e não nota a segunda chamada.
Na máquina, corre um limitador que permite apenas uma chamada por utilizador de cada vez e que nunca deixa chamadas opcionais passarem à frente das obrigatórias. Isto último aprendemos com um incidente de produção: uma chamada opcional substituiu a obrigatória, e o sonhador ficou à espera de nada.
Armazenamento e offline
O texto do sonho e as respostas são armazenados de forma encriptada por utilizador com AES-256-GCM e uma chave própria por utilizador, que por sua vez está sob uma chave mestra. No telemóvel, a fila de sonhos ainda não enviados reside numa base de dados SQLCipher encriptada, com uma chave no armazenamento seguro do dispositivo.
Cada ação de escrita carrega um id de operação estável. Se a ligação cair após o envio, mas antes da resposta, uma repetição resultará no mesmo sonho, não num segundo. O registo desses ids nunca contém o texto do sonho, apenas o tipo de ação, um hash e o resultado.
O custo
Uma máquina é um limite. Com utilizadores simultâneos, as chamadas acumulam-se e o tempo de espera aumenta. Esse é o preço de não usar cloud, e a razão pela qual começámos nos Países Baixos e não globalmente.
O segundo preço é que o modelo é menos capaz do que os maiores modelos de cloud. Por isso, medimos sempre as alterações de prompt no modelo local, nunca num modelo de cloud; o que funciona num modelo maior não funciona necessariamente aqui.
O que se segue
Perguntas sobre a arquitetura são bem-vindas. O que aqui consta é o estado de setembro de 2026; se algo mudar, este artigo será atualizado.