Exécuter EmbeddingGemma 2 sur un VPS CPU uniquement
Ollama 0.40 ne supporte pas EmbeddingGemma 2 sur CPU Linux. Utilisez sentence-transformers pour générer vos vecteurs 256d et les intégrer directement dans pgvector.
Comment exécuter EmbeddingGemma 2 sur un VPS avec CPU uniquement
Pour exécuter EmbeddingGemma 2 aujourd’hui sur un VPS avec CPU uniquement, servez-le avec sentence-transformers derrière un petit endpoint HTTP sur localhost. Ollama est l’outil évident, mais au 7 octobre 2026, les builds de sa library pour ce modèle nécessitent le moteur MLX d’Ollama. Sous Linux, ce moteur nécessite un GPU NVIDIA avec CUDA 13. Ce guide montre le contrôle qui le prouve sur votre propre serveur. Il met ensuite en place la méthode fonctionnelle : une API d’embeddings de texte sur 127.0.0.1 qui renvoie des vecteurs de dimension 256 dans une table pgvector interrogeable.
Vous exécutez vous-même chaque commande de ce guide sur votre propre VPS. Le téléchargement du modèle nécessite un accès sortant au hub Hugging Face, et le contrôle d’Ollama nécessite un accès au registre Ollama. Un serveur sans accès sortant à Internet ne peut pas exécuter ce guide jusqu’au bout.
Versions utilisées : Ollama 0.40.0 (publié le 2026-09-25) et le tag embeddinggemma-2:270m pour le contrôle d’Ollama, ainsi que le checkpoint google/embeddinggemma-2 sur Hugging Face pour la méthode fonctionnelle. Le système d’exploitation est Ubuntu 24.04.
Qu'est-ce qu'EmbeddingGemma 2 et quelle partie ce guide utilise
EmbeddingGemma 2 est un modèle d'embedding ouvert de Google DeepMind, publié sous licence Apache 2.0. Un modèle d'embedding transforme un texte en une liste fixe de nombres, appelée vecteur. Les textes ayant une signification similaire obtiennent des vecteurs pointant dans des directions proches. Il s'agit de la moitié « récupération » d'un pipeline RAG (génération augmentée par récupération).
La fiche du modèle sur Hugging Face indique un total de 740M de paramètres. La base textuelle en représente 270M. Un encodeur visuel ajoute 170M et un encodeur audio 300M ; vous pouvez ignorer ces deux derniers lors du chargement. La sortie est un vecteur de 768 nombres. Le modèle a été entraîné avec MRL (Matryoshka representation learning), ce qui signifie que les 512, 256 ou 128 premiers nombres de ce vecteur fonctionnent toujours comme un embedding autonome. La fenêtre de contexte est de 8 192 tokens, et chaque type d'entrée partage ce même budget.
Ce guide ne couvre que le texte. Les embeddings d'images, de vidéos et d'audio passent par sentence-transformers ou Transformers, comme décrit sur la fiche Hugging Face, les fichiers multimédias étant transmis en plus du texte. Ils ne passent pas par Ollama. Le point de terminaison documenté /api/embed d'Ollama n'accepte qu'une chaîne ou une liste de chaînes de caractères. Par conséquent, même sur une machine où Ollama exécute ce modèle, il ne peut pas traiter l'embedding d'une image.
Pourquoi Ollama ne peut pas encore servir EmbeddingGemma 2 sur un CPU Linux
La page de la bibliothèque Ollama, consultée le 2026-10-07, répertorie quatre tailles. Les types d’entrée ci-dessous sont repris de cette page :
embeddinggemma-2:270m: 378MB, entrée indiquée comme Textembeddinggemma-2:440m: 714MB, entrée indiquée comme Text, Imageembeddinggemma-2:570m: 990MB, entrée indiquée comme Textembeddinggemma-2:740m: 1.3GB, entrée indiquée comme Text, Image, avec également le taglatest
La page indique que 570m accepte uniquement Text, alors que la fiche Hugging Face décrit la version 570M comme acceptant le texte et l’audio. Cette différence n’a pas d’importance ici, car /api/embed n’accepte que le texte.
Les tailles correspondent aux builds 4-bit indiqués sur la page des tags. Par exemple, 270m-nvfp4-text fait également 378MB, et les trois autres tailles correspondent de la même manière à leurs builds nvfp4. Les notes de version d’Ollama 0.40.0 ajoutent ce modèle au moteur MLX : « MLX prend désormais en charge un modèle d’embedding : embeddinggemma-2 ». MLX est le framework de machine learning d’Apple, d’abord conçu pour Apple silicon. MLX fournit également un build Linux utilisable uniquement sur CPU (pip install mlx[cpu]), mais ce n’est pas celui qu’utilise Ollama. La documentation de développement d’Ollama active le moteur MLX par défaut uniquement sur les Mac équipés d’Apple silicon. Sous Linux, ce moteur est facultatif ; la documentation indique CUDA 13 et cuDNN 9 comme prérequis et ne décrit aucun backend MLX utilisable uniquement sur CPU. Un VPS uniquement équipé d’un CPU ne dispose ni de CUDA ni de cuDNN.
Un rapport de bug ouvert le 2026-10-06 (ollama/ollama issue 18825, toujours ouvert le 2026-10-12) montre qu’une machine Linux utilisant Ollama 0.40.0 échoue à télécharger le tag 740m avec le message suivant :
this model requires MLX support, but the MLX runtime is not availableVérifiez votre propre machine au lieu de vous fier à un rapport. Épinglez la version d’Ollama afin que votre résultat soit comparable :
curl -fsSL https://ollama.com/install.sh | OLLAMA_VERSION=0.40.0 sh
ollama --version
ollama pull embeddinggemma-2:270mollama --version doit afficher 0.40.0. Si le téléchargement se termine par le message MLX ci-dessus, suivez la procédure sentence-transformers dans la suite de ce guide. Si une version ultérieure d’Ollama télécharge le modèle et que ollama show embeddinggemma-2:270m affiche ses détails, passez à la dernière section, consacrée au passage à Ollama.
Les versions stables publiées depuis, jusqu’à la version 0.40.3 du 11 octobre 2026, ne mentionnent pas ce modèle dans leurs notes de version. Une piste mérite toutefois d’être surveillée. Ollama 0.40.4-rc0, une préversion publiée le 12 octobre 2026, met à jour le llama.cpp inclus « pour les modèles de décision et EmbeddingGemma 2 ». llama.cpp est le moteur qu’Ollama utilise en dehors de MLX, et il fonctionne sur CPU. Les notes n’indiquent pas si les tags de la bibliothèque changent. Considérez donc cette version comme une piste jusqu’à ce qu’une version stable télécharge le modèle sur votre machine.
La page des tags répertorie également des builds bf16, notamment 270m-bf16-text, qui fait 574MB. La documentation d’Ollama n’indique pas si son build Linux peut exécuter ces builds sur CPU. ollama pull embeddinggemma-2:270m-bf16-text permet de le vérifier sur votre machine.
Deux autres détails de la page de la bibliothèque sont importants. La liste des tags indique une « fenêtre de contexte de 256K » pour chaque tag, tandis que le readme de la même page et la fiche Hugging Face indiquent tous deux 8,192 tokens. Prévoyez 8,192 tokens. La page n’affiche par ailleurs aucune couche de template pour le tag 270m, uniquement des fichiers de configuration, de tokenizer et de poids. Ollama n’ajoute pas les préfixes de tâche décrits ci-dessous. L’appelant doit donc les ajouter, quel que soit le serveur qui traite la requête.
Installation d'une stack Python CPU uniquement
Le paquet PyTorch par défaut de PyPI installe des bibliothèques CUDA inutiles sur un VPS sans GPU, ce qui consomme plusieurs gigaoctets d'espace disque. Installez d'abord la version CPU depuis le dépôt officiel de PyTorch, puis le reste.
sudo apt update
sudo apt install -y python3-venv
sudo mkdir -p /opt/eg2
sudo python3 -m venv /opt/eg2/venv
sudo /opt/eg2/venv/bin/pip install torch --index-url https://download.pytorch.org/whl/cpu
sudo /opt/eg2/venv/bin/pip install -U sentence-transformers transformers fastapi uvicorn "psycopg[binary]" pgvector
/opt/eg2/venv/bin/python -c 'import torch, sentence_transformers, transformers; print(torch.__version__, sentence_transformers.__version__, transformers.__version__)'La dernière ligne doit afficher trois numéros de version, et la version de torch doit se terminer par +cpu. Notez ces numéros avec la date, car ils constituent votre référence pour l'environnement Python. EmbeddingGemma 2 est récent. Une version plus ancienne de transformers ne reconnaît pas son architecture, ce qui provoque l'échec du chargement avec un message indiquant que Transformers "ne reconnaît pas cette architecture". Relancez la ligne pip install -U pour corriger le problème.
Écrire le service d’embeddings
Le service ci-dessous charge une seule fois le backbone de traitement du texte et répond aux POST /api/embed. La requête et la réponse utilisent les mêmes noms de champs que les /api/embed documentés d’Ollama : model, input (une chaîne ou une liste de chaînes), dimensions et embeddings dans la réponse. C’est volontaire. Lorsqu’Ollama peut exécuter ce modèle sur votre machine, votre code client ne change que par une URL et un nom de modèle.
sudo tee /opt/eg2/eg2_server.py > /dev/null <<'EOF'
import torch
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sentence_transformers import SentenceTransformer
MODEL_ID = "google/embeddinggemma-2"
DIMS = (128, 256, 512, 768)
# Text only: the vision and audio encoders are not loaded into RAM.
# float32 on CPU, as the model card advises. Without it, Transformers 5 loads bfloat16.
st = SentenceTransformer(
MODEL_ID,
device="cpu",
config_kwargs={"vision_config": None, "audio_config": None},
model_kwargs={"dtype": torch.float32},
)
# The caller adds the task prefix, exactly as with Ollama.
# Make sure the library never adds one of its own on top.
st.default_prompt_name = None
print("loaded", MODEL_ID, flush=True)
app = FastAPI()
class EmbedRequest(BaseModel):
model: str = "embeddinggemma-2"
input: str | list[str]
dimensions: int = 768
@app.post("/api/embed")
def embed(req: EmbedRequest):
if req.dimensions not in DIMS:
raise HTTPException(status_code=400, detail=f"dimensions must be one of {DIMS}")
texts = [req.input] if isinstance(req.input, str) else req.input
vectors = st.encode(
texts,
batch_size=16,
truncate_dim=req.dimensions,
normalize_embeddings=True,
)
return {"model": req.model, "embeddings": vectors.tolist()}
EOFconfig_kwargs={"vision_config": None, "audio_config": None} est le paramètre texte uniquement défini par la fiche Hugging Face pour sentence-transformers. Il charge le backbone texte de 270M paramètres et laisse les deux autres encodeurs hors de la mémoire.
model_kwargs={"dtype": torch.float32} définit la précision des poids, et vous devez demander float32. Depuis Transformers 5.0.0 (janvier 2026), from_pretrained prend par défaut la valeur dtype="auto", qui correspond au dtype enregistré dans la configuration du checkpoint, et le config.json de ce checkpoint indique bfloat16. sentence-transformers transmet model_kwargs directement à Transformers et ne définit aucun dtype de son côté. Si vous supprimez cette ligne, le modèle utilise donc bfloat16. La fiche déconseille float16, car les activations du modèle dépassent la plage que float16 peut représenter. Elle recommande float32 sur le matériel qui ne prend pas en charge bfloat16 nativement, « notamment sur la plupart des CPU ». L’exemple fourni par la fiche utilise l’ancien nom d’argument torch_dtype, que Transformers 5 accepte toujours mais marque comme obsolète au profit de dtype.
truncate_dim tronque le vecteur à la longueur demandée, puis normalize_embeddings=True le remet à une longueur de 1. La fiche indique que cette deuxième étape est nécessaire. Une tranche d’un vecteur de longueur 1 n’a plus une longueur unitaire. Les scores de cosinus calculés sur cette tranche brute sont donc incorrects. La vérification de la section suivante confirme que le service gère correctement ce point.
Exécuter le service avec systemd
sudo tee /etc/systemd/system/eg2.service > /dev/null <<'EOF'
[Unit]
Description=EmbeddingGemma 2 text embedding API
After=network-online.target
Wants=network-online.target
[Service]
DynamicUser=yes
CacheDirectory=eg2
Environment=HF_HOME=/var/cache/eg2
Environment=HOME=/var/cache/eg2
WorkingDirectory=/opt/eg2
ExecStart=/opt/eg2/venv/bin/uvicorn eg2_server:app --host 127.0.0.1 --port 11435
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now eg2
journalctl -u eg2 -fDynamicUser=yes exécute le service en tant qu'utilisateur temporaire sans shell de connexion et sans accès en écriture en dehors de ses propres répertoires. CacheDirectory=eg2 attribue à cet utilisateur /var/cache/eg2, et HF_HOME pointe le téléchargement Hugging Face vers cet emplacement. Le modèle est ainsi téléchargé une seule fois et persiste après les redémarrages.
Le premier démarrage télécharge le checkpoint, ce qui prend du temps. Attendez l'apparition de deux lignes dans le journal : loaded google/embeddinggemma-2, puis Uvicorn running on http://127.0.0.1:11435. Uvicorn n'ouvre le port qu'une fois le modèle chargé. Jusque-là, curl échoue avec curl: (7) Failed to connect to 127.0.0.1 port 11435, ce qui est normal.
Si le journal affiche A process of this unit has been killed by the OOM killer à la place, le VPS a manqué de RAM lors du chargement du modèle. La section RAM ci-dessous explique comment dimensionner la mémoire nécessaire.
Vérification de la sortie : 256 nombres, longueur 1
curl -s http://127.0.0.1:11435/api/embed \
-H 'Content-Type: application/json' \
-d '{"model": "embeddinggemma-2", "input": ["task: search result | query: how do I add swap?"], "dimensions": 256}' \
| python3 -c 'import json,sys; v=json.load(sys.stdin)["embeddings"][0]; print(len(v), round(sum(x*x for x in v) ** 0.5, 4))'Un service sain affiche 256 1.0. Le premier nombre correspond à la longueur du vecteur demandée. Le second représente la longueur du vecteur dans l'espace, et 1.0 signifie que le vecteur tronqué a été normalisé à nouveau. Une requête avec "dimensions": 300 renvoie une erreur HTTP 400 avec dimensions must be one of (128, 256, 512, 768), car MRL n'a été entraîné que sur ces quatre points de coupure. Conservez l'en-tête Content-Type. FastAPI n'analyse le corps de la requête en tant que JSON que si cet en-tête l'indique ; sans lui, vous obtiendrez une erreur 422.
Ajout manuel des préfixes de tâche
Gemma 2 a été entraîné avec un court préfixe textuel qui nomme la tâche pour laquelle chaque embedding est généré. Pour la recherche, la fiche Hugging Face fournit ces deux éléments, copiés tels quels :
task: search result | query: {query}
title: {title} | text: {content}Le premier doit être placé devant chaque requête de recherche. Le second doit être placé devant chaque document que vous stockez. Lorsqu'un document n'a pas de titre, la fiche indique d'écrire title: none. La recherche est asymétrique, ce qui signifie que les requêtes et les documents reçoivent des préfixes différents, et c'est intentionnel. La fiche liste également d'autres préfixes de requête, tels que task: question answering | query: {question} et task: code retrieval | query: {query}. Ils sont associés au même préfixe de document.
Aucun serveur n'ajoute ces préfixes pour vous. Le service ci-dessus désactive le prompt par défaut de la bibliothèque, et /api/embed d'Ollama transmet votre chaîne de caractères telle quelle. Insérez les préfixes dans votre code client, à un seul endroit, comme le fait la section pgvector ci-dessous. Ainsi, un changement ultérieur de serveur ne pourra pas les supprimer silencieusement.
Quelle quantité de RAM utilise-t-il et quelle est sa vitesse ?
Ces deux valeurs dépendent de votre CPU et de la longueur de vos textes. Ce guide ne donne donc pas de chiffre. Mesurez-les avec votre propre plan. Vous pouvez déterminer un élément sans mesure : float32 utilise 4 octets par paramètre. Le backbone texte de 270M représente donc environ 1.1GB de poids, et le modèle complet de 740M environ 3.0GB. C’est la précision demandée par le service ci-dessus. Si vous omettez le paramètre dtype, Transformers charge bfloat16, qui utilise 2 octets par paramètre. Cela représente environ 0.54GB et 1.5GB. La carte recommande d’éviter cette configuration sur les CPU dépourvus de prise en charge native de bfloat16. PyTorch et Python ajoutent une surcharge. Considérez donc ces valeurs comme un minimum. Le même calcul s’applique à chaque modèle que vous hébergez. La page détermine la taille de modèle compatible avec la RAM de votre VPS l’explique pour les modèles de chat.
Pour connaître la quantité réelle de RAM, lisez la ligne Memory: de l’état du service après avoir exécuté une requête :
systemctl status eg2 --no-pager | grep MemoryPour mesurer le débit, envoyez un batch de 256 documents courts et mesurez le temps d’exécution :
/opt/eg2/venv/bin/python - <<'EOF'
import json, time, urllib.request
texts = [f"title: none | text: Test document {i} about swap files and server memory." for i in range(256)]
body = json.dumps({"model": "embeddinggemma-2", "input": texts, "dimensions": 256}).encode()
req = urllib.request.Request(
"http://127.0.0.1:11435/api/embed",
data=body,
headers={"Content-Type": "application/json"},
)
t = time.perf_counter()
with urllib.request.urlopen(req) as r:
n = len(json.load(r)["embeddings"])
dt = time.perf_counter() - t
print(f"{n} embeddings in {dt:.1f}s = {n / dt:.1f} per second")
EOFExécutez l’opération deux fois et conservez le deuxième résultat, car le premier lancement inclut les opérations de warm-up. Enregistrez le résultat avec la sortie de nproc et le modèle du CPU obtenu avec lscpu. Un chiffre n’a pas beaucoup de sens sans connaître la machine qui l’a produit. Vos documents réels sont plus longs que ces lignes de test, et les entrées plus longues nécessitent davantage de temps par embedding. Effectuez une nouvelle mesure avec un échantillon de vos propres textes avant de choisir un plan.
Les builds 270m et 740m renvoient-ils le même vecteur ?
Avec Ollama, ce test n’est pas encore possible sur un CPU Linux, car aucun de ces tags n’y fonctionne. Avec le checkpoint Hugging Face, vous pouvez tester la même question : le chargement text-only par rapport au chargement complet. La fiche indique que l’entrée texte utilise le même backbone texte de 270M dans chaque build. La similarité cosinus attendue doit donc être égale ou très proche de 1.0. Le script ci-dessous effectue cette vérification en float32, comme le service. Il nécessite environ 4GB de RAM libre, car il conserve successivement deux copies des poids en mémoire. Il télécharge également sa propre copie du modèle dans ~/.cache/huggingface.
/opt/eg2/venv/bin/python - <<'EOF'
import torch
from sentence_transformers import SentenceTransformer
text = ["title: none | text: A swap file lets the kernel move idle memory pages to disk."]
small = SentenceTransformer("google/embeddinggemma-2", device="cpu",
config_kwargs={"vision_config": None, "audio_config": None},
model_kwargs={"dtype": torch.float32})
small.default_prompt_name = None
a = small.encode(text, normalize_embeddings=True)
del small
full = SentenceTransformer("google/embeddinggemma-2", device="cpu",
model_kwargs={"dtype": torch.float32})
full.default_prompt_name = None
b = full.encode(text, normalize_embeddings=True)
print("cosine:", float((a * b).sum()))
EOFQuel que soit le résultat affiché, une règle s’applique. Utilisez le même build et la même dimension pour l’indexation et les requêtes. Les builds de la bibliothèque Ollama sont quantifiés en 4-bit. Ne vous attendez donc pas à ce que leurs vecteurs soient identiques à ceux en float32. Ne mélangez jamais les deux dans une même table.
Stocker des vecteurs de 256 dimensions dans pgvector
Les 256 dimensions occupent un tiers de l'espace des 768, aussi bien dans la table que dans l'index. Sur un petit VPS, c'est l'index HNSW (hierarchical navigable small world) qui doit tenir en RAM ; cette économie est donc plus importante que celle du disque. Construire un index HNSW pgvector sur un petit VPS détaille les paramètres d'indexation et leur coût en mémoire. Ubuntu 24.04 propose pgvector en tant que paquet pour PostgreSQL 16.
sudo apt install -y postgresql postgresql-16-pgvector
sudo -u postgres createuser "$USER"
sudo -u postgres createdb -O "$USER" rag
sudo -u postgres psql -d rag -c 'CREATE EXTENSION IF NOT EXISTS vector;'
psql -d rag <<'EOF'
CREATE TABLE docs (
id bigserial PRIMARY KEY,
title text NOT NULL,
body text NOT NULL,
embedding vector(256) NOT NULL
);
CREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops);
EOFLe rôle de base de données porte le même nom que votre identifiant de connexion, ainsi psql et les scripts ci-dessous se connectent via le socket local sans mot de passe. La colonne vector(256) rejette toute autre longueur. Un vecteur de 768 nombres échoue avec expected 256 dimensions, not 768. C'est l'erreur attendue si un client oublie le champ dimensions.
Passons au client. C'est le seul endroit qui contient les préfixes et les paramètres de requête.
mkdir -p ~/rag && cd ~/rag
cat > client.py <<'EOF'
import json
import urllib.request
URL = "http://127.0.0.1:11435/api/embed"
MODEL = "embeddinggemma-2"
DIM = 256
def _embed(texts):
body = json.dumps({"model": MODEL, "input": texts, "dimensions": DIM}).encode()
req = urllib.request.Request(URL, data=body, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as r:
return json.load(r)["embeddings"]
def embed_documents(docs):
return _embed([f"title: {title or 'none'} | text: {text}" for title, text in docs])
def embed_query(query):
return _embed([f"task: search result | query: {query}"])[0]
EOF
cat > index.py <<'EOF'
import numpy as np
import psycopg
from pgvector.psycopg import register_vector
from client import embed_documents
DOCS = [
("Firewall", "ufw denies inbound connections by default and allows outbound ones."),
("Swap", "A swap file lets the kernel move idle memory pages to disk when RAM runs low."),
("SSH keys", "Key-based login replaces passwords with a private key that stays on your laptop."),
]
with psycopg.connect("dbname=rag") as conn:
register_vector(conn)
for (title, body), vec in zip(DOCS, embed_documents(DOCS)):
conn.execute(
"INSERT INTO docs (title, body, embedding) VALUES (%s, %s, %s)",
(title, body, np.array(vec)),
)
print(f"indexed {len(DOCS)} documents")
EOF
cat > search.py <<'EOF'
import sys
import numpy as np
import psycopg
from pgvector.psycopg import register_vector
from client import embed_query
q = np.array(embed_query(" ".join(sys.argv[1:])))
with psycopg.connect("dbname=rag") as conn:
register_vector(conn)
rows = conn.execute(
"SELECT title, 1 - (embedding <=> %s) AS score FROM docs ORDER BY embedding <=> %s LIMIT 3",
(q, q),
).fetchall()
for title, score in rows:
print(f"{score:.3f} {title}")
EOF
/opt/eg2/venv/bin/python index.py
/opt/eg2/venv/bin/python search.py how do I stop running out of memoryindex.py devrait afficher indexed 3 documents. search.py affiche trois titres avec un score pour chacun, du plus élevé au plus bas, et le document Swap devrait arriver en tête pour cette question. <=> est la distance cosinus de pgvector, donc 1 - la convertit en un score de similarité. Exécutez index.py une seule fois, ou videz la table au préalable, car chaque exécution insère à nouveau les lignes. Pour le découpage de documents longs et la transmission des correspondances à un modèle de chat, le pipeline RAG complet auto-hébergé sur un VPS poursuit à partir de cette table.
Maintenir le port 11434 hors de l'internet public
Aucune de ces API ne dispose d'authentification. Toute personne pouvant atteindre le port peut utiliser votre CPU et, avec Ollama, elle peut également télécharger et supprimer des modèles. Ollama écoute par défaut sur 127.0.0.1:11434, et le service ci-dessus écoute sur 127.0.0.1:11435. Maintenez ces deux configurations. L'erreur habituelle consiste à définir OLLAMA_HOST=0.0.0.0:11434 pour accéder à Ollama depuis une autre machine. Cela expose le service à l'internet entier, sauf si un pare-feu le bloque. Vérifiez ce qui est réellement en écoute :
sudo ss -tlnp | grep -E ':1143[45]'Chaque ligne doit afficher 127.0.0.1 comme adresse locale. Une ligne avec 0.0.0.0 ou * signifie que le port est ouvert sur toutes les interfaces. Si une autre machine a besoin d'embeddings, utilisez un tunnel SSH plutôt qu'un port ouvert :
ssh -N -L 11435:127.0.0.1:11435 you@your-vpsLa machine distante appelle alors http://127.0.0.1:11435/api/embed comme si le service était local. Le fonctionnement du port et la manière dont les clients le trouvent sont détaillés dans l'API Ollama sur le port 11434. Si vous avez réellement besoin d'un point de terminaison accessible, sécuriser un point de terminaison d'API Ollama permet de placer un reverse proxy avec authentification devant celui-ci.
Passer à Ollama dès que le support Linux sera disponible
Relancez la vérification de la section Ollama après chaque version d'Ollama. Lorsque ollama pull embeddinggemma-2:270m réussit sur votre VPS, testez-le avec la même forme de requête :
curl -s http://127.0.0.1:11434/api/embed \
-d '{"model": "embeddinggemma-2:270m", "input": ["task: search result | query: how do I add swap?"], "dimensions": 256}' \
| python3 -c 'import json,sys; v=json.load(sys.stdin)["embeddings"][0]; print(len(v), round(sum(x*x for x in v) ** 0.5, 4))'Si cela affiche 256 et une longueur proche de 1.0, modifiez URL pour le port 11434 et MODEL pour embeddinggemma-2:270m dans client.py. Ensuite, videz la table et exécutez à nouveau index.py. Cette étape n'est pas facultative. Les vecteurs issus de la version 4-bit d'Ollama et du checkpoint float32 sont différents ; les anciennes lignes et les nouvelles requêtes ne seraient donc pas comparables sur les mêmes bases.
FAQ
Ollama peut-il exécuter EmbeddingGemma 2 sur un VPS Linux sans GPU ?
Pas avec Ollama 0.40.0, au 7 octobre 2026. Les notes de version de 0.40.0 ajoutent embeddinggemma-2 au moteur MLX d’Ollama. Sous Linux, ce moteur nécessite CUDA 13 et cuDNN 9. Un pull Linux échoue avec this model requires MLX support, but the MLX runtime is not available (issue 18825 d’ollama/ollama). Aucune version stable jusqu’à 0.40.3 (11 octobre 2026) ne mentionne de correctif. La préversion 0.40.4-rc0 met seulement à jour llama.cpp pour ce modèle. Servez le checkpoint Hugging Face avec sentence-transformers sur 127.0.0.1 à la place. Réexécutez ollama pull embeddinggemma-2:270m après chaque nouvelle version d’Ollama.
Ollama ajoute-t-il automatiquement les préfixes de tâche d’EmbeddingGemma 2 ?
Non. La page de la bibliothèque n’affiche aucun template pour ce modèle, et /api/embed intègre votre chaîne exactement telle qu’elle est envoyée. Ajoutez vous-même task: search result | query: devant chaque requête et title: {title} | text: devant chaque document. Utilisez title: none lorsqu’il n’y a pas de titre. La fiche Hugging Face indique ces préfixes, avec lesquels le modèle a été entraîné.
Quelle dimension d’embedding dois-je stocker dans pgvector ?
256 est une bonne valeur par défaut sur un petit VPS. L’entraînement MRL permet à EmbeddingGemma 2 de réduire son vecteur de 768 nombres à 512, 256 ou 128. Une dimension plus faible réduit la taille de la table et celle de l’index HNSW en RAM. Normalisez de nouveau le vecteur après sa réduction. Produisez les embeddings des requêtes et des documents avec la même dimension. Déclarez la colonne avec vector(256) afin qu’un vecteur de longueur incorrecte soit rejeté.
Puis-je produire des embeddings d’images ou d’audio avec EmbeddingGemma 2 via Ollama ?
Non. Le /api/embed d’Ollama accepte uniquement une chaîne ou une liste de chaînes. La page de la bibliothèque Ollama indique une entrée Image pour certains tags, mais l’API documentée ne permet pas d’en envoyer une. Pour les embeddings d’images, de vidéos et d’audio, utilisez sentence-transformers ou Transformers comme indiqué dans la fiche du modèle Hugging Face. Chargez l’encodeur de vision ou audio dont vous avez besoin.
De combien de RAM EmbeddingGemma 2 a-t-il besoin sur un VPS ?
Mesurez la consommation sur votre offre avec systemctl status eg2, car la surcharge varie. Les poids seuls constituent une valeur minimale. En float32, avec 4 octets par paramètre, la variante texte seule de 270M représente environ 1.1GB et le modèle complet de 740M environ 3.0GB. Transformers 5 charge ce checkpoint en bfloat16, sauf si vous passez model_kwargs={"dtype": torch.float32}. Ces valeurs sont alors réduites de moitié, à environ 0.54GB et 1.5GB. Toutefois, la fiche du modèle recommande float32 sur la plupart des CPU. Chargez uniquement la variante texte avec config_kwargs={"vision_config": None, "audio_config": None} lorsque vous produisez des embeddings de texte.