</Contexte>
Dans le cadre de mon BTS SIO option SISR, j'ai souhaité explorer l'hébergement de modèles d'intelligence artificielle en local, sur ma propre machine équipée de deux GPU (une GTX1070 en GPU0 et une Quadro M2000 en GPU1). L'objectif était de disposer d'un assistant de code performant, sans dépendre d'API distantes ni exposer mon code à des services tiers.
Pourquoi héberger un LLM soi-même ?
- Confidentialité et souveraineté : le code source ne quitte jamais la machine locale, contrairement aux API cloud.
- Zéro coût d'utilisation : pas de facturation à la requête une fois le modèle téléchargé.
- Fonctionnement hors ligne : le modèle est disponible même sans connexion Internet.
- Contrôle total : choix du modèle, du niveau de quantisation, du contexte.
Technologies utilisées
Moteur d'inférence C++ optimisé CUDA/CPU. Expose une API compatible OpenAI sur le port 8080. Supporte le multi-GPU et la quantisation GGUF.
Bibliothèques de calcul GPU NVIDIA — accélération matérielle pour l'inférence sur GTX 1070 (GPU0) et Quadro M2000 (GPU1).
Famille open-weight d'Alibaba, excellents pour le code. Utilisés en version quantisée Q4/Q8 pour tenir dans la VRAM disponible.
Clients CLI connectant l'IDE ou le terminal au serveur llama.cpp via l'API OpenAI-compatible. Remplacent Claude Code ou Cursor localement.
</Modèles>
Comparatif des modèles locaux vs cloud (SWE-Bench Verified, mars 2026)
Le benchmark SWE-Bench Verified mesure la capacité d'un modèle à résoudre des issues GitHub réelles de manière autonome. Un score de 72% signifie que le modèle a résolu 360 des 500 issues de test sans aide humaine.
| Modèle | SWE-Bench | VRAM (Q4) | Accessible |
|---|---|---|---|
| Claude Opus 4.5 / 4.6 | ~80,9 % | — | API cloud |
| Claude Sonnet 4.6 | 79,6 % | — | API cloud |
| Qwen3.6-27B ★ | 75,0 % | ~17 Go | Local 32 Go |
| Qwen3.5-27B | 72,4 % | ~17 Go | Local 32 Go |
| Qwen3-Coder-Next (80B MoE) | 70,6 % | ~24 Go | Local 32 Go |
| Qwen3.5-35B-A3B | 69,2 % | ~20 Go | Local 32 Go |
| Qwen2.5-Coder-72B | ~72 % | ~40 Go | Local 48 Go |
| DeepSeek R1 32B | ~62 % | ~22 Go | Local 32 Go |
| Llama 3.3 70B Instruct | ~52 % | ~40 Go | Local 48 Go |
Modèle retenu : Qwen3.5-27B
- Architecture dense : 27 milliards de paramètres tous actifs à chaque token (contrairement aux MoE), ce qui donne une cohérence supérieure sur les tâches longues.
- Fenêtre de contexte : 262 144 tokens natifs, extensible à 1 million via YaRN — idéal pour analyser de grands fichiers de code.
- Multimodal : supporte le texte et les images, 201 langues, instruction-following parmi les meilleurs du marché local (IFBench 76,5).
- Ecart avec Claude : ~7 à 10 points de SWE-Bench. La différence est surtout visible sur les bugs multi-fichiers et les projets privés complexes. Sur les tâches simples (génération de code, refactoring isolé), l'écart devient négligeable.
</Installation>
Compilation de llama.cpp avec support CUDA
La compilation nécessite le SDK CUDA installé sur le système hôte. llama.cpp se compile via CMake en activant explicitement le backend CUDA :
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp && mkdir build && cd build
cmake .. -DGGML_CUDA=ON
cmake --build . --config Release -j$(nproc)
Extraction des bibliothèques CUDA depuis Docker
Plutôt que d'installer le SDK CUDA complet sur le système hôte, j'ai extrait les bibliothèques nécessaires depuis une image Docker officielle NVIDIA. Cette approche évite les conflits de versions et les dépendances système lourdes.
Méthode pour libcublas :
docker create --name cudalib nvidia/cuda:11.8.0-devel-ubuntu22.04
docker cp cudalib:/usr/local/cuda-11.8/targets/x86_64-linux/lib/libcublas.so.11.11.3.6 /tmp/
docker rm cudalib
sudo cp /tmp/libcublas.so.11.11.3.6 /usr/local/lib/
sudo ln -sf /usr/local/lib/libcublas.so.11.11.3.6 /usr/local/lib/libcublas.so.11
sudo ldconfig
Important : toujours utiliser docker create + docker cp pour les fichiers binaires — ne jamais passer par cat ou une redirection stdout qui corrompt les ELF binaires.
Lancement du serveur
Une fois compilé et les bibliothèques en place, llama-server s'exécute en exposant une API compatible OpenAI sur toutes les interfaces réseau :
~/llama.cpp/build/bin/llama-server \
--model /chemin/vers/modele.gguf \
--host 0.0.0.0 \
--port 8080 \
--n-gpu-layers 99 \
--ctx-size 32768
</Modes>
4 modes de lancement selon le besoin
Chaque mode correspond à un compromis vitesse / qualité / mémoire. Un script switch.sh permet de passer de l'un à l'autre en une commande (ai 1, ai 2…) après avoir tué le serveur en cours.
| Mode | Modèle | Quant. | GPU | ngl | Vitesse | Usage |
|---|---|---|---|---|---|---|
| ⚡ ultra-fast | Qwen3.5 9B | Q8 | GTX 1070 + M2000 | 99 | ~55 tok/s | Autocomplétion rapide |
| 🔵 fast | Qwen3.6 27B | IQ3 | GTX 1070 + M2000 | 99 | ~25 tok/s | Code, no thinking |
| 🧠 thinking | Qwen3.6 27B | IQ3 | GTX 1070 + M2000 | 99 | ~25 tok/s | Debug / architecture |
| 🏆 best | Qwen3.6 27B | Q4 | GPU + RAM CPU | 38 | ~10 tok/s | Qualité maximale |
Mode 1 — ultra-fast (Qwen3.5 9B Q8)
Charge le modèle 9B entièrement sur les deux GPU. Tensor-split 7,1 pour concentrer ~87,5 % des couches sur la GTX 1070 afin d'éviter la saturation de la M2000.
~/llama.cpp/build/bin/llama-server \
-m ~/models/qwen3.5-9b-q8/*.gguf --jinja \
-ngl 99 --flash-attn auto \
--tensor-split 7,1 --split-mode layer \
--threads 8 --threads-batch 4 --prio 2 --numa distribute \
--ctx-size 16384 --port 8080 --host 0.0.0.0 \
--temp 0.7 --top-p 0.8 --top-k 20 \
--chat-template-kwargs '{"enable_thinking":false}'
Mode 2 — fast (Qwen3.6 27B IQ3, no thinking)
Modèle 27B en quantisation IQ3 (très compressée). Flash attention désactivée car non supportée par la M2000 en IQ3. Les embeddings et la couche de sortie sont déchargés sur CPU (-ot) pour libérer de la VRAM.
~/llama.cpp/build/bin/llama-server \
-m ~/models/qwen3.6-27b-iq3/*.gguf --jinja \
-ngl 99 --flash-attn off \
--tensor-split 2,1 --split-mode layer \
--threads 8 --threads-batch 8 --prio 2 --numa distribute \
--ctx-size 16384 --no-kv-offload \
-ot "token_embd=CPU" -ot "output.weight=CPU" \
--port 8080 --host 0.0.0.0 \
--temp 0.7 --top-p 0.8 --top-k 20 --presence-penalty 1.5 \
--chat-template-kwargs '{"enable_thinking":false}'
Mode 3 — thinking (Qwen3.6 27B IQ3, chain-of-thought actif)
Identique au mode fast mais avec le mode de réflexion activé (enable_thinking:true) et le format de raisonnement deepseek. Paramètres ajustés (top-p 0.95, temp 0.6) pour favoriser une réflexion plus précise.
~/llama.cpp/build/bin/llama-server \
-m ~/models/qwen3.6-27b-iq3/*.gguf --jinja --reasoning-format deepseek \
-ngl 99 --flash-attn off \
--tensor-split 2,1 --split-mode layer \
--threads 8 --threads-batch 8 --prio 2 --numa distribute \
--ctx-size 16384 --no-kv-offload \
-ot "token_embd=CPU" -ot "output.weight=CPU" \
--port 8080 --host 0.0.0.0 \
--temp 0.6 --top-p 0.95 --top-k 20 --min-p 0 --presence-penalty 1.5 \
--chat-template-kwargs '{"enable_thinking":true,"preserve_thinking":true}'
Mode 4 — best (Qwen3.6 27B Q4, déversement RAM)
Qualité maximale : modèle Q4 (meilleure précision que IQ3). Seulement 38 couches en GPU (-ngl 38), le reste déborde sur la RAM système. --no-mmap évite le mappage mémoire qui cause des latences sur disque.
~/llama.cpp/build/bin/llama-server \
-m ~/models/qwen3.6-27b-q4/*.gguf --jinja \
-ngl 38 --no-mmap --flash-attn off \
--tensor-split 2,1 --split-mode layer \
--threads 8 --threads-batch 8 --prio 2 --numa distribute \
--ctx-size 16384 --no-kv-offload \
-ot "token_embd=CPU" -ot "output.weight=CPU" \
--port 8080 --host 0.0.0.0 \
--temp 0.6 --top-p 0.95 --top-k 20 --min-p 0 --presence-penalty 1.5 \
--chat-template-kwargs '{"enable_thinking":true,"preserve_thinking":true}'
Script switch.sh — changer de mode en une commande
Un alias ai dans ~/.bashrc permet de changer de mode instantanément depuis n'importe quel terminal :
ai 1 # ⚡ ultra-fast — Qwen3.5 9B Q8 ~55 tok/s
ai 2 # 🔵 fast — Qwen3.6 27B IQ3 ~25 tok/s
ai 3 # 🧠 think — Qwen3.6 27B IQ3 ~25 tok/s
ai 4 # 🏆 best — Qwen3.6 27B Q4 ~10 tok/s
Le script tue le serveur en cours (pkill -f llama-server), attend 1 seconde, puis relance le mode demandé. Pas besoin de redémarrer le client OpenCode ou OpenClaude — il se reconnecte automatiquement au même port 8080.
</Clients>
Connexion depuis une autre machine (réseau local)
llama-server reste sur la machine GPU et expose son API sur le réseau. N'importe quelle autre machine du LAN peut s'y connecter sans avoir besoin du GPU ou des fichiers modèles.
OpenCode (configuration via fichier JSON) :
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"llamacpp": {
"npm": "@ai-sdk/openai-compatible",
"options": { "baseURL": "http://IP_SERVEUR:8080/v1" },
"models": {
"fast": { "name": "Qwen3.5 27B Q4", "tool_call": true }
}
}
},
"model": "llamacpp/fast"
}
OpenCode vs OpenClaude
| OpenCode | OpenClaude | |
|---|---|---|
| Configuration | Fichier opencode.json | Variables d'environnement |
| Changement de modèle | Édition JSON ou /model | /provider en CLI |
| Origine | Projet indépendant | Fork de Claude Code |
| Installation | bun install -g | npm install -g |
OpenClaude se configure uniquement via variables d'environnement :
export CLAUDE_CODE_USE_OPENAI=1
export OPENAI_BASE_URL=http://IP_SERVEUR:8080/v1
export OPENAI_MODEL=fast
openclaude
</Problèmes>
Bibliothèques manquantes au démarrage
Le problème le plus récurrent lors du premier lancement de llama-server était des erreurs de type shared library not found. Plusieurs bibliothèques CUDA n'étaient pas présentes sur le système hôte malgré la compilation réussie.
- libcublas.so.11 : bibliothèque d'algèbre linéaire CUDA, indispensable pour les calculs matriciels du modèle. Absente du chemin système malgré l'installation partielle de CUDA.
- libnccl.so.2 : bibliothèque de communication multi-GPU (NCCL). Bien que llama.cpp n'en ait pas besoin directement, elle apparaissait comme dépendance transitive via une autre bibliothèque CUDA.
Diagnostic et résolution
Commande de diagnostic :
ldd ~/llama.cpp/build/bin/llama-server | grep 'not found'
Cette commande liste toutes les dépendances dynamiques non résolues. Elle doit retourner un résultat vide une fois tous les problèmes corrigés.
Résolution de libnccl : la bibliothèque se trouve dans un chemin différent à l'intérieur du conteneur Docker :
docker create --name cudalib nvidia/cuda:11.8.0-devel-ubuntu22.04
docker cp cudalib:/usr/lib/x86_64-linux-gnu/libnccl.so.2.15.5 /tmp/
docker rm cudalib
sudo cp /tmp/libnccl.so.2.15.5 /usr/local/lib/
sudo ln -sf /usr/local/lib/libnccl.so.2.15.5 /usr/local/lib/libnccl.so.2
sudo ldconfig
Autres difficultés rencontrées
-
Corruption de fichiers ELF : lors d'une tentative de copie via cat et redirection de stdout, la bibliothèque extraite était corrompue (les headers ELF étaient altérés). La commande
file /tmp/lib.sopermet de vérifier l'intégrité — elle doit indiquer "ELF 64-bit LSB shared object". - Quantisation et VRAM : les modèles Q4 et Q8 ont des besoins en VRAM très différents. Un Qwen3.5-27B en Q8 requiert ~30 Go de VRAM, contre ~17 Go en Q4_K_M. Surdimensionner dépasse la capacité GPU et force un déchargement sur CPU, rendant l'inférence 10 à 20 fois plus lente.
-
Mode thinking actif par défaut : les modèles Qwen3.5 ont le mode de réflexion (chain-of-thought) activé par défaut. Pour les tâches de génération de code simple, il vaut mieux le désactiver via le préfixe
/no_thinkdans le prompt pour gagner en vitesse.
Crash VRAM — Quadro M2000 (out of memory)
La configuration dual-GPU (GTX 1070 — 8 Go VRAM + Quadro M2000 — 4 Go VRAM) a causé un crash au démarrage lors du chargement du modèle Qwen3.5-9B Q8. Le serveur s'est terminé avec un Aborted puis une Segmentation fault.
Erreur complète :
ggml_backend_cuda_buffer_type_alloc_buffer: allocating 653.07 MiB on device 1: cudaMalloc failed: out of memory
ggml_gallocr_reserve_n_impl: failed to allocate CUDA1 buffer of size 684794368
graph_reserve: failed to allocate compute buffers
sched_reserve: compute buffer allocation failed, retrying without pipeline parallelism
ggml_backend_cuda_buffer_type_alloc_buffer: allocating 493.00 MiB on device 1: cudaMalloc failed: out of memory
ggml_gallocr_reserve_n_impl: failed to allocate CUDA1 buffer of size 516947968
graph_reserve: failed to allocate compute buffers
llama_init_from_model: failed to initialize the context: failed to allocate compute pp buffers
common_init_result: failed to create context with model '.../Qwen3.5-9B-Q8_0.gguf'
ultra-fast.sh: line 13: 99871 Aborted ~/llama.cpp/build/bin/llama-server ...
ultra-fast.sh: line 17: 101164 Segmentation fault ~/llama.cpp/build/bin/llama-server ...
Analyse des lignes clés :
-
allocating 653.07 MiB on device 1: cudaMalloc failed: out of memory— llama.cpp tente d'allouer le buffer de calcul sur la M2000 (CUDA1, 4 Go), mais celle-ci n'a plus de VRAM disponible après le chargement des couches du modèle. -
retrying without pipeline parallelism— llama.cpp tente une seconde fois en désactivant le parallélisme entre GPU, mais la M2000 est déjà saturée, l'allocation échoue à nouveau (493 Mo refusés). -
failed to allocate compute pp buffers— les buffers de compute (pré-remplissage du contexte) ne peuvent pas être alloués, ce qui rend l'initialisation du contexte impossible. -
Segmentation fault— le second lancement avec--tensor-split 2,1provoque un crash mémoire car le programme tente d'accéder à une zone GPU non allouée.
Cause matérielle : la Quadro M2000 ne dispose que de 4 Go de VRAM. Avec --tensor-split 2,1, llama.cpp lui attribue ~33 % du modèle, ce qui dépasse sa capacité une fois les buffers de contexte ajoutés.
Solution — redistribuer massivement vers la GTX 1070 :
~/llama.cpp/build/bin/llama-server \
-m ~/models/qwen3.5-9b-q8/*.gguf \
--tensor-split 7,1 \
# 87,5 % sur GTX 1070 (CUDA0) — 12,5 % sur M2000 (CUDA1)
# Si les deux sont encore saturées :
# réduire -ngl et laisser le surplus déborder sur la RAM système (CPU)
Problème de compilation — CUDA incompatible avec Debian 13
La compilation native de llama.cpp sur le système hôte (Debian 13) a échoué : les versions de CUDA compatibles avec la M2000 (architecture Maxwell, sm_52) ne sont pas packagées pour Debian 13, et la version CUDA installée était trop récente pour supporter compute_52.
Erreur de compilation obtenue :
-- Check for working CUDA compiler: /usr/local/cuda/bin/nvcc - broken
nvcc fatal : Unsupported gpu architecture 'compute_52'
CMake Error at .../CMakeTestCUDACompiler.cmake:59 (message):
The CUDA compiler ".../nvcc" is not able to compile a simple test program.
CMake will not be able to correctly generate this project.
Cause : CUDA 12.x a abandonné le support des architectures Maxwell (sm_52, compute_52 — Quadro M2000) et Kepler. Seul CUDA 11.8 supporte encore sm_52, mais il n'existe pas de paquet CUDA 11.8 officiel pour Debian 13.
Solution — compilation dans un conteneur Docker Ubuntu 22.04 + CUDA 11.8 :
docker run --rm --runtime=nvidia --gpus all \
-v $HOME/llama.cpp:/llama.cpp \
nvidia/cuda:11.8.0-devel-ubuntu22.04 \
bash -c "
apt-get update -qq && apt-get install -y cmake build-essential curl libcurl4-openssl-dev &&
cmake -B /llama.cpp/build -S /llama.cpp \
-DGGML_CUDA=ON \
-DGGML_CUDA_FORCE_MMQ=ON \
-DGGML_NCCL=OFF \
-DCMAKE_CUDA_ARCHITECTURES='52-virtual;61-real' &&
cmake --build /llama.cpp/build --config Release -j\$(nproc) &&
echo '✓ BUILD SUCCESS'
"
-
52-virtual;61-real— cible les deux GPU : sm_52 (Quadro M2000, Maxwell) et sm_61 (GTX 1070, Pascal). Le binaire compilé est directement utilisable sur le système hôte. -
-DGGML_NCCL=OFF— désactive la dépendance NCCL pour éviter les erreurs de bibliothèque manquante vues précédemment. -
Le volume
-v $HOME/llama.cpp:/llama.cppmonte le répertoire source depuis l'hôte : le binaire compilé reste disponible après destruction du conteneur.
Problème de Corruption memoire graphique — M2000 defectueux
La compilation native de llama.cpp sur le système hôte (Debian 13) a échoué : les versions de CUDA compatibles avec la M2000 (architecture Maxwell, sm_52) ne sont pas packagées pour Debian 13, et la version CUDA installée était trop récente pour supporter compute_52.
Cause : Materiel creant des erreurs critique causant des plantages aleatoire ou durant l'utilisation de la vram. Suite a une modification du bios de la carte graphique.
Erreur complète :
home/sofiane/llama.cpp/build/bin/llama-server(+0x1349b6b) [0x562e65c31b6b]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x134a14f) [0x562e65c3214f]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x134a31e) [0x562e65c3231e]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x751016) [0x562e65039016]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x76c34f) [0x562e6505434f]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x75f80a) [0x562e6504780a]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x761bcd) [0x562e65049bcd]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x7656e6) [0x562e6504d6e6]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x768f11) [0x562e65050f11]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x76af82) [0x562e65052f82]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x136ba4a) [0x562e65c53a4a]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x5423a1) [0x562e64e2a3a1]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x5424f5) [0x562e64e2a4f5]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x54d3bb) [0x562e64e353bb]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x54ea00) [0x562e64e36a00]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x1cd7d9) [0x562e64ab57d9]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x247281) [0x562e64b2f281]
/home/sofiane/llama.cpp/build/bin/llama-server(+0xc288c) [0x562e649aa88c]
/lib/x86_64-linux-gnu/[libc.so](http://libc.so).6(+0x29ca8) [0x7f13f2835ca8]
/lib/x86_64-linux-gnu/[libc.so](http://libc.so).6(__libc_start_main+0x85) [0x7f13f2835d65]
/home/sofiane/llama.cpp/build/bin/llama-server(+0x12e3a5) [0x562e64a163a5]
/home/sofiane/ai-modes/[ultra-fast.sh](http://ultra-fast.sh):
line 13: 99871 Aborted ~/llama.cpp/build/bin/llama-server -m ~/models/qwen3.5-9b-q8/*.gguf --jinja -ngl 90
--flash-attn auto --tensor-split 6,3 --split-mode layer --threads 8 --ctx-size 16384 --port 8080 --host 0.0.0.0
--temp 0.7 --top-p 0.8 --top-k 20 --cache-type-k q8_0 --cache-type-v q8_0 --chat-template-kwargs '{"enable_thinking":false}'
-
(+0x134a14f) [0x562e65c3214f]— erreur memoire avec la localisation en memoire en hexa decimale -
/home/sofiane/ai-modes/[ultra-fast.sh]— la localisation de ficher script utiliser -
line 13: 99871 Aborted ~/llama.cpp/build/bin/llama-server— indique que le code c'est stopee et montre ou a ete arrete le logicel avec le ficher utiliser
Bilan du projet
Ce projet m'a permis de mettre en pratique plusieurs compétences du référentiel SISR : administration système Linux, gestion des dépendances logicielles, containerisation Docker, et configuration réseau. L'aspect résolution de problèmes :
— Notamment les bibliothèques CUDA manquantes et la corruption de fichiers binaires ont été particulièrement formateur.
— L'écosystème des LLM locaux évolue très rapidement, et cette veille active sur les modèles disponibles (Qwen, DeepSeek, Llama) s'inscrit directement dans ma démarche de veille technologique SISR.
-
52-virtual;61-real— cible les deux GPU : sm_52 (Quadro M2000, Maxwell) et sm_61 (GTX 1070, Pascal). Le binaire compilé est directement utilisable sur le système hôte. -
-DGGML_NCCL=OFF— désactive la dépendance NCCL pour éviter les erreurs de bibliothèque manquante vues précédemment. -
Le volume
-v $HOME/llama.cpp:/llama.cppmonte le répertoire source depuis l'hôte : le binaire compilé reste disponible après destruction du conteneur.