Objectif du jour : faire tourner le même modèle derrière plusieurs moteurs, et savoir lequel choisir selon le matériel et l'usage. À la fin de cette séance, vous avez un client unique qui parle à n'importe quel serveur compatible OpenAI — et vous savez pourquoi cette compatibilité est la décision d'architecture la plus rentable de votre stack IA. Vous savez aussi vérifier qu'un service tourne vraiment, ce qui n'est pas la même chose que « le processus existe ». Today's goal: run the same model behind several engines, and know which to choose by hardware and use case. By the end of this session you have a single client that talks to any OpenAI-compatible server — and you know why that compatibility is the highest-leverage architectural decision in your AI stack. You also know how to verify that a service is really running, which is not the same as "the process exists".
Il n'y a pas « un » outil pour faire tourner un LLM. Il y a un moteur et des empaquetages autour de lui. Comprendre cette hiérarchie vous évite de comparer des choses qui ne sont pas au même niveau. There is no single tool for running an LLM. There is one engine and packagings around it. Understanding that hierarchy stops you comparing things that are not at the same level.
| OutilTool | NatureNature | CibleTarget | Pour quoiBest for |
|---|---|---|---|
llama.cppllama-server |
Le moteur. C/C++, format GGUF.The engine. C/C++, GGUF format. | CPU + GPU | Contrôle total des paramètres ; CPU pur ; embarqué ; le meilleur point de comparaison pour mesurer l'effet d'un réglage.Full control over parameters; pure CPU; embedded; the best reference for measuring a setting's effect. |
| Ollama | Empaquetage de llama.cpp + registre de modèles + cycle de vie.llama.cpp packaging + model registry + lifecycle. | CPU + GPU | Simplicité opérationnelle. pull / run / create, API native et compatible OpenAI, service systemd. Le bon choix par défaut sur un serveur.Operational simplicity. pull / run / create, native and OpenAI-compatible APIs, systemd service. The right default on a server. |
| LM Studio | Application de bureau + CLI lms + serveur headless.Desktop application + lms CLI + headless server. |
Poste de travailWorkstation | Exploration : télécharger, essayer, comparer des modèles à la souris. Peut servir un endpoint compatible OpenAI, y compris en mode service. N'existe pas sur un serveur Linux headless : c'est un outil de poste.Exploration: download, try, compare models with a GUI. Can serve an OpenAI-compatible endpoint, including as a service. Does not exist on a headless Linux server: it is a workstation tool. |
| vLLM | Serveur d'inférence GPU, orienté débit (PagedAttention).GPU inference server, throughput-oriented (PagedAttention). | GPU uniquementonly | Plusieurs utilisateurs en parallèle, gros volume, latence maîtrisée sous charge. Le choix quand un service sert une équipe ou une application.Many concurrent users, high volume, controlled latency under load. The choice when a service serves a team or an application. |
| Open WebUI | Interface de chat auto-hébergée, 100 % hors-ligne.Self-hosted chat UI, fully offline. | N'importe quel backendAny backend | Donner un chat à des utilisateurs non techniques, sur votre Ollama ou n'importe quelle API compatible OpenAI.Giving non-technical users a chat, on your Ollama or any OpenAI-compatible API. |
POST /v1/chat/completions. Cela signifie que votre code applicatif ne dépend d'aucun d'eux — il dépend d'une interface. Vous pourrez changer de moteur, ou mettre un moteur en production et un autre en développement, sans réécrire une ligne.
The structuring fact of this session: all these tools expose an OpenAI-compatible HTTP API. Ollama, llama.cpp, LM Studio, vLLM: the same POST /v1/chat/completions. That means your application code depends on none of them — it depends on an interface. You can swap engines, or run one in production and another in development, without rewriting a line.
lms et mode headless · vLLM, Online Serving · Open WebUI.
Sources: llama.cpp and its HTTP server · Ollama, API and Modelfile · LM Studio, lms CLI and headless mode · vLLM, Online Serving · Open WebUI.
| EndpointEndpoint | RôleRole | Disponible partout ?Everywhere? |
|---|---|---|
POST /v1/chat/completions | Conversation (rôles system/user/assistant), streaming, outils.Conversation (system/user/assistant roles), streaming, tools. | Oui — c'est le socle.Yes — the foundation. |
POST /v1/completions | Complétion brute, sans rôles.Raw completion, no roles. | Oui, mais déconseillé (perd le gabarit de conversation).Yes, but discouraged (loses the chat template). |
POST /v1/embeddings | Vecteurs pour le RAG (séance 6).Vectors for RAG (session 6). | Non — à vérifier serveur par serveur.No — verify server by server. |
GET /v1/models | Liste des modèles servis. Votre premier test de vie.List of served models. Your first liveness test. | Oui.Yes. |
response_format, le tokenizer (donc le comptage réel), et le comportement exact des paramètres non standard. Compatible ≠ identique.
Model naming, tool support, response_format support, the tokenizer (hence real counting), and the exact behaviour of non-standard parameters. Compatible ≠ identical.
usage renvoyé par le serveur est la seule source fiable pour la facturation et le dimensionnement — pas votre estimation. Et il peut manquer : certains serveurs ne le renvoient que si vous demandez stream_options={"include_usage": true} en streaming. Le client du lab 4 le gère explicitement, et retombe sur le comptage des fragments reçus si l'usage est absent — en signalant lequel des deux a servi.
The token-counting trap. The usage field returned by the server is the only reliable source for billing and sizing — not your own estimate. And it can be missing: some servers return it only if you ask for stream_options={"include_usage": true} while streaming. The Lab 4 client handles this explicitly, and falls back to counting received chunks if usage is absent — while telling you which of the two was used.
Le client portable — un seul code, n'importe quel moteurThe portable client — one codebase, any engine
from openai import OpenAI
# Seule cette ligne change selon le moteur. / Only this line changes per engine.
client = OpenAI(base_url="http://172.16.8.81:11434/v1", api_key="not-needed")
r = client.chat.completions.create(
model="llama3.1:8b",
messages=[{"role": "user", "content": "Dis bonjour en une phrase."}],
temperature=0, seed=42, max_tokens=64,
)
print(r.choices[0].message.content)
print(r.usage) # tokens réels : la seule source de vérité
Trois mises en service, du plus simple au plus industriel. Chacune est vérifiée ensuite par le même client — c'est tout l'intérêt. Three deployments, from simplest to most industrial. Each is then verified by the same client — which is the whole point.
# État réel du lab (relevé 2026-09-18) : Ollama 0.32.13 sur le port 11434
curl -s http://172.16.8.81:11434/api/version # {"version":"0.32.13"}
curl -s http://172.16.8.81:11434/v1/models | head -c 200
# Cycle de vie d'un modèle
ollama pull llama3.1:8b
ollama list
ollama ps # modèles RÉELLEMENT chargés en mémoire
ollama run llama3.1:8b "Bonjour"
# Exposer le service au réseau (sinon 127.0.0.1 seulement)
export OLLAMA_HOST=0.0.0.0:11434
systemctl restart ollama
# Sur le lab, llama.cpp est DÉJÀ compilé (build CPU) :
# /home/sysadmin/llama.cpp/build-cpu/bin/llama-server
# /home/sysadmin/llama.cpp/build-cpu/bin/llama-cli
# Note : build-cpu = pas de CUDA. C'est voulu : c'est notre référence CPU.
# Lancer un serveur compatible OpenAI, en CPU :
~/llama.cpp/build-cpu/bin/llama-server \
-m /chemin/vers/modele.gguf \
-c 8192 \ # fenêtre de contexte
-t 8 \ # threads CPU
--host 0.0.0.0 --port 8080
# Vérifier la vie du serveur (endpoint dédié, pas une supposition)
curl -s http://127.0.0.1:8080/health
curl -s http://127.0.0.1:8080/v1/models
# vLLM n'est PAS installé sur le lab : on le fait tourner en conteneur. # Docker est disponible, et le lab a 2 x RTX 3060 (24 Go cumulés). docker run --gpus all -p 8000:8000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ vllm/vllm-openai:latest \ --model Qwen/Qwen2.5-7B-Instruct \ --tensor-parallel-size 2 \ # répartir sur les 2 GPU --gpu-memory-utilization 0.90 \ --max-model-len 8192 curl -s http://127.0.0.1:8000/v1/models
--tensor-parallel-size 2 exige que le modèle tienne sur les deux cartes, et il refuse de démarrer plutôt que de dégrader — c'est un bon comportement, mais il faut le savoir. Si le modèle ne rentre pas, réduisez la taille du modèle ou --max-model-len, exactement comme au lab 2.
Two vLLM pitfalls to know before launching. (1) It downloads the model from Hugging Face at startup: allow time and disk (the lab has 121 GB free, ample for a 7–8 B). (2) --tensor-parallel-size 2 requires the model to fit on both cards, and it refuses to start rather than degrade — good behaviour, but you need to know it. If the model does not fit, shrink the model or --max-model-len, exactly as in Lab 2.
| SituationSituation | MoteurEngine |
|---|---|
| Pas de GPU, un posteNo GPU, one workstation | llama.cpp (build CPU) ou Ollamallama.cpp (CPU build) or Ollama |
| Un GPU, un utilisateur à la foisOne GPU, one user at a time | Ollama |
| Plusieurs utilisateurs / une applicationMany users / an application | vLLM |
| Explorer, comparer à la sourisExplore, compare with a GUI | LM Studio (poste de travail)(workstation) |
| Donner un chat aux non-techniciensGive non-technical users a chat | Open WebUI devant n'importe lequelin front of any of them |
« Le processus existe » et « le service répond » sont deux affirmations différentes. Après un redéploiement, un ancien processus peut détenir le port pendant que la nouvelle instance a silencieusement échoué à s'y attacher : vous testez alors l'ancien code et vous concluez que votre correctif n'a pas marché. "The process exists" and "the service responds" are two different claims. After a redeployment, an old process may hold the port while the new instance silently failed to bind: you are then testing the old code and concluding your fix did not work.
# 1. LA PREUVE PAR L'API — jamais par le nom du processus.
curl -s -m 5 http://172.16.8.81:11434/v1/models # répond-il ?
curl -s -m 5 http://172.16.8.81:11434/api/version # quelle version ?
# 2. UN VRAI TRAVAIL, PAS UN PING : une complétion minuscule.
curl -s http://172.16.8.81:11434/v1/chat/completions \
-d '{"model":"llama3.1:8b","messages":[{"role":"user","content":"ping"}],
"max_tokens":5}' | head -c 300
# 3. QUI ÉCOUTE VRAIMENT, ET DEPUIS QUAND ?
ss -tlnp | grep 11434
ps -eo pid,lstart,cmd | grep -i ollama | grep -v grep
# ^ comparez l'heure de DÉMARRAGE du processus avec la date de modification
# du fichier que vous venez de patcher. Si le processus est plus vieux
# que votre patch, vous testez l'ancien code.
# 4. RESSOURCES RÉELLEMENT UTILISÉES
nvidia-smi --query-gpu=index,memory.used,utilization.gpu --format=csv
ollama ps
pgrepThe anti-pattern: pgrep
pgrep -f 'ollama' renvoie aussi… le bash -c de votre propre commande SSH, qui contient le mot « ollama ». Vous croyez avoir vérifié, vous avez mesuré votre propre requête. Vérifiez un service par son API, jamais par une correspondance de nom de processus.
pgrep -f 'ollama' also returns… the bash -c of your own SSH command, which contains the word "ollama". You think you verified; you measured your own query. Verify a service through its API, never through a process-name match.
python3 labs/lab4_openai_client.py probe \
--base-url http://172.16.8.81:11434/v1 --model llama3.1:8b
# Ollama natif (/v1) vs Ollama natif (/api) vs llama-server, sur le MÊME modèle.
python3 labs/lab4_openai_client.py matrix \
--endpoint http://172.16.8.81:11434/v1 --model llama3.1:8b \
--endpoint http://127.0.0.1:8080/v1 --model mistral-7b-q4.gguf
# Ajoutez vLLM dès qu'il tourne :
python3 labs/lab4_openai_client.py matrix \
--endpoint http://172.16.8.81:11434/v1 --model llama3.1:8b \
--endpoint http://127.0.0.1:8000/v1 --model Qwen/Qwen2.5-7B-Instruct
python3 labs/lab4_openai_client.py compat \
--base-url http://172.16.8.81:11434/v1 \
--model granite-embedding:latest
| EndpointEndpoint | MoteurEngine | MatérielHardware | Id du modèle renvoyéReturned model id | chat | usage | tok/s |
|---|---|---|---|---|---|---|
127.0.0.1:11434/v1 | Ollama | 1× RTX 30601× RTX 3060 | llama3.1:8b | OK | oui | 64,02 |
127.0.0.1:8080/v1 | llama.cpp | CPU, 8 cœursCPU, 8 cores | sha256-f5074b12… | OK | oui | 4,07 |
llama3.1:8b, llama-server renvoie le hash du blob GGUF (sha256-f5074b12…). « Compatible » veut dire même forme de requête, pas mêmes identifiants : votre code doit découvrir le nom du modèle via GET /v1/models au lieu de le coder en dur.
Two lessons only the lab could give. (1) The CPU/GPU ratio is about ×15 (4.07 vs 64.02 tokens/s) for comparable model sizes — the numerical justification for the whole of session 5. (2) The same client talked to both with no modification, but the model names have nothing in common: Ollama returns llama3.1:8b, llama-server returns the GGUF blob hash (sha256-f5074b12…). "Compatible" means the same request shape, not the same identifiers: your code must discover the model name via GET /v1/models rather than hard-coding it.
/v1/models OK ?, chat OK ?, embeddings OK ?, usage renvoyé ?, outils supportés ?, débit observé. C'est le document qui vous permettra de justifier un choix d'architecture devant un client, et de savoir en dix secondes pourquoi un appel échoue chez quelqu'un.
What you must produce: your endpoint matrix — per engine: model name, /v1/models OK?, chat OK?, embeddings OK?, usage returned?, tools supported?, observed throughput. This is the document that lets you justify an architectural choice to a client, and know in ten seconds why a call fails for someone else.
pull/run/create, le service systemd et les deux APIs. Comprendre la hiérarchie évite de comparer un moteur et un gestionnaire de modèles.llama.cpp is the engine; Ollama is a packaging around it, bringing the model registry, pull/run/create, the systemd service and both APIs. Understanding the hierarchy stops you comparing an engine with a model manager.ps).All engines speak that interface. Depending on it means you can move from llama.cpp to Ollama to vLLM without touching the code — and test locally what you will deploy elsewhere. The native API remains useful for administration (creating a model, checking ps).pgrep -f 'ollama' matche le bash -c de la commande SSH elle-même, qui contient le motif. Et même sans ce biais, un processus vivant ne prouve pas que le bon code sert le port. La preuve, c'est GET /v1/models et une vraie complétion.pgrep -f 'ollama' matches the bash -c of the SSH command itself, which contains the pattern. And even without that bias, a live process does not prove the right code is serving the port. The proof is GET /v1/models plus a real completion.response_format varie, et /v1/embeddings n'est pas garanti. Compatible ≠ identique : c'est précisément pour cela que le lab 4 produit une matrice par endpoint au lieu de supposer.Model names differ, the tokenizer differs (hence real counting), tool and response_format support varies, and /v1/embeddings is not guaranteed. Compatible ≠ identical: which is exactly why Lab 4 produces a per-endpoint matrix instead of assuming.response_format varient. Mesurez par endpoint.Compatible ≠ identical: names, tokenizer, tools and response_format vary. Measure per endpoint.pgrep matche votre propre commande ; un vieux processus peut servir l'ancien code.Verify through the API, not the process name. pgrep matches your own command; an old process may be serving old code.usage est la seule vérité sur les tokens — et il faut parfois le demander explicitement en streaming.The usage field is the only token truth — and sometimes you must ask for it explicitly while streaming.RESOURCES.md §8.
Session sources: llama.cpp, README and llama-server README · Ollama, API (docs/api.md, migrating to docs.ollama.com/api) and Modelfile · LM Studio, lms CLI and headless — lmstudio.ai/docs · vLLM, Online Serving — docs.vllm.ai · Open WebUI — docs.openwebui.com. Lab state (Ollama 0.32.13, llama.cpp build-cpu, 2× RTX 3060) read on 2026-09-18 — RESOURCES.md §8.