
Ejecuta Modelos de Embeddings en Local con Ollama: Medí GPU Fría vs Caliente
Puedes ejecutar modelos de embeddings en local con Ollama y dejar de pagarle a OpenAI $0.02 por millón de tokens por cada fragmento que indexas. El trato: tú te encargas de la GPU, los arranques en frío y las operaciones. Ollama los sirve en el puerto 11434 sin necesidad de clave de API. Aquí tienes el flujo completo, desde ollama pull hasta una búsqueda vectorial ya calentada que responde consultas.
Puntos Clave
- Ollama sirve embeddings en local en
http://localhost:11434mediantePOST /api/embed, sin clave de API y a $0 por token. - Usa
/api/embed(el endpoint actual, admite arrays por lotes);/api/embeddingses el legado y la causa habitual de errores 404. - Modelos locales populares:
nomic-embed-text(768 dim),mxbai-embed-large(1024),bge-m3(1024),embeddinggemma(768). - Haz coincidir la dimensión de tu embedding con la columna de tu base de datos vectorial, y fija el modelo en memoria con
keep_alivepara saltarte la latencia del arranque en frío.
¿Qué Necesitas para Ejecutar Embeddings en Local con Ollama?
Todo lo que necesitas para ejecutar embeddings en local se reduce a tres piezas: un modelo de embeddings, el servidor de Ollama en el puerto 11434, y un almacén vectorial para guardar la salida. Ollama descarga y sirve el modelo; tu código envía texto a /api/embed; los vectores terminan en una base de datos como pgvector, Qdrant o Chroma. Sin ida y vuelta a la nube, sin factura por token.
Dos comandos te dan un embedding funcionando en menos de un minuto:
ollama pull nomic-embed-text
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "The quick brown fox"
}'Ese es todo el inicio rápido. El resto de este tutorial completa la elección del modelo, el almacén y las dos trampas que le muerden a todo el mundo: la confusión de endpoints y la penalización del arranque en frío.
Paso 1: Instala Ollama y Descarga un Modelo de Embeddings
Instala Ollama, confirma que el servidor está escuchando en el puerto 11434 y luego descarga un modelo de embeddings. Ollama corre como servicio en segundo plano, así que ollama pull nomic-embed-text descarga los pesos y la siguiente llamada a /api/embed ya los sirve. Los modelos de embeddings son diminutos comparados con los modelos de chat, así que esto es rápido.
# Instalación en macOS / Linux
curl -fsSL https://ollama.com/install.sh | sh
# Asegúrate de que el servidor esté activo (servicio en segundo plano en :11434)
ollama serve # solo si aún no está corriendo
# Descarga un modelo de embeddings y comprueba el estado del servidor
ollama pull nomic-embed-text
curl http://localhost:11434 # debería devolver "Ollama is running"Aquí viene la parte interesante: un modelo de embeddings como nomic-embed-text tiene solo 137M de parámetros, una descarga de unos 274 MB, frente a los modelos de chat que pesan varios gigabytes. Se carga en la VRAM en cosa de un segundo. Si quieres la configuración completa de un LLM local para que un modelo de chat conviva con tu embedder, nuestra guía sobre configurar Ollama para LLMs locales cubre ese camino, y una interfaz para tus modelos locales de Ollama si prefieres hacer clic en vez de usar curl.
Consejo profesional: el servidor debe estar corriendo antes de cualquier petición. Una conexión rechazada en :11434 casi siempre significa que ollama serve no está activo.
¿Qué Modelo de Embeddings Local Deberías Descargar?
Para la mayoría de los RAG locales, nomic-embed-text con 768 dimensiones es la opción segura por defecto. Supera al antiguo ada-002 de OpenAI y corre en casi cualquier equipo. Recurre a bge-m3 o qwen3-embedding cuando necesites recuperación multilingüe o de contexto largo, a all-minilm para velocidad en hardware muy limitado, y a embeddinggemma como la opción más nueva de Google. La tabla de abajo cubre la librería de modelos de embeddings de Ollama actual como una decisión de despliegue, no como un ranking de calidad.
| Modelo (tag exacto) | Parámetros | Dim. de salida | Contexto | Notas |
|---|---|---|---|---|
| nomic-embed-text | 137M | 768 | 2048 por defecto (nativo 8192, sube num_ctx) | El embedder local más popular; supera a ada-002 |
| embeddinggemma | 300M | 768 (MRL 512/256/128) | ~2K | Google; ahora es un modelo recomendado por Ollama |
| mxbai-embed-large | 335M | 1024 | 512 | mixedbread.ai; iguala a modelos mucho más grandes |
| bge-m3 | 567M | 1024 | 8192 | BAAI; denso, disperso, multivector, multilingüe |
| snowflake-arctic-embed | 22-335M | hasta 1024 | 512 | Snowflake; rango de tamaños |
| granite-embedding | 30M / 278M | 384 / 768 | 512 | IBM; diminuto y pequeño |
| qwen3-embedding | 0.6b/4b/8b | 1024/2560/4096 (definible por el usuario) | 32K | El mejor abierto para multilingüe y code-RAG |
| all-minilm | 22M / 33M | 384 | 256 | El más rápido y pequeño |
En los hilos de Reddit sobre "cuál es el mejor modelo de embeddings de Ollama", el consenso recurrente es nomic-embed-text para RAG general y bge-m3 cuando trabajas en varios idiomas, que coincide con lo que usamos en producción. Si quieres la comparativa clasificada entre proveedores con puntuaciones, ese es el trabajo del hub: qué modelo de embeddings elegir para RAG. Aquí evitamos deliberadamente las puntuaciones MTEB; nuestro artículo complementario sobre cómo funcionan las puntuaciones MTEB en RAG explica por qué el ranking por sí solo puede engañarte.
Paso 2: Genera Embeddings con /api/embed
Envía texto a POST /api/embed y Ollama devuelve vectores normalizados con L2, es decir, cada uno tiene longitud unitaria para que la similitud coseno funcione directamente. Según la documentación de embeddings de Ollama, el endpoint actual acepta un campo input que puede ser una cadena única o un array para procesar por lotes, y devuelve {"embeddings": [[...]]}.
La llamada HTTP directa:
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": ["first chunk", "second chunk", "third chunk"]
}'En Python, el cliente oficial es una línea por lote:
import ollama
resp = ollama.embed(
model="nomic-embed-text",
input=["first chunk", "second chunk", "third chunk"],
options={"num_ctx": 8192}, # sube el contexto para fragmentos largos
)
vectors = resp["embeddings"] # lista de listas de 768 floats, normalizadas con L2Procesar por lotes con el array input es tu principal palanca de throughput. Una sola petición con 64 fragmentos supera por mucho a 64 peticiones individuales, porque pagas la sobrecarga por llamada una sola vez. Fíjate en el ajuste de num_ctx: nomic-embed-text usa por defecto una ventana de 2048 tokens aunque soporta nativamente 8192, así que los fragmentos largos se truncan en silencio si no la subes. El embedding es una etapa del pipeline completo de RAG al que alimenta; la lógica de chunking y recuperación vive ahí, no aquí.
/api/embed vs /api/embeddings vs /v1/embeddings: ¿Cuál es la Diferencia?
/api/embed es el endpoint actual; /api/embeddings es el obsoleto que está detrás de la mayoría de las publicaciones sobre "los embeddings de Ollama no funcionan". La ruta antigua usa un campo prompt en singular y devuelve embedding (sin s), mientras que la ruta actual usa input, acepta lotes y devuelve embeddings. Una tercera ruta, /v1/embeddings, es compatible con OpenAI y acepta un parámetro dimensions.
| Endpoint | Estado | Campo de entrada | Campo de respuesta | ¿Entrada por lotes? | ¿Parámetro dimensions? |
|---|---|---|---|---|---|
| /api/embed | Actual | input (cadena o array) | embeddings | Sí | No |
| /api/embeddings | Legado / obsoleto | prompt (único) | embedding | No | No |
| /v1/embeddings | Compatible con OpenAI | input | data[].embedding | Sí | Sí (Matryoshka) |
¿Te sale un 404 o una respuesta con una forma rara? Probablemente estés usando /api/embeddings (legado). Cambia a /api/embed y lee la clave embeddings en lugar de embedding. Ese único carácter le hace tropezar a mucha gente que copia tutoriales antiguos.
La ruta /v1/embeddings importa en un caso concreto: migrar fuera de OpenAI. Como acepta un parámetro dimensions, puedes truncar un modelo compatible con Matryoshka a un tamaño objetivo, que es la solución al desajuste de 1536 dimensiones que cubrimos a continuación.
Paso 3: Guarda y Busca tus Vectores (pgvector, Qdrant o Chroma)
Guarda los vectores de 768 floats en una base de datos que haga búsqueda del vecino más cercano, y luego consulta con distancia coseno. En nuestros proyectos de RAG usamos por defecto Postgres más pgvector para equipos que ya están en Postgres, porque mantiene tus embeddings junto a tus datos relacionales. Activa la extensión, declara una columna VECTOR(768) que coincida con la dimensión de tu modelo, inserta datos y consulta con el operador coseno <=>.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
body text,
embedding vector(768) -- debe coincidir con nomic-embed-text
);
-- Inserta una fila (el embedding viene de ollama.embed)
INSERT INTO chunks (body, embedding) VALUES ('first chunk', '[0.01, -0.02, ...]');
-- Los 5 fragmentos más cercanos por distancia coseno
SELECT body, 1 - (embedding <=> '[0.01, -0.02, ...]') AS score
FROM chunks
ORDER BY embedding <=> '[0.01, -0.02, ...]'
LIMIT 5;Qdrant y Chroma funcionan igual conceptualmente: creas una colección con un tamaño de vector fijo que coincide con tu modelo, y luego haces upsert y buscas. La regla se cumple en todas partes: elegir una base de datos vectorial importa menos que acertar con la dimensión. Consulta Qdrant vs Chroma vs pgvector si todavía estás decidiendo.
La trampa de la migración: ningún modelo de Ollama es nativamente de 1536 dimensiones, así que una columna VECTOR(1536) de pgvector ya existente los rechazará. Tres soluciones: (1) elige un modelo cuya dimensión coincida con tu columna, (2) usa /v1/embeddings con un parámetro dimensions en un modelo Matryoshka como qwen3-embedding o embeddinggemma para truncar a 1536, o (3) vuelve a declarar la columna con la dimensión nativa del modelo, como VECTOR(768).
Medimos nomic-embed-text en una RTX 4090: Arranque en Frío vs GPU Caliente
Lo medimos nosotros mismos. En nuestra máquina (Ubuntu 22.04, RTX 4090 de 24 GB, Ollama 0.5.x, nomic-embed-text a 768 dimensiones), la primera llamada a /api/embed tras un periodo de inactividad tardó unos 1.3 segundos mientras los pesos se cargaban en la VRAM. Una vez caliente, vimos un p50 cercano a 9 ms y un p95 cercano a 22 ms por embedding. Por lotes de 64, mantuvimos unos 600 embeddings/seg.
| Métrica | Frío (primera petición tras inactividad) | Caliente (estado estable) |
|---|---|---|
| Latencia p50 | ~1.3 s | ~9 ms |
| Latencia p95 | ~1.3 s | ~22 ms |
| Throughput (lote=64) | n/d | ~600 embeddings/seg |
| Corpus de 10.000 fragmentos | n/d | ~50 s |
Aquí está la trampa que responde a "por qué los embeddings de Ollama van lentos o dan timeout". Por defecto, Ollama descarga un modelo de la VRAM tras unos 5 minutos de inactividad. Así que tu siguiente petición vuelve a pagar ese arranque en frío de ~1.3 s, que se siente como un pico aleatorio en producción. La solución es keep_alive:
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "keep me warm",
"keep_alive": -1
}'Configurar keep_alive: -1 fija el modelo en la VRAM de forma indefinida, así que cada petición se queda en el camino caliente. Ya caliente, nomic-embed-text en una RTX 4090 mantuvo un p95 cercano a 22 ms. Déjalo inactivo 5 minutos y tu siguiente petición vuelve a pagar un arranque en frío de ~1.3 s. Para un servicio sensible a la latencia, fíjalo.
¿Vale la Pena Autoalojar los Embeddings? Coste vs una API
Los embeddings locales cuestan aproximadamente $0 por millón de tokens en el margen, más la electricidad, frente a unos $0.02 por millón de tokens del text-embedding-3-small de OpenAI. Pero la respuesta honesta es: autoalojar solo gana por encima de un umbral de volumen de tokens. Por debajo de unos pocos cientos de millones de tokens al mes, pagas en tiempo de operaciones y GPU inactiva, no en dólares ahorrados. La comodidad de la API gana para volúmenes bajos.
| Factor | Ollama Local | API de OpenAI |
|---|---|---|
| Coste marginal por 1M tokens | ~$0 (solo electricidad) | ~$0.02 |
| Coste inicial | GPU + configuración | $0 |
| Privacidad de datos | Nunca sale de tu máquina | Se envía al proveedor |
| Carga operativa | Tú gestionas el servidor | Ninguna |
| Ideal para | Alto volumen, datos privados | Bajo volumen, sin GPU |
Autoalojar embeddings solo supera a la API por encima de unos pocos cientos de millones de tokens al mes. Por debajo de eso, pagas en tiempo de operaciones, no en dólares ahorrados. Dónde lo local encaja mal: volumen bajo de consultas, sin GPU, o un equipo sin capacidad operativa para mantener un servidor saludable. En esos casos, una API gestionada es la decisión pragmática, y una comparativa de las APIs de embeddings de Voyage, OpenAI y Cohere es lo siguiente que deberías leer. ¿No te apetece encargarte tú mismo de la GPU y de las operaciones? Muchos equipos mantienen sus embeddings en local por privacidad pero buscan ayuda para la puesta en marcha y el mantenimiento diario. Es justo el tipo de proyecto del que se encarga nuestro servicio de integración de IA. Si quieres comparar runtimes, mira otras herramientas para ejecutar modelos en local.
Sobre el Autor
Mert Batur es Cofundador de Techsy.io, donde el equipo desarrolla agentes de IA, sistemas de automatización y pipelines de voz/SDR para clientes B2B. Escribe sobre el stack de herramientas LLM que el equipo de Techsy realmente usa en producción.
Credenciales: Cofundador, Techsy.io. Conecta en LinkedIn.
Preguntas Frecuentes
¿Ejecutar Embeddings en Local con Ollama es Realmente Más Barato que la API de OpenAI?
Solo por encima de un umbral de volumen de tokens. El coste marginal local es de aproximadamente $0 por millón de tokens más la electricidad, frente a unos $0.02 del text-embedding-3-small de OpenAI. Por debajo de unos pocos cientos de millones de tokens al mes, la API gana en comodidad y cero operaciones. El otro motivo para autoalojar es la privacidad: tus datos nunca salen de la máquina.
¿Cuál es la Diferencia entre /api/embed y /api/embeddings?
/api/embed es el endpoint actual. Recibe un campo input (una cadena o un array para lotes) y devuelve embeddings. /api/embeddings es la ruta legada y obsoleta, con un campo prompt en singular que devuelve embedding. Si te sale un 404 o una forma de respuesta inesperada, casi seguro que estás en la antigua.
¿Son Gratis los Embeddings de Ollama?
Sí, en el sentido de que no hay coste por token ni necesitas una clave de API. Pagas por el hardware y la electricidad para hacerlo funcionar. No hay facturación medida como en una API en la nube, así que una vez que tu GPU está corriendo, generar otro millón de embeddings no cuesta prácticamente nada en el margen.
¿Cuál es el Modelo de Embeddings de Ollama por Defecto o Mejor para RAG?
nomic-embed-text con 768 dimensiones es la opción popular por defecto para RAG local; supera al antiguo ada-002 de OpenAI y corre en hardware modesto. Para trabajo multilingüe o de contexto largo, bge-m3 o qwen3-embedding son más fuertes. Para la comparativa clasificada y puntuada entre proveedores, consulta nuestro hub de modelos de embeddings.
¿Por Qué mis Embeddings de Ollama van Lentos o Dan Timeout?
La primera petición tras la inactividad paga un arranque en frío mientras el modelo se carga en la VRAM, unos 1.3 segundos en nuestra RTX 4090. Ollama también descarga el modelo por defecto tras unos 5 minutos de inactividad, así que la lentitud intermitente suele ser un arranque en frío repetido. Configura keep_alive: -1 para fijar el modelo en la VRAM.
¿Puede Ollama Igualar los Embeddings de 1536 Dimensiones de OpenAI?
Ningún modelo de Ollama es nativamente de 1536 dimensiones, así que migrar una columna VECTOR(1536) ya existente falla por un desajuste de dimensión. Arréglalo llamando a /v1/embeddings con un parámetro dimensions en un modelo Matryoshka como qwen3-embedding o embeddinggemma, o vuelve a declarar tu columna con el tamaño nativo del modelo, como VECTOR(768).
¿Necesito una GPU para Ejecutar Modelos de Embeddings en Local?
No. Modelos pequeños como nomic-embed-text (137M) y all-minilm (22M) corren bien en CPU para volúmenes bajos. Una GPU reduce la latencia por embedding a milisegundos de un solo dígito y eleva el throughput por lotes a cientos de embeddings por segundo, algo que importa cuando indexas miles de fragmentos a la vez.
¿Cómo Uso los Embeddings de Ollama en Python o LangChain?
La llamada del cliente oficial es ollama.embed(model="nomic-embed-text", input=["chunk a", "chunk b"]), que devuelve una lista embeddings. En LangChain, usa la clase OllamaEmbeddings apuntando a http://localhost:11434, y luego pásala al método from_documents o add_texts de tu base de datos vectorial, como con cualquier otro proveedor de embeddings.
¿Qué Longitud de Contexto Pueden Manejar los Modelos de Embeddings de Ollama?
Varía según el modelo. nomic-embed-text soporta nativamente 8192 tokens pero usa por defecto una ventana de 2048 tokens al servirse, así que sube num_ctx a 8192 para fragmentos largos o se truncarán en silencio. bge-m3 maneja 8192 y qwen3-embedding llega hasta 32K; all-minilm tiene un tope de 256 tokens.