SÉANCE 4 / 8SESSION 4 / 8 ⏱ ≈ 90 min

Servir des modèles en local : plusieurs outilsServing models locally: multiple tools

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".

1

Le paysage des moteurs d'inférenceThe inference-engine landscape

≈ 20 min

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.cpp
llama-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.
🎯 Le fait structurant de cette séance : tous ces outils exposent une API HTTP compatible OpenAI. Ollama, llama.cpp, LM Studio, vLLM : le même 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.
2

L'API compatible OpenAI comme langue communeThe OpenAI-compatible API as a lingua franca

≈ 20 min
EndpointEndpointRôleRoleDisponible partout ?Everywhere?
POST /v1/chat/completionsConversation (rôles system/user/assistant), streaming, outils.Conversation (system/user/assistant roles), streaming, tools.Oui — c'est le socle.Yes — the foundation.
POST /v1/completionsComplé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/embeddingsVecteurs pour le RAG (séance 6).Vectors for RAG (session 6).Non — à vérifier serveur par serveur.No — verify server by server.
GET /v1/modelsListe des modèles servis. Votre premier test de vie.List of served models. Your first liveness test.Oui.Yes.
Ce que la compatibilité vous garantitWhat compatibility guarantees Le même client SDK, la même structure de requête, le streaming au même format (SSE), le même comptage d'usage. Vous testez sur le lab, vous déployez ailleurs : rien ne change côté code. The same SDK client, the same request shape, streaming in the same format (SSE), the same usage accounting. You test on the lab, you deploy elsewhere: nothing changes in your code.
Ce qu'elle ne garantit PASWhat it does NOT guarantee Le nom des modèles, le support des outils, le support de 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.
⚠️ Le piège du comptage de tokens. Le champ 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é
3

Mettre en service sur le labGetting it running on the lab

≈ 22 min

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.

A. Ollama — le défaut sur serveur (déjà en service sur le lab)A. Ollama — the server default (already running on the lab)

# É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

B. llama.cpp — le contrôle totalB. llama.cpp — total control

# 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

C. vLLM — le débit GPUC. vLLM — GPU throughput

# 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
⚠️ Deux pièges vLLM à connaître avant de lancer. (1) Il télécharge le modèle depuis Hugging Face au démarrage : prévoyez le temps et le disque (le lab a 121 Go libres, largement suffisant pour un 7–8 B). (2) --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.

Le choix, en une phraseThe choice, in one sentence

SituationSituationMoteurEngine
Pas de GPU, un posteNo GPU, one workstationllama.cpp (build CPU) ou Ollamallama.cpp (CPU build) or Ollama
Un GPU, un utilisateur à la foisOne GPU, one user at a timeOllama
Plusieurs utilisateurs / une applicationMany users / an applicationvLLM
Explorer, comparer à la sourisExplore, compare with a GUILM Studio (poste de travail)(workstation)
Donner un chat aux non-techniciensGive non-technical users a chatOpen WebUI devant n'importe lequelin front of any of them
4

Vérifier qu'un service tourne vraimentVerifying a service is really running

≈ 18 min

« 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
L'anti-pattern : 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.
🎯 La checklist de mise en service, en 4 points : (1) l'API répond ; (2) elle fait un vrai travail (une complétion) ; (3) le bon processus détient le port, et il est plus récent que votre dernier patch ; (4) les ressources consommées correspondent à votre calcul du lab 2. Le script du lab 4 automatise les points 1 et 2 pour n'importe quel endpoint. The 4-point deployment checklist: (1) the API responds; (2) it does real work (a completion); (3) the right process holds the port, and it is newer than your last patch; (4) the resources consumed match your Lab 2 calculation. The Lab 4 script automates points 1 and 2 for any endpoint.
5

Lab 4 — Un client, plusieurs moteursLab 4 — One client, several engines

≈ 20 min

🔬 Objectif : la matrice d'endpointsGoal: the endpoint matrix

Étape 1 — Sonder un endpointStep 1 — Probe one endpoint

python3 labs/lab4_openai_client.py probe \
        --base-url http://172.16.8.81:11434/v1 --model llama3.1:8b

Étape 2 — Comparer plusieurs moteursStep 2 — Compare several engines

# 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

Étape 3 — Vérifier la disponibilité des endpoints clésStep 3 — Check key endpoint availability

python3 labs/lab4_openai_client.py compat \
        --base-url http://172.16.8.81:11434/v1 \
        --model granite-embedding:latest
🎯 Résultats réels de la matrice, mesurés sur le lab le 2026-09-18 — le même client, deux moteurs, deux architectures matérielles : Real matrix results, measured on the lab on 2026-09-18 — the same client, two engines, two hardware architectures:
EndpointEndpoint MoteurEngine MatérielHardware Id du modèle renvoyéReturned model id chatusagetok/s
127.0.0.1:11434/v1Ollama 1× RTX 30601× RTX 3060 llama3.1:8bOKoui64,02
127.0.0.1:8080/v1llama.cpp CPU, 8 cœursCPU, 8 cores sha256-f5074b12…OKoui4,07
Deux enseignements que seul le lab pouvait donner. (1) Le rapport CPU/GPU est d'environ ×15 (4,07 contre 64,02 tokens/s) pour des modèles de taille comparable — c'est la justification chiffrée de toute la séance 5. (2) Le même client a parlé aux deux sans modification, mais les noms de modèles n'ont rien à voir : Ollama renvoie 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.
🎯 Ce que vous devez produire : votre matrice d'endpoints — pour chaque moteur : nom du modèle, /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.
6

Quiz — 5 questionsQuiz — 5 questions

≈ 8 min

1. Quel est le rapport entre Ollama et llama.cpp ?1. What is the relationship between Ollama and llama.cpp?

llama.cpp est le moteur ; Ollama est un empaquetage autour de lui, qui apporte le registre de modèles, 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.

2. Vous écrivez une application. De quoi doit-elle dépendre ?2. You are writing an application. What should it depend on?

Tous les moteurs parlent cette interface. En dépendre, c'est pouvoir passer de llama.cpp à Ollama puis à vLLM sans toucher au code — et tester en local ce qu'on déploiera ailleurs. L'API native reste utile pour l'administration (créer un modèle, voir 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).

3. Un collègue dit « le service tourne, j'ai fait un pgrep ». Que répondez-vous ?3. A colleague says "the service is up, I ran pgrep". What do you say?

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.

4. Vous servez une équipe de 12 ingénieurs en parallèle. Quel moteur ?4. You are serving a team of 12 engineers concurrently. Which engine?

vLLM est le seul de la liste à être conçu pour plusieurs requêtes simultanées avec une latence maîtrisée (PagedAttention, batching continu). llama.cpp et Ollama servent très bien un utilisateur ou quelques-uns ; ils décrochent quand la concurrence monte. Et LM Studio n'existe pas sur un serveur Linux headless.vLLM is the only one here designed for many simultaneous requests with controlled latency (PagedAttention, continuous batching). llama.cpp and Ollama serve one user or a few very well; they fall off as concurrency rises. And LM Studio does not exist on a headless Linux server.

5. Que garantit exactement « compatible OpenAI » ?5. What exactly does "OpenAI-compatible" guarantee?

Les noms de modèles diffèrent, le tokenizer diffère (donc le comptage réel), le support des outils et de 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.
Score : 0 / 5Score: 0 / 5

🏁 À retenirKey takeaways

🏆 Votre gain du jourToday's win
Un client portable (un seul code pour tous les moteurs) et votre matrice d'endpoints remplie sur le lab : pour chaque moteur, nom du modèle, capacités réellement disponibles et débit observé. C'est votre document de décision d'architecture. A portable client (one codebase for every engine) and your endpoint matrix filled in on the lab: per engine, model name, actually-available capabilities and observed throughput. This is your architecture-decision document.
Sources de la séance : llama.cpp, README et llama-server README · Ollama, API (docs/api.md, migration vers docs.ollama.com/api) et Modelfile · LM Studio, lms CLI et headless — lmstudio.ai/docs · vLLM, Online Serving — docs.vllm.ai · Open WebUI — docs.openwebui.com. État du lab (Ollama 0.32.13, llama.cpp build-cpu, 2× RTX 3060) relevé le 2026-09-18 — 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.