0xEtern4lW0lf
IA local

Ollama: rodando LLM e embeddings 100% locais (e por que isso importa)

6 min de leitura
#Ollama#LLM local#embeddings#self-hosted#privacidade#GPU

Quarto post da série sobre o Konhecia. Já temos infraestrutura, API e persistência. Agora o modelo.

O requisito que define o projeto inteiro: nenhuma chamada a API externa de LLM. Os documentos que o agente lê são manuais internos. Mandá-los para um endpoint de terceiro é uma decisão de segurança — e, em muitos contextos, uma que você não tem autoridade para tomar sozinho.

Por que local não é preciosismo

Vale ser concreto, porque “privacidade” virou palavra vazia.

O documento vira dado de entrada de terceiro. Todo chunk recuperado é enviado no prompt. Num RAG corporativo, isso significa que trechos de manuais internos, procedimentos e políticas saem do perímetro a cada pergunta. Não é o upload de um arquivo uma vez — é um vazamento contínuo, proporcional ao uso.

Contrato e regulação. Um NDA que proíbe compartilhar material com terceiros não abre exceção para “mas é só a API do modelo”. Sob LGPD, se houver dado pessoal nos documentos, você acabou de adicionar um operador à cadeia — com tudo que isso implica de base legal e transferência internacional.

Superfície de ataque e dependência. Chave de API é segredo que vaza. Custo por token é conta que escala com o uso. Modelo hospedado é fornecedor que deprecia versão, muda comportamento e altera preço sem pedir licença.

Rodar local troca tudo isso por um custo diferente: hardware e qualidade menor de modelo. É um trade-off real, não um almoço grátis. Para um agente que responde sobre documentos internos, a troca compensa — a tarefa é fundamentar resposta em texto recuperado, não raciocinar sobre problemas abertos.

O que o Ollama é

Ollama é um servidor de inferência com uma API HTTP simples. Ele cuida de baixar modelos, carregá-los na memória, gerenciar GPU/CPU e servir requisições.

O modelo mental correto: é o “Docker dos modelos”. Você faz pull de um modelo por nome e tag, ele fica num registro local, e você conversa com ele por HTTP.

ollama pull qwen2.5:7b
ollama list

Em container, com um one-shot de inicialização baixando os modelos num volume compartilhado — o padrão que descrevi no post sobre Compose.

Quantização: o que os números no nome significam

qwen2.5:7b — o 7b são 7 bilhões de parâmetros. É o tamanho do modelo.

Em precisão nativa (16 bits por parâmetro), 7B pediria cerca de 14 GB só de pesos. Não cabe confortavelmente na maioria das placas de consumo.

Quantização reduz a precisão numérica dos pesos — de 16 bits para 4 ou 5. O modelo encolhe para uns 4,7 GB e roda em hardware modesto, perdendo uma qualidade que, para a maioria das tarefas práticas, é pequena.

A analogia útil: é compressão com perdas, como um JPEG. Você não distingue um JPEG bem comprimido do original no uso normal; se for ampliar muito, os artefatos aparecem. Modelo quantizado é igual — bom para a tarefa típica, degrada nos casos que exigem precisão fina.

A regra prática de memória: o modelo precisa caber inteiro na VRAM para rodar rápido. Não cabendo, o Ollama divide entre GPU e RAM, e a inferência despenca de velocidade. Escolher o tamanho do modelo é, na prática, escolher em função da VRAM disponível.

Dois modelos, dois trabalhos

Este é o ponto que mais confunde quem monta o primeiro RAG. Um sistema desses usa dois modelos diferentes, e eles não são intercambiáveis:

OLLAMA_LLM_MODEL=qwen2.5:7b
OLLAMA_EMBEDDING_MODEL=nomic-embed-text

Modelo de geração (qwen2.5:7b) recebe texto e produz texto. É o que escreve a resposta.

Modelo de embedding (nomic-embed-text) recebe texto e produz um vetor de números — 768 no caso dele. Não gera nada legível. Sua única função é transformar texto num ponto no espaço, de modo que textos com significado próximo fiquem próximos.

Por que não usar o modelo grande para as duas coisas? Custo e propósito. O embedding roda em cada chunk na ingestão e em cada pergunta na consulta. Um modelo de 7B para isso seria absurdamente lento e caro, produzindo um resultado pior — o nomic-embed-text tem 274 MB, é treinado especificamente para representação semântica e é ordens de grandeza mais rápido.

A dimensão do vetor é um contrato rígido. nomic-embed-text produz 768 dimensões, e a collection no banco vetorial precisa ser criada com exatamente esse número:

QDRANT_VECTOR_SIZE=768

Trocar o modelo de embedding depois invalida toda a base indexada. Os vetores antigos foram gerados por outra função — mesmo que a dimensão bata por acaso, a geometria é outra e a busca devolve lixo silenciosamente. Trocar de modelo de embedding significa reindexar tudo. Não é opcional.

Temperatura: por que 0.1 e não 0.7

temperature controla a aleatoriedade na escolha do próximo token. Perto de 0, o modelo pega quase sempre o mais provável — saída determinística e conservadora. Alto, ele arrisca — saída variada e criativa.

OLLAMA_TEMPERATURE=0.1

Num agente de RAG, criatividade é exatamente o defeito que se está tentando evitar. A tarefa não é escrever bonito: é repetir fielmente o que está no contexto recuperado, ou recusar. Temperatura baixa é a primeira linha contra alucinação — barata, e ninguém liga.

Não confunda com garantia: temperatura 0.1 reduz a variação, não impede o modelo de inventar quando o contexto é ruim. Isso exige outras camadas, que são assunto dos próximos posts.

Servindo em container

A API do Ollama é HTTP simples, o que torna o healthcheck trivial:

async def _check_ollama(settings: Settings) -> str:
    try:
        async with httpx.AsyncClient(timeout=3) as client:
            response = await client.get(f"{settings.ollama_base_url}/api/tags")
            response.raise_for_status()
        return "ok"
    except Exception:
        logger.exception("Falha no healthcheck do Ollama")
        return "unavailable"

/api/tags lista os modelos disponíveis. Se responde, o servidor está de pé e o volume dos modelos está montado — dois problemas verificados numa requisição.

Um detalhe operacional que economiza confusão: a primeira requisição depois de um período ocioso é lenta. O Ollama descarrega o modelo da memória após um tempo sem uso e precisa recarregar os gigabytes de pesos. Não é bug, e não adianta caçar no seu código.

GPU opcional, CPU garantido

Com o NVIDIA Container Toolkit configurado, o Ollama usa a placa. Sem ela, cai para CPU automaticamente — bem mais lento, porém correto.

Isso é um princípio de projeto que vale além deste caso: a dependência que degrada é melhor que a dependência que quebra. Alguém consegue clonar o repositório e rodar num notebook sem placa dedicada. Vai esperar mais por resposta, mas não precisa editar YAML nem entender de CUDA para ver o sistema funcionando.

O que fica

Rodar o modelo localmente deixou de ser exótico. Um modelo quantizado de 7B num container, um modelo de embedding de 274 MB ao lado, e você tem um agente que nunca manda um byte de documento interno para fora.

O que se paga é qualidade bruta do modelo. O que se ganha é que o dado não sai do perímetro, o custo é fixo, não há chave de API para vazar e nenhum fornecedor muda o comportamento do seu sistema num release.

No próximo post: o banco vetorial — o que é um embedding na prática, como funciona similaridade de cosseno e por que um limiar de score sozinho não segura alucinação.