La stack logicielle

La stack logicielle

Le matériel ne fait pas tout. Le choix du moteur d’inférence impacte directement les performances, la flexibilité et la facilité d’expérimentation. Voici comment ma stack a évolué.

Les drivers

La machine est sous Ubuntu 22.04.5 LTS avec les drivers 550.163.01 et Cuda 12.4

Ce n’est pas un oubli de ma part, c’est pour avoir un système stable. Avec des drivers plus récents, j’ai régulièrement des plantages au niveau des drivers ou du système.

Note: J’ai dû mettre à jour mon BIOS également sur cette machine car j’avais des erreurs “GPU fell of the BUS” régulièrement, et je devais rebooter pour retrouver un de mes GPU

Ollama — le point de départ

Ollama est la porte d’entrée idéale pour l’IA locale. L’installation tient en une commande :

curl -fsSL https://ollama.com/download/ollama-linux-amd64.tar.zst | sudo tar x -C /usr```

Le daemon démarre automatiquement, expose une API REST sur le port 11434 et un CLI intuitif. Pour lancer un modèle, une seule ligne suffit — Ollama télécharge le modèle s'il n'est pas encore présent localement :

```bash
ollama run qwen3.5:4b

On se retrouve directement dans une session de chat interactive. Pour une utilisation programmatique, l’API est compatible OpenAI, ce qui permet d’utiliser n’importe quel client existant sans modification :

curl http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.5:4b",
    "messages": [{"role": "user", "content": "Explique-moi le VLAN en 3 lignes"}]
  }'

Le modèle qwen3.5:4b est un excellent point de départ : 4 milliards de paramètres, tient confortablement en VRAM sur n’importe quelle carte un peu récente, et ses capacités de raisonnement sont surprenantes pour sa taille.

Les limites d’Ollama

La simplicité d’Ollama a un revers : il abstrait beaucoup trop de choses.

Performances sous-optimales. Ollama encapsule llama.cpp en interne, mais avec des paramètres par défaut conservateurs. Le flash attention, la taille des batches de prompt processing, le nombre de threads CPU pour les couches déchargées — tout ça est géré automatiquement et pas toujours de façon optimale pour un setup spécifique.

Pas de métriques exploitables. La sortie d’Ollama donne quelques chiffres en fin de génération, mais isoler précisément le temps de prompt processing du temps de génération, ou comparer des configurations différentes de façon rigoureuse, c’est compliqué.

Catalogue fermé. Les modèles disponibles sont ceux packagés par Ollama ou la communauté. Pour tester un quant spécifique d’Unsloth ou un modèle fraîchement sorti sur Hugging Face en GGUF, il faut passer par un Modelfile maison — ce qui retire une bonne partie de la simplicité initiale.

vLLM — la migration vers le contrôle

vLLM est un moteur d’inférence conçu pour la performance et la scalabilité. Là où Ollama vise la simplicité, vLLM vise le débit et le contrôle.

Installation et démarrage

pip install vllm

python -m vllm.entrypoints.openai.api_server \
  --model gpt-oss-20b \
  --dtype auto \
  --api-key token-local

L’API exposée est compatible OpenAI, comme Ollama, mais avec beaucoup plus de paramètres disponibles au démarrage du serveur : taille du contexte, politique de quantification, gestion de la mémoire KV, nombre de séquences parallèles…

Ce qui change vraiment par rapport à Ollama

Le PagedAttention. C’est la contribution principale de vLLM. Plutôt que d’allouer un bloc de VRAM fixe par requête pour le cache KV (les états intermédiaires du modèle), vLLM gère ce cache comme un allocateur de mémoire virtuelle — en pages, à la demande. Le résultat : bien moins de VRAM gaspillée, et la possibilité de traiter plusieurs requêtes concurrentes sans que l’une ne bloque l’autre.

Le continuous batching. Ollama traite les requêtes de façon séquentielle. vLLM peut regrouper dynamiquement des requêtes en cours de génération dans le même batch GPU, ce qui améliore significativement le débit quand plusieurs clients appellent l’API en même temps.

L’accès aux modèles Hugging Face directement. Plus de catalogue fermé — n’importe quel modèle compatible (format safetensors) se charge en passant son identifiant HF :

source .venv/bin/activate
# Silence CPU au repos
export VLLM_SLEEP_WHEN_IDLE=1
# Aide pour faire tenir les CUDA Graphs (le secret des 100+ t/s)
export VLLM_GRAPH_RESERVATION=0.1
# Laisse les GPUs se parler via PCIe sans passer par le CPU
export NCCL_P2P_DISABLE=0

python -m vllm.entrypoints.openai.api_server     --model openai/gpt-oss-20b\
       --tensor-parallel-size 2 \
       --port 11434 \
       --host 0.0.0.0 \
       --max-model-len 16384 \
       --gpu-memory-utilization 0.65 \
       --max-num-seqs 2 \
       --max-num-batched-tokens 2048 \
       --disable-custom-all-reduce

Les limites qui m’ont poussé vers llama.cpp

La quantification GGUF absente. vLLM travaille nativement avec des poids en float16 ou bfloat16, et supporte AWQ et GPTQ pour la quantification — mais pas GGUF. Or le format GGUF de llama.cpp est devenu l’écosystème de référence pour les quants de qualité, notamment le travail d’Unsloth. Pour accéder aux meilleurs quants, il faut passer par llama.cpp.

Lourd pour un usage solo. vLLM brille quand plusieurs utilisateurs appellent l’API en parallèle. Pour un labo personnel avec un seul utilisateur à la fois, le continuous batching et le PagedAttention n’apportent pas grand chose, et la complexité des dépendances (PyTorch, CUDA toolkit en version compatible…) pèse dans la balance. De plus la quantité de VRAM demandée est très élevée. On va le réserver pour un usage en serveur d’inférence d’entreprise.

Moins de contrôle sur les couches GPU/CPU. Sur un modèle qui ne tient pas entièrement en VRAM, répartir précisément les couches entre GPU et RAM système est plus simple et plus prévisible avec llama.cpp.

llama.cpp — la stack actuelle

llama.cpp est aujourd’hui le moteur central du labo. Ce qui le distingue :

  • Contrôle granulaire sur la quantification, le nombre de couches GPU/CPU, la taille du contexte
  • Performances mesurables avec des métriques claires (prompt processing, génération)
  • Flexibilité des formats : GGUF avec tous les niveaux de quantification disponibles
  • Légèreté : pas de dépendances lourdes, tourne bien sur le setup dual RTX 3060

Pour le moment, j’ai fait un service qui tourne sur ma machine: /etc/systemd/system/llama-server.service

# /etc/systemd/system/llama-server.service
[Unit]
Description=llama.cpp Server 
After=network.target

[Service]
Type=simple
User=yves
WorkingDirectory=/home/yves/marvin/vllm/llamacpp
Environment="LLAMA_CACHE=/home/yves/marvin/vllm/llamacpp/Qwen"
EnvironmentFile=/etc/default/llama-server

#ExecStart=/home/yves/marvin/vllm/llamacpp/llama.cpp/llama-server \
#       -m /home/yves/marvin/vllm/llamacpp/unsloth/gemma-4-26B-A4B-it-UD-IQ4_NL.gguf \
#       --port 8050 \
#       --host 0.0.0.0 \
#       -b 4096 -ub 1024 \
#        --temp 0.6 \
#        --top-p 0.95 \
#        --top-k 20 \
#        --min-p 0.00 \
#       --chat-template-kwargs '{"enable_thinking":true}'
#       -c 65536 -fa on -fit off  -ctk q4_0 -ctv q4_0

ExecStart=/home/yves/marvin/vllm/llamacpp/llama.cpp/llama-server \
        -m $MODEL \
        -c 65536 \
        -fit off  \
        -fa on \
        --cache-type-k q4_0 \
        --cache-type-v q4_0 \
        -b 1024         -ub 1024 \
        --port 8050 \
        --host 0.0.0.0 \
        --chat-template-kwargs '{"enable_thinking":true}'

#         --chat-template-file qwen3.5_chat_template.jinja \    --split-mode row --tensor-split 10,12
#ExecStart=/home/yves/marvin/vllm/llamacpp/llama.cpp/llama-server \
#    -hf unsloth/Qwen3.5-35B-A3B-GGUF:Q4_K_S \
#    --ctx-size 131000 \
#    -fa on \
#    --cache-type-k q4_0 \
#    --cache-type-v q4_0 \
#    --alias "unsloth/Qwen35-35B" \
#    --host 0.0.0.0 \
#    --port 8050 \
#    --chat-template-kwargs '{"enable_thinking":true}'

et un fichier de préférences /etc/default/llama-server 

MODEL="/data/models/unsloth/gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf"

On peut voir que c’est en cours de création, car les paramètres sont à déplacer au maximum dans le fichier des préférences.

De plus, les réglages à mettre en place pour Gemma4 et pour Qwen 3.5 ne sont pas forcément les mêmes pour le service. Je prévois de mettre dans le fichier de préférences:

  • CTX: le contexte
  • Mode thinking (d’autant plus qu’une option plus propre existe maintenant)
  • La quantisation du cache, mais j’attends que turboquant soit intégrée dans llama.cpp
  • les TOPK, TOPP TEMP et MINP

Modèles actuellement testés

Les tests en préparations sont sur la page “bench”