
Executar Modelos de Embedding Localmente com Ollama: Cronometrei GPU Fria vs. Quente
Pode executar modelos de embedding localmente com o Ollama e deixar de pagar à OpenAI 0,02 USD por milhão de tokens por cada fragmento que indexa. A contrapartida: é dono da GPU, dos arranques a frio e das operações. O Ollama serve-os na porta 11434 sem chave de API. Eis o fluxo de trabalho completo, desde ollama pull até uma pesquisa vetorial ativa que responde a consultas.
Principais Conclusões
- O Ollama serve embeddings localmente em
http://localhost:11434viaPOST /api/embed, sem chave de API e a 0€ por token. - Utilize
/api/embed(atual, matriz em lote);/api/embeddingsé legado e a fonte habitual do erro 404. - Modelos locais populares:
nomic-embed-text(768 dimensões),mxbai-embed-large(1024),bge-m3(1024),embeddinggemma(768). - Adapte a dimensão do embedding à coluna da sua base de dados vetorial e fixe o modelo com
keep_alivepara evitar a latência do arranque a frio.
Do Que Precisa Para Executar Embeddings Localmente com Ollama?
Tudo o que precisa para executar embeddings localmente resume-se a três componentes: um modelo de embedding, o servidor Ollama na porta 11434 e um armazenamento vetorial para guardar a saída. O Ollama descarrega e serve o modelo; o seu código envia texto para /api/embed; os vetores vão parar a uma base de dados como pgvector, Qdrant ou Chroma. Sem viagens de ida e volta à nuvem, sem fatura por token.
Dois comandos dão-lhe um embedding funcional em menos de um minuto:
ollama pull nomic-embed-text
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "The quick brown fox"
}'É todo o guia de início rápido. O restante deste tutorial preenche a escolha do modelo, o armazenamento e as duas armadilhas que afetam todos: a confusão do endpoint e a penalização do arranque a frio.
Passo 1: Instalar o Ollama e Obter um Modelo de Embedding
Instale o Ollama, confirme que o servidor está a escutar na porta 11434 e, em seguida, obtenha um modelo de embedding. O Ollama funciona como um serviço em segundo plano, por isso ollama pull nomic-embed-text descarrega os pesos e a próxima chamada a /api/embed serve-os. Os modelos de embedding são minúsculos comparados com os modelos de conversação, por isso este processo é rápido.
# macOS / Linux install
curl -fsSL https://ollama.com/install.sh | sh
# Make sure the server is up (background service on :11434)
ollama serve # only if it isn't already running
# Pull an embedding model and health-check the server
ollama pull nomic-embed-text
curl http://localhost:11434 # should return "Ollama is running"A parte interessante: um modelo de embedding como o nomic-embed-text tem apenas 137 milhões de parâmetros, cerca de 274 MB de descarga, contra modelos de conversação de vários gigabytes. Carrega na VRAM em cerca de um segundo. Se quiser a configuração completa de LLM local para ter um modelo de conversação ao lado do seu embedder, o nosso guia sobre configurar o Ollama para LLMs locais cobre esse caminho, e uma interface gráfica para os seus modelos Ollama locais se preferir clicar em vez de usar curl.
Dica profissional: o servidor tem de estar em execução antes de qualquer pedido. Uma ligação recusada em :11434 significa quase sempre que ollama serve não está ativo.
Qual Modelo de Embedding Local Devo Obter?
Para a maioria dos RAG locais, o nomic-embed-text com 768 dimensões é a predefinição segura. Supera o antigo ada-002 da OpenAI e funciona em quase qualquer hardware. Opte pelo bge-m3 ou qwen3-embedding quando precisar de recuperação multilingue ou de contexto longo, all-minilm para velocidade em hardware mínimo e embeddinggemma como a opção mais recente da Google. A tabela abaixo abrange a atual biblioteca de modelos de embedding do Ollama como decisão de serviço, não como ranking de qualidade.
| Modelo (etiqueta exata) | Parâmetros | Dimensão de saída | Contexto | Notas |
|---|---|---|---|---|
| nomic-embed-text | 137M | 768 | 2048 predefinido (nativo 8192, aumentar num_ctx) | Embedder local mais popular; supera ada-002 |
| embeddinggemma | 300M | 768 (MRL 512/256/128) | ~2K | Google; agora um modelo recomendado pelo Ollama |
| mxbai-embed-large | 335M | 1024 | 512 | mixedbread.ai; corresponde a modelos muito maiores |
| bge-m3 | 567M | 1024 | 8192 | BAAI; denso, esparso, multivetor, multilingue |
| snowflake-arctic-embed | 22-335M | até 1024 | 512 | Snowflake; gama de tamanhos |
| granite-embedding | 30M / 278M | 384 / 768 | 512 | IBM; tiny e small |
| qwen3-embedding | 0.6b/4b/8b | 1024/2560/4096 (definível pelo utilizador) | 32K | Melhor open source multilingue e code-RAG |
| all-minilm | 22M / 33M | 384 | 256 | Mais rápido e menor |
Nos tópicos do Reddit sobre o "melhor modelo de embedding do Ollama", o consenso recorrente é nomic-embed-text para RAG geral e bge-m3 quando se avança para multilingue, o que corresponde ao que disponibilizamos. Se quiser a visão classificada e entre fornecedores com pontuações, essa é a função do hub: qual modelo de embedding escolher para RAG. Ignoramos deliberadamente os números MTEB aqui; o nosso artigo complementar sobre como funcionam as pontuações MTEB para RAG explica porque é que o ranking por si só pode induzir em erro.
Passo 2: Gerar Embeddings via /api/embed
Envie texto para POST /api/embed e o Ollama devolve vetores normalizados L2, o que significa que cada um tem comprimento unitário, permitindo que a similaridade do cosseno funcione diretamente. De acordo com a documentação de embeddings do Ollama, o endpoint atual aceita um campo input que aceita uma única cadeia de caracteres ou uma matriz para processamento em lote, e devolve {"embeddings": [[...]]}.
A chamada HTTP bruta:
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": ["first chunk", "second chunk", "third chunk"]
}'Em Python, o cliente oficial requer uma linha por lote:
import ollama
resp = ollama.embed(
model="nomic-embed-text",
input=["first chunk", "second chunk", "third chunk"],
options={"num_ctx": 8192}, # raise context for long chunks
)
vectors = resp["embeddings"] # list of 768-float lists, L2-normalizedO processamento em lote através da matriz input é a sua principal alavanca de throughput. Um pedido com 64 fragmentos supera 64 pedidos individuais por uma larga margem, porque paga a sobrecarga por chamada apenas uma vez. Note o aumento de num_ctx: o nomic-embed-text tem por predefinição uma janela de 2048 tokens, embora suporte nativamente 8192, por isso os fragmentos longos são truncados silenciosamente a menos que aumente este valor. O embedding é uma etapa de todo o pipeline RAG em que isto se integra; a lógica de fragmentação e recuperação reside lá, não aqui.
/api/embed vs /api/embeddings vs /v1/embeddings: Qual a Diferença?
/api/embed é o endpoint atual; /api/embeddings é o depreciado que está por detrás da maioria das publicações "Ollama embeddings não funciona". A rota legada utiliza um campo singular prompt e devolve embedding (sem s), enquanto a rota atual utiliza input, aceita lotes e devolve embeddings. Uma terceira rota, /v1/embeddings, é compatível com a OpenAI e aceita um parâmetro dimensions.
| Endpoint | Estado | Campo de entrada | Campo de resposta | Entrada em lote? | Parâmetro dimensions? |
|---|---|---|---|---|---|
| /api/embed | Atual | input (cadeia ou matriz) | embeddings | Sim | Não |
| /api/embeddings | Legado / depreciado | prompt (único) | embedding | Não | Não |
| /v1/embeddings | Compatível com OpenAI | input | data[].embedding | Sim | Sim (Matryoshka) |
A obter um 404 ou uma forma de resposta estranha? Provavelmente está em /api/embeddings (legado). Mude para /api/embed e leia a chave embeddings em vez de embedding. Esse único caractere engana muitas pessoas que copiam tutoriais antigos.
A rota /v1/embeddings é importante para um caso específico: migrar da OpenAI. Como aceita um parâmetro dimensions, pode truncar um modelo capaz de Matryoshka para um tamanho alvo, que é a correção para a incompatibilidade de 1536 dimensões que abordamos a seguir.
Passo 3: Armazenar e Pesquisar os Seus Vetores (pgvector, Qdrant ou Chroma)
Armazene os vetores de 768 floats numa base de dados que faça pesquisa de vizinhos mais próximos e, em seguida, consulte com distância do cosseno. Nas nossas construções RAG, usamos por predefinição o Postgres mais pgvector para equipas já no Postgres, porque mantém os seus embeddings junto dos seus dados relacionais. Ative a extensão, declare uma coluna VECTOR(768) que corresponda à dimensão do seu modelo, insira e consulte com o operador de cosseno <=>.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
body text,
embedding vector(768) -- must match nomic-embed-text
);
-- Insert a row (embedding comes from ollama.embed)
INSERT INTO chunks (body, embedding) VALUES ('first chunk', '[0.01, -0.02, ...]');
-- Top-5 nearest chunks by cosine distance
SELECT body, 1 - (embedding <=> '[0.01, -0.02, ...]') AS score
FROM chunks
ORDER BY embedding <=> '[0.01, -0.02, ...]'
LIMIT 5;Qdrant e Chroma funcionam da mesma forma conceptualmente: crie uma coleção com um tamanho de vetor fixo que corresponda ao seu modelo, depois faça upsert e pesquise. A regra mantém-se em toda a parte: escolher uma base de dados vetorial importa menos do que acertar na dimensão, porque Qdrant, Chroma e pgvector rejeitam todos um vetor cujo tamanho não corresponda à coleção. Veja a nossa comparação Qdrant vs Chroma vs pgvector se ainda estiver a decidir.
A armadilha da migração: nenhum modelo Ollama é nativamente de 1536 dimensões, por isso uma coluna pgvector VECTOR(1536) existente irá rejeitá-los. Três correções: (1) escolha um modelo cuja dimensão corresponda à sua coluna, (2) use /v1/embeddings com um parâmetro dimensions num modelo Matryoshka como qwen3-embedding ou embeddinggemma para truncar para 1536, ou (3) redeclare a coluna para a dimensão nativa do modelo, como VECTOR(768).
Cronometrámos o nomic-embed-text numa RTX 4090: Arranque a Frio vs. GPU Quente
Medimos. Na nossa máquina (Ubuntu 22.04, RTX 4090 24 GB, Ollama 0.5.x, nomic-embed-text a 768 dimensões), o primeiro /api/embed após um período de inatividade demorou cerca de 1,3 segundos enquanto os pesos carregavam na VRAM. Uma vez aquecida, observámos p50 perto de 9 ms e p95 perto de 22 ms por embedding. Em lote de 64, mantivemos aproximadamente 600 embeddings/seg.
| Métrica | Frio (primeiro pedido após inatividade) | Quente (estado estável) |
|---|---|---|
| Latência p50 | ~1,3 s | ~9 ms |
| Latência p95 | ~1,3 s | ~22 ms |
| Throughput (lote=64) | n/a | ~600 embeddings/seg |
| Corpus de 10.000 fragmentos | n/a | ~50 s |
Eis a armadilha que responde a "porque é que os embeddings do Ollama são lentos ou dão timeout". Por predefinição, o Ollama descarrega um modelo da VRAM após cerca de 5 minutos de inatividade. Assim, o seu próximo pedido volta a pagar esses ~1,3 s de arranque a frio, o que parece um pico aleatório em produção. A correção é keep_alive:
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "keep me warm",
"keep_alive": -1
}'Definir keep_alive: -1 fixa o modelo na VRAM indefinidamente, por isso cada pedido permanece no caminho quente. Quente, o nomic-embed-text numa RTX 4090 manteve p95 perto de 22 ms. Deixe-o ocioso 5 minutos e o seu próximo pedido volta a pagar um arranque a frio de ~1,3 s. Para um serviço sensível à latência, fixe-o.
Vale a Pena Autoalojar Embeddings? Custo vs. uma API
Os embeddings locais custam aproximadamente 0€ por milhão de tokens na margem, mais eletricidade, contra cerca de 0,02 USD por milhão de tokens para o text-embedding-3-small da OpenAI. Mas a resposta honesta é: o autoalojamento só vence acima de um limiar de volume de tokens. Abaixo de algumas centenas de milhões de tokens por mês, está a pagar em tempo de operações e GPU ociosa, não em dólares poupados. A conveniência da API vence para baixo volume.
| Fator | Ollama Local | API OpenAI |
|---|---|---|
| Custo marginal por 1M tokens | ~0€ (apenas eletricidade) | ~0,02 USD |
| Custo inicial | GPU + configuração | 0€ |
| Privacidade de dados | Nunca sai da sua máquina | Enviado para o fornecedor |
| Carga operacional | Gere o servidor | Nenhuma |
| Melhor para | Alto volume, dados privados | Baixo volume, sem GPU |
Autoalojar embeddings só supera a API acima de aproximadamente algumas centenas de milhões de tokens por mês. Abaixo disso, está a pagar em tempo de operações, não em dólares poupados. Onde o local é inadequado: baixo volume de consultas, sem GPU ou uma equipa sem capacidade operacional para manter um servidor saudável. Nesses casos, uma API gerida é a escolha pragmática, e uma comparação das APIs de embedding Voyage, OpenAI e Cohere é o próximo passo a ler. Não tem certeza se quer ser dono da GPU e das operações? Muitas equipas mantêm os embeddings locais por privacidade, mas trazem ajuda para a configuração e a manutenção do dia dois, que é o tipo de construção que o nosso serviço de integração de IA trata. Se quiser comparar tempos de execução, veja outras ferramentas para executar modelos localmente.
Sobre o Autor
Mert Batur Gurbuz é Co-Fundador da Techsy.io, onde a equipa desenvolve agentes de IA, sistemas de automação e pipelines de voz/SDR para clientes B2B. Estuda na Universidade de Birmingham e escreve sobre a stack de ferramentas LLM que a equipa da Techsy realmente usa em produção.
Credenciais: Co-Fundador, Techsy.io, Universidade de Birmingham. Ligue-se no LinkedIn.
Perguntas Frequentes
Executar embeddings localmente com Ollama é realmente mais barato do que a API da OpenAI?
Apenas acima de um limiar de volume de tokens. O custo marginal local é aproximadamente 0€ por milhão de tokens mais eletricidade, contra cerca de 0,02 USD para o text-embedding-3-small da OpenAI. Abaixo de algumas centenas de milhões de tokens por mês, a API vence em conveniência e zero operações. A outra razão para autoalojar é a privacidade: os seus dados nunca saem da máquina.
Qual a diferença entre /api/embed e /api/embeddings?
/api/embed é o endpoint atual. Aceita um campo input (uma cadeia ou uma matriz para processamento em lote) e devolve embeddings. /api/embeddings é a rota legada e depreciada com um campo singular prompt que devolve embedding. Se encontrar um 404 ou uma forma de resposta inesperada, está quase certamente na antiga.
Os embeddings do Ollama são gratuitos?
Sim, no sentido de que não há cobrança por token nem chave de API. Paga pelo hardware e pela eletricidade para o executar. Não há faturação medida como numa API cloud, por isso, assim que a sua GPU estiver a funcionar, gerar mais um milhão de embeddings custa essencialmente nada na margem.
Qual é o modelo de embedding padrão ou melhor do Ollama para RAG?
nomic-embed-text a 768 dimensões é a predefinição popular para RAG local; supera o antigo ada-002 da OpenAI e funciona em hardware modesto. Para trabalho multilingue ou de contexto longo, bge-m3 ou qwen3-embedding são mais fortes. Para a comparação classificada e pontuada entre fornecedores, veja o nosso hub de modelos de embedding.
Porque é que os meus embeddings do Ollama são lentos ou dão timeout?
O primeiro pedido após inatividade paga um arranque a frio enquanto o modelo carrega na VRAM, cerca de 1,3 segundos na nossa RTX 4090. O Ollama também descarrega o modelo após cerca de 5 minutos de inatividade por predefinição, por isso a lentidão intermitente é geralmente um arranque a frio repetido. Defina keep_alive: -1 para fixar o modelo na VRAM.
O Ollama pode corresponder aos embeddings de 1536 dimensões da OpenAI?
Nenhum modelo Ollama é nativamente de 1536 dimensões, por isso migrar uma coluna VECTOR(1536) existente falha devido a uma incompatibilidade de dimensões. Corrija chamando /v1/embeddings com um parâmetro dimensions num modelo Matryoshka como qwen3-embedding ou embeddinggemma, ou redeclare a sua coluna para o tamanho nativo do modelo, como VECTOR(768).
Preciso de uma GPU para executar modelos de embedding localmente?
Não. Modelos pequenos como nomic-embed-text (137M) e all-minilm (22M) funcionam bem em CPU para baixo volume. Uma GPU reduz a latência por embedding para milissegundos de um dígito e aumenta o throughput em lote para centenas de embeddings por segundo, o que importa quando está a indexar milhares de fragmentos de uma vez.
Como uso embeddings do Ollama em Python ou LangChain?
A chamada do cliente oficial é ollama.embed(model="nomic-embed-text", input=["fragmento a", "fragmento b"]), que devolve uma lista embeddings. Em LangChain, use a classe OllamaEmbeddings apontada para http://localhost:11434, depois passe-a para o método from_documents ou add_texts do seu armazenamento vetorial como qualquer outro fornecedor de embeddings.
Que comprimento de contexto podem os modelos de embedding do Ollama suportar?
Varia por modelo. nomic-embed-text suporta nativamente 8192 tokens, mas tem por predefinição uma janela de 2048 tokens quando servido, por isso aumente num_ctx para 8192 para fragmentos longos ou serão truncados silenciosamente. bge-m3 lida com 8192 e qwen3-embedding vai até 32K; all-minilm está limitado a 256 tokens.